diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..403e745ff --- /dev/null +++ b/.clang-format @@ -0,0 +1,10 @@ +BasedOnStyle: LLVM +BreakBeforeBraces: Allman +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: None +AllowShortLoopsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +ColumnLimit: 120 \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..b99a6e34a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,215 @@ +name: Build + +env: + # Multi-threaded compilation for MSVC + CL: /MP + +on: [workflow_dispatch, push] + +jobs: + Build: + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + version: [5.15.2] + include: + - os: windows-latest + os-caption: Windows + - os: ubuntu-latest + os-caption: Ubuntu + - os: macos-latest + os-caption: MacOS + + runs-on: ${{matrix.os}} + steps: + - name: Clone Project + uses: actions/checkout@v2.4.2 + + - name: Fetch Git Info + shell: bash + working-directory: ${{github.workspace}} + run: | + git_hash=$(git rev-parse --short "$GITHUB_SHA") + git_branch=${GITHUB_REF##*/} + echo "$git_hash" >> ./res/git/git_hash.txt + echo "$git_branch" >> ./res/git/git_branch.txt + + - name: Setup externals + run: | + mkdir ./3rd + mkdir ./3rd/bass + mkdir ./3rd/discord-rpc + mkdir ./3rd/openssl + + - name: Install OpenSSL (Windows) + if: contains(matrix.os, 'windows') + run: | + curl -L https://mirror.firedaemon.com/OpenSSL/openssl-1.1.1q.zip -o openssl.zip + unzip -d ./openssl -o ./openssl.zip + cp ./openssl/openssl-1.1/x64/bin/*.dll ./3rd + + - name: Install BASS (Windows) + if: contains(matrix.os, 'windows') + run: | + curl http://www.un4seen.com/files/bass24.zip -o ./bass.zip + unzip -d ./bass -o ./bass.zip + cp ./bass/c/bass.h ./3rd/bass + cp ./bass/c/x64/bass.lib ./3rd + cp ./bass/x64/bass.dll ./3rd + + curl http://www.un4seen.com/files/bassopus24.zip -o ./bassopus.zip + unzip -d ./bass -o ./bassopus.zip + cp ./bass/c/bassopus.h ./3rd/bass + cp ./bass/c/x64/bassopus.lib ./3rd + cp ./bass/x64/bassopus.dll ./3rd + + - name: Install BASS (Ubuntu) + if: contains(matrix.os, 'ubuntu') + run: | + curl http://www.un4seen.com/files/bass24-linux.zip -o ./bass.zip + unzip -d ./bass -o ./bass.zip + cp ./bass/bass.h ./3rd/bass/bass.h + cp ./bass/x64/libbass.so ./3rd/libbass.so + + curl http://www.un4seen.com/files/bassopus24-linux.zip -o ./bassopus.zip + unzip -d ./bass -o ./bassopus.zip + cp ./bass/bassopus.h ./3rd/bass/bassopus.h + cp ./bass/x64/libbassopus.so ./3rd/libbassopus.so + + - name: Install BASS (MacOS) + if: contains(matrix.os, 'macos') + run: | + curl http://www.un4seen.com/files/bass24-osx.zip -o ./bass.zip + unzip -d ./bass -o ./bass.zip + cp ./bass/bass.h ./3rd/bass + cp ./bass/libbass.dylib ./3rd + + curl http://www.un4seen.com/files/bassopus24-osx.zip -o ./bassopus.zip + unzip -d ./bass -o ./bassopus.zip + cp ./bass/bassopus.h ./3rd/bass + cp ./bass/libbassopus.dylib ./3rd + + - name: Install Discord-RPC (Windows) + if: contains(matrix.os, 'windows') + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-win.zip -o ./discord-rpc.zip + unzip ./discord-rpc.zip + cp ./discord-rpc/win64-dynamic/include/discord_register.h ./3rd/discord-rpc + cp ./discord-rpc/win64-dynamic/include/discord_rpc.h ./3rd/discord-rpc + cp ./discord-rpc/win64-dynamic/bin/discord-rpc.dll ./3rd + cp ./discord-rpc/win64-dynamic/lib/discord-rpc.lib ./3rd + + - name: Install Discord-RPC (Ubuntu) + if: contains(matrix.os, 'ubuntu') + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o ./discord-rpc.zip + unzip ./discord-rpc.zip + cp ./discord-rpc/linux-dynamic/include/discord_register.h ./3rd/discord-rpc/discord_register.h + cp ./discord-rpc/linux-dynamic/include/discord_rpc.h ./3rd/discord-rpc/discord_rpc.h + cp ./discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ./3rd/libdiscord-rpc.so + + - name: Install Discord-RPC (MacOS) + if: contains(matrix.os, 'macos') + run: | + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip -o ./discord-rpc.zip + unzip ./discord-rpc.zip + cp ./discord-rpc/osx-dynamic/include/discord_register.h ./3rd/discord-rpc + cp ./discord-rpc/osx-dynamic/include/discord_rpc.h ./3rd/discord-rpc + cp ./discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ./3rd + + - name: Update packages (Ubuntu) + if: contains(matrix.os, 'ubuntu') + run: | + sudo apt-get update + sudo apt-get upgrade + + - name: Install Qt (Ubuntu) + if: contains(matrix.os, 'ubuntu') + run: | + sudo apt-get install qt5-default libqt5designer5 qttools5-dev libqt5multimedia5 libqt5multimedia5-plugins libqt5multimediawidgets5 qtmultimedia5-dev + + - name: Install Qt (Others) + if: contains(matrix.os, 'ubuntu') != true + uses: jurplel/install-qt-action@v2.14.0 + with: + version: ${{matrix.version}} + + - name: Configure MSVC (Windows) + if: contains(matrix.os, 'windows') + uses: ilammy/msvc-dev-cmd@v1 + + - name: Clone QtApng + uses: actions/checkout@v2 + with: + repository: Skycoder42/QtApng + path: ./qtapng + + - name: Build QtApng (Windows) + if: contains(matrix.os, 'windows') + run: | + cd ./qtapng + qmake + nmake + + - name: Build QtApng (Others) + if: contains(matrix.os, 'windows') != true + run: | + cd ./qtapng + qmake + make -j3 + + - name: Make Project + run: | + cp ./data/.qmake.conf . + qmake + + - name: Build Project (Windows) + if: contains(matrix.os, 'windows') + run: | + nmake + + - name: Build Project (Others) + if: contains(matrix.os, 'windows') != true + run: | + make -j3 + + - name: Packing (Windows) + if: contains(matrix.os, 'windows') + run: | + cd ./bin + windeployqt dro-client.exe --compiler-runtime --no-quick-import --no-translations + cp ../qtapng/plugins/imageformats/qapng.dll ./imageformats + cp ../3rd/*.dll . + cd .. + + - name: Packing (Ubuntu) + if: contains(matrix.os, 'ubuntu') + run: | + cd ./bin + mkdir ./depends + cp ../3rd/*.so ./depends + mkdir ./imageformats + cp ../qtapng/plugins/imageformats/libqapng.so ./imageformats + cp ../data/dro-client.sh . + cp ../data/README-LINUX.md ./README.md + cd .. + + - name: Packing (MacOS) + if: contains(matrix.os, 'macos') + run: | + cd ./bin + mv ./dro-client.app "./Danganronpa Online.app" + macdeployqt "./Danganronpa Online.app" + cp ../qtapng/plugins/imageformats/libqapng.dylib "./Danganronpa Online.app/Contents/PlugIns/imageformats" + cp ../3rd/*.dylib "./Danganronpa Online.app/Contents/Frameworks" + hdiutil create -volname "Danganronpa Online" -srcfolder "./Danganronpa Online.app" -ov -format UDZO "Danganronpa Online.dmg" + rm -rf "./Danganronpa Online.app" + cp ../data/README-MACOS.txt ./README.txt + cd .. + + - name: Create Artifact + uses: actions/upload-artifact@v2 + with: + name: "Danganronpa Online (${{matrix.os-caption}})" + path: ./bin diff --git a/.gitignore b/.gitignore index 9028beab7..08f5ea69f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,15 @@ +3rd/ +3rd_x86/ +3rdparty/ +base/ +docs/ +.qmake.conf +.gitignore *~ *.db *.user *.dll *.so -base_override.h - -base-full/ -bass.lib -discord-rpc.lib -send-presence.exe +*.autosave +*DS_Store +*.swp diff --git a/Attorney_Online_remake.pro b/Attorney_Online_remake.pro deleted file mode 100644 index a42821f65..000000000 --- a/Attorney_Online_remake.pro +++ /dev/null @@ -1,117 +0,0 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2016-12-29T01:14:46 -# -#------------------------------------------------- - -QT += core gui multimedia network - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -RC_ICONS = logo.ico - -TARGET = Attorney_Online_remake -TEMPLATE = app - -VERSION = 2.4.8.0 - -SOURCES += main.cpp\ - lobby.cpp \ - text_file_functions.cpp \ - path_functions.cpp \ - aoimage.cpp \ - file_functions.cpp \ - aobutton.cpp \ - debug_functions.cpp \ - networkmanager.cpp \ - aoapplication.cpp \ - aopacket.cpp \ - packet_distribution.cpp \ - hex_functions.cpp \ - encryption_functions.cpp \ - courtroom.cpp \ - aocharbutton.cpp \ - hardware_functions.cpp \ - aoscene.cpp \ - aomovie.cpp \ - misc_functions.cpp \ - aocharmovie.cpp \ - aoemotebutton.cpp \ - emotes.cpp \ - aosfxplayer.cpp \ - aomusicplayer.cpp \ - aoblipplayer.cpp \ - evidence.cpp \ - aoevidencebutton.cpp \ - charselect.cpp \ - aotextarea.cpp \ - aolineedit.cpp \ - aotextedit.cpp \ - aoevidencedisplay.cpp \ - discord_rich_presence.cpp \ - aonotepad.cpp \ - aobasshandle.cpp \ - aoexception.cpp \ - aoabstractplayer.cpp \ - aoshoutplayer.cpp \ - aonotearea.cpp \ - aonotepicker.cpp \ - aolabel.cpp - -HEADERS += lobby.h \ - aoimage.h \ - file_functions.h \ - aobutton.h \ - debug_functions.h \ - networkmanager.h \ - aoapplication.h \ - datatypes.h \ - aopacket.h \ - hex_functions.h \ - encryption_functions.h \ - courtroom.h \ - aocharbutton.h \ - hardware_functions.h \ - aoscene.h \ - aomovie.h \ - misc_functions.h \ - aocharmovie.h \ - aoemotebutton.h \ - bass.h \ - aosfxplayer.h \ - aomusicplayer.h \ - aoblipplayer.h \ - aoevidencebutton.h \ - aotextarea.h \ - aolineedit.h \ - aotextedit.h \ - aoevidencedisplay.h \ - discord_rich_presence.h \ - discord-rpc.h \ - aonotepad.h \ - aobasshandle.hpp \ - aoexception.hpp \ - aoabstractplayer.hpp \ - aoshoutplayer.hpp \ - aonotearea.hpp \ - aonotepicker.hpp \ - aolabel.hpp - -# 1. You need to get BASS and put the x86 bass DLL/headers in the project root folder -# AND the compilation output folder. If you want a static link, you'll probably -# need the .lib file too. MinGW-GCC is really finicky finding BASS, it seems. -# 2. You need to compile the Discord Rich Presence SDK separately and add the lib/headers -# in the same way as BASS. Discord RPC uses CMake, which does not play nicely with -# QMake, so this step must be manual. -unix:LIBS += -L$$PWD -lbass -ldiscord-rpc -win32:LIBS += -L$$PWD "$$PWD/bass.lib" -ldiscord-rpc #"$$PWD/discord-rpc.dll" -android:LIBS += -L$$PWD\android\libs\armeabi-v7a\ -lbass - -CONFIG += c++11 - -ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android - -RESOURCES += \ - resources.qrc - -DISTFILES += diff --git a/README.md b/README.md index b034d333f..4c9a5fe80 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ -# Attorney-Online-Client-Remake -This is a remake of Attorney Online made by FanatSors, licensed under GPLv3. +# Danganronpa-Online-Client +This is the official client used, it is a derivative of [Attorney-Online-Client-Remake](https://github.com/AttorneyOnline/AO2-Client). ## Qt -This project uses Qt 5.7, which is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.txt) with [certain licensing restrictions and exceptions](https://www.qt.io/qt-licensing-terms/). To comply with licensing requirements for static linking, object code is available if you would like to relink with an alternative version of Qt, and the source code for Qt may be found at https://github.com/qt/qtbase, http://code.qt.io/cgit/, or at https://qt.io. +This project uses Qt 5.15.2 (5.12.8 for Linux), which is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.txt) with [certain licensing restrictions and exceptions](https://www.qt.io/qt-licensing-terms/). To comply with licensing requirements for static linking, object code is available if you would like to relink with an alternative version of Qt, and the source code for Qt may be found at https://github.com/qt/qtbase, http://code.qt.io/cgit/, or at https://qt.io. -Copyright (C) 2016 The Qt Company Ltd. +Copyright (C) 2022 The Qt Company Ltd. ## BASS +This project uses [BASS shared library](http://www.un4seen.com/). -This project depends on the BASS shared library. Get it here: http://www.un4seen.com/ - -Copyright (c) 1999-2016 Un4seen Developments Ltd. All rights reserved. +Copyright (c) 1999-2022 Un4seen Developments Ltd. All rights reserved. diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml deleted file mode 100644 index f458c6a78..000000000 --- a/android/AndroidManifest.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/project.properties b/android/project.properties deleted file mode 100644 index a08f37edd..000000000 --- a/android/project.properties +++ /dev/null @@ -1 +0,0 @@ -target=android-21 \ No newline at end of file diff --git a/aoabstractplayer.cpp b/aoabstractplayer.cpp deleted file mode 100644 index a371a3625..000000000 --- a/aoabstractplayer.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "aoabstractplayer.hpp" - -AOAbstractPlayer::AOAbstractPlayer(QObject *p_parent, AOApplication *p_ao_app) - : QObject(p_parent), ao_app(p_ao_app) -{} - -int AOAbstractPlayer::get_volume() -{ - return m_volume; -} - -void AOAbstractPlayer::set_volume(int p_volume) -{ - m_volume = p_volume; - - emit new_volume(m_volume); -} diff --git a/aoabstractplayer.hpp b/aoabstractplayer.hpp deleted file mode 100644 index 0e7d9fc13..000000000 --- a/aoabstractplayer.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef AOABSTRACTPLAYER_HPP -#define AOABSTRACTPLAYER_HPP - -#include -#include - -#include "aoapplication.h" -#include "aobasshandle.hpp" - -class AOAbstractPlayer : public QObject -{ - Q_OBJECT - -public: - AOAbstractPlayer(QObject *p_parent, AOApplication *p_ao_app); - - int get_volume(); - -public slots: - void set_volume(int p_volume); - -signals: - void starting(); - void stopping(); - void new_volume(int p_volume); - -protected: - AOApplication* ao_app = nullptr; - -private: - int m_volume = 0; -}; - -#endif // AOABSTRACTPLAYER_HPP diff --git a/aoapplication.cpp b/aoapplication.cpp deleted file mode 100644 index 35259bc2f..000000000 --- a/aoapplication.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include "aoapplication.h" - -#include "lobby.h" -#include "courtroom.h" -#include "networkmanager.h" -#include "debug_functions.h" - -#include -#include -#include - -AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) -{ - net_manager = new NetworkManager(this); - discord = new AttorneyOnline::Discord(); - QObject::connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)), - SLOT(ms_connect_finished(bool, bool))); -} - -AOApplication::~AOApplication() -{ - destruct_lobby(); - destruct_courtroom(); - delete discord; -} - -void AOApplication::construct_lobby() -{ - if (lobby_constructed) - { - qDebug() << "W: lobby was attempted constructed when it already exists"; - return; - } - - w_lobby = new Lobby(this); - lobby_constructed = true; - - QRect screenGeometry = QApplication::desktop()->screenGeometry(); - int x = (screenGeometry.width()-w_lobby->width()) / 2; - int y = (screenGeometry.height()-w_lobby->height()) / 2; - w_lobby->move(x, y); - - discord->state_lobby(); - - w_lobby->show(); -} - -void AOApplication::destruct_lobby() -{ - if(!lobby_constructed) - { - qDebug() << "W: lobby was attempted destructed when it did not exist"; - return; - } - - delete w_lobby; - lobby_constructed = false; -} - -void AOApplication::construct_courtroom() -{ - if (courtroom_constructed) - { - qDebug() << "W: courtroom was attempted constructed when it already exists"; - return; - } - - w_courtroom = new Courtroom(this); - courtroom_constructed = true; - - QRect screenGeometry = QApplication::desktop()->screenGeometry(); - int x = (screenGeometry.width()-w_courtroom->width()) / 2; - int y = (screenGeometry.height()-w_courtroom->height()) / 2; - w_courtroom->move(x, y); -} - -void AOApplication::destruct_courtroom() -{ - if (!courtroom_constructed) - { - qDebug() << "W: courtroom was attempted destructed when it did not exist"; - return; - } - - delete w_courtroom; - courtroom_constructed = false; -} - -QString AOApplication::get_version_string() -{ - return - QString::number(RELEASE) + "." + - QString::number(MAJOR_VERSION) + "." + - QString::number(MINOR_VERSION); -} - -void AOApplication::reload_theme() -{ - current_theme = read_theme(); -} - -void AOApplication::set_favorite_list() -{ - favorite_list = read_serverlist_txt(); -} - -QString AOApplication::get_current_char() -{ - if (courtroom_constructed) - return w_courtroom->get_current_char(); - else - return ""; -} - -void AOApplication::add_favorite_server(int p_server) -{ - if (p_server < 0 || p_server >= server_list.size()) - return; - - server_type fav_server = server_list.at(p_server); - - QString str_port = QString::number(fav_server.port); - - QString server_line = fav_server.ip + ":" + str_port + ":" + fav_server.name; - - write_to_serverlist_txt(server_line); -} - -void AOApplication::server_disconnected() -{ - if (courtroom_constructed) - { - call_notice("Disconnected from server."); - construct_lobby(); - destruct_courtroom(); - } -} - -void AOApplication::loading_cancelled() -{ - destruct_courtroom(); - - w_lobby->hide_loading_overlay(); -} - -void AOApplication::ms_connect_finished(bool connected, bool will_retry) -{ - if (connected) - { - AOPacket *f_packet = new AOPacket("ALL#%"); - send_ms_packet(f_packet); - } - else - { - if (!lobby_constructed) - { - return; - } - else if (will_retry) - { - w_lobby->append_error("Error connecting to master server. Will try again in " - + QString::number(net_manager->ms_reconnect_delay_ms / 1000.f) + " seconds."); - } - else - { - call_error("There was an error connecting to the master server.\n" - "We deploy multiple master servers to mitigate any possible downtime, " - "but the client appears to have exhausted all possible methods of finding " - "and connecting to one.\n" - "Please check your Internet connection and firewall, and please try again."); - } - } -} diff --git a/aoapplication.h b/aoapplication.h deleted file mode 100644 index b28ea7af9..000000000 --- a/aoapplication.h +++ /dev/null @@ -1,276 +0,0 @@ -#ifndef AOAPPLICATION_H -#define AOAPPLICATION_H - -#include "aopacket.h" -#include "datatypes.h" -#include "discord_rich_presence.h" - -#include -#include -#include - -class NetworkManager; -class Lobby; -class Courtroom; - -class AOApplication : public QApplication -{ - Q_OBJECT - -public: - AOApplication(int &argc, char **argv); - ~AOApplication(); - - NetworkManager *net_manager; - Lobby *w_lobby; - Courtroom *w_courtroom; - AttorneyOnline::Discord *discord; - - bool lobby_constructed = false; - bool courtroom_constructed = false; - - void construct_lobby(); - void destruct_lobby(); - - void construct_courtroom(); - void destruct_courtroom(); - - void ms_packet_received(AOPacket *p_packet); - void server_packet_received(AOPacket *p_packet); - - void send_ms_packet(AOPacket *p_packet); - void send_server_packet(AOPacket *p_packet, bool encoded = true); - - /////////////////server metadata////////////////// - - unsigned int s_decryptor = 5; - bool encryption_needed = true; - - bool yellow_text_enabled = false; - bool prezoom_enabled = false; - bool flipping_enabled = false; - bool custom_objection_enabled = false; - bool improved_loading_enabled = false; - bool desk_mod_enabled = false; - bool evidence_enabled = false; - - ///////////////loading info/////////////////// - - //player number, it's hardly used but might be needed for some old servers - int s_pv = 0; - - QString server_software = ""; - - int char_list_size = 0; - int loaded_chars = 0; - int evidence_list_size = 0; - int loaded_evidence = 0; - int music_list_size = 0; - int loaded_music = 0; - - bool courtroom_loaded = false; - - //////////////////versioning/////////////// - - int get_release() {return RELEASE;} - int get_major_version() {return MAJOR_VERSION;} - int get_minor_version() {return MINOR_VERSION;} - QString get_version_string(); - - /////////////////////////////////////////// - - void set_favorite_list(); - QVector& get_favorite_list() {return favorite_list;} - void add_favorite_server(int p_server); - - void set_server_list(); - QVector& get_server_list() {return server_list;} - - //reads the theme from config.ini and sets it accordingly - void reload_theme(); - - //Returns the character the player has currently selected - QString get_current_char(); - - //implementation in path_functions.cpp - QString get_base_path(); - QString get_data_path(); - QString get_theme_path(); - QString get_default_theme_path(); - QString get_character_path(QString p_character); - QString get_demothings_path(); - QString get_sounds_path(); - QString get_music_path(QString p_song); - QString get_background_path(); - QString get_default_background_path(); - QString get_evidence_path(); - - ////// Functions for reading and writing files ////// - // Implementations file_functions.cpp - - //Returns the config value for the passed searchline from a properly formatted config ini file - QString read_config(QString searchline); - - //Returns text from note file - QString read_note(QString filename); - - //Reads the theme from config.ini and loads it into the current_theme variable - QString read_theme(); - - //Returns the blip rate from config.ini - int read_blip_rate(); - - //Returns true if blank blips is enabled in config.ini and false otherwise - bool get_blank_blip(); - - //Returns the value of default_music in config.ini - int get_default_music(); - - //Returns the value of default_sfx in config.ini - int get_default_sfx(); - - //Returns the value of default_blip in config.ini - int get_default_blip(); - - //Returns the list of words in callwords.ini - QStringList get_call_words(); - - QStringList get_sfx_list(); - - //Appends the argument string to serverlist.txt - void write_to_serverlist_txt(QString p_line); - - //Writes to note file - void write_note(QString p_text, QString filename); - - //appends to note file - void append_note(QString p_line, QString filename); - - //Overwrites config.ini with new theme - void write_theme(QString theme); - - - //Returns the contents of serverlist.txt - QVector read_serverlist_txt(); - - //Returns the value of p_identifier in the design.ini file in p_design_path - QString read_design_ini(QString p_identifier, QString p_design_path); - - //Helper function for returning an int in a file inside of the theme folder - int get_design_ini_value(QString p_identifier, QString p_design_file); - - //Returns the coordinates of widget with p_identifier from p_file - QPoint get_button_spacing(QString p_identifier, QString p_file); - - //Returns the dimensions of widget with specified identifier from p_file - pos_size_type get_element_dimensions(QString p_identifier, QString p_file); - - //Returns the value of font_size with p_identifier from p_file - int get_font_size(QString p_identifier, QString p_file); - - //Returns the name of the font with p_identifier from p_file - QString get_font_name(QString p_identifier, QString p_file); - - //Returns the color with p_identifier from p_file - QColor get_color(QString p_identifier, QString p_file); - - //Returns the sfx with p_identifier from sounds.ini in the current theme path - QString get_sfx(QString p_identifier); - - //Returns the value of p_search_line within target_tag and terminator_tag - QString read_char_ini(QString p_char, QString p_search_line, QString target_tag, QString terminator_tag); - - //Returns the text between target_tag and terminator_tag in p_file - QString get_stylesheet(QString target_tag, QString p_file); - - //Returns string list (characters, color) from p_file - QVector get_highlight_color(); - - //Returns special button on cc_config according to index - QString get_spbutton(QString p_tag, int index); - - //Returns effect on cc_config according to index - QStringList get_effect(int index); - - //Returns wtce on cc_config according to index - QStringList get_wtce(int index); - - //Returns the side of the p_char character from that characters ini file - QString get_char_side(QString p_char); - - //Returns the showname from the ini of p_char - QString get_showname(QString p_char); - - //Returns showname from showname.ini - QString read_showname(QString p_char); - - //Returns the value of chat from the specific p_char's ini file - QString get_chat(QString p_char); - - //Returns the value of shouts from the specified p_char's ini file - QString get_char_shouts(QString p_char); - - //Returns the preanim duration of p_char's p_emote - int get_preanim_duration(QString p_char, QString p_emote); - - //Same as above, but only returns if it has a % in front(refer to Preanims section in the manual) - int get_ao2_preanim_duration(QString p_char, QString p_emote); - - //Not in use - int get_text_delay(QString p_char, QString p_emote); - - //Returns the name of p_char - QString get_char_name(QString p_char); - - //Returns the total amount of emotes of p_char - int get_emote_number(QString p_char); - - //Returns the emote comment of p_char's p_emote - QString get_emote_comment(QString p_char, int p_emote); - - //Returns the base name of p_char's p_emote - QString get_emote(QString p_char, int p_emote); - - //Returns the preanimation name of p_char's p_emote - QString get_pre_emote(QString p_char, int p_emote); - - //Returns x,y offset for effect p_effect - QStringList get_effect_offset(QString p_char, int p_effect); - - //Returns overlay at p_effect in char_path/overlay - QStringList get_overlay(QString p_char, int p_effect); - - //Returns the sfx of p_char's p_emote - QString get_sfx_name(QString p_char, int p_emote); - - //Not in use - int get_sfx_delay(QString p_char, int p_emote); - - //Returns the modifier for p_char's p_emote - int get_emote_mod(QString p_char, int p_emote); - - //Returns the desk modifier for p_char's p_emote - int get_desk_mod(QString p_char, int p_emote); - - //Returns p_char's gender - QString get_gender(QString p_char); - -private: - const int RELEASE = 2; - const int MAJOR_VERSION = 4; - const int MINOR_VERSION = 8; - - QString current_theme = "default"; - - QVector server_list; - QVector favorite_list; - -private slots: - void ms_connect_finished(bool connected, bool will_retry); - -public slots: - void server_disconnected(); - void loading_cancelled(); -}; - -#endif // AOAPPLICATION_H diff --git a/aobasshandle.cpp b/aobasshandle.cpp deleted file mode 100644 index 468d3c524..000000000 --- a/aobasshandle.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "aobasshandle.hpp" - -AOBassHandle::AOBassHandle(QObject *p_parent) - : QObject(p_parent) -{} - -AOBassHandle::AOBassHandle(QString p_file, bool p_suicide, QObject *p_parent) noexcept(false) - : QObject(p_parent) -{ - set_file(p_file, p_suicide); -} - -AOBassHandle::~AOBassHandle() -{ - if (m_handle && m_sync) - { - BASS_ChannelRemoveSync(m_handle, m_sync); - } - - // nothing will go wrong if the handle isn't initialized, I promise...! - if (m_handle) - { - BASS_StreamFree(m_handle); - } -} - -void AOBassHandle::suicide() -{ - // safety first - disconnect(); - - // suicide note - emit body_discovery(); - - delete this; -} - -QString AOBassHandle::get_file() -{ - return m_file; -} - -void AOBassHandle::set_file(QString p_file, bool p_suicide) noexcept(false) -{ - if (m_handle) - return; // already created - - m_file = p_file; - - // create a handle based on the file - m_handle = BASS_StreamCreateFile(FALSE, m_file.utf16(), 0, 0, BASS_UNICODE|BASS_ASYNCFILE); - if (!m_handle) - throw AOException(QString("%1 could not be initialized to play").arg(p_file)); - - m_sync = BASS_ChannelSetSync(m_handle, BASS_SYNC_END, 0, &AOBassHandle::static_sync, this); - if (!m_sync) - { - // free stream since we can't sync - BASS_StreamFree(m_handle); - - throw AOException(QString("could not set sync for %1").arg(m_file)); - } - - m_suicide = p_suicide; - if (m_suicide) - connect(this, &AOBassHandle::stopped, this, &AOBassHandle::suicide); -} - -void AOBassHandle::set_volume(int p_volume) -{ - BASS_ChannelSetAttribute(m_handle, BASS_ATTRIB_VOL, p_volume/100.0f); -} - -void AOBassHandle::play() -{ - BASS_ChannelPlay(m_handle, FALSE); -} - -void AOBassHandle::stop() -{ - BASS_ChannelStop(m_handle); -} - -void CALLBACK AOBassHandle::static_sync(HSYNC handle, DWORD channel, DWORD data, void *user) -{ - if (auto self = static_cast(user)) - { - self->sync(data); - } -} - -void AOBassHandle::sync(DWORD data) -{ - Q_UNUSED(data) - - emit stopped(); -} diff --git a/aobasshandle.hpp b/aobasshandle.hpp deleted file mode 100644 index 3ff50cd26..000000000 --- a/aobasshandle.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef AOBASSHANDLE_HPP -#define AOBASSHANDLE_HPP - -#include -#include - -#include - -#include "aoexception.hpp" - -/** - * @brief The AOBassHandle is a small class to play sounds. - * The class may destroy itself once the audio provided is done playing. - */ - -class AOBassHandle : public QObject -{ - Q_OBJECT - -public: - AOBassHandle(QObject *p_parent = nullptr); - AOBassHandle(QString p_file, bool p_suicide, QObject *p_parent = nullptr) noexcept(false); - ~AOBassHandle(); - - QString get_file(); - void set_file(QString p_file, bool p_suicide = false) noexcept(false); - - // static - static void CALLBACK static_sync(HSYNC handle, DWORD channel, DWORD data, void *user); - -public slots: - void play(); - void stop(); - - void set_volume(int p_volume); - -signals: - void stopped(); - void body_discovery(); - -private: - QString m_file; - HSTREAM m_handle = 0; - HSYNC m_sync = 0; - bool m_suicide = false; - - void sync(DWORD data); - -private slots: - void suicide(); -}; - -#endif // AOBASSHANDLE_HPP diff --git a/aoblipplayer.cpp b/aoblipplayer.cpp deleted file mode 100644 index 9e7d0e5d9..000000000 --- a/aoblipplayer.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "aoblipplayer.h" - -AOBlipPlayer::AOBlipPlayer(QWidget *parent, AOApplication *p_ao_app) -{ - m_parent = parent; - ao_app = p_ao_app; -} - -void AOBlipPlayer::set_blips(QString p_sfx) -{ - QString f_path = ao_app->get_sounds_path() + p_sfx.toLower(); - - for (int n_stream = 0 ; n_stream < BLIP_COUNT ; ++n_stream) - { - BASS_StreamFree(m_stream_list[n_stream]); - - m_stream_list[n_stream] = BASS_StreamCreateFile(FALSE, f_path.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); - } - - set_volume(m_volume); -} - -void AOBlipPlayer::blip_tick() -{ - int f_cycle = m_cycle++; - - if (m_cycle == BLIP_COUNT) - m_cycle = 0; - - HSTREAM f_stream = m_stream_list[f_cycle]; - - BASS_ChannelPlay(f_stream, false); -} - -void AOBlipPlayer::set_volume(int p_value) -{ - m_volume = p_value; - - float volume = p_value / 100.0f; - - for (int n_stream = 0 ; n_stream < BLIP_COUNT ; ++n_stream) - { - BASS_ChannelSetAttribute(m_stream_list[n_stream], BASS_ATTRIB_VOL, volume); - } -} diff --git a/aoblipplayer.h b/aoblipplayer.h deleted file mode 100644 index 6b75ba289..000000000 --- a/aoblipplayer.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef AOBLIPPLAYER_H -#define AOBLIPPLAYER_H - -#include "bass.h" -#include "aoapplication.h" - -#include -#include -#include - -const int BLIP_COUNT = 5; - -class AOBlipPlayer -{ -public: - AOBlipPlayer(QWidget *parent, AOApplication *p_ao_app); - - void set_blips(QString p_sfx); - void blip_tick(); - void set_volume(int p_volume); - - int m_cycle = 0; - -private: - QWidget *m_parent; - AOApplication *ao_app; - - int m_volume; - HSTREAM m_stream_list[BLIP_COUNT]; -}; - -#endif // AOBLIPPLAYER_H diff --git a/aobutton.cpp b/aobutton.cpp deleted file mode 100644 index 6ba97caa0..000000000 --- a/aobutton.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "aobutton.h" - -#include "debug_functions.h" -#include "file_functions.h" - -#include - -AOButton::AOButton(QWidget *parent, AOApplication *p_ao_app) : QPushButton(parent) -{ - ao_app = p_ao_app; -} - -AOButton::~AOButton() -{ - -} - -void AOButton::set_image(QString p_image) -{ - QString f_image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); - QString image_path = ao_app->get_theme_path() + p_image; - QString hover_image_path = ao_app->get_theme_path() + f_image_name + "_hover.png"; - QString default_image_path = ao_app->get_default_theme_path() + p_image; - - if (file_exists(image_path)) - { - if(file_exists(hover_image_path)) - this->setStyleSheet("QPushButton {border-image:url(\"" + image_path + "\");}" - "QPushButton:hover {border-image:url(\"" + hover_image_path + "\");}"); - else - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); - } - - else - this->setStyleSheet("border-image:url(\"" + default_image_path + "\")"); -} diff --git a/aocharbutton.cpp b/aocharbutton.cpp deleted file mode 100644 index 07eca3086..000000000 --- a/aocharbutton.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "aocharbutton.h" - -#include "file_functions.h" - -#include - -AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos) : QPushButton(parent) -{ - ao_app = p_ao_app; - - this->resize(60, 60); - this->move(x_pos, y_pos); - - ui_taken = new AOImage(this, ao_app); - ui_taken->resize(60, 60); - ui_taken->set_image("char_taken.png"); - ui_taken->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_taken->hide(); - - ui_passworded = new AOImage(this, ao_app); - ui_passworded->resize(60, 60); - ui_passworded->set_image("char_passworded"); - ui_passworded->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_passworded->hide(); - - ui_selector = new AOImage(parent, ao_app); - ui_selector->resize(62, 62); - ui_selector->move(x_pos - 1, y_pos - 1); - ui_selector->set_image("char_selector.png"); - ui_selector->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_selector->hide(); -} - -void AOCharButton::reset() -{ - ui_taken->hide(); - ui_passworded->hide(); - ui_selector->hide(); -} - -void AOCharButton::set_taken() -{ - ui_taken->show(); -} - -void AOCharButton::set_passworded() -{ - ui_passworded->show(); -} - -void AOCharButton::set_image(QString p_character) -{ - QString image_path = ao_app->get_character_path(p_character) + "char_icon.png"; - QString legacy_path = ao_app->get_demothings_path() + p_character.toLower() + "_char_icon.png"; - QString alt_path = ao_app->get_demothings_path() + p_character.toLower() + "_off.png"; - - this->setText(""); - - if (file_exists(image_path)) - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); - else if (file_exists(legacy_path)) - { - this->setStyleSheet("border-image:url(\"" + legacy_path + "\")"); - //ninja optimization - QFile::copy(legacy_path, image_path); - } - else - { - this->setStyleSheet("border-image:url()"); - this->setText(p_character); - } -} - -void AOCharButton::enterEvent(QEvent * e) -{ - ui_selector->raise(); - ui_selector->show(); - - setFlat(false); - QPushButton::enterEvent(e); -} - -void AOCharButton::leaveEvent(QEvent * e) -{ - ui_selector->hide(); - QPushButton::leaveEvent(e); -} - - diff --git a/aocharmovie.cpp b/aocharmovie.cpp deleted file mode 100644 index 0c144a0ea..000000000 --- a/aocharmovie.cpp +++ /dev/null @@ -1,200 +0,0 @@ -#include "aocharmovie.h" -#include "courtroom.h" - -#include "misc_functions.h" -#include "file_functions.h" -#include "aoapplication.h" - -#include -#include - -AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) -{ - ao_app = p_ao_app; - - m_movie = new QMovie(this); - - preanim_timer = new QTimer(this); - preanim_timer->setSingleShot(true); - - connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); - connect(preanim_timer, SIGNAL(timeout()), this, SLOT(timer_done())); -} - -void AOCharMovie::play(QString p_char, QString p_emote, QString emote_prefix, bool show) -{ - QStringList f_vec; - - QString original_path = ao_app->get_character_path(p_char) + emote_prefix + p_emote.toLower(); // .gif - QString alt_path = ao_app->get_character_path(p_char) + p_emote.toLower(); // .png - QString placeholder_path = ao_app->get_theme_path() + "placeholder"; // .gif - QString placeholder_default_path = ao_app->get_default_theme_path() + "placeholder"; // .gif - QString gif_path; - -// if (file_exists(original_path)) -// gif_path = original_path; -// else if (file_exists(alt_path)) -// gif_path = alt_path; -// else if (file_exists(placeholder_path)) -// gif_path = placeholder_path; -// else -// gif_path = placeholder_default_path; - - f_vec.push_back(original_path); - f_vec.push_back(alt_path); - f_vec.push_back(placeholder_path); - f_vec.push_back(placeholder_default_path); - - for(auto &f_file : f_vec) - { - bool found = false; - for (auto &ext : decltype(f_vec){".apng", ".gif", ".png"}) - { - QString fullPath = f_file + ext; - found = file_exists(fullPath); - if (found) - { - gif_path = fullPath; - break; - } - } - - if (found) - break; - } - - m_movie->stop(); - m_movie->setFileName(gif_path); - - QImageReader *reader = new QImageReader(gif_path); - - movie_frames.clear(); - QImage f_image = reader->read(); - while (!f_image.isNull()) - { - if (m_flipped) - movie_frames.append(f_image.mirrored(true, false)); - else - movie_frames.append(f_image); - f_image = reader->read(); - } - - delete reader; - - this->show(); - if(!show) this->hide(); - - m_movie->start(); -} - -void AOCharMovie::play_pre(QString p_char, QString p_emote, int duration, bool show) -{ - QString gif_path = ao_app->get_character_path(p_char) + p_emote.toLower(); - - m_movie->stop(); - this->clear(); - m_movie->setFileName(gif_path); - m_movie->jumpToFrame(0); - - int full_duration = duration * time_mod; - int real_duration = 0; - - play_once = false; - - for (int n_frame = 0 ; n_frame < m_movie->frameCount() ; ++n_frame) - { - real_duration += m_movie->nextFrameDelay(); - m_movie->jumpToFrame(n_frame + 1); - } - qDebug() << "full_duration: " << full_duration; - qDebug() << "real_duration: " << real_duration; - - double percentage_modifier = 100.0; - - if (real_duration != 0 && duration != 0) - { - double modifier = full_duration / static_cast(real_duration); - percentage_modifier = 100 / modifier; - - if (percentage_modifier > 100.0) - percentage_modifier = 100.0; - } - qDebug() << "% mod: " << percentage_modifier; - - if (full_duration == 0 || full_duration >= real_duration) - { - play_once = true; - } - else - { - play_once = false; - preanim_timer->start(full_duration); - } - - - m_movie->setSpeed(static_cast(percentage_modifier)); - play(p_char, p_emote, "", show); -} - -void AOCharMovie::play_talking(QString p_char, QString p_emote, bool show) -{ - QString gif_path = ao_app->get_character_path(p_char) + "(b)" + p_emote.toLower(); - - m_movie->stop(); - this->clear(); - m_movie->setFileName(gif_path); - - play_once = false; - m_movie->setSpeed(100); - play(p_char, p_emote, "(b)", show); -} - -void AOCharMovie::play_idle(QString p_char, QString p_emote, bool show) -{ - QString gif_path = ao_app->get_character_path(p_char) + "(a)" + p_emote.toLower(); - - m_movie->stop(); - this->clear(); - m_movie->setFileName(gif_path); - - play_once = false; - m_movie->setSpeed(100); - play(p_char, p_emote, "(a)", show); -} - -void AOCharMovie::stop() -{ - //for all intents and purposes, stopping is the same as hiding. at no point do we want a frozen gif to display - m_movie->stop(); - preanim_timer->stop(); - this->hide(); -} - -void AOCharMovie::combo_resize(int w, int h) -{ - QSize f_size(w, h); - this->resize(f_size); - m_movie->setScaledSize(f_size); -} - -void AOCharMovie::frame_change(int n_frame) -{ - if (movie_frames.size() > n_frame) - { - QPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(n_frame)); - - this->setPixmap(f_pixmap.scaled(this->width(), this->height())); - } - - if (m_movie->frameCount() - 1 == n_frame && play_once) - { - preanim_timer->start(m_movie->nextFrameDelay()); - m_movie->stop(); - } -} - -void AOCharMovie::timer_done() -{ - - done(); -} diff --git a/aocharmovie.h b/aocharmovie.h deleted file mode 100644 index 7ceb293ea..000000000 --- a/aocharmovie.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef AOCHARMOVIE_H -#define AOCHARMOVIE_H - -#include -#include -#include - -class AOApplication; - -class AOCharMovie : public QLabel -{ - Q_OBJECT - -public: - AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app); - - void play(QString p_char, QString p_emote, QString emote_prefix, bool show); - void play_pre(QString p_char, QString p_emote, int duration, bool show); - void play_talking(QString p_char, QString p_emote, bool show); - void play_idle(QString p_char, QString p_emote, bool show); - - void set_flipped(bool p_flipped) {m_flipped = p_flipped;} - - void stop(); - - void combo_resize(int w, int h); - -private: - AOApplication *ao_app = nullptr; - - QMovie *m_movie; - QVector movie_frames; - QTimer *preanim_timer; - - const int time_mod = 62; - - bool m_flipped = false; - - bool play_once = true; - -signals: - void done(); - -private slots: - void frame_change(int n_frame); - void timer_done(); -}; - -#endif // AOCHARMOVIE_H diff --git a/aoemotebutton.cpp b/aoemotebutton.cpp deleted file mode 100644 index 81a73d7a4..000000000 --- a/aoemotebutton.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "aoemotebutton.h" - -#include "file_functions.h" -#include - -AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) : QPushButton(p_parent) -{ - ao_app = p_ao_app; - - this->move(p_x, p_y); - this->resize(40, 40); - - connect(this, SIGNAL(clicked()), this, SLOT(on_clicked())); -} - -void AOEmoteButton::set_image(QString p_char, int p_emote, QString suffix) -{ - QString emotion_number = QString::number(p_emote + 1); - QString image_path = ao_app->get_character_path(p_char) + "emotions/ao2/button" + emotion_number + suffix; - QString alt_path = ao_app->get_character_path(p_char) + "emotions/button" + emotion_number + suffix; - QString hover_path = ao_app->get_character_path(p_char) + "emotions/hovers/button" + emotion_number + "_hover" + suffix; - - if (file_exists(image_path)) - { - this->setText(""); - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); - } - else if (file_exists(alt_path)) - { - this->setText(""); - if(file_exists(hover_path)) - { - this->setStyleSheet("QPushButton {border-image:url(\"" + alt_path + "\");}" - "QPushButton:hover {border-image:url(\"" + hover_path + "\");}"); - } - else this->setStyleSheet("border-image:url(\"" + alt_path + "\")"); - } - else - { - this->setText(ao_app->get_emote_comment(p_char, p_emote)); - this->setStyleSheet("border-image:url(\"\")"); - } -} - -void AOEmoteButton::on_clicked() -{ - emote_clicked(m_id); -} diff --git a/aoemotebutton.h b/aoemotebutton.h deleted file mode 100644 index edc528c08..000000000 --- a/aoemotebutton.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef AOEMOTEBUTTON_H -#define AOEMOTEBUTTON_H - -#include - -#include "aoapplication.h" - -class AOEmoteButton : public QPushButton -{ - Q_OBJECT - -public: - AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y); - - //void set_on(QString p_char, int p_emote); - //void set_off(QString p_char, int p_emote); - void set_image(QString p_char, int p_emote, QString suffix); - - void set_id(int p_id) {m_id = p_id;} - int get_id() {return m_id;} - -private: - AOApplication *ao_app = nullptr; - - int m_id = 0; - -signals: - void emote_clicked(int p_id); - -private slots: - void on_clicked(); -}; - -#endif // AOEMOTEBUTTON_H diff --git a/aoevidencebutton.cpp b/aoevidencebutton.cpp deleted file mode 100644 index 7c8abb66d..000000000 --- a/aoevidencebutton.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "aoevidencebutton.h" - -#include "file_functions.h" - -#include - -AOEvidenceButton::AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) : QPushButton(p_parent) -{ - ao_app = p_ao_app; - - ui_selected = new AOImage(p_parent, ao_app); - ui_selected->resize(70, 70); - ui_selected->move(p_x, p_y); - ui_selected->set_image("evidence_selected.png"); - ui_selected->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_selected->hide(); - - ui_selector = new AOImage(p_parent, ao_app); - ui_selector->resize(71, 71); - ui_selector->move(p_x - 1, p_y - 1); - ui_selector->set_image("evidence_selector.png"); - ui_selector->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_selector->hide(); - - this->move(p_x, p_y); - this->resize(70, 70); - this->setAcceptDrops(true); - - connect(this, SIGNAL(clicked()), this, SLOT(on_clicked())); -} - -void AOEvidenceButton::reset() -{ - this->hide(); - ui_selected->hide(); - ui_selector->hide(); -} - -void AOEvidenceButton::set_image(QString p_image) -{ - QString image_path = ao_app->get_evidence_path() + p_image; - - if (file_exists(image_path)) - { - this->setText(""); - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); - } - else - { - this->setText(p_image); - this->setStyleSheet(""); - } -} - -void AOEvidenceButton::set_theme_image(QString p_image) -{ - QString theme_image_path = ao_app->get_theme_path() + p_image; - QString default_image_path = ao_app->get_default_theme_path() + p_image; - - QString final_image_path; - - if (file_exists(theme_image_path)) - final_image_path = theme_image_path; - else - final_image_path = default_image_path; - - this->setText(""); - this->setStyleSheet("border-image:url(\"" + final_image_path + "\")"); -} - -void AOEvidenceButton::set_selected(bool p_selected) -{ - if (p_selected) - ui_selected->show(); - else - ui_selected->hide(); -} - -void AOEvidenceButton::on_clicked() -{ - evidence_clicked(m_id); -} - -void AOEvidenceButton::mouseDoubleClickEvent(QMouseEvent *e) -{ - QPushButton::mouseDoubleClickEvent(e); - evidence_double_clicked(m_id); -} - -void AOEvidenceButton::dragLeaveEvent(QMouseEvent *e) -{ - //QWidget::dragLeaveEvent(e); - - qDebug() << "drag leave event"; -} - -void AOEvidenceButton::dragEnterEvent(QMouseEvent *e) -{ - //QWidget::dragEnterEvent(e); - - qDebug() << "drag enter event"; -} - -void AOEvidenceButton::enterEvent(QEvent * e) -{ - ui_selector->show(); - - on_hover(m_id, true); - - setFlat(false); - QPushButton::enterEvent(e); -} - -void AOEvidenceButton::leaveEvent(QEvent * e) -{ - ui_selector->hide(); - - on_hover(m_id, false); - QPushButton::leaveEvent(e); -} diff --git a/aoevidencebutton.h b/aoevidencebutton.h deleted file mode 100644 index 8af8999d6..000000000 --- a/aoevidencebutton.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef AOEVIDENCEBUTTON_H -#define AOEVIDENCEBUTTON_H - -#include "aoapplication.h" -#include "aoimage.h" - -#include -#include - -class AOEvidenceButton : public QPushButton -{ - Q_OBJECT - -public: - AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y); - - void reset(); - void set_image(QString p_image); - void set_theme_image(QString p_image); - void set_id(int p_id) {m_id = p_id;} - - void set_selected(bool p_selected); - -private: - AOApplication *ao_app = nullptr; - - AOImage *ui_selected; - AOImage *ui_selector; - - int m_id = 0; - -protected: - void enterEvent(QEvent *e); - void leaveEvent(QEvent *e); - void mouseDoubleClickEvent(QMouseEvent *e); - void dragLeaveEvent(QMouseEvent *e); - void dragEnterEvent(QMouseEvent *e); - -signals: - void evidence_clicked(int p_id); - void evidence_double_clicked(int p_id); - void on_hover(int p_id, bool p_state); - -private slots: - void on_clicked(); -}; - -#endif // AOEVIDENCEBUTTON_H diff --git a/aoevidencedisplay.cpp b/aoevidencedisplay.cpp deleted file mode 100644 index cbe37c0e5..000000000 --- a/aoevidencedisplay.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include - -#include "aoevidencedisplay.h" - -#include "file_functions.h" -#include "datatypes.h" -#include "misc_functions.h" - -AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) -{ - ao_app = p_ao_app; - - evidence_movie = new QMovie(this); - evidence_icon = new QLabel(this); - sfx_player = new AOSfxPlayer(this, ao_app); - - connect(evidence_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); -} - -void AOEvidenceDisplay::show_evidence(QString p_evidence_image, bool is_left_side, int p_volume) -{ - this->reset(); - - sfx_player->set_volume(p_volume); - - QString f_evidence_path = ao_app->get_evidence_path() + p_evidence_image; - - QPixmap f_pixmap(f_evidence_path); - - QString final_gif_path; - QString gif_name; - QString icon_identifier; - - if (is_left_side) - { - icon_identifier = "left_evidence_icon"; - gif_name = "evidence_appear_left.gif"; - } - else - { - icon_identifier = "right_evidence_icon"; - gif_name = "evidence_appear_right.gif"; - } - - pos_size_type icon_dimensions = ao_app->get_element_dimensions(icon_identifier, "courtroom_design.ini"); - - evidence_icon->move(icon_dimensions.x, icon_dimensions.y); - evidence_icon->resize(icon_dimensions.width, icon_dimensions.height); - - evidence_icon->setPixmap(f_pixmap.scaled(evidence_icon->width(), evidence_icon->height(), Qt::IgnoreAspectRatio)); - - QString f_default_gif_path = ao_app->get_default_theme_path() + gif_name; - QString f_gif_path = ao_app->get_theme_path() + gif_name; - - if (file_exists(f_gif_path)) - final_gif_path = f_gif_path; - else - final_gif_path = f_default_gif_path; - - evidence_movie->setFileName(final_gif_path); - - if(evidence_movie->frameCount() < 1) - return; - - this->setMovie(evidence_movie); - - evidence_movie->start(); - sfx_player->play(ao_app->get_sfx("evidence_present")); -} - -void AOEvidenceDisplay::frame_change(int p_frame) -{ - if (p_frame == (evidence_movie->frameCount() - 1)) - { - //we need this or else the last frame wont show - delay(evidence_movie->nextFrameDelay()); - - evidence_movie->stop(); - this->clear(); - - evidence_icon->show(); - } -} - -void AOEvidenceDisplay::reset() -{ - sfx_player->stop(); - evidence_movie->stop(); - evidence_icon->hide(); - this->clear(); -} - -QLabel* AOEvidenceDisplay::get_evidence_icon() -{ - return evidence_icon; -} - - diff --git a/aoevidencedisplay.h b/aoevidencedisplay.h deleted file mode 100644 index b90cf0678..000000000 --- a/aoevidencedisplay.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef AOEVIDENCEDISPLAY_H -#define AOEVIDENCEDISPLAY_H - -#include -#include - -#include "aoapplication.h" -#include "aosfxplayer.h" - -class AOEvidenceDisplay : public QLabel -{ - Q_OBJECT - -public: - AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app); - - void show_evidence(QString p_evidence_image, bool is_left_side, int p_volume); - QLabel* get_evidence_icon(); - void reset(); - -private: - AOApplication *ao_app = nullptr; - QMovie *evidence_movie; - QLabel *evidence_icon; - AOSfxPlayer *sfx_player; - -private slots: - void frame_change(int p_frame); -}; - -#endif // AOEVIDENCEDISPLAY_H diff --git a/aoexception.cpp b/aoexception.cpp deleted file mode 100644 index 93618dd9f..000000000 --- a/aoexception.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "aoexception.hpp" - -AOException::AOException(QString p_msg) - : m_msg(p_msg) -{} - -const char *AOException::what() const noexcept -{ - return m_msg.toStdString().c_str(); -} diff --git a/aoexception.hpp b/aoexception.hpp deleted file mode 100644 index de0a55d84..000000000 --- a/aoexception.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef AOEXCEPTION_HPP -#define AOEXCEPTION_HPP - -#include - -#include - -class AOException : public std::exception -{ -public: - AOException() = default; - AOException(QString p_msg); - virtual ~AOException() noexcept = default; - - virtual const char *what() const noexcept; - -private: - QString m_msg; -}; - -#endif // AOEXCEPTION_HPP diff --git a/aoimage.cpp b/aoimage.cpp deleted file mode 100644 index 03e6ebcb3..000000000 --- a/aoimage.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "file_functions.h" - -#include "aoimage.h" - -#include - -AOImage::AOImage(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) -{ - ao_app = p_ao_app; -} - -AOImage::~AOImage() -{ - -} - -void AOImage::set_image(QString p_image) -{ - QString theme_image_path = ao_app->get_theme_path() + p_image; - QString default_image_path = ao_app->get_default_theme_path() + p_image; - - QString final_image_path; - - if (file_exists(theme_image_path)) - final_image_path = theme_image_path; - else - final_image_path = default_image_path; - - QPixmap f_pixmap(final_image_path); - - this->setPixmap(f_pixmap.scaled(this->width(), this->height(), Qt::IgnoreAspectRatio)); -} - -void AOImage::set_image_from_path(QString p_path) -{ - QString default_path = ao_app->get_default_theme_path() + "chatmed.png"; - - QString final_path; - - if (file_exists(p_path)) - final_path = p_path; - else - final_path = default_path; - - QPixmap f_pixmap(final_path); - - this->setPixmap(f_pixmap.scaled(this->width(), this->height(), Qt::IgnoreAspectRatio)); -} diff --git a/aoimage.h b/aoimage.h deleted file mode 100644 index edc1efe48..000000000 --- a/aoimage.h +++ /dev/null @@ -1,23 +0,0 @@ -//This class represents a static theme-dependent image - -#ifndef AOIMAGE_H -#define AOIMAGE_H - -#include "aoapplication.h" - -#include - -class AOImage : public QLabel -{ -public: - AOImage(QWidget *parent, AOApplication *p_ao_app); - ~AOImage(); - - AOApplication *ao_app = nullptr; - - void set_image(QString p_image); - void set_image_from_path(QString p_path); - void set_size_and_pos(QString identifier); -}; - -#endif // AOIMAGE_H diff --git a/aolabel.cpp b/aolabel.cpp deleted file mode 100644 index 0e893751d..000000000 --- a/aolabel.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "aolabel.hpp" - -#include "file_functions.h" - -AOLabel::AOLabel(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) -{ - ao_app = p_ao_app; -} - -void AOLabel::set_image(QString p_image) -{ - QString image_path = ao_app->get_theme_path() + p_image; - QString default_image_path = ao_app->get_default_theme_path() + p_image; - - if (file_exists(image_path)) - { - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); - } - else - this->setStyleSheet("border-image:url(\"" + default_image_path + "\")"); -} diff --git a/aomovie.cpp b/aomovie.cpp deleted file mode 100644 index 72cbb435a..000000000 --- a/aomovie.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include "aomovie.h" - -#include "file_functions.h" -#include "courtroom.h" -#include "misc_functions.h" - -AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) -{ - ao_app = p_ao_app; - - m_movie = new QMovie(); - - this->setMovie(m_movie); - - connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int))); -} - -void AOMovie::set_play_once(bool p_play_once) -{ - play_once = p_play_once; -} - -void AOMovie::play(QString p_file, QString p_char, QString p_custom_theme) -{ - m_movie->stop(); - - QVector f_vec; - - QString file_path = ""; - - QString custom_path; - if (p_file == "custom") - custom_path = ao_app->get_character_path(p_char) + p_file; - else - custom_path = ao_app->get_character_path(p_char) + p_file + "_bubble"; - - f_vec.push_back(custom_path); - - QString overlay_path = ao_app->get_character_path(p_char) + "overlay/" + p_file; - QString custom_theme_path = ao_app->get_base_path() + "themes/" + p_custom_theme + "/" + p_file; - QString theme_path = ao_app->get_theme_path() + p_file; - QString default_theme_path = ao_app->get_default_theme_path() + p_file; - QString placeholder_path = ao_app->get_theme_path() + "placeholder"; - QString default_placeholder_path = ao_app->get_default_theme_path() + "placeholder"; - - f_vec.push_back(overlay_path); - f_vec.push_back(custom_theme_path); - f_vec.push_back(theme_path); - f_vec.push_back(default_theme_path); - f_vec.push_back(placeholder_path); - f_vec.push_back(default_placeholder_path); - - for(auto &f_file : f_vec) - { - bool found = false; - for (auto &ext : decltype(f_vec){".apng", ".gif", ".png"}) - { - QString fullPath = f_file + ext; - found = file_exists(fullPath); - if (found) - { - file_path = fullPath; - break; - } - } - - if (found) - break; - } - - m_movie->setFileName(file_path); - - this->show(); - m_movie->setScaledSize(this->size()); - m_movie->start(); -} - -void AOMovie::stop() -{ - m_movie->stop(); - this->hide(); -} - -void AOMovie::frame_change(int n_frame) -{ - if (n_frame == (m_movie->frameCount() - 1) && play_once) - { - //we need this or else the last frame wont show - delay(m_movie->nextFrameDelay()); - - this->stop(); - - //signal connected to courtroom object, let it figure out what to do - emit done(); - } -} - -void AOMovie::combo_resize(int w, int h) -{ - QSize f_size(w, h); - this->resize(f_size); - m_movie->setScaledSize(f_size); -} diff --git a/aomovie.h b/aomovie.h deleted file mode 100644 index e116b6837..000000000 --- a/aomovie.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef AOMOVIE_H -#define AOMOVIE_H - -#include -#include - -class Courtroom; -class AOApplication; - -class AOMovie : public QLabel -{ - Q_OBJECT - -public: - AOMovie(QWidget *p_parent, AOApplication *p_ao_app); - - void set_play_once(bool p_play_once); - void play(QString p_file, QString p_char = "", QString p_custom_theme = ""); - void combo_resize(int w, int h); - void stop(); - -private: - QMovie *m_movie; - AOApplication *ao_app = nullptr; - bool play_once = true; - -signals: - void done(); - -private slots: - void frame_change(int n_frame); -}; - -#endif // AOMOVIE_H diff --git a/aomusicplayer.cpp b/aomusicplayer.cpp deleted file mode 100644 index 47f2a26f8..000000000 --- a/aomusicplayer.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "aomusicplayer.h" - -#include - -#include - -AOMusicPlayer::AOMusicPlayer(QObject *p_parent, AOApplication *p_ao_app) - : AOAbstractPlayer(p_parent, p_ao_app) -{} - -void AOMusicPlayer::play(QString p_file) -{ - QString f_file = ao_app->get_music_path(p_file); - - stop(); - - m_file = f_file; - - try { // create new song - AOBassHandle *handle = new AOBassHandle(m_file, false, this); - connect(this, &AOMusicPlayer::new_volume, handle, &AOBassHandle::set_volume); - connect(this, &AOMusicPlayer::stopping, handle, &AOBassHandle::stop); - - // delete previous - if (m_handle) - delete m_handle; - - m_handle = handle; - m_handle->set_volume(get_volume()); - m_handle->play(); - } catch(const std::exception &e_exception) { - qDebug() << e_exception.what(); - } -} - -void AOMusicPlayer::stop() -{ - emit stopping(); -} diff --git a/aomusicplayer.h b/aomusicplayer.h deleted file mode 100644 index ed2cc05e5..000000000 --- a/aomusicplayer.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef AOMUSICPLAYER_H -#define AOMUSICPLAYER_H - -#include "aoabstractplayer.hpp" - -class AOMusicPlayer : public AOAbstractPlayer -{ - Q_OBJECT - -public: - AOMusicPlayer(QObject *p_parent, AOApplication *p_ao_app); - - void play(QString p_file); - void stop(); - -private: - AOBassHandle* m_handle = nullptr; - QString m_file; -}; - -#endif // AOMUSICPLAYER_H diff --git a/aonotearea.cpp b/aonotearea.cpp deleted file mode 100644 index 884a26a69..000000000 --- a/aonotearea.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include "aonotepicker.hpp" -#include "aonotearea.hpp" - -#include "courtroom.h" - -#include - -AONoteArea::AONoteArea(QWidget *p_parent, AOApplication *p_ao_app) : AOImage(p_parent, p_ao_app) -{ - ao_app = p_ao_app; -} - -AONoteArea::~AONoteArea() -{ -} - -void Courtroom::on_add_button_clicked() -{ - if(ui_note_area->m_layout->count() > 6) - return; - - AONotePicker *f_notepicker = new AONotePicker(ui_note_area, ao_app); - AOButton *f_button = new AOButton(f_notepicker, ao_app); - AOButton *f_delete = new AOButton(f_notepicker, ao_app); - QLineEdit *f_line = new QLineEdit(f_notepicker); - AOButton *f_hover = new AOButton(f_notepicker, ao_app); - QHBoxLayout *f_layout = new QHBoxLayout(f_notepicker); - - f_notepicker->m_line = f_line; - f_notepicker->m_button = f_button; - f_notepicker->m_layout = f_layout; - f_notepicker->m_delete_button = f_delete; - f_notepicker->m_hover = f_hover; - f_notepicker->setProperty("index", ui_note_area->m_layout->count()-1); - - f_button->set_image("note_edit.png"); - f_delete->set_image("note_delete.png"); - f_hover->set_image("note_select.png"); - - f_line->setReadOnly(true); - - f_layout->setSizeConstraint(QLayout::SetFixedSize); - - f_layout->addWidget(f_hover); - f_layout->addWidget(f_line); - f_layout->addWidget(f_button); - f_layout->addWidget(f_delete); - f_notepicker->setLayout(f_layout); - ui_note_area->m_layout->addWidget(f_notepicker); - - if(contains_add_button) - { - ui_note_area->m_layout->removeWidget(ui_note_area->add_button); - ui_note_area->m_layout->addWidget(ui_note_area->add_button); - set_note_files(); - } - - set_dropdown(f_line, "[LINE EDIT]"); - - connect(f_button, SIGNAL(clicked(bool)), this, SLOT(on_set_file_button_clicked())); - connect(f_delete, SIGNAL(clicked(bool)), this, SLOT(on_delete_button_clicked())); - connect(f_hover, SIGNAL(clicked(bool)), this, SLOT(on_file_selected())); -} - -void Courtroom::set_note_files() -{ - QString filename = ao_app->get_base_path() + "configs/filesabstract.ini"; - QFile config_file(filename); - - if(!config_file.open(QIODevice::ReadOnly | QIODevice::Text)) - { - qDebug() << "Couldn't open" << filename; - return; - } - - QTextStream in(&config_file); - - QByteArray t = ""; - - for(int i = 0; i < ui_note_area->m_layout->count()-1; ++i) - { - AONotePicker *f_notepicker = static_cast(ui_note_area->m_layout->itemAt(i)->widget()); - QString f_filestring = f_notepicker->real_file; - QString f_filename = f_notepicker->m_line->text(); - - t += QString::number(i) + " = " + f_filestring + " = " + f_filename + "\n\n"; - } - - - config_file.close(); - - QFile ex(filename); - if(!ex.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) - { - qDebug() << "Couldn't open" << filename; - return; - } - - ex.write(t); - ex.close(); -} diff --git a/aonotearea.hpp b/aonotearea.hpp deleted file mode 100644 index 40a72c4e8..000000000 --- a/aonotearea.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef AONOTEAREA_HPP -#define AONOTEAREA_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "aoapplication.h" - -class AONoteArea : public AOImage -{ - Q_OBJECT - -public: - AONoteArea(QWidget *p_parent, AOApplication *p_ao_app); - ~AONoteArea(); - - AOButton *add_button; - QVBoxLayout *m_layout; - AOButton *f_button; - -private: - AOApplication *ao_app; - void set_layout(); - -}; - -#endif // AONOTEAREA_HPP diff --git a/aonotepad.cpp b/aonotepad.cpp deleted file mode 100644 index 8a1297168..000000000 --- a/aonotepad.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "aonotepad.h" - -AONotepad::AONotepad(QWidget* p_parent, AOApplication *p_ao_app) - : QTextEdit(p_parent), ao_app(p_ao_app) -{} diff --git a/aonotepad.h b/aonotepad.h deleted file mode 100644 index de04e182f..000000000 --- a/aonotepad.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef AONOTEPAD_H -#define AONOTEPAD_H - -#include - -#include "aoapplication.h" - -class AONotepad : public QTextEdit -{ - Q_OBJECT - -public: - AONotepad(QWidget* p_parent, AOApplication *p_ao_app); - -private: - AOApplication *ao_app = nullptr; -}; - -#endif // AONOTEPAD_H diff --git a/aonotepicker.cpp b/aonotepicker.cpp deleted file mode 100644 index ba3585afe..000000000 --- a/aonotepicker.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "aonotepicker.hpp" - -#include "courtroom.h" - -#include -#include - -AONotePicker::AONotePicker(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) -{ - ao_app = p_ao_app; -} - -AONotePicker::~AONotePicker() -{} - -void Courtroom::on_file_selected() -{ - for(int i=0; i < ui_note_area->m_layout->count() -1; ++i) - { - AONotePicker *f_notepicker = static_cast(ui_note_area->m_layout->itemAt(i)->widget()); - f_notepicker->m_hover->set_image("note_select.png"); - } - - AOButton *f_button = static_cast(sender()); - AONotePicker *f_notepicker = static_cast(f_button->parent()); - current_file = f_notepicker->real_file; - load_note(); - f_button->set_image("note_select_selected.png"); -} - - -void Courtroom::on_set_file_button_clicked() -{ - AOButton *f_button = static_cast(sender()); - AONotePicker *f_notepicker = static_cast(f_button->parent()); - QString f_filename = QFileDialog::getOpenFileName(this, "Open File"); - if(f_filename != "") - { - f_notepicker->m_line->setText(f_filename); - f_notepicker->real_file = f_filename; - - set_note_files(); - } -} - -void Courtroom::on_delete_button_clicked() -{ - AOButton *f_button = static_cast(sender()); - AONotePicker *f_notepicker = static_cast(f_button->parent()); - ui_note_area->m_layout->removeWidget(f_notepicker); - delete f_notepicker; - set_note_files(); -} diff --git a/aonotepicker.hpp b/aonotepicker.hpp deleted file mode 100644 index cfc8881b4..000000000 --- a/aonotepicker.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef AONOTEPICKER_HPP -#define AONOTEPICKER_HPP - -#include -#include -#include -#include "aobutton.h" - -class AONotePicker : public QLabel -{ - Q_OBJECT - -public: - AONotePicker(QWidget *p_parent, AOApplication *p_ao_app); - ~AONotePicker(); - - QLineEdit *m_line; - AOButton *m_button; - AOButton *m_delete_button; - AOButton *m_hover; - QHBoxLayout *m_layout; - QString real_file = ""; - -private: - AOApplication *ao_app; -}; - -#endif // AONOTEPICKER_HPP diff --git a/aopacket.cpp b/aopacket.cpp deleted file mode 100644 index fa8f5be11..000000000 --- a/aopacket.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "aopacket.h" - -#include "encryption_functions.h" - -#include - -AOPacket::AOPacket(QString p_packet_string) -{ - QStringList packet_contents = p_packet_string.split("#"); - - m_header = packet_contents.at(0); - - for(int n_string = 1 ; n_string < packet_contents.size() - 1 ; ++n_string) - { - m_contents.append(packet_contents.at(n_string)); - } -} - -AOPacket::AOPacket(QString p_header, QStringList &p_contents) -{ - m_header = p_header; - m_contents = p_contents; -} - -AOPacket::~AOPacket() -{ - -} - -QString AOPacket::to_string() -{ - QString f_string = m_header; - - for (QString i_string : m_contents) - { - f_string += ("#" + i_string); - } - - f_string += "#%"; - - - if (encrypted) - return "#" + f_string; - else - return f_string; -} - -void AOPacket::encrypt_header(unsigned int p_key) -{ - m_header = fanta_encrypt(m_header, p_key); - - encrypted = true; -} - -void AOPacket::decrypt_header(unsigned int p_key) -{ - m_header = fanta_decrypt(m_header, p_key); - - encrypted = false; -} - -void AOPacket::net_encode() -{ - for (int n_element = 0 ; n_element < m_contents.size() ; ++n_element) - { - QString f_element = m_contents.at(n_element); - f_element.replace("#", "").replace("%", "").replace("$", "").replace("&", ""); - - m_contents.removeAt(n_element); - m_contents.insert(n_element, f_element); - } -} - -void AOPacket::net_decode() -{ - for (int n_element = 0 ; n_element < m_contents.size() ; ++n_element) - { - QString f_element = m_contents.at(n_element); - f_element.replace("", "#").replace("", "%").replace("", "$").replace("", "&"); - - m_contents.removeAt(n_element); - m_contents.insert(n_element, f_element); - } -} - diff --git a/aopacket.h b/aopacket.h deleted file mode 100644 index 40dd3ec38..000000000 --- a/aopacket.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef AOPACKET_H -#define AOPACKET_H - -#include -#include - -class AOPacket -{ -public: - AOPacket(QString p_packet_string); - AOPacket(QString header, QStringList &p_contents); - ~AOPacket(); - - QString get_header() {return m_header;} - QStringList &get_contents() {return m_contents;} - QString to_string(); - - void encrypt_header(unsigned int p_key); - void decrypt_header(unsigned int p_key); - - void net_encode(); - void net_decode(); - -private: - bool encrypted = false; - - QString m_header; - QStringList m_contents; -}; - -#endif // AOPACKET_H diff --git a/aoscene.cpp b/aoscene.cpp deleted file mode 100644 index d86b849b4..000000000 --- a/aoscene.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include "aoscene.h" - -#include "courtroom.h" - -#include "file_functions.h" - -// core -#include - -// gui -#include - -AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) -{ - m_parent = parent; - m_movie = new QMovie(this); - ao_app = p_ao_app; -} - -void AOScene::set_image(QString p_image) -{ - QString animated_background_path = ao_app->get_background_path() + p_image; - QString background_path = ao_app->get_background_path() + p_image + ".png"; - QString default_path = ao_app->get_default_background_path() + p_image; - - for (auto& ext : QVector{".apng", ".gif"}) - { - QString full_path = animated_background_path + ext; - if (file_exists(full_path)) - { - animated_background_path = full_path; - break; - } - } - - QPixmap animated_background(animated_background_path); - QPixmap background(background_path); - QPixmap default_bg(default_path); - - int w = this->width(); - int h = this->height(); - - // remove movie - this->clear(); - this->setMovie(nullptr); - // stop current movie - m_movie->stop(); - m_movie->setFileName(animated_background_path); - m_movie->setScaledSize(QSize(w, h)); - - if (m_movie->isValid()) - { - this->setMovie(m_movie); - m_movie->start(); - } - else if (file_exists(background_path)) - { - this->setPixmap(background.scaled(w, h)); - } - else - { - this->setPixmap(default_bg.scaled(w, h)); - } -} - -void AOScene::set_legacy_desk(QString p_image) -{ - //vanilla desks vary in both width and height. in order to make that work with viewport rescaling, - //some INTENSE math is needed. - - QString desk_path = ao_app->get_background_path() + p_image; - QString default_path = ao_app->get_default_background_path() + p_image; - - QPixmap f_desk; - - if (file_exists(desk_path)) - f_desk.load(desk_path); - else - f_desk.load(default_path); - - int vp_width = m_parent->width(); - int vp_height = m_parent->height(); - - //double y_modifier = 147 / 192; - //double w_modifier = vp_width / 256; - double h_modifier = vp_height / 192; - - //int final_y = y_modifier * vp_height; - //int final_w = w_modifier * f_desk.width(); - int final_h = h_modifier * f_desk.height(); - - //this->resize(final_w, final_h); - //this->setPixmap(f_desk.scaled(final_w, final_h)); - this->resize(vp_width, final_h); - this->setPixmap(f_desk.scaled(vp_width, final_h)); -} diff --git a/aoscene.h b/aoscene.h deleted file mode 100644 index 468b0778b..000000000 --- a/aoscene.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef AOSCENE_H -#define AOSCENE_H - -#include - -class Courtroom; -class AOApplication; - -class AOScene : public QLabel -{ - Q_OBJECT -public: - explicit AOScene(QWidget *parent, AOApplication *p_ao_app); - - void set_image(QString p_image); - void set_legacy_desk(QString p_image); - -private: - QWidget* m_parent = nullptr; - QMovie* m_movie = nullptr; - AOApplication* ao_app = nullptr; - -}; - -#endif // AOSCENE_H diff --git a/aosfxplayer.cpp b/aosfxplayer.cpp deleted file mode 100644 index cd191a67e..000000000 --- a/aosfxplayer.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "aosfxplayer.h" - -#include - -#include - -AOSfxPlayer::AOSfxPlayer(QObject *p_parent, AOApplication *p_ao_app) - : AOAbstractPlayer(p_parent, p_ao_app) -{} - -void AOSfxPlayer::play(QString p_name) -{ - QString f_file = ao_app->get_sounds_path() + p_name.toLower(); - - try { - AOBassHandle *handle = new AOBassHandle(f_file, true, this); - connect(this, &AOSfxPlayer::new_volume, handle, &AOBassHandle::set_volume); - connect(this, &AOSfxPlayer::stopping, handle, &AOBassHandle::stop); - handle->set_volume(get_volume()); - handle->play(); - } catch(const std::exception &e_exception) { - qDebug() << e_exception.what(); - } -} - -void AOSfxPlayer::stop() -{ - emit stopping(); -} diff --git a/aosfxplayer.h b/aosfxplayer.h deleted file mode 100644 index 57ee3aeac..000000000 --- a/aosfxplayer.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef AOSFXPLAYER_H -#define AOSFXPLAYER_H - -#include "aoabstractplayer.hpp" - -class AOSfxPlayer : public AOAbstractPlayer -{ - Q_OBJECT - -public: - AOSfxPlayer(QObject *p_parent, AOApplication *p_ao_app); - - void play(QString p_file); - void stop(); -}; - -#endif // AOSFXPLAYER_H diff --git a/aoshoutplayer.cpp b/aoshoutplayer.cpp deleted file mode 100644 index c82a29ee7..000000000 --- a/aoshoutplayer.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "aoshoutplayer.hpp" -#include "file_functions.h" - -#include - -AOShoutPlayer::AOShoutPlayer(QObject *p_parent, AOApplication *p_ao_app) - : AOAbstractPlayer(p_parent, p_ao_app) -{} - -void AOShoutPlayer::play(QString p_name, QString p_char) -{ - QString f_file; - - QString char_path = ao_app->get_character_path(p_char) + p_name.toLower(); - QString theme_path = ao_app->get_theme_path() + p_name.toLower(); - QString default_theme_path = ao_app->get_default_theme_path() + p_name.toLower(); - - qDebug() << char_path; - qDebug() << theme_path; - qDebug() << default_theme_path; - - if(file_exists(char_path)) - f_file = char_path; - else if (file_exists(theme_path)) - f_file = theme_path; - else if (file_exists(default_theme_path)) - f_file = default_theme_path; - else - f_file = ""; - - try { - AOBassHandle *handle = new AOBassHandle(f_file, true, this); - connect(this, &AOShoutPlayer::new_volume, handle, &AOBassHandle::set_volume); - connect(this, &AOShoutPlayer::stopping, handle, &AOBassHandle::stop); - handle->set_volume(get_volume()); - handle->play(); - } catch(const std::exception &e_exception) { - qDebug() << e_exception.what(); - } -} - -void AOShoutPlayer::stop() -{ - emit stopping(); -} diff --git a/aoshoutplayer.hpp b/aoshoutplayer.hpp deleted file mode 100644 index b5d7b4e22..000000000 --- a/aoshoutplayer.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef AOSHOUTPLAYER_HPP -#define AOSHOUTPLAYER_HPP - -#include "aoabstractplayer.hpp" - -class AOShoutPlayer : public AOAbstractPlayer -{ - Q_OBJECT - -public: - AOShoutPlayer(QObject *p_parent, AOApplication *p_ao_app); - - void play(QString p_name, QString p_char); - void stop(); -}; - -#endif // AOSHOUTPLAYER_HPP diff --git a/aotextarea.cpp b/aotextarea.cpp deleted file mode 100644 index 40cc31480..000000000 --- a/aotextarea.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "aotextarea.h" - -#include -#include -#include -#include - -AOTextArea::AOTextArea(QWidget *p_parent) : QTextBrowser(p_parent) -{ - -} - -void AOTextArea::append_chatmessage(QString p_name, QString p_message) -{ - const QTextCursor old_cursor = this->textCursor(); - const int old_scrollbar_value = this->verticalScrollBar()->value(); - const bool is_scrolled_down = old_scrollbar_value == this->verticalScrollBar()->maximum(); - - this->moveCursor(QTextCursor::End); - - this->append(""); - this->insertHtml("" + p_name.toHtmlEscaped() + ": "); - - //cheap workarounds ahoy - p_message += " "; - QString result = p_message.toHtmlEscaped().replace("\n", "
").replace(omnis_dank_url_regex, "\\1" ); - - this->insertHtml(result); - - this->auto_scroll(old_cursor, old_scrollbar_value, is_scrolled_down); -} - -void AOTextArea::append_error(QString p_message) -{ - const QTextCursor old_cursor = this->textCursor(); - const int old_scrollbar_value = this->verticalScrollBar()->value(); - const bool is_scrolled_down = old_scrollbar_value == this->verticalScrollBar()->maximum(); - - this->moveCursor(QTextCursor::End); - - this->append(""); - - p_message += " "; - QString result = p_message.replace("\n", "
").replace(omnis_dank_url_regex, "\\1" ); - - this->insertHtml("" + result + ""); - - this->auto_scroll(old_cursor, old_scrollbar_value, is_scrolled_down); -} - -void AOTextArea::auto_scroll(QTextCursor old_cursor, int old_scrollbar_value, bool is_scrolled_down) -{ - if (old_cursor.hasSelection() || !is_scrolled_down) - { - // The user has selected text or scrolled away from the bottom: maintain position. - this->setTextCursor(old_cursor); - this->verticalScrollBar()->setValue(old_scrollbar_value); - } - else - { - // The user hasn't selected any text and the scrollbar is at the bottom: scroll to the bottom. - this->moveCursor(QTextCursor::End); - this->verticalScrollBar()->setValue(this->verticalScrollBar()->maximum()); - } -} diff --git a/aotextarea.h b/aotextarea.h deleted file mode 100644 index 32635fdb0..000000000 --- a/aotextarea.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef AOTEXTAREA_H -#define AOTEXTAREA_H - -#include - -class AOTextArea : public QTextBrowser -{ -public: - AOTextArea(QWidget *p_parent = nullptr); - - void append_chatmessage(QString p_name, QString p_message); - void append_error(QString p_message); - -private: - const QRegExp omnis_dank_url_regex = QRegExp("\\b(https?://\\S+\\.\\S+)\\b"); - - void auto_scroll(QTextCursor old_cursor, int scrollbar_value, bool is_scrolled_down); -}; - -#endif // AOTEXTAREA_H diff --git a/aotextedit.cpp b/aotextedit.cpp deleted file mode 100644 index 30e48b733..000000000 --- a/aotextedit.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "aotextedit.h" - -AOTextEdit::AOTextEdit(QWidget *parent) : QPlainTextEdit(parent) -{ - this->setReadOnly(true); - - //connect(this, SIGNAL(returnPressed()), this, SLOT(on_enter_pressed())); -} - -void AOTextEdit::mouseDoubleClickEvent(QMouseEvent *e) -{ - QPlainTextEdit::mouseDoubleClickEvent(e); - - this->setReadOnly(false); -} - -void AOTextEdit::on_enter_pressed() -{ - this->setReadOnly(true); -} - diff --git a/aotextedit.h b/aotextedit.h deleted file mode 100644 index 85909c6e1..000000000 --- a/aotextedit.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef AOTEXTEDIT_H -#define AOTEXTEDIT_H - -#include - -class AOTextEdit : public QPlainTextEdit -{ - Q_OBJECT -public: - AOTextEdit(QWidget *parent); - -protected: - void mouseDoubleClickEvent(QMouseEvent *e); - -signals: - void double_clicked(); - -private slots: - void on_enter_pressed(); - -}; - -#endif // AOTEXTEDIT_H diff --git a/bass.h b/bass.h deleted file mode 100644 index 06195de2f..000000000 --- a/bass.h +++ /dev/null @@ -1,1051 +0,0 @@ -/* - BASS 2.4 C/C++ header file - Copyright (c) 1999-2016 Un4seen Developments Ltd. - - See the BASS.CHM file for more detailed documentation -*/ - -#ifndef BASS_H -#define BASS_H - -#ifdef _WIN32 -#include -typedef unsigned __int64 QWORD; -#else -#include -#define WINAPI -#define CALLBACK -typedef uint8_t BYTE; -typedef uint16_t WORD; -typedef uint32_t DWORD; -typedef uint64_t QWORD; -#ifndef __OBJC__ -typedef int BOOL; -#endif -#ifndef TRUE -#define TRUE 1 -#define FALSE 0 -#endif -#define LOBYTE(a) (BYTE)(a) -#define HIBYTE(a) (BYTE)((a)>>8) -#define LOWORD(a) (WORD)(a) -#define HIWORD(a) (WORD)((a)>>16) -#define MAKEWORD(a,b) (WORD)(((a)&0xff)|((b)<<8)) -#define MAKELONG(a,b) (DWORD)(((a)&0xffff)|((b)<<16)) -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#define BASSVERSION 0x204 // API version -#define BASSVERSIONTEXT "2.4" - -#ifndef BASSDEF -#define BASSDEF(f) WINAPI f -#else -#define NOBASSOVERLOADS -#endif - -typedef DWORD HMUSIC; // MOD music handle -typedef DWORD HSAMPLE; // sample handle -typedef DWORD HCHANNEL; // playing sample's channel handle -typedef DWORD HSTREAM; // sample stream handle -typedef DWORD HRECORD; // recording handle -typedef DWORD HSYNC; // synchronizer handle -typedef DWORD HDSP; // DSP handle -typedef DWORD HFX; // DX8 effect handle -typedef DWORD HPLUGIN; // Plugin handle - -// Error codes returned by BASS_ErrorGetCode -#define BASS_OK 0 // all is OK -#define BASS_ERROR_MEM 1 // memory error -#define BASS_ERROR_FILEOPEN 2 // can't open the file -#define BASS_ERROR_DRIVER 3 // can't find a free/valid driver -#define BASS_ERROR_BUFLOST 4 // the sample buffer was lost -#define BASS_ERROR_HANDLE 5 // invalid handle -#define BASS_ERROR_FORMAT 6 // unsupported sample format -#define BASS_ERROR_POSITION 7 // invalid position -#define BASS_ERROR_INIT 8 // BASS_Init has not been successfully called -#define BASS_ERROR_START 9 // BASS_Start has not been successfully called -#define BASS_ERROR_SSL 10 // SSL/HTTPS support isn't available -#define BASS_ERROR_ALREADY 14 // already initialized/paused/whatever -#define BASS_ERROR_NOCHAN 18 // can't get a free channel -#define BASS_ERROR_ILLTYPE 19 // an illegal type was specified -#define BASS_ERROR_ILLPARAM 20 // an illegal parameter was specified -#define BASS_ERROR_NO3D 21 // no 3D support -#define BASS_ERROR_NOEAX 22 // no EAX support -#define BASS_ERROR_DEVICE 23 // illegal device number -#define BASS_ERROR_NOPLAY 24 // not playing -#define BASS_ERROR_FREQ 25 // illegal sample rate -#define BASS_ERROR_NOTFILE 27 // the stream is not a file stream -#define BASS_ERROR_NOHW 29 // no hardware voices available -#define BASS_ERROR_EMPTY 31 // the MOD music has no sequence data -#define BASS_ERROR_NONET 32 // no internet connection could be opened -#define BASS_ERROR_CREATE 33 // couldn't create the file -#define BASS_ERROR_NOFX 34 // effects are not available -#define BASS_ERROR_NOTAVAIL 37 // requested data is not available -#define BASS_ERROR_DECODE 38 // the channel is/isn't a "decoding channel" -#define BASS_ERROR_DX 39 // a sufficient DirectX version is not installed -#define BASS_ERROR_TIMEOUT 40 // connection timedout -#define BASS_ERROR_FILEFORM 41 // unsupported file format -#define BASS_ERROR_SPEAKER 42 // unavailable speaker -#define BASS_ERROR_VERSION 43 // invalid BASS version (used by add-ons) -#define BASS_ERROR_CODEC 44 // codec is not available/supported -#define BASS_ERROR_ENDED 45 // the channel/file has ended -#define BASS_ERROR_BUSY 46 // the device is busy -#define BASS_ERROR_UNKNOWN -1 // some other mystery problem - -// BASS_SetConfig options -#define BASS_CONFIG_BUFFER 0 -#define BASS_CONFIG_UPDATEPERIOD 1 -#define BASS_CONFIG_GVOL_SAMPLE 4 -#define BASS_CONFIG_GVOL_STREAM 5 -#define BASS_CONFIG_GVOL_MUSIC 6 -#define BASS_CONFIG_CURVE_VOL 7 -#define BASS_CONFIG_CURVE_PAN 8 -#define BASS_CONFIG_FLOATDSP 9 -#define BASS_CONFIG_3DALGORITHM 10 -#define BASS_CONFIG_NET_TIMEOUT 11 -#define BASS_CONFIG_NET_BUFFER 12 -#define BASS_CONFIG_PAUSE_NOPLAY 13 -#define BASS_CONFIG_NET_PREBUF 15 -#define BASS_CONFIG_NET_PASSIVE 18 -#define BASS_CONFIG_REC_BUFFER 19 -#define BASS_CONFIG_NET_PLAYLIST 21 -#define BASS_CONFIG_MUSIC_VIRTUAL 22 -#define BASS_CONFIG_VERIFY 23 -#define BASS_CONFIG_UPDATETHREADS 24 -#define BASS_CONFIG_DEV_BUFFER 27 -#define BASS_CONFIG_VISTA_TRUEPOS 30 -#define BASS_CONFIG_IOS_MIXAUDIO 34 -#define BASS_CONFIG_DEV_DEFAULT 36 -#define BASS_CONFIG_NET_READTIMEOUT 37 -#define BASS_CONFIG_VISTA_SPEAKERS 38 -#define BASS_CONFIG_IOS_SPEAKER 39 -#define BASS_CONFIG_MF_DISABLE 40 -#define BASS_CONFIG_HANDLES 41 -#define BASS_CONFIG_UNICODE 42 -#define BASS_CONFIG_SRC 43 -#define BASS_CONFIG_SRC_SAMPLE 44 -#define BASS_CONFIG_ASYNCFILE_BUFFER 45 -#define BASS_CONFIG_OGG_PRESCAN 47 -#define BASS_CONFIG_MF_VIDEO 48 -#define BASS_CONFIG_AIRPLAY 49 -#define BASS_CONFIG_DEV_NONSTOP 50 -#define BASS_CONFIG_IOS_NOCATEGORY 51 -#define BASS_CONFIG_VERIFY_NET 52 -#define BASS_CONFIG_DEV_PERIOD 53 -#define BASS_CONFIG_FLOAT 54 -#define BASS_CONFIG_NET_SEEK 56 - -// BASS_SetConfigPtr options -#define BASS_CONFIG_NET_AGENT 16 -#define BASS_CONFIG_NET_PROXY 17 -#define BASS_CONFIG_IOS_NOTIFY 46 - -// BASS_Init flags -#define BASS_DEVICE_8BITS 1 // 8 bit -#define BASS_DEVICE_MONO 2 // mono -#define BASS_DEVICE_3D 4 // enable 3D functionality -#define BASS_DEVICE_16BITS 8 // limit output to 16 bit -#define BASS_DEVICE_LATENCY 0x100 // calculate device latency (BASS_INFO struct) -#define BASS_DEVICE_CPSPEAKERS 0x400 // detect speakers via Windows control panel -#define BASS_DEVICE_SPEAKERS 0x800 // force enabling of speaker assignment -#define BASS_DEVICE_NOSPEAKER 0x1000 // ignore speaker arrangement -#define BASS_DEVICE_DMIX 0x2000 // use ALSA "dmix" plugin -#define BASS_DEVICE_FREQ 0x4000 // set device sample rate -#define BASS_DEVICE_STEREO 0x8000 // limit output to stereo - -// DirectSound interfaces (for use with BASS_GetDSoundObject) -#define BASS_OBJECT_DS 1 // IDirectSound -#define BASS_OBJECT_DS3DL 2 // IDirectSound3DListener - -// Device info structure -typedef struct { -#if defined(_WIN32_WCE) || (WINAPI_FAMILY && WINAPI_FAMILY!=WINAPI_FAMILY_DESKTOP_APP) - const wchar_t *name; // description - const wchar_t *driver; // driver -#else - const char *name; // description - const char *driver; // driver -#endif - DWORD flags; -} BASS_DEVICEINFO; - -// BASS_DEVICEINFO flags -#define BASS_DEVICE_ENABLED 1 -#define BASS_DEVICE_DEFAULT 2 -#define BASS_DEVICE_INIT 4 - -#define BASS_DEVICE_TYPE_MASK 0xff000000 -#define BASS_DEVICE_TYPE_NETWORK 0x01000000 -#define BASS_DEVICE_TYPE_SPEAKERS 0x02000000 -#define BASS_DEVICE_TYPE_LINE 0x03000000 -#define BASS_DEVICE_TYPE_HEADPHONES 0x04000000 -#define BASS_DEVICE_TYPE_MICROPHONE 0x05000000 -#define BASS_DEVICE_TYPE_HEADSET 0x06000000 -#define BASS_DEVICE_TYPE_HANDSET 0x07000000 -#define BASS_DEVICE_TYPE_DIGITAL 0x08000000 -#define BASS_DEVICE_TYPE_SPDIF 0x09000000 -#define BASS_DEVICE_TYPE_HDMI 0x0a000000 -#define BASS_DEVICE_TYPE_DISPLAYPORT 0x40000000 - -// BASS_GetDeviceInfo flags -#define BASS_DEVICES_AIRPLAY 0x1000000 - -typedef struct { - DWORD flags; // device capabilities (DSCAPS_xxx flags) - DWORD hwsize; // size of total device hardware memory - DWORD hwfree; // size of free device hardware memory - DWORD freesam; // number of free sample slots in the hardware - DWORD free3d; // number of free 3D sample slots in the hardware - DWORD minrate; // min sample rate supported by the hardware - DWORD maxrate; // max sample rate supported by the hardware - BOOL eax; // device supports EAX? (always FALSE if BASS_DEVICE_3D was not used) - DWORD minbuf; // recommended minimum buffer length in ms (requires BASS_DEVICE_LATENCY) - DWORD dsver; // DirectSound version - DWORD latency; // delay (in ms) before start of playback (requires BASS_DEVICE_LATENCY) - DWORD initflags; // BASS_Init "flags" parameter - DWORD speakers; // number of speakers available - DWORD freq; // current output rate -} BASS_INFO; - -// BASS_INFO flags (from DSOUND.H) -#define DSCAPS_CONTINUOUSRATE 0x00000010 // supports all sample rates between min/maxrate -#define DSCAPS_EMULDRIVER 0x00000020 // device does NOT have hardware DirectSound support -#define DSCAPS_CERTIFIED 0x00000040 // device driver has been certified by Microsoft -#define DSCAPS_SECONDARYMONO 0x00000100 // mono -#define DSCAPS_SECONDARYSTEREO 0x00000200 // stereo -#define DSCAPS_SECONDARY8BIT 0x00000400 // 8 bit -#define DSCAPS_SECONDARY16BIT 0x00000800 // 16 bit - -// Recording device info structure -typedef struct { - DWORD flags; // device capabilities (DSCCAPS_xxx flags) - DWORD formats; // supported standard formats (WAVE_FORMAT_xxx flags) - DWORD inputs; // number of inputs - BOOL singlein; // TRUE = only 1 input can be set at a time - DWORD freq; // current input rate -} BASS_RECORDINFO; - -// BASS_RECORDINFO flags (from DSOUND.H) -#define DSCCAPS_EMULDRIVER DSCAPS_EMULDRIVER // device does NOT have hardware DirectSound recording support -#define DSCCAPS_CERTIFIED DSCAPS_CERTIFIED // device driver has been certified by Microsoft - -// defines for formats field of BASS_RECORDINFO (from MMSYSTEM.H) -#ifndef WAVE_FORMAT_1M08 -#define WAVE_FORMAT_1M08 0x00000001 /* 11.025 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_1S08 0x00000002 /* 11.025 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_1M16 0x00000004 /* 11.025 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_1S16 0x00000008 /* 11.025 kHz, Stereo, 16-bit */ -#define WAVE_FORMAT_2M08 0x00000010 /* 22.05 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_2S08 0x00000020 /* 22.05 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_2M16 0x00000040 /* 22.05 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_2S16 0x00000080 /* 22.05 kHz, Stereo, 16-bit */ -#define WAVE_FORMAT_4M08 0x00000100 /* 44.1 kHz, Mono, 8-bit */ -#define WAVE_FORMAT_4S08 0x00000200 /* 44.1 kHz, Stereo, 8-bit */ -#define WAVE_FORMAT_4M16 0x00000400 /* 44.1 kHz, Mono, 16-bit */ -#define WAVE_FORMAT_4S16 0x00000800 /* 44.1 kHz, Stereo, 16-bit */ -#endif - -// Sample info structure -typedef struct { - DWORD freq; // default playback rate - float volume; // default volume (0-1) - float pan; // default pan (-1=left, 0=middle, 1=right) - DWORD flags; // BASS_SAMPLE_xxx flags - DWORD length; // length (in bytes) - DWORD max; // maximum simultaneous playbacks - DWORD origres; // original resolution bits - DWORD chans; // number of channels - DWORD mingap; // minimum gap (ms) between creating channels - DWORD mode3d; // BASS_3DMODE_xxx mode - float mindist; // minimum distance - float maxdist; // maximum distance - DWORD iangle; // angle of inside projection cone - DWORD oangle; // angle of outside projection cone - float outvol; // delta-volume outside the projection cone - DWORD vam; // voice allocation/management flags (BASS_VAM_xxx) - DWORD priority; // priority (0=lowest, 0xffffffff=highest) -} BASS_SAMPLE; - -#define BASS_SAMPLE_8BITS 1 // 8 bit -#define BASS_SAMPLE_FLOAT 256 // 32 bit floating-point -#define BASS_SAMPLE_MONO 2 // mono -#define BASS_SAMPLE_LOOP 4 // looped -#define BASS_SAMPLE_3D 8 // 3D functionality -#define BASS_SAMPLE_SOFTWARE 16 // not using hardware mixing -#define BASS_SAMPLE_MUTEMAX 32 // mute at max distance (3D only) -#define BASS_SAMPLE_VAM 64 // DX7 voice allocation & management -#define BASS_SAMPLE_FX 128 // old implementation of DX8 effects -#define BASS_SAMPLE_OVER_VOL 0x10000 // override lowest volume -#define BASS_SAMPLE_OVER_POS 0x20000 // override longest playing -#define BASS_SAMPLE_OVER_DIST 0x30000 // override furthest from listener (3D only) - -#define BASS_STREAM_PRESCAN 0x20000 // enable pin-point seeking/length (MP3/MP2/MP1) -#define BASS_MP3_SETPOS BASS_STREAM_PRESCAN -#define BASS_STREAM_AUTOFREE 0x40000 // automatically free the stream when it stop/ends -#define BASS_STREAM_RESTRATE 0x80000 // restrict the download rate of internet file streams -#define BASS_STREAM_BLOCK 0x100000 // download/play internet file stream in small blocks -#define BASS_STREAM_DECODE 0x200000 // don't play the stream, only decode (BASS_ChannelGetData) -#define BASS_STREAM_STATUS 0x800000 // give server status info (HTTP/ICY tags) in DOWNLOADPROC - -#define BASS_MUSIC_FLOAT BASS_SAMPLE_FLOAT -#define BASS_MUSIC_MONO BASS_SAMPLE_MONO -#define BASS_MUSIC_LOOP BASS_SAMPLE_LOOP -#define BASS_MUSIC_3D BASS_SAMPLE_3D -#define BASS_MUSIC_FX BASS_SAMPLE_FX -#define BASS_MUSIC_AUTOFREE BASS_STREAM_AUTOFREE -#define BASS_MUSIC_DECODE BASS_STREAM_DECODE -#define BASS_MUSIC_PRESCAN BASS_STREAM_PRESCAN // calculate playback length -#define BASS_MUSIC_CALCLEN BASS_MUSIC_PRESCAN -#define BASS_MUSIC_RAMP 0x200 // normal ramping -#define BASS_MUSIC_RAMPS 0x400 // sensitive ramping -#define BASS_MUSIC_SURROUND 0x800 // surround sound -#define BASS_MUSIC_SURROUND2 0x1000 // surround sound (mode 2) -#define BASS_MUSIC_FT2PAN 0x2000 // apply FastTracker 2 panning to XM files -#define BASS_MUSIC_FT2MOD 0x2000 // play .MOD as FastTracker 2 does -#define BASS_MUSIC_PT1MOD 0x4000 // play .MOD as ProTracker 1 does -#define BASS_MUSIC_NONINTER 0x10000 // non-interpolated sample mixing -#define BASS_MUSIC_SINCINTER 0x800000 // sinc interpolated sample mixing -#define BASS_MUSIC_POSRESET 0x8000 // stop all notes when moving position -#define BASS_MUSIC_POSRESETEX 0x400000 // stop all notes and reset bmp/etc when moving position -#define BASS_MUSIC_STOPBACK 0x80000 // stop the music on a backwards jump effect -#define BASS_MUSIC_NOSAMPLE 0x100000 // don't load the samples - -// Speaker assignment flags -#define BASS_SPEAKER_FRONT 0x1000000 // front speakers -#define BASS_SPEAKER_REAR 0x2000000 // rear/side speakers -#define BASS_SPEAKER_CENLFE 0x3000000 // center & LFE speakers (5.1) -#define BASS_SPEAKER_REAR2 0x4000000 // rear center speakers (7.1) -#define BASS_SPEAKER_N(n) ((n)<<24) // n'th pair of speakers (max 15) -#define BASS_SPEAKER_LEFT 0x10000000 // modifier: left -#define BASS_SPEAKER_RIGHT 0x20000000 // modifier: right -#define BASS_SPEAKER_FRONTLEFT BASS_SPEAKER_FRONT|BASS_SPEAKER_LEFT -#define BASS_SPEAKER_FRONTRIGHT BASS_SPEAKER_FRONT|BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_REARLEFT BASS_SPEAKER_REAR|BASS_SPEAKER_LEFT -#define BASS_SPEAKER_REARRIGHT BASS_SPEAKER_REAR|BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_CENTER BASS_SPEAKER_CENLFE|BASS_SPEAKER_LEFT -#define BASS_SPEAKER_LFE BASS_SPEAKER_CENLFE|BASS_SPEAKER_RIGHT -#define BASS_SPEAKER_REAR2LEFT BASS_SPEAKER_REAR2|BASS_SPEAKER_LEFT -#define BASS_SPEAKER_REAR2RIGHT BASS_SPEAKER_REAR2|BASS_SPEAKER_RIGHT - -#define BASS_ASYNCFILE 0x40000000 -#define BASS_UNICODE 0x80000000 - -#define BASS_RECORD_PAUSE 0x8000 // start recording paused -#define BASS_RECORD_ECHOCANCEL 0x2000 -#define BASS_RECORD_AGC 0x4000 - -// DX7 voice allocation & management flags -#define BASS_VAM_HARDWARE 1 -#define BASS_VAM_SOFTWARE 2 -#define BASS_VAM_TERM_TIME 4 -#define BASS_VAM_TERM_DIST 8 -#define BASS_VAM_TERM_PRIO 16 - -// Channel info structure -typedef struct { - DWORD freq; // default playback rate - DWORD chans; // channels - DWORD flags; // BASS_SAMPLE/STREAM/MUSIC/SPEAKER flags - DWORD ctype; // type of channel - DWORD origres; // original resolution - HPLUGIN plugin; // plugin - HSAMPLE sample; // sample - const char *filename; // filename -} BASS_CHANNELINFO; - -// BASS_CHANNELINFO types -#define BASS_CTYPE_SAMPLE 1 -#define BASS_CTYPE_RECORD 2 -#define BASS_CTYPE_STREAM 0x10000 -#define BASS_CTYPE_STREAM_OGG 0x10002 -#define BASS_CTYPE_STREAM_MP1 0x10003 -#define BASS_CTYPE_STREAM_MP2 0x10004 -#define BASS_CTYPE_STREAM_MP3 0x10005 -#define BASS_CTYPE_STREAM_AIFF 0x10006 -#define BASS_CTYPE_STREAM_CA 0x10007 -#define BASS_CTYPE_STREAM_MF 0x10008 -#define BASS_CTYPE_STREAM_WAV 0x40000 // WAVE flag, LOWORD=codec -#define BASS_CTYPE_STREAM_WAV_PCM 0x50001 -#define BASS_CTYPE_STREAM_WAV_FLOAT 0x50003 -#define BASS_CTYPE_MUSIC_MOD 0x20000 -#define BASS_CTYPE_MUSIC_MTM 0x20001 -#define BASS_CTYPE_MUSIC_S3M 0x20002 -#define BASS_CTYPE_MUSIC_XM 0x20003 -#define BASS_CTYPE_MUSIC_IT 0x20004 -#define BASS_CTYPE_MUSIC_MO3 0x00100 // MO3 flag - -typedef struct { - DWORD ctype; // channel type -#if defined(_WIN32_WCE) || (WINAPI_FAMILY && WINAPI_FAMILY!=WINAPI_FAMILY_DESKTOP_APP) - const wchar_t *name; // format description - const wchar_t *exts; // file extension filter (*.ext1;*.ext2;etc...) -#else - const char *name; // format description - const char *exts; // file extension filter (*.ext1;*.ext2;etc...) -#endif -} BASS_PLUGINFORM; - -typedef struct { - DWORD version; // version (same form as BASS_GetVersion) - DWORD formatc; // number of formats - const BASS_PLUGINFORM *formats; // the array of formats -} BASS_PLUGININFO; - -// 3D vector (for 3D positions/velocities/orientations) -typedef struct BASS_3DVECTOR { -#ifdef __cplusplus - BASS_3DVECTOR() {}; - BASS_3DVECTOR(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}; -#endif - float x; // +=right, -=left - float y; // +=up, -=down - float z; // +=front, -=behind -} BASS_3DVECTOR; - -// 3D channel modes -#define BASS_3DMODE_NORMAL 0 // normal 3D processing -#define BASS_3DMODE_RELATIVE 1 // position is relative to the listener -#define BASS_3DMODE_OFF 2 // no 3D processing - -// software 3D mixing algorithms (used with BASS_CONFIG_3DALGORITHM) -#define BASS_3DALG_DEFAULT 0 -#define BASS_3DALG_OFF 1 -#define BASS_3DALG_FULL 2 -#define BASS_3DALG_LIGHT 3 - -// EAX environments, use with BASS_SetEAXParameters -enum -{ - EAX_ENVIRONMENT_GENERIC, - EAX_ENVIRONMENT_PADDEDCELL, - EAX_ENVIRONMENT_ROOM, - EAX_ENVIRONMENT_BATHROOM, - EAX_ENVIRONMENT_LIVINGROOM, - EAX_ENVIRONMENT_STONEROOM, - EAX_ENVIRONMENT_AUDITORIUM, - EAX_ENVIRONMENT_CONCERTHALL, - EAX_ENVIRONMENT_CAVE, - EAX_ENVIRONMENT_ARENA, - EAX_ENVIRONMENT_HANGAR, - EAX_ENVIRONMENT_CARPETEDHALLWAY, - EAX_ENVIRONMENT_HALLWAY, - EAX_ENVIRONMENT_STONECORRIDOR, - EAX_ENVIRONMENT_ALLEY, - EAX_ENVIRONMENT_FOREST, - EAX_ENVIRONMENT_CITY, - EAX_ENVIRONMENT_MOUNTAINS, - EAX_ENVIRONMENT_QUARRY, - EAX_ENVIRONMENT_PLAIN, - EAX_ENVIRONMENT_PARKINGLOT, - EAX_ENVIRONMENT_SEWERPIPE, - EAX_ENVIRONMENT_UNDERWATER, - EAX_ENVIRONMENT_DRUGGED, - EAX_ENVIRONMENT_DIZZY, - EAX_ENVIRONMENT_PSYCHOTIC, - - EAX_ENVIRONMENT_COUNT // total number of environments -}; - -// EAX presets, usage: BASS_SetEAXParameters(EAX_PRESET_xxx) -#define EAX_PRESET_GENERIC EAX_ENVIRONMENT_GENERIC,0.5F,1.493F,0.5F -#define EAX_PRESET_PADDEDCELL EAX_ENVIRONMENT_PADDEDCELL,0.25F,0.1F,0.0F -#define EAX_PRESET_ROOM EAX_ENVIRONMENT_ROOM,0.417F,0.4F,0.666F -#define EAX_PRESET_BATHROOM EAX_ENVIRONMENT_BATHROOM,0.653F,1.499F,0.166F -#define EAX_PRESET_LIVINGROOM EAX_ENVIRONMENT_LIVINGROOM,0.208F,0.478F,0.0F -#define EAX_PRESET_STONEROOM EAX_ENVIRONMENT_STONEROOM,0.5F,2.309F,0.888F -#define EAX_PRESET_AUDITORIUM EAX_ENVIRONMENT_AUDITORIUM,0.403F,4.279F,0.5F -#define EAX_PRESET_CONCERTHALL EAX_ENVIRONMENT_CONCERTHALL,0.5F,3.961F,0.5F -#define EAX_PRESET_CAVE EAX_ENVIRONMENT_CAVE,0.5F,2.886F,1.304F -#define EAX_PRESET_ARENA EAX_ENVIRONMENT_ARENA,0.361F,7.284F,0.332F -#define EAX_PRESET_HANGAR EAX_ENVIRONMENT_HANGAR,0.5F,10.0F,0.3F -#define EAX_PRESET_CARPETEDHALLWAY EAX_ENVIRONMENT_CARPETEDHALLWAY,0.153F,0.259F,2.0F -#define EAX_PRESET_HALLWAY EAX_ENVIRONMENT_HALLWAY,0.361F,1.493F,0.0F -#define EAX_PRESET_STONECORRIDOR EAX_ENVIRONMENT_STONECORRIDOR,0.444F,2.697F,0.638F -#define EAX_PRESET_ALLEY EAX_ENVIRONMENT_ALLEY,0.25F,1.752F,0.776F -#define EAX_PRESET_FOREST EAX_ENVIRONMENT_FOREST,0.111F,3.145F,0.472F -#define EAX_PRESET_CITY EAX_ENVIRONMENT_CITY,0.111F,2.767F,0.224F -#define EAX_PRESET_MOUNTAINS EAX_ENVIRONMENT_MOUNTAINS,0.194F,7.841F,0.472F -#define EAX_PRESET_QUARRY EAX_ENVIRONMENT_QUARRY,1.0F,1.499F,0.5F -#define EAX_PRESET_PLAIN EAX_ENVIRONMENT_PLAIN,0.097F,2.767F,0.224F -#define EAX_PRESET_PARKINGLOT EAX_ENVIRONMENT_PARKINGLOT,0.208F,1.652F,1.5F -#define EAX_PRESET_SEWERPIPE EAX_ENVIRONMENT_SEWERPIPE,0.652F,2.886F,0.25F -#define EAX_PRESET_UNDERWATER EAX_ENVIRONMENT_UNDERWATER,1.0F,1.499F,0.0F -#define EAX_PRESET_DRUGGED EAX_ENVIRONMENT_DRUGGED,0.875F,8.392F,1.388F -#define EAX_PRESET_DIZZY EAX_ENVIRONMENT_DIZZY,0.139F,17.234F,0.666F -#define EAX_PRESET_PSYCHOTIC EAX_ENVIRONMENT_PSYCHOTIC,0.486F,7.563F,0.806F - -typedef DWORD (CALLBACK STREAMPROC)(HSTREAM handle, void *buffer, DWORD length, void *user); -/* User stream callback function. NOTE: A stream function should obviously be as quick -as possible, other streams (and MOD musics) can't be mixed until it's finished. -handle : The stream that needs writing -buffer : Buffer to write the samples in -length : Number of bytes to write -user : The 'user' parameter value given when calling BASS_StreamCreate -RETURN : Number of bytes written. Set the BASS_STREAMPROC_END flag to end - the stream. */ - -#define BASS_STREAMPROC_END 0x80000000 // end of user stream flag - -// special STREAMPROCs -#define STREAMPROC_DUMMY (STREAMPROC*)0 // "dummy" stream -#define STREAMPROC_PUSH (STREAMPROC*)-1 // push stream - -// BASS_StreamCreateFileUser file systems -#define STREAMFILE_NOBUFFER 0 -#define STREAMFILE_BUFFER 1 -#define STREAMFILE_BUFFERPUSH 2 - -// User file stream callback functions -typedef void (CALLBACK FILECLOSEPROC)(void *user); -typedef QWORD (CALLBACK FILELENPROC)(void *user); -typedef DWORD (CALLBACK FILEREADPROC)(void *buffer, DWORD length, void *user); -typedef BOOL (CALLBACK FILESEEKPROC)(QWORD offset, void *user); - -typedef struct { - FILECLOSEPROC *close; - FILELENPROC *length; - FILEREADPROC *read; - FILESEEKPROC *seek; -} BASS_FILEPROCS; - -// BASS_StreamPutFileData options -#define BASS_FILEDATA_END 0 // end & close the file - -// BASS_StreamGetFilePosition modes -#define BASS_FILEPOS_CURRENT 0 -#define BASS_FILEPOS_DECODE BASS_FILEPOS_CURRENT -#define BASS_FILEPOS_DOWNLOAD 1 -#define BASS_FILEPOS_END 2 -#define BASS_FILEPOS_START 3 -#define BASS_FILEPOS_CONNECTED 4 -#define BASS_FILEPOS_BUFFER 5 -#define BASS_FILEPOS_SOCKET 6 -#define BASS_FILEPOS_ASYNCBUF 7 -#define BASS_FILEPOS_SIZE 8 - -typedef void (CALLBACK DOWNLOADPROC)(const void *buffer, DWORD length, void *user); -/* Internet stream download callback function. -buffer : Buffer containing the downloaded data... NULL=end of download -length : Number of bytes in the buffer -user : The 'user' parameter value given when calling BASS_StreamCreateURL */ - -// BASS_ChannelSetSync types -#define BASS_SYNC_POS 0 -#define BASS_SYNC_END 2 -#define BASS_SYNC_META 4 -#define BASS_SYNC_SLIDE 5 -#define BASS_SYNC_STALL 6 -#define BASS_SYNC_DOWNLOAD 7 -#define BASS_SYNC_FREE 8 -#define BASS_SYNC_SETPOS 11 -#define BASS_SYNC_MUSICPOS 10 -#define BASS_SYNC_MUSICINST 1 -#define BASS_SYNC_MUSICFX 3 -#define BASS_SYNC_OGG_CHANGE 12 -#define BASS_SYNC_MIXTIME 0x40000000 // flag: sync at mixtime, else at playtime -#define BASS_SYNC_ONETIME 0x80000000 // flag: sync only once, else continuously - -typedef void (CALLBACK SYNCPROC)(HSYNC handle, DWORD channel, DWORD data, void *user); -/* Sync callback function. NOTE: a sync callback function should be very -quick as other syncs can't be processed until it has finished. If the sync -is a "mixtime" sync, then other streams and MOD musics can't be mixed until -it's finished either. -handle : The sync that has occured -channel: Channel that the sync occured in -data : Additional data associated with the sync's occurance -user : The 'user' parameter given when calling BASS_ChannelSetSync */ - -typedef void (CALLBACK DSPPROC)(HDSP handle, DWORD channel, void *buffer, DWORD length, void *user); -/* DSP callback function. NOTE: A DSP function should obviously be as quick as -possible... other DSP functions, streams and MOD musics can not be processed -until it's finished. -handle : The DSP handle -channel: Channel that the DSP is being applied to -buffer : Buffer to apply the DSP to -length : Number of bytes in the buffer -user : The 'user' parameter given when calling BASS_ChannelSetDSP */ - -typedef BOOL (CALLBACK RECORDPROC)(HRECORD handle, const void *buffer, DWORD length, void *user); -/* Recording callback function. -handle : The recording handle -buffer : Buffer containing the recorded sample data -length : Number of bytes -user : The 'user' parameter value given when calling BASS_RecordStart -RETURN : TRUE = continue recording, FALSE = stop */ - -// BASS_ChannelIsActive return values -#define BASS_ACTIVE_STOPPED 0 -#define BASS_ACTIVE_PLAYING 1 -#define BASS_ACTIVE_STALLED 2 -#define BASS_ACTIVE_PAUSED 3 - -// Channel attributes -#define BASS_ATTRIB_FREQ 1 -#define BASS_ATTRIB_VOL 2 -#define BASS_ATTRIB_PAN 3 -#define BASS_ATTRIB_EAXMIX 4 -#define BASS_ATTRIB_NOBUFFER 5 -#define BASS_ATTRIB_VBR 6 -#define BASS_ATTRIB_CPU 7 -#define BASS_ATTRIB_SRC 8 -#define BASS_ATTRIB_NET_RESUME 9 -#define BASS_ATTRIB_SCANINFO 10 -#define BASS_ATTRIB_NORAMP 11 -#define BASS_ATTRIB_BITRATE 12 -#define BASS_ATTRIB_MUSIC_AMPLIFY 0x100 -#define BASS_ATTRIB_MUSIC_PANSEP 0x101 -#define BASS_ATTRIB_MUSIC_PSCALER 0x102 -#define BASS_ATTRIB_MUSIC_BPM 0x103 -#define BASS_ATTRIB_MUSIC_SPEED 0x104 -#define BASS_ATTRIB_MUSIC_VOL_GLOBAL 0x105 -#define BASS_ATTRIB_MUSIC_ACTIVE 0x106 -#define BASS_ATTRIB_MUSIC_VOL_CHAN 0x200 // + channel # -#define BASS_ATTRIB_MUSIC_VOL_INST 0x300 // + instrument # - -// BASS_ChannelGetData flags -#define BASS_DATA_AVAILABLE 0 // query how much data is buffered -#define BASS_DATA_FIXED 0x20000000 // flag: return 8.24 fixed-point data -#define BASS_DATA_FLOAT 0x40000000 // flag: return floating-point sample data -#define BASS_DATA_FFT256 0x80000000 // 256 sample FFT -#define BASS_DATA_FFT512 0x80000001 // 512 FFT -#define BASS_DATA_FFT1024 0x80000002 // 1024 FFT -#define BASS_DATA_FFT2048 0x80000003 // 2048 FFT -#define BASS_DATA_FFT4096 0x80000004 // 4096 FFT -#define BASS_DATA_FFT8192 0x80000005 // 8192 FFT -#define BASS_DATA_FFT16384 0x80000006 // 16384 FFT -#define BASS_DATA_FFT32768 0x80000007 // 32768 FFT -#define BASS_DATA_FFT_INDIVIDUAL 0x10 // FFT flag: FFT for each channel, else all combined -#define BASS_DATA_FFT_NOWINDOW 0x20 // FFT flag: no Hanning window -#define BASS_DATA_FFT_REMOVEDC 0x40 // FFT flag: pre-remove DC bias -#define BASS_DATA_FFT_COMPLEX 0x80 // FFT flag: return complex data - -// BASS_ChannelGetLevelEx flags -#define BASS_LEVEL_MONO 1 -#define BASS_LEVEL_STEREO 2 -#define BASS_LEVEL_RMS 4 - -// BASS_ChannelGetTags types : what's returned -#define BASS_TAG_ID3 0 // ID3v1 tags : TAG_ID3 structure -#define BASS_TAG_ID3V2 1 // ID3v2 tags : variable length block -#define BASS_TAG_OGG 2 // OGG comments : series of null-terminated UTF-8 strings -#define BASS_TAG_HTTP 3 // HTTP headers : series of null-terminated ANSI strings -#define BASS_TAG_ICY 4 // ICY headers : series of null-terminated ANSI strings -#define BASS_TAG_META 5 // ICY metadata : ANSI string -#define BASS_TAG_APE 6 // APE tags : series of null-terminated UTF-8 strings -#define BASS_TAG_MP4 7 // MP4/iTunes metadata : series of null-terminated UTF-8 strings -#define BASS_TAG_WMA 8 // WMA tags : series of null-terminated UTF-8 strings -#define BASS_TAG_VENDOR 9 // OGG encoder : UTF-8 string -#define BASS_TAG_LYRICS3 10 // Lyric3v2 tag : ASCII string -#define BASS_TAG_CA_CODEC 11 // CoreAudio codec info : TAG_CA_CODEC structure -#define BASS_TAG_MF 13 // Media Foundation tags : series of null-terminated UTF-8 strings -#define BASS_TAG_WAVEFORMAT 14 // WAVE format : WAVEFORMATEEX structure -#define BASS_TAG_RIFF_INFO 0x100 // RIFF "INFO" tags : series of null-terminated ANSI strings -#define BASS_TAG_RIFF_BEXT 0x101 // RIFF/BWF "bext" tags : TAG_BEXT structure -#define BASS_TAG_RIFF_CART 0x102 // RIFF/BWF "cart" tags : TAG_CART structure -#define BASS_TAG_RIFF_DISP 0x103 // RIFF "DISP" text tag : ANSI string -#define BASS_TAG_APE_BINARY 0x1000 // + index #, binary APE tag : TAG_APE_BINARY structure -#define BASS_TAG_MUSIC_NAME 0x10000 // MOD music name : ANSI string -#define BASS_TAG_MUSIC_MESSAGE 0x10001 // MOD message : ANSI string -#define BASS_TAG_MUSIC_ORDERS 0x10002 // MOD order list : BYTE array of pattern numbers -#define BASS_TAG_MUSIC_AUTH 0x10003 // MOD author : UTF-8 string -#define BASS_TAG_MUSIC_INST 0x10100 // + instrument #, MOD instrument name : ANSI string -#define BASS_TAG_MUSIC_SAMPLE 0x10300 // + sample #, MOD sample name : ANSI string - -// ID3v1 tag structure -typedef struct { - char id[3]; - char title[30]; - char artist[30]; - char album[30]; - char year[4]; - char comment[30]; - BYTE genre; -} TAG_ID3; - -// Binary APE tag structure -typedef struct { - const char *key; - const void *data; - DWORD length; -} TAG_APE_BINARY; - -// BWF "bext" tag structure -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4200) -#endif -#pragma pack(push,1) -typedef struct { - char Description[256]; // description - char Originator[32]; // name of the originator - char OriginatorReference[32]; // reference of the originator - char OriginationDate[10]; // date of creation (yyyy-mm-dd) - char OriginationTime[8]; // time of creation (hh-mm-ss) - QWORD TimeReference; // first sample count since midnight (little-endian) - WORD Version; // BWF version (little-endian) - BYTE UMID[64]; // SMPTE UMID - BYTE Reserved[190]; -#if defined(__GNUC__) && __GNUC__<3 - char CodingHistory[0]; // history -#elif 1 // change to 0 if compiler fails the following line - char CodingHistory[]; // history -#else - char CodingHistory[1]; // history -#endif -} TAG_BEXT; -#pragma pack(pop) - -// BWF "cart" tag structures -typedef struct -{ - DWORD dwUsage; // FOURCC timer usage ID - DWORD dwValue; // timer value in samples from head -} TAG_CART_TIMER; - -typedef struct -{ - char Version[4]; // version of the data structure - char Title[64]; // title of cart audio sequence - char Artist[64]; // artist or creator name - char CutID[64]; // cut number identification - char ClientID[64]; // client identification - char Category[64]; // category ID, PSA, NEWS, etc - char Classification[64]; // classification or auxiliary key - char OutCue[64]; // out cue text - char StartDate[10]; // yyyy-mm-dd - char StartTime[8]; // hh:mm:ss - char EndDate[10]; // yyyy-mm-dd - char EndTime[8]; // hh:mm:ss - char ProducerAppID[64]; // name of vendor or application - char ProducerAppVersion[64]; // version of producer application - char UserDef[64]; // user defined text - DWORD dwLevelReference; // sample value for 0 dB reference - TAG_CART_TIMER PostTimer[8]; // 8 time markers after head - char Reserved[276]; - char URL[1024]; // uniform resource locator -#if defined(__GNUC__) && __GNUC__<3 - char TagText[0]; // free form text for scripts or tags -#elif 1 // change to 0 if compiler fails the following line - char TagText[]; // free form text for scripts or tags -#else - char TagText[1]; // free form text for scripts or tags -#endif -} TAG_CART; -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -// CoreAudio codec info structure -typedef struct { - DWORD ftype; // file format - DWORD atype; // audio format - const char *name; // description -} TAG_CA_CODEC; - -#ifndef _WAVEFORMATEX_ -#define _WAVEFORMATEX_ -#pragma pack(push,1) -typedef struct tWAVEFORMATEX -{ - WORD wFormatTag; - WORD nChannels; - DWORD nSamplesPerSec; - DWORD nAvgBytesPerSec; - WORD nBlockAlign; - WORD wBitsPerSample; - WORD cbSize; -} WAVEFORMATEX, *PWAVEFORMATEX, *LPWAVEFORMATEX; -typedef const WAVEFORMATEX *LPCWAVEFORMATEX; -#pragma pack(pop) -#endif - -// BASS_ChannelGetLength/GetPosition/SetPosition modes -#define BASS_POS_BYTE 0 // byte position -#define BASS_POS_MUSIC_ORDER 1 // order.row position, MAKELONG(order,row) -#define BASS_POS_OGG 3 // OGG bitstream number -#define BASS_POS_INEXACT 0x8000000 // flag: allow seeking to inexact position -#define BASS_POS_DECODE 0x10000000 // flag: get the decoding (not playing) position -#define BASS_POS_DECODETO 0x20000000 // flag: decode to the position instead of seeking -#define BASS_POS_SCAN 0x40000000 // flag: scan to the position - -// BASS_RecordSetInput flags -#define BASS_INPUT_OFF 0x10000 -#define BASS_INPUT_ON 0x20000 - -#define BASS_INPUT_TYPE_MASK 0xff000000 -#define BASS_INPUT_TYPE_UNDEF 0x00000000 -#define BASS_INPUT_TYPE_DIGITAL 0x01000000 -#define BASS_INPUT_TYPE_LINE 0x02000000 -#define BASS_INPUT_TYPE_MIC 0x03000000 -#define BASS_INPUT_TYPE_SYNTH 0x04000000 -#define BASS_INPUT_TYPE_CD 0x05000000 -#define BASS_INPUT_TYPE_PHONE 0x06000000 -#define BASS_INPUT_TYPE_SPEAKER 0x07000000 -#define BASS_INPUT_TYPE_WAVE 0x08000000 -#define BASS_INPUT_TYPE_AUX 0x09000000 -#define BASS_INPUT_TYPE_ANALOG 0x0a000000 - -// DX8 effect types, use with BASS_ChannelSetFX -enum -{ - BASS_FX_DX8_CHORUS, - BASS_FX_DX8_COMPRESSOR, - BASS_FX_DX8_DISTORTION, - BASS_FX_DX8_ECHO, - BASS_FX_DX8_FLANGER, - BASS_FX_DX8_GARGLE, - BASS_FX_DX8_I3DL2REVERB, - BASS_FX_DX8_PARAMEQ, - BASS_FX_DX8_REVERB -}; - -typedef struct { - float fWetDryMix; - float fDepth; - float fFeedback; - float fFrequency; - DWORD lWaveform; // 0=triangle, 1=sine - float fDelay; - DWORD lPhase; // BASS_DX8_PHASE_xxx -} BASS_DX8_CHORUS; - -typedef struct { - float fGain; - float fAttack; - float fRelease; - float fThreshold; - float fRatio; - float fPredelay; -} BASS_DX8_COMPRESSOR; - -typedef struct { - float fGain; - float fEdge; - float fPostEQCenterFrequency; - float fPostEQBandwidth; - float fPreLowpassCutoff; -} BASS_DX8_DISTORTION; - -typedef struct { - float fWetDryMix; - float fFeedback; - float fLeftDelay; - float fRightDelay; - BOOL lPanDelay; -} BASS_DX8_ECHO; - -typedef struct { - float fWetDryMix; - float fDepth; - float fFeedback; - float fFrequency; - DWORD lWaveform; // 0=triangle, 1=sine - float fDelay; - DWORD lPhase; // BASS_DX8_PHASE_xxx -} BASS_DX8_FLANGER; - -typedef struct { - DWORD dwRateHz; // Rate of modulation in hz - DWORD dwWaveShape; // 0=triangle, 1=square -} BASS_DX8_GARGLE; - -typedef struct { - int lRoom; // [-10000, 0] default: -1000 mB - int lRoomHF; // [-10000, 0] default: 0 mB - float flRoomRolloffFactor; // [0.0, 10.0] default: 0.0 - float flDecayTime; // [0.1, 20.0] default: 1.49s - float flDecayHFRatio; // [0.1, 2.0] default: 0.83 - int lReflections; // [-10000, 1000] default: -2602 mB - float flReflectionsDelay; // [0.0, 0.3] default: 0.007 s - int lReverb; // [-10000, 2000] default: 200 mB - float flReverbDelay; // [0.0, 0.1] default: 0.011 s - float flDiffusion; // [0.0, 100.0] default: 100.0 % - float flDensity; // [0.0, 100.0] default: 100.0 % - float flHFReference; // [20.0, 20000.0] default: 5000.0 Hz -} BASS_DX8_I3DL2REVERB; - -typedef struct { - float fCenter; - float fBandwidth; - float fGain; -} BASS_DX8_PARAMEQ; - -typedef struct { - float fInGain; // [-96.0,0.0] default: 0.0 dB - float fReverbMix; // [-96.0,0.0] default: 0.0 db - float fReverbTime; // [0.001,3000.0] default: 1000.0 ms - float fHighFreqRTRatio; // [0.001,0.999] default: 0.001 -} BASS_DX8_REVERB; - -#define BASS_DX8_PHASE_NEG_180 0 -#define BASS_DX8_PHASE_NEG_90 1 -#define BASS_DX8_PHASE_ZERO 2 -#define BASS_DX8_PHASE_90 3 -#define BASS_DX8_PHASE_180 4 - -typedef void (CALLBACK IOSNOTIFYPROC)(DWORD status); -/* iOS notification callback function. -status : The notification (BASS_IOSNOTIFY_xxx) */ - -#define BASS_IOSNOTIFY_INTERRUPT 1 // interruption started -#define BASS_IOSNOTIFY_INTERRUPT_END 2 // interruption ended - -BOOL BASSDEF(BASS_SetConfig)(DWORD option, DWORD value); -DWORD BASSDEF(BASS_GetConfig)(DWORD option); -BOOL BASSDEF(BASS_SetConfigPtr)(DWORD option, const void *value); -void *BASSDEF(BASS_GetConfigPtr)(DWORD option); -DWORD BASSDEF(BASS_GetVersion)(); -int BASSDEF(BASS_ErrorGetCode)(); -BOOL BASSDEF(BASS_GetDeviceInfo)(DWORD device, BASS_DEVICEINFO *info); -#if defined(_WIN32) && !defined(_WIN32_WCE) && !(WINAPI_FAMILY && WINAPI_FAMILY!=WINAPI_FAMILY_DESKTOP_APP) -BOOL BASSDEF(BASS_Init)(int device, DWORD freq, DWORD flags, HWND win, const GUID *dsguid); -#else -BOOL BASSDEF(BASS_Init)(int device, DWORD freq, DWORD flags, void *win, void *dsguid); -#endif -BOOL BASSDEF(BASS_SetDevice)(DWORD device); -DWORD BASSDEF(BASS_GetDevice)(); -BOOL BASSDEF(BASS_Free)(); -#if defined(_WIN32) && !defined(_WIN32_WCE) && !(WINAPI_FAMILY && WINAPI_FAMILY!=WINAPI_FAMILY_DESKTOP_APP) -void *BASSDEF(BASS_GetDSoundObject)(DWORD object); -#endif -BOOL BASSDEF(BASS_GetInfo)(BASS_INFO *info); -BOOL BASSDEF(BASS_Update)(DWORD length); -float BASSDEF(BASS_GetCPU)(); -BOOL BASSDEF(BASS_Start)(); -BOOL BASSDEF(BASS_Stop)(); -BOOL BASSDEF(BASS_Pause)(); -BOOL BASSDEF(BASS_SetVolume)(float volume); -float BASSDEF(BASS_GetVolume)(); - -HPLUGIN BASSDEF(BASS_PluginLoad)(const char *file, DWORD flags); -BOOL BASSDEF(BASS_PluginFree)(HPLUGIN handle); -const BASS_PLUGININFO *BASSDEF(BASS_PluginGetInfo)(HPLUGIN handle); - -BOOL BASSDEF(BASS_Set3DFactors)(float distf, float rollf, float doppf); -BOOL BASSDEF(BASS_Get3DFactors)(float *distf, float *rollf, float *doppf); -BOOL BASSDEF(BASS_Set3DPosition)(const BASS_3DVECTOR *pos, const BASS_3DVECTOR *vel, const BASS_3DVECTOR *front, const BASS_3DVECTOR *top); -BOOL BASSDEF(BASS_Get3DPosition)(BASS_3DVECTOR *pos, BASS_3DVECTOR *vel, BASS_3DVECTOR *front, BASS_3DVECTOR *top); -void BASSDEF(BASS_Apply3D)(); -#if defined(_WIN32) && !defined(_WIN32_WCE) && !(WINAPI_FAMILY && WINAPI_FAMILY!=WINAPI_FAMILY_DESKTOP_APP) -BOOL BASSDEF(BASS_SetEAXParameters)(int env, float vol, float decay, float damp); -BOOL BASSDEF(BASS_GetEAXParameters)(DWORD *env, float *vol, float *decay, float *damp); -#endif - -HMUSIC BASSDEF(BASS_MusicLoad)(BOOL mem, const void *file, QWORD offset, DWORD length, DWORD flags, DWORD freq); -BOOL BASSDEF(BASS_MusicFree)(HMUSIC handle); - -HSAMPLE BASSDEF(BASS_SampleLoad)(BOOL mem, const void *file, QWORD offset, DWORD length, DWORD max, DWORD flags); -HSAMPLE BASSDEF(BASS_SampleCreate)(DWORD length, DWORD freq, DWORD chans, DWORD max, DWORD flags); -BOOL BASSDEF(BASS_SampleFree)(HSAMPLE handle); -BOOL BASSDEF(BASS_SampleSetData)(HSAMPLE handle, const void *buffer); -BOOL BASSDEF(BASS_SampleGetData)(HSAMPLE handle, void *buffer); -BOOL BASSDEF(BASS_SampleGetInfo)(HSAMPLE handle, BASS_SAMPLE *info); -BOOL BASSDEF(BASS_SampleSetInfo)(HSAMPLE handle, const BASS_SAMPLE *info); -HCHANNEL BASSDEF(BASS_SampleGetChannel)(HSAMPLE handle, BOOL onlynew); -DWORD BASSDEF(BASS_SampleGetChannels)(HSAMPLE handle, HCHANNEL *channels); -BOOL BASSDEF(BASS_SampleStop)(HSAMPLE handle); - -HSTREAM BASSDEF(BASS_StreamCreate)(DWORD freq, DWORD chans, DWORD flags, STREAMPROC *proc, void *user); -HSTREAM BASSDEF(BASS_StreamCreateFile)(BOOL mem, const void *file, QWORD offset, QWORD length, DWORD flags); -HSTREAM BASSDEF(BASS_StreamCreateURL)(const char *url, DWORD offset, DWORD flags, DOWNLOADPROC *proc, void *user); -HSTREAM BASSDEF(BASS_StreamCreateFileUser)(DWORD system, DWORD flags, const BASS_FILEPROCS *proc, void *user); -BOOL BASSDEF(BASS_StreamFree)(HSTREAM handle); -QWORD BASSDEF(BASS_StreamGetFilePosition)(HSTREAM handle, DWORD mode); -DWORD BASSDEF(BASS_StreamPutData)(HSTREAM handle, const void *buffer, DWORD length); -DWORD BASSDEF(BASS_StreamPutFileData)(HSTREAM handle, const void *buffer, DWORD length); - -BOOL BASSDEF(BASS_RecordGetDeviceInfo)(DWORD device, BASS_DEVICEINFO *info); -BOOL BASSDEF(BASS_RecordInit)(int device); -BOOL BASSDEF(BASS_RecordSetDevice)(DWORD device); -DWORD BASSDEF(BASS_RecordGetDevice)(); -BOOL BASSDEF(BASS_RecordFree)(); -BOOL BASSDEF(BASS_RecordGetInfo)(BASS_RECORDINFO *info); -const char *BASSDEF(BASS_RecordGetInputName)(int input); -BOOL BASSDEF(BASS_RecordSetInput)(int input, DWORD flags, float volume); -DWORD BASSDEF(BASS_RecordGetInput)(int input, float *volume); -HRECORD BASSDEF(BASS_RecordStart)(DWORD freq, DWORD chans, DWORD flags, RECORDPROC *proc, void *user); - -double BASSDEF(BASS_ChannelBytes2Seconds)(DWORD handle, QWORD pos); -QWORD BASSDEF(BASS_ChannelSeconds2Bytes)(DWORD handle, double pos); -DWORD BASSDEF(BASS_ChannelGetDevice)(DWORD handle); -BOOL BASSDEF(BASS_ChannelSetDevice)(DWORD handle, DWORD device); -DWORD BASSDEF(BASS_ChannelIsActive)(DWORD handle); -BOOL BASSDEF(BASS_ChannelGetInfo)(DWORD handle, BASS_CHANNELINFO *info); -const char *BASSDEF(BASS_ChannelGetTags)(DWORD handle, DWORD tags); -DWORD BASSDEF(BASS_ChannelFlags)(DWORD handle, DWORD flags, DWORD mask); -BOOL BASSDEF(BASS_ChannelUpdate)(DWORD handle, DWORD length); -BOOL BASSDEF(BASS_ChannelLock)(DWORD handle, BOOL lock); -BOOL BASSDEF(BASS_ChannelPlay)(DWORD handle, BOOL restart); -BOOL BASSDEF(BASS_ChannelStop)(DWORD handle); -BOOL BASSDEF(BASS_ChannelPause)(DWORD handle); -BOOL BASSDEF(BASS_ChannelSetAttribute)(DWORD handle, DWORD attrib, float value); -BOOL BASSDEF(BASS_ChannelGetAttribute)(DWORD handle, DWORD attrib, float *value); -BOOL BASSDEF(BASS_ChannelSlideAttribute)(DWORD handle, DWORD attrib, float value, DWORD time); -BOOL BASSDEF(BASS_ChannelIsSliding)(DWORD handle, DWORD attrib); -BOOL BASSDEF(BASS_ChannelSetAttributeEx)(DWORD handle, DWORD attrib, void *value, DWORD size); -DWORD BASSDEF(BASS_ChannelGetAttributeEx)(DWORD handle, DWORD attrib, void *value, DWORD size); -BOOL BASSDEF(BASS_ChannelSet3DAttributes)(DWORD handle, int mode, float min, float max, int iangle, int oangle, float outvol); -BOOL BASSDEF(BASS_ChannelGet3DAttributes)(DWORD handle, DWORD *mode, float *min, float *max, DWORD *iangle, DWORD *oangle, float *outvol); -BOOL BASSDEF(BASS_ChannelSet3DPosition)(DWORD handle, const BASS_3DVECTOR *pos, const BASS_3DVECTOR *orient, const BASS_3DVECTOR *vel); -BOOL BASSDEF(BASS_ChannelGet3DPosition)(DWORD handle, BASS_3DVECTOR *pos, BASS_3DVECTOR *orient, BASS_3DVECTOR *vel); -QWORD BASSDEF(BASS_ChannelGetLength)(DWORD handle, DWORD mode); -BOOL BASSDEF(BASS_ChannelSetPosition)(DWORD handle, QWORD pos, DWORD mode); -QWORD BASSDEF(BASS_ChannelGetPosition)(DWORD handle, DWORD mode); -DWORD BASSDEF(BASS_ChannelGetLevel)(DWORD handle); -BOOL BASSDEF(BASS_ChannelGetLevelEx)(DWORD handle, float *levels, float length, DWORD flags); -DWORD BASSDEF(BASS_ChannelGetData)(DWORD handle, void *buffer, DWORD length); -HSYNC BASSDEF(BASS_ChannelSetSync)(DWORD handle, DWORD type, QWORD param, SYNCPROC *proc, void *user); -BOOL BASSDEF(BASS_ChannelRemoveSync)(DWORD handle, HSYNC sync); -HDSP BASSDEF(BASS_ChannelSetDSP)(DWORD handle, DSPPROC *proc, void *user, int priority); -BOOL BASSDEF(BASS_ChannelRemoveDSP)(DWORD handle, HDSP dsp); -BOOL BASSDEF(BASS_ChannelSetLink)(DWORD handle, DWORD chan); -BOOL BASSDEF(BASS_ChannelRemoveLink)(DWORD handle, DWORD chan); -HFX BASSDEF(BASS_ChannelSetFX)(DWORD handle, DWORD type, int priority); -BOOL BASSDEF(BASS_ChannelRemoveFX)(DWORD handle, HFX fx); - -BOOL BASSDEF(BASS_FXSetParameters)(HFX handle, const void *params); -BOOL BASSDEF(BASS_FXGetParameters)(HFX handle, void *params); -BOOL BASSDEF(BASS_FXReset)(HFX handle); -BOOL BASSDEF(BASS_FXSetPriority)(HFX handle, int priority); - -#ifdef __cplusplus -} - -#if defined(_WIN32) && !defined(NOBASSOVERLOADS) -static inline HPLUGIN BASS_PluginLoad(const WCHAR *file, DWORD flags) -{ - return BASS_PluginLoad((const char*)file, flags|BASS_UNICODE); -} - -static inline HMUSIC BASS_MusicLoad(BOOL mem, const WCHAR *file, QWORD offset, DWORD length, DWORD flags, DWORD freq) -{ - return BASS_MusicLoad(mem, (const void*)file, offset, length, flags|BASS_UNICODE, freq); -} - -static inline HSAMPLE BASS_SampleLoad(BOOL mem, const WCHAR *file, QWORD offset, DWORD length, DWORD max, DWORD flags) -{ - return BASS_SampleLoad(mem, (const void*)file, offset, length, max, flags|BASS_UNICODE); -} - -static inline HSTREAM BASS_StreamCreateFile(BOOL mem, const WCHAR *file, QWORD offset, QWORD length, DWORD flags) -{ - return BASS_StreamCreateFile(mem, (const void*)file, offset, length, flags|BASS_UNICODE); -} - -static inline HSTREAM BASS_StreamCreateURL(const WCHAR *url, DWORD offset, DWORD flags, DOWNLOADPROC *proc, void *user) -{ - return BASS_StreamCreateURL((const char*)url, offset, flags|BASS_UNICODE, proc, user); -} - -static inline BOOL BASS_SetConfigPtr(DWORD option, const WCHAR *value) -{ - return BASS_SetConfigPtr(option|BASS_UNICODE, (const void*)value); -} -#endif -#endif - -#endif diff --git a/charselect.cpp b/charselect.cpp deleted file mode 100644 index 4e4bccbf3..000000000 --- a/charselect.cpp +++ /dev/null @@ -1,162 +0,0 @@ -#include "courtroom.h" - -#include "file_functions.h" -#include "debug_functions.h" -#include "hardware_functions.h" - -#include - -void Courtroom::construct_char_select() -{ - ui_char_select_background = new AOImage(this, ao_app); - - ui_char_buttons = new QWidget(ui_char_select_background); - - ui_selector = new AOImage(ui_char_select_background, ao_app); - ui_selector->setAttribute(Qt::WA_TransparentForMouseEvents); - ui_selector->resize(62, 62); - - ui_back_to_lobby = new AOButton(ui_char_select_background, ao_app); - - ui_char_password = new QLineEdit(ui_char_select_background); - - ui_char_select_left = new AOButton(ui_char_select_background, ao_app); - ui_char_select_right = new AOButton(ui_char_select_background, ao_app); - - ui_spectator = new AOButton(ui_char_select_background, ao_app); - ui_spectator->setText("Spectator"); - - QPoint f_spacing = ao_app->get_button_spacing("char_button_spacing", "courtroom_design.ini"); - - const int button_width = 60; - int x_spacing = f_spacing.x(); - int x_mod_count = 0; - - const int button_height = 60; - int y_spacing = f_spacing.y(); - int y_mod_count = 0; - - set_size_and_pos(ui_char_buttons, "char_buttons"); - - char_columns = ((ui_char_buttons->width() - button_width) / (x_spacing + button_width)) + 1; - char_rows = ((ui_char_buttons->height() - button_height) / (y_spacing + button_height)) + 1; - - max_chars_on_page = char_columns * char_rows; - - for (int n = 0 ; n < max_chars_on_page ; ++n) - { - int x_pos = (button_width + x_spacing) * x_mod_count; - int y_pos = (button_height + y_spacing) * y_mod_count; - - ui_char_button_list.append(new AOCharButton(ui_char_buttons, ao_app, x_pos, y_pos)); - - connect(ui_char_button_list.at(n), SIGNAL(clicked()), char_button_mapper, SLOT(map())) ; - char_button_mapper->setMapping (ui_char_button_list.at(n), n) ; - - ++x_mod_count; - - if (x_mod_count == char_columns) - { - ++y_mod_count; - x_mod_count = 0; - } - } - - connect (char_button_mapper, SIGNAL(mapped(int)), this, SLOT(char_clicked(int))); - connect(ui_back_to_lobby, SIGNAL(clicked()), this, SLOT(on_back_to_lobby_clicked())); - - connect(ui_char_select_left, SIGNAL(clicked()), this, SLOT(on_char_select_left_clicked())); - connect(ui_char_select_right, SIGNAL(clicked()), this, SLOT(on_char_select_right_clicked())); - - connect(ui_spectator, SIGNAL(clicked()), this, SLOT(on_spectator_clicked())); -} - -void Courtroom::set_char_select() -{ - QString filename = "courtroom_design.ini"; - - pos_size_type f_charselect = ao_app->get_element_dimensions("char_select", filename); - - if (f_charselect.width < 0 || f_charselect.height < 0) - { - qDebug() << "W: did not find courtroom width or height in courtroom_design.ini!"; - this->resize(714, 668); - } - else - this->resize(f_charselect.width, f_charselect.height); - - ui_char_select_background->resize(f_charselect.width, f_charselect.height); - ui_char_select_background->set_image("charselect_background.png"); -} - -void Courtroom::set_char_select_page() -{ - ui_char_select_background->show(); - - ui_char_select_left->hide(); - ui_char_select_right->hide(); - - for (AOCharButton *i_button : ui_char_button_list) - i_button->hide(); - - int total_pages = char_list.size() / max_chars_on_page; - int chars_on_page = 0; - - if (char_list.size() % max_chars_on_page != 0) - { - ++total_pages; - //i. e. not on the last page - if (total_pages > current_char_page + 1) - chars_on_page = max_chars_on_page; - else - chars_on_page = char_list.size() % max_chars_on_page; - - } - else - chars_on_page = max_chars_on_page; - - if (total_pages > current_char_page + 1) - ui_char_select_right->show(); - - if (current_char_page > 0) - ui_char_select_left->show(); - - for (int n_button = 0 ; n_button < chars_on_page ; ++n_button) - { - int n_real_char = n_button + current_char_page * max_chars_on_page; - AOCharButton *f_button = ui_char_button_list.at(n_button); - - f_button->reset(); - f_button->set_image(char_list.at(n_real_char).name); - f_button->show(); - - if (char_list.at(n_real_char).taken) - f_button->set_taken(); - } - -} - -void Courtroom::char_clicked(int n_char) -{ - int n_real_char = n_char + current_char_page * max_chars_on_page; - - QString char_ini_path = ao_app->get_character_path(char_list.at(n_real_char).name) + "char.ini"; - qDebug() << "char_ini_path" << char_ini_path; - - if (!file_exists(char_ini_path)) - { - qDebug() << "did not find " << char_ini_path; - call_notice("Could not find " + char_ini_path); - return; - } - - if (n_real_char == m_cid) - { - enter_courtroom(m_cid); - } - else - { - ao_app->send_server_packet(new AOPacket("CC#" + QString::number(ao_app->s_pv) + "#" + QString::number(n_real_char) + "#" + get_hdid() + "#%")); - } -} - diff --git a/courtroom.cpp b/courtroom.cpp deleted file mode 100644 index 25f8ccfbd..000000000 --- a/courtroom.cpp +++ /dev/null @@ -1,3556 +0,0 @@ -#include "courtroom.h" - -#include "aoapplication.h" -#include "lobby.h" -#include "hardware_functions.h" -#include "file_functions.h" -#include "datatypes.h" -#include "debug_functions.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() -{ - ao_app = p_ao_app; - - //initializing sound device - BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, 0, NULL); - BASS_PluginLoad("bassopus.dll", BASS_UNICODE); - - keepalive_timer = new QTimer(this); - keepalive_timer->start(60000); - - chat_tick_timer = new QTimer(this); - - text_delay_timer = new QTimer(this); - text_delay_timer->setSingleShot(true); - - sfx_delay_timer = new QTimer(this); - sfx_delay_timer->setSingleShot(true); - - realization_timer = new QTimer(this); - realization_timer->setSingleShot(true); - - testimony_show_timer = new QTimer(this); - testimony_show_timer->setSingleShot(true); - - testimony_hide_timer = new QTimer(this); - testimony_hide_timer->setSingleShot(true); - - char_button_mapper = new QSignalMapper(this); - - m_blip_player = new AOBlipPlayer(this, ao_app); - m_blip_player->set_volume(0); - m_sfx_player = new AOSfxPlayer(this, ao_app); - m_sfx_player->set_volume(0); - m_shout_player = new AOShoutPlayer(this, ao_app); - m_shout_player->set_volume(0); - m_mod_player = new AOSfxPlayer(this, ao_app); - m_mod_player->set_volume(50); - m_cycle_player = new AOSfxPlayer(this, ao_app); - m_cycle_player->set_volume(0); - m_music_player = new AOMusicPlayer(this, ao_app); - m_music_player->set_volume(0); - - ui_background = new AOImage(this, ao_app); - - ui_viewport = new QWidget(this); - ui_vp_background = new AOScene(ui_viewport, ao_app); - ui_vp_speedlines = new AOMovie(ui_viewport, ao_app); - ui_vp_speedlines->set_play_once(false); - ui_vp_player_char = new AOCharMovie(ui_viewport, ao_app); - ui_vp_desk = new AOScene(ui_viewport, ao_app); - ui_vp_legacy_desk = new AOScene(ui_viewport, ao_app); - - ui_vp_music_display_a = new AOImage(this, ao_app); - ui_vp_music_display_b = new AOImage(this, ao_app); - ui_vp_music_area = new QWidget(ui_vp_music_display_a); - ui_vp_music_name = new QTextEdit(ui_vp_music_area); - ui_vp_music_name->setText("DANGANRONPA ONLINE"); - ui_vp_music_name->setFrameStyle(QFrame::NoFrame); - ui_vp_music_name->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_vp_music_name->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_vp_music_name->setReadOnly(true); - music_anim = new QPropertyAnimation(ui_vp_music_name, "geometry", this); - - ui_vp_clock = new AOMovie(this, ao_app); - ui_vp_clock->set_play_once(true); - - ui_vp_evidence_display = new AOEvidenceDisplay(this, ao_app); - - ui_vp_chatbox = new AOImage(this, ao_app); - ui_vp_showname = new QLabel(ui_vp_chatbox); - ui_vp_message = new QTextEdit(ui_vp_chatbox); - ui_vp_message->setFrameStyle(QFrame::NoFrame); - ui_vp_message->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_vp_message->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_vp_message->setReadOnly(true); - - ui_vp_showname_image = new AOImage(this, ao_app); - - ui_vp_testimony = new AOImage(this, ao_app); - ui_vp_effect = new AOMovie(this, ao_app); - ui_vp_wtce = new AOMovie(this, ao_app); - ui_vp_objection = new AOMovie(this, ao_app); - - ui_ic_chatlog = new QTextEdit(this); - ui_ic_chatlog->setReadOnly(true); - - ui_ms_chatlog = new AOTextArea(this); - ui_ms_chatlog->setReadOnly(true); - ui_ms_chatlog->setOpenExternalLinks(true); - ui_ms_chatlog->hide(); - - ui_server_chatlog = new AOTextArea(this); - ui_server_chatlog->setReadOnly(true); - ui_server_chatlog->setOpenExternalLinks(true); - - ui_mute_list = new QListWidget(this); - ui_area_list = new QListWidget(this); - ui_music_list = new QListWidget(this); - ui_sfx_list = new QListWidget(this); - - ui_ic_chat_message = new QLineEdit(this); - ui_ic_chat_message->setFrame(false); - - ui_muted = new AOImage(ui_ic_chat_message, ao_app); - ui_muted->hide(); - - ui_ooc_chat_message = new QLineEdit(this); - ui_ooc_chat_message->setFrame(false); - - ui_ooc_chat_name = new QLineEdit(this); - ui_ooc_chat_name->setFrame(false); - ui_ooc_chat_name->setPlaceholderText("Name"); - - //ui_area_password = new QLineEdit(this); - //ui_area_password->setFrame(false); - ui_music_search = new QLineEdit(this); - ui_music_search->setFrame(false); - - ui_sfx_search = new QLineEdit(this); - ui_sfx_search->setFrame(false); - - ui_note_area = new AONoteArea(this, ao_app); - ui_note_area->add_button = new AOButton(ui_note_area, ao_app); - ui_note_area->m_layout = new QVBoxLayout(ui_note_area); - note_scroll_area = new QScrollArea(this); - - note_scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - note_scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - note_scroll_area->setWidgetResizable(false); - - ui_set_notes = new AOButton(this, ao_app); - - construct_emotes(); - - ui_emote_left = new AOButton(this, ao_app); - ui_emote_right = new AOButton(this, ao_app); - - ui_emote_dropdown = new QComboBox(this); - ui_pos_dropdown = new QComboBox(this); - ui_pos_dropdown->addItem("wit"); - ui_pos_dropdown->addItem("def"); - ui_pos_dropdown->addItem("pro"); - ui_pos_dropdown->addItem("jud"); - ui_pos_dropdown->addItem("hld"); - ui_pos_dropdown->addItem("hlp"); - - ui_defense_bar = new AOImage(this, ao_app); - ui_prosecution_bar = new AOImage(this, ao_app); - - ui_music_label = new AOLabel(this, ao_app); - ui_sfx_label = new AOLabel(this, ao_app); - ui_blip_label = new AOLabel(this, ao_app); - - - int shout_number = ao_app->get_design_ini_value("shout_number", cc_config_ini); - shouts_enabled.resize(shout_number); - ui_shouts.resize(shout_number); - - for(int i = 0; i < ui_shouts.size(); ++i) - { - ui_shouts[i] = new AOButton(this, ao_app); - ui_shouts[i]->setProperty("shout_id", i+1); - } - - ui_shout_up = new AOButton(this, ao_app); - ui_shout_up->setProperty("cycle_id", 1); - ui_shout_down = new AOButton(this, ao_app); - ui_shout_down->setProperty("cycle_id", 0); - - ui_ooc_toggle = new AOButton(this, ao_app); - - ui_change_character = new AOButton(this, ao_app); - ui_reload_theme = new AOButton(this, ao_app); - ui_call_mod = new AOButton(this, ao_app); - ui_switch_area_music = new AOButton(this, ao_app); - - ui_theme_list = new QComboBox(this); - ui_confirm_theme = new AOButton(this, ao_app); - ui_note_button = new AOButton(this, ao_app); - - ui_label_images.resize(7); - for(int i = 0; i < ui_label_images.size(); ++i) - { - ui_label_images[i] = new AOImage(this, ao_app); - } - - ui_pre = new QCheckBox(this); - ui_pre->setText("Pre"); - ui_flip = new QCheckBox(this); - ui_flip->setText("Flip"); - ui_flip->hide(); - ui_guard = new QCheckBox(this); - ui_guard->setText("Guard"); - ui_guard->hide(); - ui_hidden = new QCheckBox(this); - ui_hidden->setText("Hidden"); - - // filling vectors with existing label/checkbox pointers - ui_checks.push_back(ui_pre); - ui_checks.push_back(ui_flip); - ui_checks.push_back(ui_guard); - ui_checks.push_back(ui_hidden); - ui_labels.push_back(ui_music_label); - ui_labels.push_back(ui_sfx_label); - ui_labels.push_back(ui_blip_label); - // - - int effect_number = ao_app->get_design_ini_value("effect_number", cc_config_ini); - effects_enabled.resize(effect_number); - ui_effects.resize(effect_number); - for(int i = 0; i < ui_effects.size(); ++i) - { - ui_effects[i] = new AOButton(this, ao_app); - ui_effects[i]->setProperty("effect_id", i+1); - } - - ui_effect_down = new AOButton(this, ao_app); - ui_effect_down->setProperty("cycle_id", 2); - ui_effect_up = new AOButton(this, ao_app); - ui_effect_up->setProperty("cycle_id", 3); - - int wtce_number = ao_app->get_design_ini_value("wtce_number", cc_config_ini); - wtce_enabled.resize(wtce_number); - ui_wtce.resize(wtce_number); - - for(int i = 0; i < ui_wtce.size(); ++i) - { - ui_wtce[i] = new AOButton(this, ao_app); - ui_wtce[i]->setProperty("wtce_id", i+1); - } - - ui_wtce_up = new AOButton(this, ao_app); - ui_wtce_up->setProperty("cycle_id", 5); - ui_wtce_down = new AOButton(this, ao_app); - ui_wtce_down->setProperty("cycle_id", 4); - - ui_mute = new AOButton(this, ao_app); - - ui_defense_plus = new AOButton(this, ao_app); - ui_defense_minus = new AOButton(this, ao_app); - - ui_prosecution_plus = new AOButton(this, ao_app); - ui_prosecution_minus = new AOButton(this, ao_app); - - ui_text_color = new QComboBox(this); - ui_text_color->addItem("White"); - ui_text_color->addItem("Green"); - ui_text_color->addItem("Red"); - ui_text_color->addItem("Orange"); - ui_text_color->addItem("Blue"); - if (ao_app->yellow_text_enabled) - ui_text_color->addItem("Yellow"); - - ui_music_slider = new QSlider(Qt::Horizontal, this); - ui_music_slider->setRange(0, 100); - ui_music_slider->setValue(ao_app->get_default_music()); - - ui_sfx_slider = new QSlider(Qt::Horizontal, this); - ui_sfx_slider->setRange(0, 100); - ui_sfx_slider->setValue(ao_app->get_default_sfx()); - - ui_blip_slider = new QSlider(Qt::Horizontal, this); - ui_blip_slider->setRange(0, 100); - ui_blip_slider->setValue(ao_app->get_default_blip()); - - ui_evidence_button = new AOButton(this, ao_app); - - ui_vp_notepad_image = new AOImage(this, ao_app); - ui_vp_notepad = new QTextEdit(this); - ui_vp_notepad->setFrameStyle(QFrame::NoFrame); - - construct_evidence(); - - construct_char_select(); - - connect(keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); - - connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); - connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); - - connect(text_delay_timer, SIGNAL(timeout()), this, SLOT(start_chat_ticking())); - connect(sfx_delay_timer, SIGNAL(timeout()), this, SLOT(play_sfx())); - - connect(chat_tick_timer, SIGNAL(timeout()), this, SLOT(chat_tick())); - - connect(realization_timer, SIGNAL(timeout()), this, SLOT(realization_done())); - - connect(testimony_show_timer, SIGNAL(timeout()), this, SLOT(hide_testimony())); - connect(testimony_hide_timer, SIGNAL(timeout()), this, SLOT(show_testimony())); - - connect(ui_emote_left, SIGNAL(clicked()), this, SLOT(on_emote_left_clicked())); - connect(ui_emote_right, SIGNAL(clicked()), this, SLOT(on_emote_right_clicked())); - - connect(ui_emote_dropdown, SIGNAL(activated(int)), this, SLOT(on_emote_dropdown_changed(int))); - connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed(int))); - - connect(ui_mute_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_mute_list_clicked(QModelIndex))); - - connect(ui_ic_chat_message, SIGNAL(returnPressed()), this, SLOT(on_chat_return_pressed())); - - connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_return_pressed())); - - connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_music_list_clicked())); - connect(ui_area_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_area_list_clicked())); - connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_music_list_double_clicked(QModelIndex))); - connect(ui_area_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_area_list_double_clicked(QModelIndex))); - - for(auto & shout : ui_shouts) - connect(shout, SIGNAL(clicked(bool)), this, SLOT(on_shout_clicked())); - - connect(ui_shout_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - connect(ui_shout_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - - for(auto & effect : ui_effects) - connect(effect, SIGNAL(clicked(bool)), this, SLOT(on_effect_button_clicked())); - - connect(ui_effect_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - connect(ui_effect_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - - for(auto & wtce : ui_wtce) - connect(wtce, SIGNAL(clicked(bool)), this, SLOT(on_wtce_clicked())); - - connect(ui_wtce_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - connect(ui_wtce_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); - - connect(ui_mute, SIGNAL(clicked()), this, SLOT(on_mute_clicked())); - - connect(ui_defense_minus, SIGNAL(clicked()), this, SLOT(on_defense_minus_clicked())); - connect(ui_defense_plus, SIGNAL(clicked()), this, SLOT(on_defense_plus_clicked())); - connect(ui_prosecution_minus, SIGNAL(clicked()), this, SLOT(on_prosecution_minus_clicked())); - connect(ui_prosecution_plus, SIGNAL(clicked()), this, SLOT(on_prosecution_plus_clicked())); - - connect(ui_text_color, SIGNAL(currentIndexChanged(int)), this, SLOT(on_text_color_changed(int))); - - connect(ui_music_slider, SIGNAL(valueChanged(int)), this, SLOT(on_music_slider_moved(int))); - connect(ui_sfx_slider, SIGNAL(valueChanged(int)), this, SLOT(on_sfx_slider_moved(int))); - connect(ui_blip_slider, SIGNAL(valueChanged(int)), this, SLOT(on_blip_slider_moved(int))); - - connect(ui_ooc_toggle, SIGNAL(clicked()), this, SLOT(on_ooc_toggle_clicked())); - - connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); - connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(on_sfx_search_edited(QString))); - - connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); - connect(ui_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); - connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); - - connect(ui_switch_area_music, SIGNAL(clicked()), this, SLOT(on_switch_area_music_clicked())); - - connect(ui_confirm_theme, SIGNAL(clicked()), this, SLOT(on_confirm_theme_clicked())); - connect(ui_note_button, SIGNAL(clicked()), this, SLOT(on_note_button_clicked())); - - connect(ui_vp_notepad, SIGNAL(textChanged()), this, SLOT(on_note_text_changed())); - - connect(ui_pre, SIGNAL(clicked()), this, SLOT(on_pre_clicked())); - connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); - connect(ui_guard, SIGNAL(clicked()), this, SLOT(on_guard_clicked())); - - connect(ui_hidden, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); - - connect(ui_sfx_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_sfx_list_clicked())); - - connect(ui_evidence_button, SIGNAL(clicked()), this, SLOT(on_evidence_button_clicked())); - - connect(ui_note_area->add_button, SIGNAL(clicked(bool)), this, SLOT(on_add_button_clicked())); - connect(ui_set_notes, SIGNAL(clicked(bool)), this, SLOT(on_set_notes_clicked())); - set_widgets(); - - set_bullets(); - - set_char_select(); -} - -void Courtroom::set_mute_list() -{ - mute_map.clear(); - - //maps which characters are muted based on cid, none are muted by default - for (int n_cid = 0 ; n_cid < char_list.size() ; n_cid++) - { - mute_map.insert(n_cid, false); - } - - QStringList sorted_mute_list; - - for (char_type i_char : char_list) - sorted_mute_list.append(i_char.name); - - sorted_mute_list.sort(); - - for (QString i_name : sorted_mute_list) - { - //mute_map.insert(i_name, false); - ui_mute_list->addItem(i_name); - } -} - -void Courtroom::set_widgets() -{ - blip_rate = ao_app->read_blip_rate(); - blank_blip = ao_app->get_blank_blip(); - - QString filename = design_ini; - pos_size_type f_courtroom = ao_app->get_element_dimensions("courtroom", filename); - - if (f_courtroom.width < 0 || f_courtroom.height < 0) - { - qDebug() << "W: did not find courtroom width or height in " << filename; - - this->resize(714, 668); - } - else - { - m_courtroom_width = f_courtroom.width; - m_courtroom_height = f_courtroom.height; - - this->resize(f_courtroom.width, f_courtroom.height); - } - - shout_names.clear(); - for(int i = 1; i <= ui_shouts.size(); ++i) - { - QString name = ao_app->get_spbutton("[SHOUTS]", i); - if(!name.isEmpty()) - shout_names.append(name.trimmed()); - } - - effect_names.clear(); - for(int i = 1; i <= ui_effects.size(); ++i) - { - QStringList names = ao_app->get_effect(i); - if(!names.isEmpty()) - effect_names.append(names.at(0).trimmed()); - } - - wtce_names.clear(); - for(int i = 1; i <= ui_wtce.size(); ++i) - { - QString name = ao_app->get_spbutton("[WTCE]", i); - if(!name.isEmpty()) - wtce_names.append(name.trimmed()); - } - - set_fonts(); - - ui_background->move(0, 0); - ui_background->resize(m_courtroom_width, m_courtroom_height); - ui_background->set_image("courtroombackground.png"); - - set_size_and_pos(ui_viewport, "viewport"); - - ui_vp_background->move(0, 0); - ui_vp_background->resize(ui_viewport->width(), ui_viewport->height()); - - ui_vp_speedlines->move(0, 0); - ui_vp_speedlines->combo_resize(ui_viewport->width(), ui_viewport->height()); - - ui_vp_player_char->move(0, 0); - ui_vp_player_char->combo_resize(ui_viewport->width(), ui_viewport->height()); - - //the AO2 desk element - ui_vp_desk->move(0, 0); - ui_vp_desk->resize(ui_viewport->width(), ui_viewport->height()); - - //the size of the ui_vp_legacy_desk element relies on various factors and is set in set_scene() - - double y_modifier = 147.0 / 192.0; - int final_y = static_cast(y_modifier * ui_viewport->height()); - ui_vp_legacy_desk->move(0, final_y); - ui_vp_legacy_desk->hide(); - - ui_vp_evidence_display->move(0, 0); - ui_vp_evidence_display->resize(ui_viewport->width(), ui_viewport->height()); - - set_size_and_pos(ui_vp_notepad_image, "notepad_image"); - ui_vp_notepad_image->set_image("notepad_image.png"); - ui_vp_notepad_image->hide(); - - set_size_and_pos(ui_vp_notepad, "notepad"); - ui_vp_notepad->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - ui_vp_notepad->verticalScrollBar()->hide(); - ui_vp_notepad->verticalScrollBar()->resize(0, 0); - ui_vp_notepad->hide(); - - set_size_and_pos(ui_vp_showname, "showname"); - - set_size_and_pos(ui_vp_showname_image, "showname_image"); - ui_vp_showname_image->hide(); - - set_size_and_pos(ui_vp_message, "message"); - ui_vp_message->setTextInteractionFlags(Qt::NoTextInteraction); - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: white"); - - ui_vp_testimony->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_testimony->resize(ui_viewport->width(), ui_viewport->height()); - ui_vp_testimony->set_image("testimony.png"); - ui_vp_testimony->hide(); - - ui_vp_effect->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_effect->resize(ui_viewport->width(), ui_viewport->height()); - ui_vp_effect->hide(); - - ui_vp_wtce->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_wtce->combo_resize(ui_viewport->width(), ui_viewport->height()); - - ui_vp_objection->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_objection->combo_resize(ui_viewport->width(), ui_viewport->height()); - - set_size_and_pos(ui_ic_chatlog, "ic_chatlog"); - - set_size_and_pos(ui_ms_chatlog, "ms_chatlog"); - - set_size_and_pos(ui_server_chatlog, "server_chatlog"); - - set_size_and_pos(ui_mute_list, "mute_list"); - ui_mute_list->hide(); - - set_size_and_pos(ui_area_list, "area_list"); - ui_area_list->hide(); -// ui_area_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - - set_size_and_pos(ui_music_list, "music_list"); - - set_size_and_pos(ui_sfx_list, "sfx_list"); - - if (is_ao2_bg) - { - set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message"); - set_size_and_pos(ui_vp_chatbox, "ao2_chatbox"); - } - else - { - set_size_and_pos(ui_ic_chat_message, "ic_chat_message"); - set_size_and_pos(ui_vp_chatbox, "chatbox"); - } - - set_size_and_pos(ui_vp_music_area, "music_area"); - ui_vp_music_area->show(); - set_size_and_pos(ui_vp_music_name, "music_name"); - - set_size_and_pos(ui_vp_music_display_a, "music_display_a"); - ui_vp_music_display_a->set_image("music_display_a.png"); - ui_vp_music_display_a->show(); - - set_size_and_pos(ui_vp_music_display_b, "music_display_b"); - ui_vp_music_display_b->set_image("music_display_b.png"); - ui_vp_music_display_b->show(); - - set_size_and_pos(ui_vp_clock, "clock"); - ui_vp_clock->show(); - - ui_ic_chat_message->setStyleSheet("QLineEdit{background-color: rgba(100, 100, 100, 255);}"); - - ui_vp_chatbox->set_image("chatmed.png"); - ui_vp_chatbox->hide(); - - ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); - ui_muted->set_image("muted.png"); - - set_size_and_pos(ui_ooc_chat_message, "ooc_chat_message"); - ui_ooc_chat_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - - set_size_and_pos(ui_ooc_chat_name, "ooc_chat_name"); - ui_ooc_chat_name->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); - - //set_size_and_pos(ui_area_password, "area_password"); - set_size_and_pos(ui_music_search, "music_search"); - - set_size_and_pos(ui_sfx_search, "sfx_search"); - - set_size_and_pos(ui_emotes, "emotes"); - - set_size_and_pos(ui_emote_left, "emote_left"); - ui_emote_left->set_image("arrow_left.png"); - - set_size_and_pos(ui_emote_right, "emote_right"); - ui_emote_right->set_image("arrow_right.png"); - - set_size_and_pos(ui_emote_dropdown, "emote_dropdown"); - - set_size_and_pos(ui_pos_dropdown, "pos_dropdown"); - - set_size_and_pos(ui_defense_bar, "defense_bar"); - ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state) + ".png"); - - set_size_and_pos(ui_prosecution_bar, "prosecution_bar"); - ui_prosecution_bar->set_image("prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); - - set_size_and_pos(ui_music_label, "music_label"); - ui_music_label->setText("Music"); - set_size_and_pos(ui_sfx_label, "sfx_label"); - ui_sfx_label->setText("Sfx"); - set_size_and_pos(ui_blip_label, "blip_label"); - ui_blip_label->setText("Blips"); - -// set_size_and_pos(ui_shouts[0], "hold_it"); -// ui_shouts[0]->show(); -// set_size_and_pos(ui_shouts[1], "objection"); -// ui_shouts[1]->show(); -// set_size_and_pos(ui_shouts[2], "take_that"); -// ui_shouts[2]->show(); -// set_size_and_pos(ui_shouts[3], "custom_objection"); -// set_size_and_pos(ui_shouts[4], "got_it"); -// set_size_and_pos(ui_shouts[5], "cross_swords"); -// set_size_and_pos(ui_shouts[6], "counter_alt"); - for(int i = 0; i < shout_names.size(); ++i) - { - set_size_and_pos(ui_shouts[i], shout_names[i]); - } -// ui_shouts[0]->show(); -// ui_shouts[1]->show(); -// ui_shouts[2]->show(); - reset_shout_buttons(); - - set_size_and_pos(ui_shout_up, "shout_up"); - ui_shout_up->set_image("shoutup.png"); - ui_shout_up->hide(); - set_size_and_pos(ui_shout_down, "shout_down"); - ui_shout_down->set_image("shoutdown.png"); - ui_shout_down->hide(); - - if (ao_app->read_design_ini( "enable_single_shout", ao_app->get_theme_path() + cc_config_ini ) == "true" && ui_shouts.size() > 0) // courtroom_config.ini necessary + check for crash - { - for(auto & shout : ui_shouts) move_widget(shout, "bullet"); - - set_shouts(); - - ui_shout_up->show(); - ui_shout_down->show(); - } - -// set_size_and_pos(ui_effects[0], "effect_flash"); -// set_size_and_pos(ui_effects[1], "effect_gloom"); -// set_size_and_pos(ui_effects[2], "effect_question"); -// set_size_and_pos(ui_effects[3], "effect_pow"); - for(int i = 0; i < effect_names.size(); ++i) - { - set_size_and_pos(ui_effects[i], effect_names[i]); - } - reset_effect_buttons(); - - set_size_and_pos(ui_effect_up, "effect_up"); - ui_effect_up->set_image("effectup.png"); - ui_effect_up->hide(); - set_size_and_pos(ui_effect_down, "effect_down"); - ui_effect_down->set_image("effectdown.png"); - ui_effect_down->hide(); - - if (ao_app->read_design_ini( "enable_single_effect", ao_app->get_theme_path() + cc_config_ini ) == "true" && ui_effects.size() > 0) // check to prevent crashing - { - for(auto & effect : ui_effects) move_widget(effect, "effect"); - - set_effects(); - - ui_effect_up->show(); - ui_effect_down->show(); - } - - for(int i = 0; i < wtce_names.size(); ++i) - { - set_size_and_pos(ui_wtce[i], wtce_names[i]); - } - - set_size_and_pos(ui_wtce_up, "wtce_up"); - ui_wtce_up->set_image("wtceup.png"); - ui_wtce_up->hide(); - set_size_and_pos(ui_wtce_down, "wtce_down"); - ui_wtce_down->set_image("wtcedown.png"); - ui_wtce_down->hide(); - - if( ao_app->read_design_ini( "enable_single_wtce", ao_app->get_theme_path() + cc_config_ini ) == "true" ) // courtroom_config.ini necessary - { - for(auto & wtce : ui_wtce) move_widget(wtce, "wtce"); - qDebug() << "AA: single wtce"; - set_wtce(); - } - - set_size_and_pos(ui_ooc_toggle, "ooc_toggle"); - ui_ooc_toggle->setText("Server"); - - set_size_and_pos(ui_call_mod, "call_mod"); - - - set_size_and_pos(ui_change_character, "change_character"); - set_size_and_pos(ui_reload_theme, "reload_theme"); - set_size_and_pos(ui_call_mod, "call_mod"); - set_size_and_pos(ui_confirm_theme, "confirm_theme"); - set_size_and_pos(ui_note_button, "note_button"); - - set_size_and_pos(ui_switch_area_music, "switch_area_music"); -// ui_switch_area_music->setText("A/M"); - - if(ao_app->read_design_ini("enable_button_images", ao_app->get_theme_path() + cc_config_ini) == "true") - { - if(file_exists(ao_app->get_theme_path() + "changecharacter.png")) - { - ui_change_character->set_image("changecharacter.png"); - ui_change_character->setText(""); - } - else - { - ui_change_character->setStyleSheet(""); - ui_change_character->setText("Change character"); - } - - if(file_exists(ao_app->get_theme_path() + "reloadtheme.png")) - { - ui_reload_theme->set_image("reloadtheme.png"); - ui_reload_theme->setText(""); - } - else - { - ui_reload_theme->setStyleSheet(""); - ui_reload_theme->setText("Reload theme"); - } - - if(file_exists(ao_app->get_theme_path() + "callmod.png")) - { - ui_call_mod->set_image("callmod.png"); - ui_call_mod->setText(""); - } - else - { - ui_call_mod->setStyleSheet(""); - ui_call_mod->setText("Call mod"); - } - - if(file_exists(ao_app->get_theme_path() + "switch_area_music.png")) - { - ui_switch_area_music->set_image("switch_area_music.png"); - ui_switch_area_music->setText(""); - } - else - { - ui_switch_area_music->setStyleSheet(""); - ui_switch_area_music->setText("A/M"); - } - - if(file_exists(ao_app->get_theme_path() + "confirmtheme.png")) - { - ui_confirm_theme->set_image("confirmtheme.png"); - ui_confirm_theme->setText(""); - } - else - { - ui_confirm_theme->setStyleSheet(""); - ui_confirm_theme->setText("^"); - } - - if(file_exists(ao_app->get_theme_path() + "notebutton.png")) - { - ui_note_button->set_image("notebutton.png"); - ui_note_button->setText(""); - } - else - { - ui_note_button->setStyleSheet(""); - ui_note_button->setText("><:"); - } - } - else - { - ui_change_character->setText("Change character"); - ui_change_character->setStyleSheet(""); - - ui_reload_theme->setText("Reload theme"); - ui_reload_theme->setStyleSheet(""); - - ui_call_mod->setText("Call mod"); - ui_call_mod->setStyleSheet(""); - - ui_switch_area_music->setText("A/M"); - ui_switch_area_music->setStyleSheet(""); - - ui_confirm_theme->setText("^"); - ui_confirm_theme->setStyleSheet(""); - - ui_note_button->setText("><:"); - ui_note_button->setStyleSheet(""); - } - - set_size_and_pos(ui_theme_list, "theme_list"); - - set_size_and_pos(ui_pre, "pre"); - ui_pre->setText("Pre"); - - set_size_and_pos(ui_flip, "flip"); - - set_size_and_pos(ui_guard, "guard"); - - set_size_and_pos(ui_hidden, "hidden"); - - for(int i = 0; i < ui_label_images.size(); ++i) - { - set_size_and_pos(ui_label_images[i], label_images[i].toLower() + "_image"); - } - - if(ao_app->read_design_ini("enable_label_images", ao_app->get_theme_path() + cc_config_ini) == "true") - { - for(int i = 0 ; i < ui_checks.size(); ++i) // loop through checks - { - QString image = label_images[i].toLower() + ".png"; - QString path = ao_app->get_theme_path() + image; - if(file_exists(path)) - { - ui_label_images[i]->set_image(image); - ui_checks[i]->setText(""); - } - else - { - ui_label_images[i]->set_image(""); - ui_checks[i]->setText(label_images[i]); - } - } - - for(int i = 0 ; i < ui_labels.size(); ++i) // now through labels.......... - { - int j = i + ui_checks.size(); - QString image = label_images[j].toLower() + ".png"; - QString path = ao_app->get_theme_path() + image; - if(file_exists(path)) - { - ui_label_images[j]->set_image(image); - ui_labels[i]->setText(""); - } - else - { - ui_label_images[j]->set_image(""); - ui_labels[i]->setText(label_images[j]); - } - } - } - else - { - for(int i = 0; i < ui_checks.size(); ++i) //same thing - { - ui_checks[i]->setText(label_images[i]); - ui_label_images[i]->set_image(""); - } - - for(int i = 0; i < ui_labels.size(); ++i) //same thing - { - int j = i + ui_checks.size(); - ui_labels[i]->setText(label_images[j]); - ui_label_images[j]->set_image(""); - } - } - - set_size_and_pos(ui_mute, "mute_button"); - ui_mute->set_image("mute.png"); - - set_size_and_pos(ui_defense_plus, "defense_plus"); - ui_defense_plus->set_image("defplus.png"); - - set_size_and_pos(ui_defense_minus, "defense_minus"); - ui_defense_minus->set_image("defminus.png"); - - set_size_and_pos(ui_prosecution_plus, "prosecution_plus"); - ui_prosecution_plus->set_image("proplus.png"); - - set_size_and_pos(ui_prosecution_minus, "prosecution_minus"); - ui_prosecution_minus->set_image("prominus.png"); - - set_size_and_pos(ui_text_color, "text_color"); - - set_size_and_pos(ui_music_slider, "music_slider"); - set_size_and_pos(ui_sfx_slider, "sfx_slider"); - set_size_and_pos(ui_blip_slider, "blip_slider"); - - set_size_and_pos(ui_evidence_button, "evidence_button"); - ui_evidence_button->set_image("evidencebutton.png"); - - set_size_and_pos(ui_evidence, "evidence_background"); - ui_evidence->set_image("evidencebackground.png"); - - set_size_and_pos(ui_evidence_name, "evidence_name"); - - set_size_and_pos(ui_evidence_buttons, "evidence_buttons"); - - set_size_and_pos(ui_evidence_left, "evidence_left"); - ui_evidence_left->set_image("arrow_left.png"); - - set_size_and_pos(ui_evidence_right, "evidence_right"); - ui_evidence_right->set_image("arrow_right.png"); - - set_size_and_pos(ui_evidence_present, "evidence_present"); - ui_evidence_present->set_image("present_disabled.png"); - - set_size_and_pos(ui_evidence_overlay, "evidence_overlay"); - ui_evidence_overlay->set_image("evidenceoverlay.png"); - - set_size_and_pos(ui_evidence_delete, "evidence_delete"); - ui_evidence_delete->set_image("deleteevidence.png"); - - set_size_and_pos(ui_evidence_image_name, "evidence_image_name"); - - set_size_and_pos(ui_evidence_image_button, "evidence_image_button"); - - set_size_and_pos(ui_evidence_x, "evidence_x"); - ui_evidence_x->set_image("evidencex.png"); - - set_size_and_pos(ui_evidence_description, "evidence_description"); - - ui_selector->set_image("char_selector.png"); - ui_selector->hide(); - - set_size_and_pos(ui_back_to_lobby, "back_to_lobby"); - ui_back_to_lobby->setText("Back to Lobby"); - - set_size_and_pos(ui_char_password, "char_password"); - - set_size_and_pos(ui_char_buttons, "char_buttons"); - - set_size_and_pos(ui_char_select_left, "char_select_left"); - ui_char_select_left->set_image("arrow_left.png"); - - set_size_and_pos(ui_char_select_right, "char_select_right"); - ui_char_select_right->set_image("arrow_right.png"); - - set_size_and_pos(ui_spectator, "spectator"); - - handle_music_anim(); - - set_size_and_pos(ui_set_notes, "set_notes_button"); - ui_set_notes->set_image("set_notes.png"); - ui_note_area->m_layout->setSpacing(10); - set_size_and_pos(ui_note_area, "note_area"); - set_size_and_pos(note_scroll_area, "note_area"); - note_scroll_area->setWidget(ui_note_area); - ui_note_area->set_image("note_area.png"); - ui_note_area->add_button->set_image("add_button.png"); - ui_note_area->add_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - ui_note_area->setLayout(ui_note_area->m_layout); - ui_note_area->show(); - note_scroll_area->hide(); - - list_note_files(); - - if(!contains_add_button) - { - ui_note_area->m_layout->addWidget(ui_note_area->add_button); - contains_add_button = true; - } - - set_dropdowns(); -} - -void Courtroom::set_fonts() -{ - set_font(ui_vp_showname, "showname"); - set_font(ui_vp_message, "message"); - set_font(ui_ic_chatlog, "ic_chatlog"); - set_font(ui_ms_chatlog, "ms_chatlog"); - set_font(ui_server_chatlog, "server_chatlog"); - set_font(ui_music_list, "music_list"); - set_font(ui_area_list, "area_list"); - set_font(ui_sfx_list, "sfx_list"); - set_font(ui_vp_music_name, "music_name"); - set_font(ui_vp_notepad, "notepad"); -} - -void Courtroom::set_font(QWidget *widget, QString p_identifier) -{ - QString design_file = fonts_ini; - int f_weight = ao_app->get_font_size(p_identifier, design_file); - QString class_name = widget->metaObject()->className(); - - QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); - - widget->setFont(QFont(font_name, f_weight)); - - QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); - - int bold = ao_app->get_font_size(p_identifier + "_bold", design_file); // is the font bold or not? - - QString is_bold = ""; - if(bold == 1) is_bold = "bold"; - - QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + - "color: rgba(" + - QString::number(f_color.red()) + ", " + - QString::number(f_color.green()) + ", " + - QString::number(f_color.blue()) + ", 255);\n" - "font: " + is_bold + "; }"; - - widget->setStyleSheet(style_sheet_string); -} - -void Courtroom::set_dropdown(QWidget *widget, QString target_tag) -{ - QString f_file = "courtroom_stylesheets.css"; - QString style_sheet_string = ao_app->get_stylesheet(target_tag, f_file); - if (style_sheet_string != "") - widget->setStyleSheet(style_sheet_string); -} - -void Courtroom::set_dropdowns() -{ - set_dropdown(ui_text_color, "[TEXT COLOR]"); - set_dropdown(ui_pos_dropdown, "[POS DROPDOWN]"); - set_dropdown(ui_theme_list, "[THEME LIST]"); - set_dropdown(ui_emote_dropdown, "[EMOTE DROPDOWN]"); - set_dropdown(ui_mute_list, "[MUTE LIST]"); - set_dropdown(ui_ic_chat_message, "[IC LINE]"); - set_dropdown(ui_ooc_chat_message, "[OOC LINE]"); -} - -void Courtroom::move_widget(QWidget *p_widget, QString p_identifier) -{ - QString filename = design_ini; - - pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); - - if (design_ini_result.width < 0 || design_ini_result.height < 0) - { - qDebug() << "W: could not find \"" << p_identifier << "\" in " << filename; - p_widget->hide(); - } - else - { - p_widget->move(design_ini_result.x, design_ini_result.y); - } -} - -void Courtroom::set_shouts() -{ - for(auto & shout : ui_shouts) shout->hide(); - if (ui_shouts.size() > 0) ui_shouts[m_shout_state]->show(); // check to prevent crashing -} - -void Courtroom::set_effects() -{ - for(auto & effect : ui_effects) effect->hide(); - if (ui_effects.size() > 0) ui_effects[m_effect_current]->show(); // check to prevent crashing -} - -void Courtroom::set_wtce() -{ - for(auto & wtce : ui_wtce) wtce->hide(); - - if(is_judge && ui_wtce.size() > 0) // check to prevent crashing - { - if( ao_app->read_design_ini( "enable_single_wtce", ao_app->get_theme_path() + cc_config_ini ) == "true" ) - { - ui_wtce[m_wtce_current]->show(); - ui_wtce_up->show(); - ui_wtce_down->show(); - } - else - { - for(auto & wtce : ui_wtce) wtce->show(); - ui_wtce_up->hide(); - ui_wtce_down->hide(); - } - } -} - -void Courtroom::handle_music_anim() -{ - QString file_a = design_ini; - QString file_b = fonts_ini; - pos_size_type res_a = ao_app->get_element_dimensions("music_name", file_a); - pos_size_type res_b = ao_app->get_element_dimensions("music_area", file_a); - float speed = static_cast(ao_app->get_font_size("music_name_speed", file_b)); - - QFont f_font = ui_vp_music_name->font(); - QFontMetrics fm(f_font); - int dist; - if ( ao_app->read_design_ini( "enable_const_music_speed", ao_app->get_theme_path() + cc_config_ini ) == "true") - dist = res_b.width; - else dist = fm.width(ui_vp_music_name->toPlainText()); - int time = static_cast(1000000*dist/speed); - music_anim->setLoopCount(-1); - music_anim->setDuration(time); - music_anim->setStartValue(QRect(res_b.width + res_b.x, res_a.y, res_a.width, res_a.height)); - music_anim->setEndValue(QRect(-dist + res_a.x, res_a.y, res_a.width, res_a.height)); - music_anim->start(); -} - -void Courtroom::handle_clock(QString time) -{ - current_clock = time.toInt(); - - QString string = "hours\\" + time; // expected to be 0, 1, 2... - qDebug() << "hours:" << string; - ui_vp_clock->play(string); -} - -void Courtroom::set_window_title(QString p_title) -{ - this->setWindowTitle(p_title); -} - -void Courtroom::set_char_rpc() -{ - rpc_char_list.clear(); - - QFile config_file(ao_app->get_base_path() + rpc_ini); - if (!config_file.open(QIODevice::ReadOnly)) - { qDebug() << "Error reading" << ao_app->get_base_path() + rpc_ini; return; } - - QTextStream in(&config_file); - - while(!in.atEnd()) - { - QString f_line = in.readLine().trimmed(); - - QStringList line_elements = f_line.split("-"); - - rpc_char_list.append(line_elements.at(1).trimmed().toLower()); - } - - config_file.close(); -} - -void Courtroom::set_size_and_pos(QWidget *p_widget, QString p_identifier) -{ - QString filename = design_ini; - - - pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); - - if (design_ini_result.width < 0 || design_ini_result.height < 0) - { - qDebug() << "W: could not find \"" << p_identifier << "\" in " << filename; - p_widget->hide(); - } - else - { - p_widget->move(design_ini_result.x, design_ini_result.y); - p_widget->resize(design_ini_result.width, design_ini_result.height); - } -} - -void Courtroom::set_taken(int n_char, bool p_taken) -{ - if (n_char >= char_list.size()) - { - qDebug() << "W: set_taken attempted to set an index bigger than char_list size"; - return; - } - - char_type f_char; - f_char.name = char_list.at(n_char).name; - f_char.description = char_list.at(n_char).description; - f_char.taken = p_taken; - f_char.evidence_string = char_list.at(n_char).evidence_string; - - char_list.replace(n_char, f_char); -} - -void Courtroom::done_received() -{ - m_cid = -1; - - m_music_player->set_volume(0); - m_sfx_player->set_volume(0); - m_shout_player->set_volume(0); - m_blip_player->set_volume(0); - - set_char_select_page(); - - set_mute_list(); - - set_char_select(); - - show(); - - ui_spectator->show(); -} - -void Courtroom::set_background(QString p_background) -{ - testimony_in_progress = false; - - current_background = p_background; - QString bg_path = get_background_path(); - QVector exts{".apng", ".gif", ".png"}; - - QString ini_path = ao_app->get_background_path() + "backgrounds.ini"; - - if (file_exists(ini_path)) - { - is_ao2_bg = true; - } - else - { - is_ao2_bg = file_exists(bg_path + "defensedesk", exts) != "" && - file_exists(bg_path + "prosecutiondesk", exts) != "" && - file_exists(bg_path + "stand", exts) != ""; - } - - if (is_ao2_bg) - { - set_size_and_pos(ui_vp_chatbox, "ao2_chatbox"); - set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message"); - } - else - { - set_size_and_pos(ui_vp_chatbox, "chatbox"); - set_size_and_pos(ui_ic_chat_message, "ic_chat_message"); - } -} - -void Courtroom::enter_courtroom(int p_cid) -{ - m_cid = p_cid; - - QString f_char; - - set_char_rpc(); - - if (m_cid == -1) - { - ao_app->discord->state_spectate(); - f_char = ""; - } - else - { - f_char = ao_app->get_char_name(char_list.at(m_cid).name); - QString r_char = f_char; - QRegularExpression re(QString::fromUtf8("[-`~!@#$%^&*()—+=|:;<>«»,.?/{}\'\"\\[\\]]")); // regex for removing non letter (except _) characters - r_char.remove(re); - - if(!rpc_char_list.contains(f_char.toLower())) - { - ao_app->discord->toggle(1); - } - else - { - ao_app->discord->toggle(0); - } - - ao_app->discord->state_character(r_char.toStdString()); - } - - current_char = f_char; - - current_emote_page = 0; - current_emote = 0; - - if (m_cid == -1) - ui_emotes->hide(); - else - ui_emotes->show(); - - set_emote_page(); - set_emote_dropdown(); - - current_evidence_page = 0; - current_evidence = 0; - - m_shout_state = 0; - m_wtce_current = 0; - reset_wtce_buttons(); - - if(const int log_limit = ao_app->read_config("chatlog_limit").toInt()) - m_log_limit = log_limit; - - m_scroll_down = ao_app->read_config("scroll_type") == "down"; - if(m_previously_scroll_down != m_scroll_down) - m_scroll_type_changed = !m_scroll_type_changed; - - m_previously_scroll_down = m_scroll_down; - - set_evidence_page(); - - QString side = ao_app->get_char_side(f_char); - - if (side == "jud") - { - is_judge = true; - //set_wtce(); - //ui_wtce_down->show(); - //ui_wtce_up->show(); - - ui_defense_minus->show(); - ui_defense_plus->show(); - ui_prosecution_minus->show(); - ui_prosecution_plus->show(); - } - else - { - is_judge = false; - //set_wtce(); - //ui_wtce_down->hide(); - //ui_wtce_up->hide(); - - ui_defense_minus->hide(); - ui_defense_plus->hide(); - ui_prosecution_minus->hide(); - ui_prosecution_plus->hide(); - } - - check_shouts(); - check_effects(); - check_wtce(); - - if (ao_app->custom_objection_enabled) - { - - for(int i = 0; i < ui_shouts.size(); ++i) - { - if(shouts_enabled[i]) - { - ui_shouts[i]->show(); - } - else - { - ui_shouts[i]->hide(); - } - } - } - else - { - for(int i = 3; i < ui_shouts.size(); i++) // the non-official shouts - ui_shouts[i]->hide(); - } - - for(int i = 0; i < ui_effects.size(); ++i) - { - if(effects_enabled[i]) - { - ui_effects[i]->show(); - } - else - { - ui_effects[i]->hide(); - } - } - - for(int i = 0; i < ui_wtce.size(); ++i) - { - if(wtce_enabled[i] && is_judge) - { - ui_wtce[i]->show(); - } - else - { - ui_wtce[i]->hide(); - } - } - - if (ao_app->flipping_enabled) - ui_flip->show(); - else - ui_flip->hide(); - - list_music(); - list_areas(); - list_sfx(); - list_themes(); - - ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); // prevents undefined errors - - m_music_player->set_volume(ui_music_slider->value()); - m_sfx_player->set_volume(ui_sfx_slider->value()); - m_cycle_player->set_volume(ui_sfx_slider->value()); - m_shout_player->set_volume(ui_sfx_slider->value()); - m_blip_player->set_volume(ui_blip_slider->value()); - - testimony_in_progress = false; - - set_widgets(); - - //ui_server_chatlog->setHtml(ui_server_chatlog->toHtml()); - - ui_char_select_background->hide(); - - bool ok; - chat_tick_interval = ao_app->read_config("chat_tick_interval").toInt(&ok, 10); // ok will be set to false and this will return 0 if goes bad - if(!ok) chat_tick_interval = 60; - - ui_ic_chat_message->setEnabled(m_cid != -1); - ui_ic_chat_message->setFocus(); - - set_bullets(); -} - -void Courtroom::list_music() -{ - ui_music_list->clear(); - - QString f_file = design_ini; - - QBrush found_brush(ao_app->get_color("found_song_color", f_file)); - QBrush missing_brush(ao_app->get_color("missing_song_color", f_file)); - - int n_listed_songs = 0; - - for (int n_song = 0 ; n_song < music_list.size() ; ++n_song) - { - QString i_song = music_list.at(n_song); - bool found = false; - - for (auto& ext : QStringList { "", ".wav", ".ogg", ".mp3" }) - { - QString r_song = i_song + ext; - QString song_path = ao_app->get_base_path() + "sounds/music/" + r_song.toLower(); - if (file_exists(song_path)) - { - found = true; - break; - } - } - - if (i_song.toLower().contains(ui_music_search->text().toLower())) - { - ui_music_list->addItem(i_song); - - if (found) - ui_music_list->item(n_listed_songs)->setBackground(found_brush); - else - ui_music_list->item(n_listed_songs)->setBackground(missing_brush); - - ++n_listed_songs; - } - } -} - -void Courtroom::list_areas() -{ - ui_area_list->clear(); -// area_names.clear(); - - QString f_file = "courtroom_design.ini"; - - QBrush free_brush(ao_app->get_color("area_free_color", f_file)); - QBrush lfp_brush(ao_app->get_color("area_lfp_color", f_file)); - QBrush casing_brush(ao_app->get_color("area_casing_color", f_file)); - QBrush recess_brush(ao_app->get_color("area_recess_color", f_file)); - QBrush rp_brush(ao_app->get_color("area_rp_color", f_file)); - QBrush gaming_brush(ao_app->get_color("area_gaming_color", f_file)); - QBrush locked_brush(ao_app->get_color("area_locked_color", f_file)); - - int n_listed_areas = 0; - - for (int n_area = 0 ; n_area < area_list.size() ; ++n_area) - { - QString i_area = ""; - - i_area.append(area_list.at(n_area)); - - if (i_area.toLower().contains(ui_music_search->text().toLower())) - { - ui_area_list->addItem(i_area); -// area_names.append(i_); - - - ui_area_list->item(n_listed_areas)->setBackground(free_brush); - - ++n_listed_areas; - } - } -} - -void Courtroom::list_sfx() -{ - ui_sfx_list->clear(); - sfx_names.clear(); - - QString f_file = design_ini; - - QStringList sfx_list = ao_app->get_sfx_list(); - - QBrush found_brush(ao_app->get_color("found_song_color", f_file)); - QBrush missing_brush(ao_app->get_color("missing_song_color", f_file)); - - ui_sfx_list->addItem("Default"); - ui_sfx_list->addItem("Silence"); - - sfx_names.append("1"); // Default - sfx_names.append("1"); // Silence - - int n_listed_sfxs = 0; - - for (int n_sfx = 0 ; n_sfx < sfx_list.size() ; ++n_sfx) - { - QStringList sfx = sfx_list.at(n_sfx).split("="); - QString i_sfx = sfx.at(0).trimmed(); - QString d_sfx = ""; - sfx_names.append(i_sfx); - if(sfx_list.at(n_sfx).split("=").size() < 2) - d_sfx = i_sfx; - else d_sfx = sfx.at(1).trimmed(); - - // qDebug() << "isfx=" << i_sfx << "dsfx=" << d_sfx << "sfx=" << sfx; - - if (i_sfx.toLower().contains(ui_sfx_search->text().toLower())) - { - ui_sfx_list->addItem(d_sfx); - ui_sfx_list->item(ui_sfx_list->count()-1)->setStatusTip(QString::number(n_sfx+2)); - - QString sfx_path = ao_app->get_base_path() + "sounds/general/" + i_sfx.toLower(); - - if (file_exists(sfx_path)) - ui_sfx_list->item(n_listed_sfxs)->setBackground(found_brush); - else - ui_sfx_list->item(n_listed_sfxs)->setBackground(missing_brush); - - ++n_listed_sfxs; - } - } -} - -void Courtroom::list_note_files() -{ - QString f_config = ao_app->get_base_path() + file_select_ini; - QFile f_file(f_config); - if(!f_file.open(QIODevice::ReadOnly)) - { qDebug() << "Couldn't open" << f_config; return; } - - note_list.clear(); - - QString f_filestring = ""; - QString f_filename = ""; - - QTextStream in(&f_file); - - QVBoxLayout *f_layout = ui_note_area->m_layout; - - while(!in.atEnd()) - { - QString line = in.readLine().trimmed(); - - QStringList f_contents = line.split("="); - if(f_contents.size() < 2) - continue; - - int f_index = f_contents.at(0).toInt(); - f_filestring = f_filename = f_contents.at(1).trimmed(); - - if(f_contents.size() > 2) - f_filename = f_contents.at(2).trimmed(); - - while(f_index >= f_layout->count()) - on_add_button_clicked(); - - AONotePicker *f_notepicker = static_cast(f_layout->itemAt(f_index)->widget()); - f_notepicker->m_line->setText(f_filename); - f_notepicker->real_file = f_filestring; - } -} - -void Courtroom::load_note() -{ - QString f_text = ao_app->read_note(current_file); - ui_vp_notepad->setText(f_text); -} - -void Courtroom::save_note() -{ - QString f_text = ui_vp_notepad->toPlainText(); - - ao_app->write_note(f_text, current_file); -} - -void Courtroom::save_textlog(QString p_text) -{ - QString f_file = ao_app->get_base_path() + icchatlogsfilename; - - ao_app->append_note(p_text, f_file); -} - -void Courtroom::list_themes() -{ - QString themes = ao_app->get_base_path() + "themes/"; - QDir dir(themes); - ui_theme_list->clear(); - - QStringList lis = dir.entryList(); - for(QString s : lis) - { - if(s != "." && s != "..") - ui_theme_list->addItem(s); - } -} - -void Courtroom::append_ms_chatmessage(QString f_name, QString f_message) -{ - ui_ms_chatlog->append_chatmessage(f_name, f_message); -} - -void Courtroom::append_server_chatmessage(QString p_name, QString p_message) -{ - ui_server_chatlog->append_chatmessage(p_name, p_message); - if(ao_app->read_config("enable_logging") == "true") - save_textlog("(OOC)[" + QTime::currentTime().toString() + "] " + p_name + ": " + p_message); -} - -void Courtroom::on_chat_return_pressed() -{ - if (ui_ic_chat_message->text() == "" || is_muted) - return; - - if ((anim_state < 3 || text_state < 2) && - m_objection_state == 0) - return; - - // qDebug() << "prev_emote = " << prev_emote << "current_emote = " << current_emote; - - // qDebug() << "same_emote = " << same_emote; - //MS# - //deskmod# - //pre-emote# - //character# - //emote# - //message# - //side# - //sfx-name# - //emote_modifier# - //char_id# - //sfx_delay# - //objection_modifier# - //evidence# - //placeholder# - //realization# - //text_color#% - - QStringList packet_contents; - - QString f_side = ao_app->get_char_side(current_char); - - QString f_desk_mod = "chat"; - - if (ao_app->desk_mod_enabled) - { - f_desk_mod = QString::number(ao_app->get_desk_mod(current_char, current_emote)); - if (f_desk_mod == "-1") - f_desk_mod = "chat"; - } - - packet_contents.append(f_desk_mod); - - packet_contents.append(ao_app->get_pre_emote(current_char, current_emote)); - - packet_contents.append(current_char); - - if(ui_hidden->isChecked()) - packet_contents.append("../../misc/blank"); - else - packet_contents.append(ao_app->get_emote(current_char, current_emote)); - - - - packet_contents.append(ui_ic_chat_message->text()); - - packet_contents.append(f_side); - - // packet_contents.append(ao_app->get_sfx_name(current_char, current_emote)); - // packet_contents.append(ui_sfx_search->text()); - - int row = ui_sfx_list->currentRow(); - if (row == -1 || row == 0) // default - packet_contents.append(ao_app->get_sfx_name(current_char, current_emote)); - else if (QListWidgetItem *item = ui_sfx_list->item(row)) // selection - { - double d_ind = item->statusTip().toDouble(); - int ind = int(d_ind); - qDebug() << ind; - packet_contents.append(sfx_names.at(ind)); - // packet_contents.append(sfx_names.at(row)); - } - - int f_emote_mod = ao_app->get_emote_mod(current_char, current_emote); - - //needed or else legacy won't understand what we're saying - if (m_objection_state > 0) - { - if (f_emote_mod == 5) - f_emote_mod = 6; - else - f_emote_mod = 2; - } - else if (ui_pre->isChecked()) - { - if (f_emote_mod == 0) - f_emote_mod = 1; - else if (f_emote_mod == 5 && ao_app->prezoom_enabled) - f_emote_mod = 4; - } - else - { - if (f_emote_mod == 1) - f_emote_mod = 0; - else if (f_emote_mod == 4) - f_emote_mod = 5; - } - - packet_contents.append(QString::number(f_emote_mod)); - packet_contents.append(QString::number(m_cid)); - - packet_contents.append(QString::number(ao_app->get_sfx_delay(current_char, current_emote))); - - QString f_obj_state; - - if (m_objection_state < 0 || (!ao_app->custom_objection_enabled && m_objection_state > 3)) - f_obj_state = "0"; - else - f_obj_state = QString::number(m_objection_state); - - packet_contents.append(f_obj_state); - - if (is_presenting_evidence) - //the evidence index is shifted by 1 because 0 is no evidence per legacy standards - //besides, older clients crash if we pass -1 - packet_contents.append(QString::number(current_evidence + 1)); - else - packet_contents.append("0"); - - QString f_flip; - - if (ao_app->flipping_enabled) - { - if (ui_flip->isChecked()) - f_flip = "1"; - else - f_flip = "0"; - } - else - f_flip = QString::number(m_cid); - - packet_contents.append(f_flip); - - packet_contents.append(QString::number(m_effect_state)); - - QString f_text_color; - - if (m_text_color < 0) - f_text_color = "0"; - else if (m_text_color > 4 && !ao_app->yellow_text_enabled) - f_text_color = "0"; - else - f_text_color = QString::number(m_text_color); - - packet_contents.append(f_text_color); - - prev_emote = current_emote; - - ao_app->send_server_packet(new AOPacket("MS", packet_contents)); -} - -void Courtroom::handle_char_anim(AOCharMovie *charPlayer) -{ - int time = ao_app->read_config("opacity_time").toInt(); - int op = ao_app->read_config("char_opacity").toInt(); - - QGraphicsOpacityEffect *opacity = new QGraphicsOpacityEffect; - QPropertyAnimation *anim = new QPropertyAnimation(opacity, "opacity"); - - charPlayer->setGraphicsEffect(opacity); - - anim->setDuration(time); - anim->setStartValue(1.0); - anim->setEndValue(op); - anim->setEasingCurve(QEasingCurve::InCubic); - anim->start(); -} - -void Courtroom::handle_char_anim_2(AOCharMovie *charPlayer) -{ - int time = ao_app->read_config("opacity_time").toInt(); - int op = ao_app->read_config("char_opacity").toInt(); - - QGraphicsOpacityEffect *opacity = new QGraphicsOpacityEffect; - QPropertyAnimation *anim = new QPropertyAnimation(opacity, "opacity"); - - charPlayer->setGraphicsEffect(opacity); - - anim->setDuration(time); - anim->setStartValue(op); - anim->setEndValue(1.0); - anim->setEasingCurve(QEasingCurve::InCubic); - anim->start(); -} - -void Courtroom::handle_chatmessage(QStringList *p_contents) -{ - if (p_contents->size() < 15) - return; - else if (p_contents->size() == 15) - p_contents->append(""); - - for (int n_string = 0 ; n_string < chatmessage_size ; ++n_string) - { - m_chatmessage[n_string] = p_contents->at(n_string); - // qDebug() << "m_chatmessage[" << n_string << "] = " << m_chatmessage[n_string]; - } - - int f_char_id = m_chatmessage[CHAR_ID].toInt(); - - if (f_char_id == -1) - { - is_system_speaking = true; - m_chatmessage[CHAR_ID] = "0"; - f_char_id = 0; - } - else is_system_speaking = false; - - if (f_char_id < 0 || f_char_id >= char_list.size()) - return; - - if (mute_map.value(m_chatmessage[CHAR_ID].toInt())) - return; - - QString f_showname; - qDebug() << "handle_chatmessage"; - qDebug() << m_chatmessage[SHOWNAME] << ao_app->get_showname(char_list.at(f_char_id).name); - - if(m_chatmessage[SHOWNAME].isEmpty()) - { - f_showname = ao_app->get_showname(char_list.at(f_char_id).name); - } - else - { - f_showname = m_chatmessage[SHOWNAME]; - } - - QString f_message = f_showname + ": " + m_chatmessage[MESSAGE] + "\n"; - - if (f_message == previous_ic_message && is_system_speaking == false) - return; - - text_state = 0; - anim_state = 0; - ui_vp_objection->stop(); - chat_tick_timer->stop(); - ui_vp_evidence_display->reset(); - - // reset effect - ui_vp_effect->stop(); - - chatmessage_is_empty = m_chatmessage[MESSAGE] == " " || m_chatmessage[MESSAGE] == ""; - showed = true; - - if (m_chatmessage[MESSAGE] == ui_ic_chat_message->text()) - { - ui_ic_chat_message->clear(); - ui_sfx_list->setCurrentItem(ui_sfx_list->item(0)); - - m_objection_state = 0; - reset_shout_buttons(); - - m_effect_state = 0; - reset_effect_buttons(); - - m_wtce_current = 0; - reset_wtce_buttons(); - - is_presenting_evidence = false; - ui_evidence_present->set_image("present_disabled.png"); - - // if(ao_app->read_config("first_person") == "true"){ - // ui_vp_player_char->hide(); - // qDebug() << "wow!"; - // } - // else ui_vp_player_char->show(); - - if(ao_app->read_config("first_person") == "true") showed = false; - else showed = true; - } - - if (is_system_speaking) - append_system_text(m_chatmessage[MESSAGE]); - else append_ic_text(m_chatmessage[MESSAGE], f_showname); - - if(ao_app->read_config("enable_logging") == "true") - save_textlog("[" + QTime::currentTime().toString() + "] " + f_showname + ": " + m_chatmessage[MESSAGE]); - - previous_ic_message = f_message; - - int objection_mod = m_chatmessage[OBJECTION_MOD].toInt(); - QString f_char = m_chatmessage[CHAR_NAME]; - QString f_custom_theme = ao_app->get_char_shouts(f_char); - - //if an objection is used - if (objection_mod > 0) - { - int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); - if (emote_mod == 0) - m_chatmessage[EMOTE_MOD] = 1; - - //handles cases 1-7 (5-7 are DRO only) - if(objection_mod >= 1 && objection_mod <= ui_shouts.size() && ui_shouts.size() > 0) // check to prevent crashing - { - ui_vp_objection->play(shout_names.at(objection_mod-1), f_char, f_custom_theme); - m_shout_player->play(shout_names.at(objection_mod-1) + ".wav", f_char); - } - else - qDebug() << "W: Shout identifier unknown" << objection_mod; - - } - else - handle_chatmessage_2(); -} - -void Courtroom::objection_done() -{ - handle_chatmessage_2(); -} - -void Courtroom::handle_chatmessage_2() // handles IC -{ - ui_vp_speedlines->stop(); - ui_vp_player_char->stop(); - - qDebug() << "handle_chatmessage_2"; - - QString real_name = char_list.at(m_chatmessage[CHAR_ID].toInt()).name; - - QString f_showname; - - if(m_chatmessage[SHOWNAME].isEmpty()) - { - f_showname = ao_app->get_showname(real_name); - } - else - { - f_showname = m_chatmessage[SHOWNAME]; - } - - QString f_color = ao_app->read_char_ini(real_name, "color", "[Options]", "[Time]"); - if (f_color == "") - f_color = "rgb(" + ao_app->read_design_ini("showname_color" , ao_app->get_theme_path() + "courtroom_fonts.ini") + ")"; - - int bold = ao_app->get_font_size("showname_bold", fonts_ini); // is the font bold or not? // taken directly from function up there lol // kinda hacky - QString is_bold = ""; - if(bold == 1) is_bold = "bold"; - - ui_vp_showname->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: " + f_color + ";font: " + is_bold); - ui_vp_showname->setText(f_showname); - - ui_vp_message->clear(); - ui_vp_chatbox->hide(); - ui_vp_showname_image->hide(); - - QString chatbox = ao_app->get_chat(m_chatmessage[CHAR_NAME]); - - if (chatbox == "") - if (ao_app->read_design_ini("daynight_theme", ao_app->get_theme_path() + cc_config_ini) == "true") - { - if (current_clock < 0) - ui_vp_chatbox->set_image("chatmed.png"); - else if (current_clock >= 7 && current_clock < 22) - ui_vp_chatbox->set_image("chatmed_day.png"); - else - ui_vp_chatbox->set_image("chatmed_night.png"); - } - else - ui_vp_chatbox->set_image("chatmed.png"); - else - { - QString chatbox_path = ao_app->get_base_path() + "misc/" + chatbox + ".png"; - ui_vp_chatbox->set_image_from_path(chatbox_path); - } - - set_scene(); - set_text_color(); - - int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); - - if (ao_app->flipping_enabled && m_chatmessage[FLIP].toInt() == 1) - ui_vp_player_char->set_flipped(true); - else - ui_vp_player_char->set_flipped(false); - - - switch (emote_mod) - { - case 1: case 2: case 6: - play_preanim(); - break; - default: - qDebug() << "W: invalid emote mod: " << QString::number(emote_mod); - //intentional fallthru - case 0: case 5: - handle_chatmessage_3(); - } -} - -void Courtroom::handle_chatmessage_3() -{ - qDebug() << "handle_chatmessage_3"; - - start_chat_ticking(); - - int f_evi_id = m_chatmessage[EVIDENCE_ID].toInt(); - QString f_side = m_chatmessage[SIDE]; - - if (f_evi_id > 0 && f_evi_id <= local_evidence_list.size()) - { - //shifted by 1 because 0 is no evidence per legacy standards - QString f_image = local_evidence_list.at(f_evi_id - 1).image; - //def jud and hlp should display the evidence icon on the RIGHT side - bool is_left_side = !(f_side == "def" || f_side == "hlp" || f_side == "jud"); - ui_vp_evidence_display->show_evidence(f_image, is_left_side, ui_sfx_slider->value()); - } - - int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); - - if (emote_mod == 5 || - emote_mod == 6) - { - QString side = m_chatmessage[SIDE]; - ui_vp_desk->hide(); - ui_vp_legacy_desk->hide(); - - if (side == "pro" || - side == "hlp" || - side == "wit") - ui_vp_speedlines->play("prosecution_speedlines"); - else - ui_vp_speedlines->play("defense_speedlines"); - - } - - int f_anim_state = 0; - //BLUE is from an enum in datatypes.h - bool text_is_blue = m_chatmessage[TEXT_COLOR].toInt() == BLUE; - - if (!text_is_blue && text_state == 1) - //talking - f_anim_state = 2; - else - //idle - f_anim_state = 3; - - if (f_anim_state <= anim_state) - return; - - ui_vp_player_char->stop(); - QString f_char = m_chatmessage[CHAR_NAME]; - QString f_emote = m_chatmessage[EMOTE]; - - ui_vp_showname_image->show(); - QVector exts = {".png", ".jpg", ".bmp"}; - - QString ext = file_exists(ao_app->get_character_path(f_char) + "showname", exts); - if(ext != "" && !chatmessage_is_empty && ao_app->read_design_ini("enable_showname_image", ao_app->get_theme_path() + cc_config_ini) == "true") - { - ui_vp_showname->hide(); - QString path = ao_app->get_character_path(f_char) + "showname" + ext; - ui_vp_showname_image->set_image_from_path(path); - ui_vp_showname_image->show(); - } - else - { - ui_vp_showname->show(); - ui_vp_showname_image->hide(); - } - - - switch (f_anim_state) - { - case 2: - ui_vp_player_char->play_talking(f_char, f_emote, showed); - anim_state = 2; - break; - default: - qDebug() << "W: invalid anim_state: " << f_anim_state; - case 3: - ui_vp_player_char->play_idle(f_char, f_emote, showed); - anim_state = 3; - } - - int effect = m_chatmessage[EFFECT_STATE].toInt(); - QStringList offset = ao_app->get_effect_offset(f_char, effect); - - ui_vp_effect->move(ui_viewport->x() + offset.at(0).toInt(), ui_viewport->y() + offset.at(1).toInt()); - - QStringList overlay = ao_app->get_overlay(f_char, effect); - QString overlay_name = overlay.at(0); - QString overlay_sfx = overlay.at(1); - - QString design_ini = ao_app->get_theme_path() + cc_config_ini; - bool do_it = ao_app->read_design_ini("non_vanilla_effects", design_ini) == "true"; - - if (effect == 1 && !do_it) - { - if (overlay_sfx == "") - overlay_sfx = ao_app->get_sfx("effect_flash"); - m_sfx_player->play(overlay_sfx); - ui_vp_effect->set_play_once(true); - if (overlay_name == "") - overlay_name = "effect_flash"; - ui_vp_effect->play(overlay_name, f_char); - realization_timer->start(60); - } -// else if (effect == 2) -// { -// if (overlay_sfx == "") -// overlay_sfx = ao_app->get_sfx("effect_gloom"); -// m_sfx_player->play(overlay_sfx); -// ui_vp_effect->set_play_once(false); -// if (overlay_name == "") -// overlay_name = "effect_gloom"; -// ui_vp_effect->play(overlay_name, f_char); -// } - else if (do_it && effect > 0 && effect <= ui_effects.size() && effect_names.size() > 0) // check to prevent crashing - { - QString s_eff = effect_names.at(effect - 1); - QStringList f_eff = ao_app->get_effect(effect); - -// QString s_eff = f_eff.at(0).trimmed(); - bool once = f_eff.at(1).trimmed().toInt(); - - if (overlay_sfx == "") - overlay_sfx = ao_app->get_sfx(s_eff); - // qDebug() << overlay_sfx << ao_app->get_sfx(s_eff); - m_sfx_player->play(overlay_sfx); - ui_vp_effect->set_play_once(once); - if (overlay_name == "") - overlay_name = s_eff; - ui_vp_effect->play(overlay_name, f_char); - } - - QString f_message = m_chatmessage[MESSAGE]; - QStringList call_words = ao_app->get_call_words(); - - for (QString word : call_words) - { - if (f_message.contains(word, Qt::CaseInsensitive)) - { - m_mod_player->play(ao_app->get_sfx("word_call")); - ao_app->alert(this); - - break; - } - } - -} - -void Courtroom::append_ic_text(QString p_text, QString p_name) -{ - QTextCharFormat bold; - QTextCharFormat normal; - QString color = ao_app->read_design_ini("chatlog_showname", ao_app->get_theme_path() + fonts_ini); - bold.setFontWeight(QFont::Bold); - normal.setFontWeight(QFont::Normal); - const QTextCursor old_cursor = ui_ic_chatlog->textCursor(); - const int old_scrollbar_value = ui_ic_chatlog->verticalScrollBar()->value(); - const bool is_scrolled_up = old_scrollbar_value == ui_ic_chatlog->verticalScrollBar()->maximum(); - const QTextCursor::MoveOperation f_operation = m_scroll_down ? QTextCursor::End : QTextCursor::Start; - - //rewrite everything that was written before - if(m_scroll_type_changed) - { - ui_ic_chatlog->clear(); - - for (record_type_ptr record : m_ic_records) - { - ui_ic_chatlog->moveCursor(f_operation); - if (record->system == false) - { - ui_ic_chatlog->insertHtml("color + "\">" + record->name + ""); - ui_ic_chatlog->textCursor().insertText("\n"); - ui_ic_chatlog->textCursor().insertText(record->line + "\n\n", normal); - } - else - { - ui_ic_chatlog->insertHtml("color + "\">" + record->line + ""); - ui_ic_chatlog->textCursor().insertText("\n\n"); - } - } - m_scroll_type_changed = false; - } - - // new record - m_ic_records.append(std::make_shared(p_name, p_text, color, false)); - - int len = m_ic_records.length(); - - if (len > m_log_limit) // magic numbers, woo! - { - m_ic_records = m_ic_records.mid(len - m_log_limit); - } - - - if(m_scroll_down) - { - ui_ic_chatlog->moveCursor(QTextCursor::End); - ui_ic_chatlog->insertHtml("" + p_name + ""); - ui_ic_chatlog->textCursor().insertText("\n" + p_text + "\n\n", normal); - } - else - { - ui_ic_chatlog->clear(); - - for (record_type_ptr record : m_ic_records) - { - ui_ic_chatlog->moveCursor(QTextCursor::Start); - if (record->system == false) - { - ui_ic_chatlog->insertHtml("color + "\">" + record->name + ""); - ui_ic_chatlog->textCursor().insertText("\n"); - ui_ic_chatlog->textCursor().insertText(record->line + "\n\n", normal); - } - else - { - ui_ic_chatlog->insertHtml("color + "\">" + record->line + ""); - ui_ic_chatlog->textCursor().insertText("\n\n"); - } - } - } - - if (old_cursor.hasSelection() || !is_scrolled_up) - { - // The user has selected text or scrolled away from the top: maintain position. - ui_ic_chatlog->setTextCursor(old_cursor); - ui_ic_chatlog->verticalScrollBar()->setValue(old_scrollbar_value); - } - else - { - // The user hasn't selected any text and the scrollbar is at the top: scroll to the top. - ui_ic_chatlog->moveCursor(f_operation); - ui_ic_chatlog->verticalScrollBar()->setValue(ui_ic_chatlog->verticalScrollBar()->maximum()); - } -} - -void Courtroom::append_system_text(QString p_text) -{ - if (chatmessage_is_empty) return; - - QTextCharFormat bold; - QTextCharFormat normal; - QString color = ao_app->read_design_ini("system_msg", ao_app->get_theme_path() + fonts_ini); - bold.setFontWeight(QFont::Bold); - normal.setFontWeight(QFont::Normal); - const QTextCursor old_cursor = ui_ic_chatlog->textCursor(); - const int old_scrollbar_value = ui_ic_chatlog->verticalScrollBar()->value(); - const bool is_scrolled_up = old_scrollbar_value == ui_ic_chatlog->verticalScrollBar()->maximum(); - const QTextCursor::MoveOperation f_operation = m_scroll_down ? QTextCursor::End : QTextCursor::Start; - - //rewrite everything that was written before - if(m_scroll_type_changed) - { - ui_ic_chatlog->clear(); - - for (record_type_ptr record : m_ic_records) - { - ui_ic_chatlog->moveCursor(f_operation); - if (record->system == false) - { - ui_ic_chatlog->insertHtml("color + "\">" + record->name + ""); - ui_ic_chatlog->textCursor().insertText("\n" + record->line + "\n\n", normal); - - } - else - { - ui_ic_chatlog->insertHtml("color + "\">" + record->line + ""); - ui_ic_chatlog->textCursor().insertText("\n\n"); - } - } - m_scroll_type_changed = false; - } - - // new record - m_ic_records.append(std::make_shared("", p_text, color, true)); - - int len = m_ic_records.length(); - - if (len > m_log_limit) // magic numbers, woo! - { - m_ic_records = m_ic_records.mid(len - m_log_limit); - } - - if(m_scroll_down) - { - ui_ic_chatlog->moveCursor(QTextCursor::End); - ui_ic_chatlog->insertHtml("" + p_text + ""); - ui_ic_chatlog->textCursor().insertText("\n\n"); - } - else - { - ui_ic_chatlog->clear(); - - for (record_type_ptr record : m_ic_records) - { - ui_ic_chatlog->moveCursor(QTextCursor::Start); - if (record->system == false) - { - ui_ic_chatlog->insertHtml("color + "\">" + record->name + ""); - ui_ic_chatlog->textCursor().insertText("\n" + record->line + "\n\n", normal); - } - else - { - qDebug() << "color + ">" + record->line + ""; - ui_ic_chatlog->insertHtml("color + "\">" + record->line + ""); - ui_ic_chatlog->textCursor().insertText("\n\n"); - } - } - } - - ui_ic_chatlog->setTextColor("#000000"); - - if (old_cursor.hasSelection() || !is_scrolled_up) - { - // The user has selected text or scrolled away from the top: maintain position. - ui_ic_chatlog->setTextCursor(old_cursor); - ui_ic_chatlog->verticalScrollBar()->setValue(old_scrollbar_value); - } - else - { - // The user hasn't selected any text and the scrollbar is at the top: scroll to the top. - ui_ic_chatlog->moveCursor(f_operation); - ui_ic_chatlog->verticalScrollBar()->setValue(ui_ic_chatlog->verticalScrollBar()->maximum()); - } -} - -void Courtroom::play_preanim() -{ - QString f_char = m_chatmessage[CHAR_NAME]; - QString f_preanim = m_chatmessage[PRE_EMOTE]; - - //all time values in char.inis are multiplied by a constant(time_mod) to get the actual time - int ao2_duration = ao_app->get_ao2_preanim_duration(f_char, f_preanim); - int text_delay = ao_app->get_text_delay(f_char, f_preanim) * time_mod; - int sfx_delay = m_chatmessage[SFX_DELAY].toInt() * 60; - - int preanim_duration; - - if (ao2_duration < 0) - preanim_duration = ao_app->get_preanim_duration(f_char, f_preanim); - else - preanim_duration = ao2_duration; - - sfx_delay_timer->start(sfx_delay); - - if (!file_exists(ao_app->get_character_path(f_char) + f_preanim.toLower() + ".gif") || - preanim_duration < 0) - { - anim_state = 1; - preanim_done(); - qDebug() << "could not find " + ao_app->get_character_path(f_char) + f_preanim.toLower() + ".gif"; - return; - } - - ui_vp_player_char->play_pre(f_char, f_preanim, preanim_duration, showed); - - anim_state = 1; - if (text_delay >= 0) - text_delay_timer->start(text_delay); - -} - -void Courtroom::preanim_done() -{ - handle_chatmessage_3(); -} - -void Courtroom::realization_done() -{ - ui_vp_effect->stop(); -} - -QString Courtroom::parse_message(QString message) -{ - QVector f_vec = ao_app->get_highlight_color(); - - for(int i=0; i")); - message.remove(i, 1); - message.insert(i, QString("")); - } - } - break; - } - } - } - - return message; -} - -void Courtroom::start_chat_ticking() -{ - ui_vp_message->clear(); - set_text_color(); - rainbow_counter = 0; - //we need to ensure that the text isn't already ticking because this function can be called by two logic paths - if (text_state != 0) - return; - - if (chatmessage_is_empty) - { - //since the message is empty, it's technically done ticking - text_state = 2; - return; - } - - ui_vp_chatbox->show(); - - tick_pos = 0; - blip_pos = 0; - chat_tick_timer->start(chat_tick_interval); - - QString f_gender = ao_app->get_gender(m_chatmessage[CHAR_NAME]); - - // m_blip_player->set_file(f_gender); - m_blip_player->set_blips("sfx-blip" + f_gender + ".wav"); - - //means text is currently ticking - text_state = 1; -} - -void Courtroom::chat_tick() -{ - //note: this is called fairly often(every 60 ms when char is talking) - //do not perform heavy operations here - - QString f_message = m_chatmessage[MESSAGE]; -// QString parsed_message = parse_message(f_message); -// qDebug() << "parsed:" << parsed_message; - - if (tick_pos >= f_message.size()) - { - text_state = 2; - chat_tick_timer->stop(); - anim_state = 3; - ui_vp_player_char->play_idle(m_chatmessage[CHAR_NAME], m_chatmessage[EMOTE], showed); - m_string_color = ""; - m_color_stack.clear(); - } - - else - { - QString f_character = f_message.at(tick_pos); - f_character = f_character.toHtmlEscaped(); - //qDebug() << f_character; - - if (f_character == " ") - ui_vp_message->insertPlainText(" "); - else if (m_chatmessage[TEXT_COLOR].toInt() == RAINBOW) - { - QString html_color; - - switch (rainbow_counter) - { - case 0: - html_color = "#FF0000"; - break; - case 1: - html_color = "#FF7F00"; - break; - case 2: - html_color = "#FFFF00"; - break; - case 3: - html_color = "#00FF00"; - break; - default: - html_color = "#2d96ff"; - rainbow_counter = -1; - } - - ++rainbow_counter; - - ui_vp_message->insertHtml("" + f_character + ""); - } - else if(ao_app->read_design_ini("enable_highlighting", ao_app->get_theme_path() + cc_config_ini) == "true") - { - bool found = false; - QVector f_vec = ao_app->get_highlight_color(); - if(m_color_stack.isEmpty()) m_color_stack.push(""); - - for(const auto& col : f_vec) - { - if(f_character == col[0].trimmed()[0] && m_string_color != col[1].trimmed()) - { - m_color_stack.push(col[1].trimmed()); - m_string_color = m_color_stack.top(); - found = true; - break; - } - } - - //qDebug() << "character = " << f_character << "color=" << m_string_color; - ui_vp_message->insertHtml("" + f_character + ""); - - for(const auto& col : f_vec) - { - if(f_character == col[0].trimmed()[1] && !found) - { - if(m_color_stack.size() > 1) m_color_stack.pop(); - m_string_color = m_color_stack.top(); - break; - } - - } - } -// else if(ao_app->read_design_ini("enable_highlighting", ao_app->get_theme_path() + cc_config_ini) == "extra") -// { -// QString ch = parsed_message.at(tick_pos); -// ui_vp_message->insertHtml(ch); -// } - else - { - ui_vp_message->insertHtml(f_character); - } - QScrollBar *scroll = ui_vp_message->verticalScrollBar(); - scroll->setValue(scroll->maximum()); - - if(blank_blip) - qDebug() << "blank_blip found true"; - - if ((f_message.at(tick_pos) != ' ' || blank_blip)) - { - - if (blip_pos % blip_rate == 0) - { - blip_pos = 0; - - // play blip - // m_blip_player->play(); - m_blip_player->blip_tick(); - } - - ++blip_pos; - } - - ++tick_pos; - } -} - -void Courtroom::show_testimony() -{ - if (!testimony_in_progress || m_chatmessage[SIDE] != "wit") - return; - - ui_vp_testimony->show(); - - testimony_show_timer->start(testimony_show_time); -} - -void Courtroom::hide_testimony() -{ - ui_vp_testimony->hide(); - - if (!testimony_in_progress) - return; - - testimony_hide_timer->start(testimony_hide_time); -} - -void Courtroom::play_sfx() -{ - QString sfx_name = m_chatmessage[SFX_NAME]; - if (sfx_name == "1") - return; - - QString f_ext = file_exists(ao_app->get_base_path() + "/sounds/general/" + sfx_name, QVector{ "", ".ogg", ".wav", ".mp3"}); - - m_sfx_player->play(sfx_name + f_ext); -} - -void Courtroom::set_scene() -{ - if (testimony_in_progress) - show_testimony(); - - //witness is default if pos is invalid - QString f_background = "witnessempty"; - QString f_desk_image = "stand"; - QString f_desk_mod = m_chatmessage[DESK_MOD]; - QString f_side = m_chatmessage[SIDE]; - QString ini_path = ao_app->get_background_path() + "backgrounds.ini"; - - if (file_exists(ini_path)) - { - f_background = ao_app->read_design_ini(f_side, ini_path); - f_desk_image = ao_app->read_design_ini(f_side + "_desk", ini_path); - - if (f_desk_mod == "0") // keeping a bit of the functionality for now - { - ui_vp_desk->hide(); - ui_vp_legacy_desk->hide(); - } - } - else - { - if (f_side == "def") - { - f_background = "defenseempty"; - if (is_ao2_bg) - f_desk_image = "defensedesk"; - else - f_desk_image = "bancodefensa"; - } - else if (f_side == "pro") - { - f_background = "prosecutorempty"; - if (is_ao2_bg) - f_desk_image = "prosecutiondesk"; - else - f_desk_image = "bancoacusacion"; - } - else if (f_side == "jud") - { - f_background = "judgestand"; - f_desk_image = "judgedesk"; - } - else if (f_side == "hld") - { - f_background = "helperstand"; - f_desk_image = "helperdesk"; - } - else if (f_side == "hlp") - { - f_background = "prohelperstand"; - f_desk_image = "prohelperdesk"; - } - else - { - if (is_ao2_bg) - f_desk_image = "stand"; - else - f_desk_image = "estrado"; - } - - if (f_desk_mod == "0" || (f_desk_mod != "1" && - (f_side == "jud" || - f_side == "hld" || - f_side == "hlp"))) - { - ui_vp_desk->hide(); - ui_vp_legacy_desk->hide(); - } - else if (is_ao2_bg || (f_side == "jud" || - f_side == "hld" || - f_side == "hlp")) - { - ui_vp_legacy_desk->hide(); - ui_vp_desk->show(); - } - else - { - if (f_side == "wit") - { - ui_vp_desk->show(); - ui_vp_legacy_desk->hide(); - } - else - { - ui_vp_desk->hide(); - ui_vp_legacy_desk->show(); - } - } - } - - ui_vp_background->set_image(f_background); - ui_vp_desk->set_image(f_desk_image); - ui_vp_legacy_desk->set_legacy_desk(f_desk_image); -} - -void Courtroom::set_text_color() -{ - switch (m_chatmessage[TEXT_COLOR].toInt()) - { - case GREEN: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: rgb(0, 255, 0)"); - break; - case RED: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: red"); - break; - case ORANGE: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: orange"); - break; - case BLUE: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: rgb(45, 150, 255)"); - break; - case YELLOW: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: yellow"); - break; - default: - qDebug() << "W: undefined text color: " << m_chatmessage[TEXT_COLOR]; - case WHITE: - ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: white"); - - } -} - -void Courtroom::set_ip_list(QString p_list) -{ - QString f_list = p_list.replace("|", ":").replace("*", "\n"); - - ui_server_chatlog->append(f_list); -} - -void Courtroom::set_mute(bool p_muted, int p_cid) -{ - if (p_cid != m_cid && p_cid != -1) - return; - - if (p_muted) - ui_muted->show(); - else - { - ui_muted->hide(); - ui_ic_chat_message->setFocus(); - } - - ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); - ui_muted->set_image("muted.png"); - - is_muted = p_muted; - ui_ic_chat_message->setEnabled(!p_muted); -} - -void Courtroom::set_ban(int p_cid) -{ - if (p_cid != m_cid && p_cid != -1) - return; - - call_notice("You have been banned."); - - ao_app->construct_lobby(); - ao_app->destruct_courtroom(); -} - -void Courtroom::handle_song(QStringList *p_contents) -{ - - QStringList f_contents = *p_contents; - - if (f_contents.size() < 2) - return; - - QString f_song = f_contents.at(0); - int n_char = f_contents.at(1).toInt(); - - for (auto& ext : QStringList { "", ".wav", ".ogg", ".mp3" }) - { - QString r_song = f_song + ext; - QString song_path = ao_app->get_base_path() + "sounds/music/" + r_song.toLower(); - if (file_exists(song_path)) - { - f_song = r_song; - break; - } - } - - if (n_char < 0 || n_char >= char_list.size()) - { - m_music_player->play(f_song); - } - else - { - // This 2th argument corresponds to the showname to use when displaying the - // music change message in IC - // Backwards compatibility is explicitly kept for older versions of tsuserver - // that do not send such an argument by assuming an empty showname - // If there is an empty showname, the client will use instead the default - // showname of the character. - QString f_showname; - if (f_contents.size() == 3) { - f_showname = f_contents.at(2); - } else { - f_showname = ""; - } - - QString str_char; - if (f_showname.isEmpty()) { - str_char = ao_app->get_showname(char_list.at(n_char).name); - } else { - str_char = f_showname; - } - - if (!mute_map.value(n_char)) - { - QString music_change_log = ao_app->read_config("music_change_log"); - - if (music_change_log == ("false")) - { - m_music_player->play(f_song); - } - else - { - append_ic_text("has played a song: " + f_song, str_char); - m_music_player->play(f_song); - } - } - } - - int pos = f_song.lastIndexOf(QChar('.')); - QString r_song = f_song.left(pos); - - ui_vp_music_name->setText(r_song); - - handle_music_anim(); -} - -void Courtroom::handle_wtce(QString p_wtce) -{ - QString sfx_file = cc_sounds_ini; - - int index = p_wtce.at(p_wtce.size() - 1).digitValue(); - if (index > 0 && index < wtce_names.size() + 1 && wtce_names.size() > 0) // check to prevent crash - { - p_wtce.chop(1); // looking for the 'testimony' part - if (p_wtce == "testimony") - { - m_sfx_player->play(ao_app->get_sfx(wtce_names[index-1])); - ui_vp_wtce->play(wtce_names[index-1]); - if (index == 1) - { - testimony_in_progress = true; - } - else if (index == 2) - testimony_in_progress = false; - } - } -} - -void Courtroom::set_hp_bar(int p_bar, int p_state) -{ - if (p_state < 0 || p_state > 10) - return; - - if (p_bar == 1) - { - ui_defense_bar->set_image("defensebar" + QString::number(p_state) + ".png"); - defense_bar_state = p_state; - } - else if (p_bar == 2) - { - ui_prosecution_bar->set_image("prosecutionbar" + QString::number(p_state) + ".png"); - prosecution_bar_state = p_state; - } -} - -void Courtroom::check_shouts() -{ - QString char_path = ao_app->get_character_path(current_char); - QString theme_path = ao_app->get_theme_path(); - for(int i = 0; i < ui_shouts.size(); ++i) - { - if(file_exists(char_path + shout_names.at(i) + ".gif") || file_exists(theme_path + shout_names.at(i) + ".gif") || file_exists(theme_path + shout_names.at(i) + ".apng")) - shouts_enabled[i] = true; - else shouts_enabled[i] = false; - } -} - -void Courtroom::check_effects() -{ - QString char_path = ao_app->get_character_path(current_char); - QString theme_path = ao_app->get_theme_path(); - for(int i = 0; i < effect_names.size(); ++i) - { - if(file_exists(theme_path + effect_names.at(i) + ".gif") || file_exists(theme_path + effect_names.at(i) + ".apng")) - effects_enabled[i] = true; - else effects_enabled[i] = false; - } -} - -void Courtroom::check_wtce() -{ - QString char_path = ao_app->get_character_path(current_char); - QString theme_path = ao_app->get_theme_path(); - for(int i = 0; i < ui_wtce.size(); ++i) - { - if(file_exists(char_path + wtce_names.at(i) + ".gif") || file_exists(theme_path + wtce_names.at(i) + ".gif") || file_exists(theme_path + wtce_names.at(i) + ".apng")) - wtce_enabled[i] = true; - else wtce_enabled[i] = false; - } -} - -void Courtroom::mod_called(QString p_ip) -{ - ui_server_chatlog->append(p_ip); - if (ui_guard->isChecked()) - { - m_mod_player->play(ao_app->get_sfx("mod_call")); - ao_app->alert(this); - } -} - -void Courtroom::on_ooc_return_pressed() -{ - QString ooc_message = ui_ooc_chat_message->text(); - - if (ooc_message == "" || ui_ooc_chat_name->text() == "") - return; - - if (ooc_message.startsWith("/pos")) - { - if (ooc_message.startsWith("/pos jud")) - { - is_judge = true; - set_wtce(); - - ui_defense_minus->show(); - ui_defense_plus->show(); - ui_prosecution_minus->show(); - ui_prosecution_plus->show(); - } - else - { - is_judge = false; - set_wtce(); - - ui_defense_minus->hide(); - ui_defense_plus->hide(); - ui_prosecution_minus->hide(); - ui_prosecution_plus->hide(); - } - } - else if (ooc_message.startsWith("/login")) - ui_guard->show(); - else if (ooc_message.startsWith("/rainbow") && ao_app->yellow_text_enabled && !rainbow_appended) - { - ui_text_color->addItem("Rainbow"); - ui_ooc_chat_message->clear(); - rainbow_appended = true; - return; - } - else if (ooc_message.startsWith("/switch_am")) - { - on_switch_area_music_clicked(); - ui_ooc_chat_message->clear(); - return; - } - else if (ooc_message.startsWith("/roll")) - { - m_sfx_player->play(ao_app->get_sfx("dice")); - } - else if (ooc_message.startsWith("/rollp")) - { - m_sfx_player->play(ao_app->get_sfx("dice")); - } - else if (ooc_message.startsWith("/coinflip")) - { - m_sfx_player->play(ao_app->get_sfx("coinflip")); - } - QStringList packet_contents; - packet_contents.append(ui_ooc_chat_name->text()); - packet_contents.append(ooc_message); - - AOPacket *f_packet = new AOPacket("CT", packet_contents); - - if (server_ooc) - ao_app->send_server_packet(f_packet); - else - ao_app->send_ms_packet(f_packet); - - ui_ooc_chat_message->clear(); - - ui_ooc_chat_message->setFocus(); -} - -void Courtroom::on_ooc_toggle_clicked() -{ - if (server_ooc) - { - ui_ms_chatlog->show(); - ui_server_chatlog->hide(); - ui_ooc_toggle->setText("Master"); - - server_ooc = false; - } - else - { - ui_ms_chatlog->hide(); - ui_server_chatlog->show(); - ui_ooc_toggle->setText("Server"); - - server_ooc = true; - } -} - -void Courtroom::on_music_search_edited(QString p_text) -{ - //preventing compiler warnings - p_text += "a"; - list_music(); - list_areas(); -} - -void Courtroom::on_sfx_search_edited(QString p_text) -{ - //preventing compiler warnings - p_text += "a"; - list_sfx(); -} - -void Courtroom::on_pos_dropdown_changed(int p_index) -{ - ui_ic_chat_message->setFocus(); - - if (p_index < 0 || p_index > 5) - return; - - QString f_pos; - - switch (p_index) - { - case 0: - f_pos = "wit"; - break; - case 1: - f_pos = "def"; - break; - case 2: - f_pos = "pro"; - break; - case 3: - f_pos = "jud"; - break; - case 4: - f_pos = "hld"; - break; - case 5: - f_pos = "hlp"; - break; - default: - f_pos = ""; - } - - if (f_pos == "" || ui_ooc_chat_name->text() == "") - return; - - ao_app->send_server_packet(new AOPacket("CT#" + ui_ooc_chat_name->text() + "#/pos " + f_pos + "#%")); -} - -void Courtroom::on_mute_list_clicked(QModelIndex p_index) -{ - QListWidgetItem *f_item = ui_mute_list->item(p_index.row()); - QString f_char = f_item->text(); - QString real_char; - - if (f_char.endsWith(" [x]")) - real_char = f_char.left(f_char.size() - 4); - else - real_char = f_char; - - int f_cid = -1; - - for (int n_char = 0 ; n_char < char_list.size() ; n_char++) - { - if (char_list.at(n_char).name == real_char) - f_cid = n_char; - } - - if (f_cid < 0 || f_cid >= char_list.size()) - { - qDebug() << "W: " << real_char << " not present in char_list"; - return; - } - - if (mute_map.value(f_cid)) - { - mute_map.insert(f_cid, false); - f_item->setText(real_char); - } - else - { - mute_map.insert(f_cid, true); - f_item->setText(real_char + " [x]"); - } - - - - /* - if (f_char.endsWith(" [x]")) - { - real_char = f_char.left(f_char.size() - 4); - mute_map.remove(real_char); - mute_map.insert(real_char, false); - f_item->setText(real_char); - } - else - { - real_char = f_char; - mute_map.remove(real_char); - mute_map.insert(real_char, true); - f_item->setText(real_char + " [x]"); - } - */ -} - -void Courtroom::on_music_list_clicked() -{ - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_area_list_clicked() -{ - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_music_list_double_clicked(QModelIndex p_model) -{ - if (is_muted) - return; - - QString p_song = ui_music_list->item(p_model.row())->text(); - - ao_app->send_server_packet(new AOPacket("MC#" + p_song + "#" + QString::number(m_cid) + "#%"), false); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_area_list_double_clicked(QModelIndex p_model) -{ - QString p_area = ui_area_list->item(p_model.row())->text(); - - ao_app->send_server_packet(new AOPacket("MC#" + p_area + "#" + QString::number(m_cid) + "#%"), false); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::reset_shout_buttons() -{ - for(int i = 0; i < ui_shouts.size(); ++i) - ui_shouts[i]->set_image(shout_names.at(i) + ".png"); - - if(m_objection_state != 0 && ui_shouts.size() > 0) // check to prevent crashing - ui_shouts.at(m_objection_state-1)->set_image(shout_names.at(m_objection_state-1) + "_selected.png"); -} - -void Courtroom::on_shout_clicked() -{ - AOButton *f_shout_button = static_cast(sender()); - int f_shout_id = f_shout_button->property("shout_id").toInt(); - - // update based on current button selected - if (f_shout_id == m_objection_state) - m_objection_state = 0; - else - m_objection_state = f_shout_id; - - reset_shout_buttons(); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_cycle_clicked() -{ - AOButton *f_cycle_button = static_cast(sender()); - int f_cycle_id = f_cycle_button->property("cycle_id").toInt(); - - switch(f_cycle_id) - { - case 5: - cycle_wtce(-1); - break; - case 4: - cycle_wtce(1); - break; - case 3: - cycle_effect(-1); - break; - case 2: - cycle_effect(1); - break; - case 1: - cycle_shout(-1); - break; - case 0: - cycle_shout(1); - break; - default: - break; - } - - if(ao_app->read_design_ini("enable_cycle_ding", ao_app->get_theme_path() + cc_config_ini) == "true") - m_cycle_player->play(ao_app->get_sfx("cycle")); - - set_shouts(); - ui_ic_chat_message->setFocus(); -} - -void Courtroom::cycle_shout(int p_index) -{ - int n = ui_shouts.size(); - do { m_shout_state = (m_shout_state - p_index + n) % n; } while( !shouts_enabled[m_shout_state] ); - - set_shouts(); -} - -void Courtroom::cycle_effect(int p_index) -{ - int n = ui_effects.size(); - do { m_effect_current = (m_effect_current - p_index + n) % n; } while( !effects_enabled[m_effect_current] ); - - set_effects(); -} - -void Courtroom::cycle_wtce(int p_index) -{ - int n = ui_wtce.size(); - do { m_wtce_current = (m_wtce_current - p_index + n) % n; } while( !wtce_enabled[m_wtce_current] ); - - set_wtce(); -} - -void Courtroom::reset_effect_buttons() -{ - for(int i = 0; i < effect_names.size(); ++i) // effect names does not necessarily have the same size as ui effects - { -// qDebug() << effect_names << i; - ui_effects[i]->set_image(effect_names.at(i) + ".png"); - } - - if(m_effect_state != 0 && ui_effects.size() > 0) // check to prevent crashing - ui_effects[m_effect_state-1]->set_image(effect_names.at(m_effect_state-1) + "_pressed.png"); -} - -void Courtroom::on_effect_button_clicked() -{ - AOButton *f_button = static_cast(this->sender()); - - int f_effect_id = f_button->property("effect_id").toInt(); - if (m_effect_state == f_effect_id) - m_effect_state = 0; - else - m_effect_state = f_effect_id; - - reset_effect_buttons(); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_mute_clicked() -{ - if (ui_mute_list->isHidden()) - { - ui_mute_list->show(); - ui_mute->set_image("mute_pressed.png"); - } - else - { - ui_mute_list->hide(); - ui_mute->set_image("mute.png"); - } -} - -void Courtroom::on_defense_minus_clicked() -{ - int f_state = defense_bar_state - 1; - - if (f_state >= 0) - ao_app->send_server_packet(new AOPacket("HP#1#" + QString::number(f_state) + "#%")); -} - -void Courtroom::on_defense_plus_clicked() -{ - int f_state = defense_bar_state + 1; - - if (f_state <= 10) - ao_app->send_server_packet(new AOPacket("HP#1#" + QString::number(f_state) + "#%")); -} - -void Courtroom::on_prosecution_minus_clicked() -{ - int f_state = prosecution_bar_state - 1; - - if (f_state >= 0) - ao_app->send_server_packet(new AOPacket("HP#2#" + QString::number(f_state) + "#%")); -} - -void Courtroom::on_prosecution_plus_clicked() -{ - int f_state = prosecution_bar_state + 1; - - if (f_state <= 10) - ao_app->send_server_packet(new AOPacket("HP#2#" + QString::number(f_state) + "#%")); -} - -void Courtroom::on_text_color_changed(int p_color) -{ - m_text_color = p_color; - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_music_slider_moved(int p_value) -{ - m_music_player->set_volume(p_value); - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_sfx_slider_moved(int p_value) -{ - m_sfx_player->set_volume(p_value); - m_shout_player->set_volume(p_value); - m_cycle_player->set_volume(p_value); - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_blip_slider_moved(int p_value) -{ - m_blip_player->set_volume(p_value); - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_witness_testimony_clicked() -{ - if (is_muted) - return; - - ao_app->send_server_packet(new AOPacket("RT#testimony1#%")); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_cross_examination_clicked() -{ - if (is_muted) - return; - - ao_app->send_server_packet(new AOPacket("RT#testimony2#%")); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::reset_wtce_buttons() // kind of an unnecessary function, but I added it just in case -{ - for(int i = 0; i < wtce_names.size(); ++i) // effect names does not necessarily have the same size as ui effects - { - ui_wtce[i]->set_image(wtce_names.at(i) + ".png"); - } -} - -void Courtroom::on_wtce_clicked() -{ -// qDebug() << "AA: wtce clicked!"; - if (is_muted) - return; - - AOButton* f_sig = static_cast(sender()); - QString id = f_sig->property("wtce_id").toString(); - - QString packet = QString("RT#testimony%1#%").arg(id); - - ao_app->send_server_packet(new AOPacket(packet)); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_change_character_clicked() -{ - m_music_player->set_volume(0); - m_sfx_player->set_volume(0); - m_sfx_player->set_volume(0); - m_blip_player->set_volume(0); - - set_char_select(); - - ui_char_select_background->show(); - ui_spectator->hide(); -} - -void Courtroom::on_reload_theme_clicked() -{ - ao_app->reload_theme(); - - //to update status on the background - set_background(current_background); - enter_courtroom(m_cid); - - anim_state = 4; - text_state = 3; -} - -void Courtroom::on_back_to_lobby_clicked() -{ - ao_app->construct_lobby(); - ao_app->w_lobby->list_servers(); - ao_app->destruct_courtroom(); -} - -void Courtroom::on_confirm_theme_clicked() -{ - ao_app->write_theme(ui_theme_list->currentText()); -} - -void Courtroom::on_char_select_left_clicked() -{ - --current_char_page; - set_char_select_page(); -} - -void Courtroom::on_char_select_right_clicked() -{ - ++current_char_page; - set_char_select_page(); -} - -void Courtroom::on_spectator_clicked() -{ - enter_courtroom(-1); - - ui_emotes->hide(); - - ui_char_select_background->hide(); -} - -void Courtroom::on_call_mod_clicked() -{ - QMessageBox::StandardButton reply; - reply = QMessageBox::warning(this, "Warning", "Are you sure you want to call a mod? They will get angry at you if the reason is no good.", QMessageBox::Yes|QMessageBox::No, QMessageBox::No); - - if(reply == QMessageBox::Yes) - { - ao_app->send_server_packet(new AOPacket("ZZ#%")); - qDebug() << "Called mod"; - } - else - qDebug() << "Did not call mod"; - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_switch_area_music_clicked() -{ - if (ui_area_list->isHidden()) - { - ui_area_list->show(); - ui_music_list->hide(); - } - else - { - ui_area_list->hide(); - ui_music_list->show(); - } -} - - -void Courtroom::on_pre_clicked() -{ - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_flip_clicked() -{ - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_guard_clicked() -{ - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_hidden_clicked() -{ - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_evidence_button_clicked() -{ - if (ui_evidence->isHidden()) - { - ui_evidence->show(); - ui_evidence_overlay->hide(); - } - else - { - ui_evidence->hide(); - } -} - -void Courtroom::on_note_button_clicked() -{ - if(!note_shown) - { - load_note(); - ui_vp_notepad_image->show(); - ui_vp_notepad->show(); - ui_vp_notepad->setFocus(); - note_shown = true; - } - else - { - save_note(); - ui_vp_notepad_image->hide(); - ui_vp_notepad->hide(); - ui_ic_chat_message->setFocus(); - note_shown = false; - } -} - -void Courtroom::on_note_text_changed() -{ - ao_app->write_note(ui_vp_notepad->toPlainText(), current_file); -} - -void Courtroom::ping_server() -{ - ao_app->send_server_packet(new AOPacket("CH#" + QString::number(m_cid) + "#%")); -} - -void Courtroom::on_sfx_list_clicked() -{ - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_set_notes_clicked() -{ - if(note_scroll_area->isHidden()) - note_scroll_area->show(); - else - note_scroll_area->hide(); -} - -void Courtroom::set_bullets() -{ - QString somethingsomethingpath = ao_app->get_base_path() + "configs/" + "wow.ini"; - - QString thing = ""; - int i = 0; - do - { - thing = ao_app->read_design_ini(QString::number(++i), somethingsomethingpath); - //qDebug() << QString::number(i) << thing; - } while(thing != ""); -} diff --git a/courtroom.h b/courtroom.h deleted file mode 100644 index 27393ef21..000000000 --- a/courtroom.h +++ /dev/null @@ -1,739 +0,0 @@ -#ifndef COURTROOM_H -#define COURTROOM_H - -#include "aoimage.h" -#include "aobutton.h" -#include "aocharbutton.h" -#include "aoemotebutton.h" -#include "aopacket.h" -#include "aoscene.h" -#include "aomovie.h" -#include "aocharmovie.h" - -#include "aobasshandle.hpp" -#include "aoblipplayer.h" -#include "aomusicplayer.h" -#include "aosfxplayer.h" -#include "aoshoutplayer.hpp" - -#include "aoevidencebutton.h" -#include "aotextarea.h" -#include "aolineedit.h" -#include "aotextedit.h" -#include "aoevidencedisplay.h" -#include "aonotepad.h" -#include "aonotearea.hpp" -#include "aolabel.hpp" -#include "datatypes.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class AOApplication; - -class Courtroom : public QMainWindow -{ - Q_OBJECT -public: - explicit Courtroom(AOApplication *p_ao_app); - - void append_char(char_type p_char){char_list.append(p_char);} - void append_evidence(evi_type p_evi){evidence_list.append(p_evi);} - void append_music(QString f_music){music_list.append(f_music);} - void append_area(QString f_area){area_list.append(f_area);} - void clear_music(){music_list.clear();} - void clear_areas(){area_list.clear();} - - void fix_last_area() - { - if (area_list.size() > 0) - { - QString malplaced = area_list.last(); - area_list.removeLast(); - append_music(malplaced); -// qDebug() << "what" << malplaced; - } - } - - //sets position of widgets based on theme ini files - void set_widgets(); - //sets font size based on theme ini files - void set_font(QWidget *widget, QString p_identifier); - //helper function that calls above function on the relevant widgets - void set_fonts(); - - //sets dropdown menu stylesheet - void set_dropdown(QWidget *widget, QString target_tag); - - //helper funciton that call above function on the relevant widgets - void set_dropdowns(); - - void set_window_title(QString p_title); - - //reads theme inis and sets size and pos based on the identifier - void set_size_and_pos(QWidget *p_widget, QString p_identifier); - - //sets status as taken on character with cid n_char and places proper shading on charselect - void set_taken(int n_char, bool p_taken); - - //sets the current background to argument. also does some checks to see if it's a legacy bg - void set_background(QString p_background); - - //sets the evidence list member variable to argument - void set_evidence_list(QVector &p_evi_list); - - //called when a DONE#% from the server was received - void done_received(); - - //sets the local mute list based on characters available on the server - void set_mute_list(); - - //sets desk and bg based on pos in chatmessage - void set_scene(); - - //sets text color based on text color in chatmessage - void set_text_color(); - - //takes in serverD-formatted IP list as prints a converted version to server OOC - //admittedly poorly named - void set_ip_list(QString p_list); - - //disables chat if current cid matches second argument - //enables if p_muted is false - void set_mute(bool p_muted, int p_cid); - - //send a message that the player is banned and quits the server - void set_ban(int p_cid); - - //implementations in path_functions.cpp - QString get_background_path(); - QString get_default_background_path(); - - //cid = character id, returns the cid of the currently selected character - int get_cid() {return m_cid;} - QString get_current_char() {return current_char;} - - //properly sets up some varibles: resets user state - void enter_courtroom(int p_cid); - - //helper function that populates ui_music_list with the contents of music_list - void list_music(); - - void list_areas(); - - void list_sfx(); - - void list_themes(); - - void list_note_files(); - - void set_note_files(); - - void move_widget(QWidget *p_widget, QString p_identifier); - - void set_shouts(); - - void set_effects(); - - void set_wtce(); - - //these are for OOC chat - void append_ms_chatmessage(QString f_name, QString f_message); - void append_server_chatmessage(QString p_name, QString p_message); - - //these functions handle chatmessages sequentially. - //The process itself is very convoluted and merits separate documentation - //But the general idea is objection animation->pre animation->talking->idle - void handle_chatmessage(QStringList *p_contents); - void handle_chatmessage_2(); - void handle_chatmessage_3(); - - //handles character portrait animation - void handle_char_anim(AOCharMovie *charPlayer); - void handle_char_anim_2(AOCharMovie *charPlayer); - - //adds text to the IC chatlog. p_name first as bold then p_text then a newlin - //this function keeps the chatlog scrolled to the top unless there's text selected - // or the user isn't already scrolled to the top - void append_ic_text(QString p_text, QString p_name = ""); - - void append_system_text(QString p_text); - - //prints who played the song to IC chat and plays said song(if found on local filesystem) - //takes in a list where the first element is the song name and the second is the char id of who played it - void handle_song(QStringList *p_contents); - - //animates music text - void handle_music_anim(); - - //handle server-side clock animation and display - void handle_clock(QString time); - - void play_preanim(); - - QString parse_message(QString message); - - //plays the witness testimony or cross examination animation based on argument - void handle_wtce(QString p_wtce); - - //sets the hp bar of defense(p_bar 1) or pro(p_bar 2) - //state is an number between 0 and 10 inclusive - void set_hp_bar(int p_bar, int p_state); - - void check_connection_received(); - - //checks whether shout/effect files are found - void check_shouts(); - void check_effects(); - void check_wtce(); - -private: - AOApplication *ao_app = nullptr; - - int m_courtroom_width = 714; - int m_courtroom_height = 668; - - int m_viewport_x = 0; - int m_viewport_y = 0; - - int m_viewport_width = 256; - int m_viewport_height = 192; - - QVector char_list; - QVector evidence_list; - QVector music_list; - QVector area_list; - QVector sfx_names; - QVector area_names; - QVector note_list; - - QSignalMapper *char_button_mapper; - - //triggers ping_server() every 60 seconds - QTimer *keepalive_timer; - - //determines how fast messages tick onto screen - QTimer *chat_tick_timer; - int chat_tick_interval = 60; - //which tick position(character in chat message) we are at - int tick_pos = 0; - //used to determine how often blips sound - int blip_pos = 0; - int blip_rate = 1; - int rainbow_counter = 0; - bool rainbow_appended = false; - bool blank_blip = false; - bool note_shown = false; - bool contains_add_button = false; - - ////////////// - QScrollArea *note_scroll_area; - - //delay before chat messages starts ticking - QTimer *text_delay_timer; - - //delay before sfx plays - QTimer *sfx_delay_timer; - - //keeps track of how long realization is visible(it's just a white square and should be visible less than a second) - QTimer *realization_timer; - - //times how long the blinking testimony should be shown(green one in the corner) - QTimer *testimony_show_timer; - //times how long the blinking testimony should be hidden - QTimer *testimony_hide_timer; - - //Generate a File Name based on the time you launched the client - QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM yyyy hh.mm.ss.z'.txt'"); - - //configuration files locations - QString rpc_ini = "configs/rpccharlist.ini"; - QString file_select_ini = "configs/filesabstract.ini"; - QString shownames_ini = "configs/shownames.ini"; - //theme files locations - QString design_ini = "courtroom_design.ini"; - QString fonts_ini = "courtroom_fonts.ini"; - QString cc_config_ini = "courtroom_config.ini"; - QString cc_sounds_ini = "courtroom_sounds.ini"; - - //every time point in char.inis times this equals the final time - const int time_mod = 40; - - static const int chatmessage_size = 16; - QString m_chatmessage[chatmessage_size]; - bool chatmessage_is_empty = false; - - QString previous_ic_message = ""; - - QString m_string_color = ""; - - QStack m_color_stack; - - bool testimony_in_progress = false; - - //in milliseconds - const int testimony_show_time = 1500; - - //in milliseconds - const int testimony_hide_time = 500; - - //char id, muted or not - QMap mute_map; - - //QVector muted_cids; - - bool is_muted = false; - - //state of animation, 0 = objecting, 1 = preanim, 2 = talking, 3 = idle - int anim_state = 3; - - //state of text ticking, 0 = not yet ticking, 1 = ticking in progress, 2 = ticking done - int text_state = 2; - - //character id, which index of the char_list the player is - int m_cid = -1; - - bool showed = true; - - //cid and this may differ in cases of ini-editing - QString current_char = ""; - - QString current_file = ""; - - int m_objection_state = 0; - int m_effect_state = 0; - int m_text_color = 0; - int m_shout_state = 0; - int m_effect_current = 0; - int m_wtce_current = 0; - bool is_presenting_evidence = false; - bool is_judge = false; - bool is_system_speaking = false; - - int defense_bar_state = 0; - int prosecution_bar_state = 0; - - int current_char_page = 0; - int char_columns = 10; - int char_rows = 9; - int max_chars_on_page = 90; - - int current_emote_page = 0; - int current_emote = 0; - int prev_emote = 0; - int emote_columns = 5; - int emote_rows = 2; - int max_emotes_on_page = 10; - - int m_log_limit = 200; - bool m_scroll_down = false; - bool m_previously_scroll_down = false; - bool m_scroll_type_changed = false; - -// int note_amount = 0; - - QVector local_evidence_list; - - int current_evidence_page = 0; - int current_evidence = 0; - int evidence_columns = 6; - int evidence_rows = 3; - int max_evidence_on_page = 18; - - //is set to true if the bg folder contains defensedesk.png, prosecutiondesk.png and stand.png - bool is_ao2_bg = false; - - //whether the ooc chat is server or master chat, true is server - bool server_ooc = true; - - int current_clock = -1; - - QString current_background = "gs4"; - - AOBlipPlayer* m_blip_player = nullptr; - AOSfxPlayer* m_mod_player = nullptr; - AOMusicPlayer* m_music_player = nullptr; - AOSfxPlayer* m_sfx_player = nullptr; - AOSfxPlayer* m_cycle_player = nullptr; - AOShoutPlayer* m_shout_player = nullptr; - - AOImage *ui_background; - - QWidget *ui_viewport; - AOScene *ui_vp_background; - AOMovie *ui_vp_speedlines; - AOCharMovie *ui_vp_player_char; - AOScene *ui_vp_desk; - AOScene *ui_vp_legacy_desk; - AOEvidenceDisplay *ui_vp_evidence_display; - - AONoteArea *ui_note_area; - -// AONotepad *ui_vp_notepad; - - // list of characters that require a second application ID - // note that since it's hardcoded, it won't be of much use in other servers - QVector rpc_char_list; - - AOImage *ui_vp_notepad_image; - QTextEdit *ui_vp_notepad; - - AOImage* ui_vp_chatbox = nullptr; - QLabel* ui_vp_showname = nullptr; - QTextEdit* ui_vp_message = nullptr; - AOImage* ui_vp_testimony = nullptr; - AOMovie* ui_vp_effect = nullptr; - AOMovie* ui_vp_wtce = nullptr; - AOMovie* ui_vp_objection = nullptr; - - AOImage* ui_vp_music_display_a = nullptr; - AOImage* ui_vp_music_display_b = nullptr; - - AOImage* ui_vp_showname_image = nullptr; - - QTextEdit* ui_vp_music_name = nullptr; - QPropertyAnimation* music_anim = nullptr; - - QWidget *ui_vp_music_area; - - AOMovie *ui_vp_clock; - - QTextEdit* ui_ic_chatlog = nullptr; - QVector m_ic_records; - - AOTextArea *ui_ms_chatlog; - AOTextArea *ui_server_chatlog; - - QListWidget *ui_mute_list; - QListWidget *ui_area_list; - QListWidget *ui_music_list; - QListWidget *ui_sfx_list; - -// QListWidget *ui_sfx_list; - - QLineEdit *ui_ic_chat_message; - - QLineEdit *ui_ooc_chat_message; - QLineEdit *ui_ooc_chat_name; - - //QLineEdit *ui_area_password; - QLineEdit *ui_music_search; - - QLineEdit *ui_sfx_search; - - QWidget *ui_emotes = nullptr; - QVector ui_emote_list; - AOButton *ui_emote_left; - AOButton *ui_emote_right; - - QComboBox *ui_emote_dropdown; - QComboBox *ui_pos_dropdown; - - AOImage *ui_defense_bar; - AOImage *ui_prosecution_bar; - - AOLabel *ui_music_label; - AOLabel *ui_sfx_label; - AOLabel *ui_blip_label; - - //buttons to cycle through shouts - AOButton* ui_shout_up = nullptr; - AOButton* ui_shout_down = nullptr; - //buttons to cycle through effects - AOButton* ui_effect_up = nullptr; - AOButton* ui_effect_down = nullptr; - //buttons to cycle through wtce - AOButton* ui_wtce_up = nullptr; - AOButton* ui_wtce_down = nullptr; - - //holds all the shout button objects - QVector ui_shouts; - //holds all the effect button objects - QVector ui_effects; - //holds all the shout buttons objects - QVector ui_wtce; - - //holds all the names for sound files for the shouts -// QVector shout_names = {"holdit", "objection", "takethat", "custom", "gotit", "crossswords", "counteralt"}; - QVector shout_names; - - //holds all the names for sound/anim files for the effects - //QVector effect_names = {"effect_flash", "effect_gloom", "effect_question", "effect_pow"}; - QVector effect_names; - - //holds all the names for sound/anim files for the shouts - //QVector shout_names = {"witnesstestimony", "crossexamination", "investigation", "nonstop"}; - QVector wtce_names; - - //holds whether the animation file exists for a determined shout/effect - QVector shouts_enabled; - QVector effects_enabled; - QVector wtce_enabled; - -// AOButton* ui_shout_hold_it = nullptr; // 1 -// AOButton* ui_shout_objection = nullptr; // 2 -// AOButton* ui_shout_take_that = nullptr; // 3 -// AOButton* ui_shout_custom = nullptr; // 4 -// AOButton* ui_shout_got_it = nullptr; // 5 -// AOButton* ui_shout_cross_swords = nullptr; // 6 -// AOButton* ui_shout_counter_alt = nullptr; // 7 - - AOButton *ui_ooc_toggle; - - AOButton *ui_witness_testimony; - AOButton *ui_cross_examination; - AOButton *ui_investigation; - AOButton *ui_nonstop; - - AOButton *ui_change_character; - AOButton *ui_reload_theme; - AOButton *ui_call_mod; - AOButton *ui_switch_area_music; - - - QComboBox *ui_theme_list; - - AOButton *ui_confirm_theme; - - AOButton *ui_set_notes; - - QCheckBox *ui_pre; - QCheckBox *ui_flip; - QCheckBox *ui_guard; - QCheckBox *ui_hidden; - - QVector ui_checks; // 0 = pre, 1 = flip, 2 = guard, 3 = hidden - QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip - QVector ui_label_images; - QVector label_images = {"Pre", "Flip", "Guard", "Hidden", "Music", "SFX", "Blip"}; - - AOButton* ui_effect_flash = nullptr; - AOButton* ui_effect_gloom = nullptr; - - AOButton* ui_mute = nullptr; - - AOButton *ui_defense_plus; - AOButton *ui_defense_minus; - - AOButton *ui_prosecution_plus; - AOButton *ui_prosecution_minus; - - QComboBox *ui_text_color; - - QSlider *ui_music_slider; - QSlider *ui_sfx_slider; - QSlider *ui_blip_slider; - - AOImage *ui_muted; - - AOButton *ui_note_button; - - AOButton *ui_evidence_button; - AOImage *ui_evidence; - AOLineEdit *ui_evidence_name; - QWidget *ui_evidence_buttons; - QVector ui_evidence_list; - AOButton *ui_evidence_left; - AOButton *ui_evidence_right; - AOButton *ui_evidence_present; - AOImage *ui_evidence_overlay; - AOButton *ui_evidence_delete; - AOLineEdit *ui_evidence_image_name; - AOButton *ui_evidence_image_button; - AOButton *ui_evidence_x; - AOTextEdit *ui_evidence_description; - - AOImage *ui_char_select_background; - - //abstract widget to hold char buttons - QWidget *ui_char_buttons = nullptr; - - QVector ui_char_button_list; - AOImage *ui_selector; - - AOButton *ui_back_to_lobby; - - QLineEdit *ui_char_password; - - AOButton *ui_char_select_left; - AOButton *ui_char_select_right; - - AOButton *ui_spectator; - - void construct_char_select(); - void set_char_select(); - void set_char_select_page(); - - void construct_emotes(); - void set_emote_page(); - void set_emote_dropdown(); - - void construct_evidence(); - void set_evidence_page(); - - void load_note(); - void save_note(); - void save_textlog(QString p_text); - - void set_bullets(); - - void set_char_rpc(); - - -public slots: - void objection_done(); - void preanim_done(); - - void realization_done(); - - void show_testimony(); - void hide_testimony(); - - void mod_called(QString p_ip); - -private slots: - void start_chat_ticking(); - void play_sfx(); - - void chat_tick(); - - void on_mute_list_clicked(QModelIndex p_index); - - void on_chat_return_pressed(); - - void on_ooc_return_pressed(); - - void on_music_search_edited(QString p_text); - void on_music_list_clicked(); - void on_area_list_clicked(); - void on_music_list_double_clicked(QModelIndex p_model); - void on_area_list_double_clicked(QModelIndex p_model); - - void on_sfx_search_edited(QString p_text); - - void select_emote(int p_id); - - void on_emote_clicked(int p_id); - - void on_emote_left_clicked(); - void on_emote_right_clicked(); - - void on_emote_dropdown_changed(int p_index); - void on_pos_dropdown_changed(int p_index); - - void on_evidence_name_edited(); - void on_evidence_image_name_edited(); - void on_evidence_image_button_clicked(); - void on_evidence_clicked(int p_id); - void on_evidence_double_clicked(int p_id); - - void on_evidence_hover(int p_id, bool p_state); - - void on_evidence_left_clicked(); - void on_evidence_right_clicked(); - void on_evidence_present_clicked(); - - void on_cycle_clicked(); - - void cycle_shout(int p_index); - - void cycle_effect(int p_index); - - void cycle_wtce(int p_index); - - void on_add_button_clicked(); - - void on_delete_button_clicked(); - - void on_set_file_button_clicked(); - - void on_file_selected(); - - /** - * @brief reset the shout button's texture to default - * DOES NOT MODIFY OBJECTION_STATE - */ - void reset_shout_buttons(); - - /** - * @brief a general purpose function to toggle button selection - */ - void on_shout_clicked(); - - void reset_effect_buttons(); - void on_effect_button_clicked(); - - void on_mute_clicked(); - - void on_defense_minus_clicked(); - void on_defense_plus_clicked(); - void on_prosecution_minus_clicked(); - void on_prosecution_plus_clicked(); - - void on_text_color_changed(int p_color); - - void on_music_slider_moved(int p_value); - void on_sfx_slider_moved(int p_value); - void on_blip_slider_moved(int p_value); - - void on_ooc_toggle_clicked(); - - void on_witness_testimony_clicked(); - void on_cross_examination_clicked(); - void reset_wtce_buttons(); - void on_wtce_clicked(); - - void on_change_character_clicked(); - void on_reload_theme_clicked(); - void on_call_mod_clicked(); - - void on_switch_area_music_clicked(); - - void on_confirm_theme_clicked(); - void on_note_button_clicked(); - - void on_set_notes_clicked(); - - void on_note_text_changed(); - - void on_pre_clicked(); - void on_flip_clicked(); - void on_guard_clicked(); - - void on_hidden_clicked(); - - void on_sfx_list_clicked(); - - void on_evidence_button_clicked(); - - void on_evidence_delete_clicked(); - void on_evidence_x_clicked(); - - void on_back_to_lobby_clicked(); - - void on_char_select_left_clicked(); - void on_char_select_right_clicked(); - - void on_spectator_clicked(); - - void char_clicked(int n_char); - - void ping_server(); -}; - -#endif // COURTROOM_H diff --git a/data/.qmake.conf b/data/.qmake.conf new file mode 100644 index 000000000..89cd07d0d --- /dev/null +++ b/data/.qmake.conf @@ -0,0 +1 @@ +DESTDIR = $$PWD/bin diff --git a/data/README-LINUX.md b/data/README-LINUX.md new file mode 100644 index 000000000..7af0b109a --- /dev/null +++ b/data/README-LINUX.md @@ -0,0 +1,21 @@ +# Installation instructions +1. Open your terminal and run the following commands in order. + +For Ubuntu 20.04 and lower: +``` +sudo apt-get install qt5-default libqt5multimedia5 libqt5multimedia5-plugins libqt5multimediawidgets5 gstreamer1.0-libav +``` +For Ubuntu 21.10 and higher: +``` +sudo apt-get install libqt5core5a libqt5concurrent5 libqt5multimedia5 libqt5multimedia5-plugins libqt5widgets5 libqt5x11extras5 gstreamer1.0-libav +``` + +2. Update dro-client and dro-client.sh permissions +``` +chmod +x dro-client.sh +chmod +x dro-client +``` +3. Launch +``` +./dro-client.sh +``` diff --git a/data/README-MACOS.txt b/data/README-MACOS.txt new file mode 100644 index 000000000..2eec8ebad --- /dev/null +++ b/data/README-MACOS.txt @@ -0,0 +1,5 @@ +To extract the application properly, double click Danganronpa Online.dmg, drag "Danganronpa Online" to your Danganronpa Online folder, and delete the dmg file. + +If you attempt to open the application as is, MacOS will give you a warning indicating it has blocked the launch of the application as the developer could not be verified. If you downloaded these files from the Danganronpa Online discord, you can trust these files. To open Danganronpa Online for the first time, you have to Right Click the application while holding the Control key, then click Open, and on the prompt click Open again. + +If at some point you get a prompt indicating application would like to access files in your Desktop folder (or wherever you put your files), accept. Otherwise, the application will be unable to display images or play audio files. \ No newline at end of file diff --git a/data/dro-client.sh b/data/dro-client.sh new file mode 100644 index 000000000..33baddbdd --- /dev/null +++ b/data/dro-client.sh @@ -0,0 +1 @@ +LD_LIBRARY_PATH=.:./depends:$LD_LIBRARY_PATH ./dro-client diff --git a/data/sample.avi b/data/sample.avi new file mode 100644 index 000000000..3e4ab0eab Binary files /dev/null and b/data/sample.avi differ diff --git a/datatypes.h b/datatypes.h deleted file mode 100644 index ec44accc0..000000000 --- a/datatypes.h +++ /dev/null @@ -1,126 +0,0 @@ -#ifndef DATATYPES_H -#define DATATYPES_H - -#include -#include - -struct record_type -{ - record_type() = default; - record_type(QString p_name, QString p_line, QString p_color, bool p_system) - : name(p_name), line(p_line), color(p_color), system(p_system) - {} - - QString name; - QString line; - QString color; - bool system; -}; - -typedef std::shared_ptr record_type_ptr; - -struct server_type -{ - QString name; - QString desc; - QString ip; - int port; -}; - -struct emote_type -{ - QString comment; - QString preanim; - QString anim; - int mod; - QString sfx_name; - int sfx_delay; - int sfx_duration; -}; - -struct char_type -{ - QString name; - QString description; - QString evidence_string; - bool taken; -}; - -struct evi_type -{ - QString name; - QString description; - QString image; -}; - -struct chatmessage_type -{ - QString message; - QString character; - QString side; - QString sfx_name; - QString pre_emote; - QString emote; - int emote_modifier; - int objection_modifier; - int realization; - int text_color; - int evidence; - int cid; - int sfx_delay; - int flip; -}; - -struct area_type -{ - QString name; - QString background; - bool passworded; -}; - -struct pos_type -{ - int x; - int y; -}; - -struct pos_size_type -{ - int x = 0; - int y = 0; - int width = 0; - int height = 0; -}; - -enum CHAT_MESSAGE -{ - DESK_MOD = 0, - PRE_EMOTE, - CHAR_NAME, - EMOTE, - MESSAGE, - SIDE, - SFX_NAME, - EMOTE_MOD, - CHAR_ID, - SFX_DELAY, - OBJECTION_MOD, - EVIDENCE_ID, - FLIP, - EFFECT_STATE, - TEXT_COLOR, - SHOWNAME -}; - -enum COLOR -{ - WHITE = 0, - GREEN, - RED, - ORANGE, - BLUE, - YELLOW, - RAINBOW -}; - -#endif // DATATYPES_H diff --git a/debug_functions.cpp b/debug_functions.cpp deleted file mode 100644 index 0ff1ccd75..000000000 --- a/debug_functions.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -#include "debug_functions.h" - -void call_error(QString p_message) -{ - QMessageBox *f_box = new QMessageBox; - - f_box->setText("Error: " + p_message); - f_box->setWindowTitle("Error"); - - //msgBox->setWindowModality(Qt::NonModal); - f_box->exec(); - delete f_box; -} - -void call_notice(QString p_message) -{ - QMessageBox *f_box = new QMessageBox; - - f_box->setText(p_message); - f_box->setWindowTitle("Notice"); - - //msgBox->setWindowModality(Qt::NonModal); - f_box->exec(); - delete f_box; -} diff --git a/discord-rpc.h b/discord-rpc.h deleted file mode 100644 index feb874b20..000000000 --- a/discord-rpc.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once -#include - -// clang-format off - -#if defined(DISCORD_DYNAMIC_LIB) -# if defined(_WIN32) -# if defined(DISCORD_BUILDING_SDK) -# define DISCORD_EXPORT __declspec(dllexport) -# else -# define DISCORD_EXPORT __declspec(dllimport) -# endif -# else -# define DISCORD_EXPORT __attribute__((visibility("default"))) -# endif -#else -# define DISCORD_EXPORT -#endif - -// clang-format on - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct DiscordRichPresence { - const char* state; /* max 128 bytes */ - const char* details; /* max 128 bytes */ - int64_t startTimestamp; - int64_t endTimestamp; - const char* largeImageKey; /* max 32 bytes */ - const char* largeImageText; /* max 128 bytes */ - const char* smallImageKey; /* max 32 bytes */ - const char* smallImageText; /* max 128 bytes */ - const char* partyId; /* max 128 bytes */ - int partySize; - int partyMax; - const char* matchSecret; /* max 128 bytes */ - const char* joinSecret; /* max 128 bytes */ - const char* spectateSecret; /* max 128 bytes */ - int8_t instance; -} DiscordRichPresence; - -typedef struct DiscordJoinRequest { - const char* userId; - const char* username; - const char* discriminator; - const char* avatar; -} DiscordJoinRequest; - -typedef struct DiscordEventHandlers { - void (*ready)(void); - void (*disconnected)(int errorCode, const char* message); - void (*errored)(int errorCode, const char* message); - void (*joinGame)(const char* joinSecret); - void (*spectateGame)(const char* spectateSecret); - void (*joinRequest)(const DiscordJoinRequest* request); -} DiscordEventHandlers; - -#define DISCORD_REPLY_NO 0 -#define DISCORD_REPLY_YES 1 -#define DISCORD_REPLY_IGNORE 2 - -DISCORD_EXPORT void Discord_Initialize(const char* applicationId, - DiscordEventHandlers* handlers, - int autoRegister, - const char* optionalSteamId); -DISCORD_EXPORT void Discord_Shutdown(void); - -/* checks for incoming messages, dispatches callbacks */ -DISCORD_EXPORT void Discord_RunCallbacks(void); - -/* If you disable the lib starting its own io thread, you'll need to call this from your own */ -#ifdef DISCORD_DISABLE_IO_THREAD -DISCORD_EXPORT void Discord_UpdateConnection(void); -#endif - -DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); -DISCORD_EXPORT void Discord_ClearPresence(void); - -DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); - -#ifdef __cplusplus -} /* extern "C" */ -#endif diff --git a/discord_register.h b/discord_register.h deleted file mode 100644 index 2058cc4c6..000000000 --- a/discord_register.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#if defined(DISCORD_DYNAMIC_LIB) -# if defined(_WIN32) -# if defined(DISCORD_BUILDING_SDK) -# define DISCORD_EXPORT __declspec(dllexport) -# else -# define DISCORD_EXPORT __declspec(dllimport) -# endif -# else -# define DISCORD_EXPORT __attribute__((visibility("default"))) -# endif -#else -# define DISCORD_EXPORT -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); -DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); - -#ifdef __cplusplus -} -#endif diff --git a/discord_rich_presence.cpp b/discord_rich_presence.cpp deleted file mode 100644 index ab9772628..000000000 --- a/discord_rich_presence.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include "discord_rich_presence.h" - -#include -#include - -#include - -namespace AttorneyOnline { - -Discord::Discord() -{ -// DiscordEventHandlers handlers; -// std::memset(&handlers, 0, sizeof(handlers)); -// handlers = {}; -// handlers.ready = [] { -// qInfo() << "Discord RPC ready"; -// }; -// handlers.disconnected = [](int errorCode, const char* message) { -// qInfo() << "Discord RPC disconnected! " << message; -// }; -// handlers.errored = [](int errorCode, const char* message) { -// qWarning() << "Discord RPC errored out! " << message; -// }; -// qInfo() << "Initializing Discord RPC"; -// Discord_Initialize(APPLICATION_ID1, &handlers, 1, nullptr); - - start(APPLICATION_ID[0]); -} - -void Discord::start(const char *APPLICATION_ID) -{ - DiscordEventHandlers handlers; - std::memset(&handlers, 0, sizeof(handlers)); - handlers = {}; - handlers.ready = [] { - qInfo() << "Discord RPC ready"; - }; - handlers.disconnected = [](int errorCode, const char* message) { - qInfo() << "Discord RPC disconnected! " << message; - }; - handlers.errored = [](int errorCode, const char* message) { - qWarning() << "Discord RPC errored out! " << message; - }; - qInfo() << "Initializing Discord RPC"; - Discord_Initialize(APPLICATION_ID, &handlers, 1, nullptr); -} - -Discord::~Discord() -{ - Discord_Shutdown(); -} - -void Discord::restart(const char *APPLICATION_ID) -{ - Discord_Shutdown(); - start(APPLICATION_ID); -} - -void Discord::toggle(int p_index) -{ - if(p_index >=0 && p_index < 2) - { - if(p_index!=m_index) { restart(APPLICATION_ID[p_index]); m_index = p_index; } - } - else qDebug() << p_index << "is not a valid APPLICATION_ID Index"; -} - -void Discord::state_lobby() -{ - DiscordRichPresence presence; - std::memset(&presence, 0, sizeof(presence)); - presence.largeImageKey = "danganronpa_online"; - presence.largeImageText = "Sore Wa Chigau Yo!"; - presence.instance = 1; - - presence.state = "In Lobby"; - presence.details = "Idle"; - Discord_UpdatePresence(&presence); -} - -void Discord::state_server(std::string name, std::string server_id) -{ - qDebug() << "Discord RPC: Setting server state"; - - DiscordRichPresence presence; - std::memset(&presence, 0, sizeof(presence)); - presence.largeImageKey = "danganronpa_online"; - presence.largeImageText = "Sore Wa Chigau Yo!"; - presence.instance = 1; - - auto timestamp = static_cast(std::time(nullptr)); - - presence.state = "In a Server"; - presence.details = name.c_str(); - presence.matchSecret = server_id.c_str(); - presence.startTimestamp = this->timestamp; - - this->server_id = server_id; - this->server_name = name; - this->timestamp = timestamp; - Discord_UpdatePresence(&presence); -} - -void Discord::state_character(std::string name) -{ - auto name_internal = QString(name.c_str()).toLower().replace(' ', '_').toStdString(); - auto name_friendly = QString(name.c_str()).replace('_', ' ').toStdString(); - const std::string playing_as = "Playing as " + name_friendly; - qDebug() << "Discord RPC: Setting character state (" << playing_as.c_str() << ")"; - - DiscordRichPresence presence; - std::memset(&presence, 0, sizeof(presence)); - presence.largeImageKey = name_internal.c_str(); - presence.instance = 1; - presence.details = this->server_name.c_str(); - presence.matchSecret = this->server_id.c_str(); - presence.startTimestamp = this->timestamp; - - presence.state = playing_as.c_str(); -// presence.smallImageKey = "danganronpa_online"; - presence.smallImageKey = "danganronpa_online"; - presence.smallImageText = "Danganronpa Online"; - Discord_UpdatePresence(&presence); -} - -void Discord::state_spectate() -{ - qDebug() << "Discord RPC: Setting specator state"; - - DiscordRichPresence presence; - std::memset(&presence, 0, sizeof(presence)); - presence.largeImageKey = "danganronpa_online"; - presence.largeImageText = "Sore Wa Chigau Yo!"; - presence.instance = 1; - presence.details = this->server_name.c_str(); - presence.matchSecret = this->server_id.c_str(); - presence.startTimestamp = this->timestamp; - - presence.state = "Spectating"; - Discord_UpdatePresence(&presence); -} - -} diff --git a/discord_rich_presence.h b/discord_rich_presence.h deleted file mode 100644 index 3e8ec47d6..000000000 --- a/discord_rich_presence.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef DISCORD_RICH_PRESENCE_H -#define DISCORD_RICH_PRESENCE_H - -#include -#include - -namespace AttorneyOnline { - -class Discord -{ -private: - const char* APPLICATION_ID[2] = { "538080629535801347" , "538080629535801347", }; // insert second one here blah blah - int m_index = 0; - std::string server_name, server_id; - int64_t timestamp; -public: - Discord(); - ~Discord(); - - void state_lobby(); - void state_server(std::string name, std::string server_id); - void state_character(std::string name); - void state_spectate(); - void start(const char *APPLICATION_ID); - void restart(const char *APPLICATION_ID); - void toggle(int p_index); -}; - -} -#endif // DISCORD_RICH_PRESENCE_H diff --git a/discord_rpc.h b/discord_rpc.h deleted file mode 100644 index 97a6e9ec8..000000000 --- a/discord_rpc.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once -#include - -// clang-format off - -#if defined(DISCORD_DYNAMIC_LIB) -# if defined(_WIN32) -# if defined(DISCORD_BUILDING_SDK) -# define DISCORD_EXPORT __declspec(dllexport) -# else -# define DISCORD_EXPORT __declspec(dllimport) -# endif -# else -# define DISCORD_EXPORT __attribute__((visibility("default"))) -# endif -#else -# define DISCORD_EXPORT -#endif - -// clang-format on - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct DiscordRichPresence { - const char* state; /* max 128 bytes */ - const char* details; /* max 128 bytes */ - int64_t startTimestamp; - int64_t endTimestamp; - const char* largeImageKey; /* max 32 bytes */ - const char* largeImageText; /* max 128 bytes */ - const char* smallImageKey; /* max 32 bytes */ - const char* smallImageText; /* max 128 bytes */ - const char* partyId; /* max 128 bytes */ - int partySize; - int partyMax; - const char* matchSecret; /* max 128 bytes */ - const char* joinSecret; /* max 128 bytes */ - const char* spectateSecret; /* max 128 bytes */ - int8_t instance; -} DiscordRichPresence; - -typedef struct DiscordJoinRequest { - const char* userId; - const char* username; - const char* discriminator; - const char* avatar; -} DiscordJoinRequest; - -typedef struct DiscordEventHandlers { - void (*ready)(void); - void (*disconnected)(int errorCode, const char* message); - void (*errored)(int errorCode, const char* message); - void (*joinGame)(const char* joinSecret); - void (*spectateGame)(const char* spectateSecret); - void (*joinRequest)(const DiscordJoinRequest* request); -} DiscordEventHandlers; - -#define DISCORD_REPLY_NO 0 -#define DISCORD_REPLY_YES 1 -#define DISCORD_REPLY_IGNORE 2 - -DISCORD_EXPORT void Discord_Initialize(const char* applicationId, - DiscordEventHandlers* handlers, - int autoRegister, - const char* optionalSteamId); -DISCORD_EXPORT void Discord_Shutdown(void); - -/* checks for incoming messages, dispatches callbacks */ -DISCORD_EXPORT void Discord_RunCallbacks(void); - -/* If you disable the lib starting its own io thread, you'll need to call this from your own */ -#ifdef DISCORD_DISABLE_IO_THREAD -DISCORD_EXPORT void Discord_UpdateConnection(void); -#endif - -DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); -DISCORD_EXPORT void Discord_ClearPresence(void); - -DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); - -#ifdef __cplusplus -} /* extern "C" */ -#endif diff --git a/dronline-client.pro b/dronline-client.pro new file mode 100644 index 000000000..ee06906ab --- /dev/null +++ b/dronline-client.pro @@ -0,0 +1,185 @@ +QT += core gui widgets uitools network multimedia multimediawidgets + +CONFIG += c++17 + +TEMPLATE = app +VERSION = 1.2.3 +TARGET = dro-client + +RC_ICONS = icon.ico + +INCLUDEPATH += $$PWD/include $$PWD/src $$PWD/3rd +DEPENDPATH += $$PWD/include $$PWD/src $$PWD/3rd + +HEADERS += \ + src/aoapplication.h \ + src/aoblipplayer.h \ + src/aobutton.h \ + src/aocharbutton.h \ + src/aoconfig.h \ + src/aoconfigpanel.h \ + src/aoemotebutton.h \ + src/aoguiloader.h \ + src/aoimagedisplay.h \ + src/aolabel.h \ + src/aolineedit.h \ + src/aomusicplayer.h \ + src/aonotearea.h \ + src/aonotepad.h \ + src/aonotepicker.h \ + src/aoobject.h \ + src/aopixmap.h \ + src/aosfxplayer.h \ + src/aoshoutplayer.h \ + src/aosystemplayer.h \ + src/aotimer.h \ + src/commondefs.h \ + src/courtroom.h \ + src/datatypes.h \ + src/debug_functions.h \ + src/draudio.h \ + src/draudiodevice.h \ + src/draudioengine.h \ + src/draudioengine_p.h \ + src/draudioerror.h \ + src/draudiostream.h \ + src/draudiostreamfamily.h \ + src/draudiotrackmetadata.h \ + src/drcharactermovie.h \ + src/drchatlog.h \ + src/drdiscord.h \ + src/dreffectmovie.h \ + src/drgraphicscene.h \ + src/drmasterclient.h \ + src/drmediatester.h \ + src/drmovie.h \ + src/drpacket.h \ + src/drpather.h \ + src/drposition.h \ + src/drscenemovie.h \ + src/drserverinfoeditor.h \ + src/drserversocket.h \ + src/drshoutmovie.h \ + src/drsplashmovie.h \ + src/drstickerviewer.h \ + src/drtextedit.h \ + src/drthememovie.h \ + src/file_functions.h \ + src/hardware_functions.h \ + src/lobby.h \ + src/logger.h \ + src/misc_functions.h \ + src/mk2/graphicsspriteitem.h \ + src/mk2/graphicsvideoscreen.h \ + src/mk2/spritecachingreader.h \ + src/mk2/spritedynamicreader.h \ + src/mk2/spriteplayer.h \ + src/mk2/spritereader.h \ + src/mk2/spritereadersynchronizer.h \ + src/mk2/spriteseekingreader.h \ + src/mk2/spriteviewer.h \ + src/theme.h \ + src/utils.h \ + src/version.h + +SOURCES += \ + src/aoapplication.cpp \ + src/aoblipplayer.cpp \ + src/aobutton.cpp \ + src/aocharbutton.cpp \ + src/aoconfig.cpp \ + src/aoconfigpanel.cpp \ + src/aoemotebutton.cpp \ + src/aoguiloader.cpp \ + src/aoimagedisplay.cpp \ + src/aolabel.cpp \ + src/aolineedit.cpp \ + src/aomusicplayer.cpp \ + src/aonotearea.cpp \ + src/aonotepad.cpp \ + src/aonotepicker.cpp \ + src/aoobject.cpp \ + src/aopixmap.cpp \ + src/aosfxplayer.cpp \ + src/aoshoutplayer.cpp \ + src/aosystemplayer.cpp \ + src/aotimer.cpp \ + src/audio_functions.cpp \ + src/charselect.cpp \ + src/commondefs.cpp \ + src/courtroom.cpp \ + src/courtroom_character.cpp \ + src/courtroom_sfx.cpp \ + src/courtroom_widgets.cpp \ + src/datatypes.cpp \ + src/debug_functions.cpp \ + src/draudio.cpp \ + src/draudiodevice.cpp \ + src/draudioengine.cpp \ + src/draudioengine_p.cpp \ + src/draudioerror.cpp \ + src/draudiostream.cpp \ + src/draudiostreamfamily.cpp \ + src/draudiotrackmetadata.cpp \ + src/drcharactermovie.cpp \ + src/drchatlog.cpp \ + src/dreffectmovie.cpp \ + src/drgraphicscene.cpp \ + src/drmasterclient.cpp \ + src/drmediatester.cpp \ + src/drmovie.cpp \ + src/drpacket.cpp \ + src/drpather.cpp \ + src/drposition.cpp \ + src/drscenemovie.cpp \ + src/drserverinfoeditor.cpp \ + src/drserversocket.cpp \ + src/drshoutmovie.cpp \ + src/drsplashmovie.cpp \ + src/drstickerviewer.cpp \ + src/drtextedit.cpp \ + src/drdiscord.cpp \ + src/drthememovie.cpp \ + src/emotes.cpp \ + src/file_functions.cpp \ + src/hardware_functions.cpp \ + src/lobby.cpp \ + src/logger.cpp \ + src/main.cpp \ + src/misc_functions.cpp \ + src/mk2/graphicsspriteitem.cpp \ + src/mk2/graphicsvideoscreen.cpp \ + src/mk2/spritecachingreader.cpp \ + src/mk2/spritedynamicreader.cpp \ + src/mk2/spriteplayer.cpp \ + src/mk2/spriteseekingreader.cpp \ + src/path_functions.cpp \ + src/server_socket.cpp \ + src/mk2/spritereader.cpp \ + src/mk2/spritereadersynchronizer.cpp \ + src/mk2/spriteviewer.cpp \ + src/text_file_functions.cpp \ + src/theme.cpp \ + src/utils.cpp \ + src/version.cpp + +# 1. You need to get BASS and put the x86 bass DLL/headers in the project root folder +# AND the compilation output folder. If you want a static link, you'll probably +# need the .lib file too. MinGW-GCC is really finicky finding BASS, it seems. +# 2. You need to compile the Discord Rich Presence SDK separately and add the lib/headers +# in the same way as BASS. Discord RPC uses CMake, which does not play nicely with +# QMake, so this step must be manual. +LIBS += -L$$PWD/3rd -lbass -lbassopus -ldiscord-rpc + +RESOURCES += \ + res.qrc + +DISTFILES += + +FORMS += \ + res/ui/config_panel.ui \ + src/drserverinfoeditor.ui + +# Mac stuff +QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.13 +ICON = icon.icns diff --git a/emotes.cpp b/emotes.cpp deleted file mode 100644 index 12f41a3b2..000000000 --- a/emotes.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#include "courtroom.h" - -#include "aoemotebutton.h" - -#include - -void Courtroom::construct_emotes() -{ - ui_emotes = new QWidget(this); - - set_size_and_pos(ui_emotes, "emotes"); - - QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", "courtroom_design.ini"); - - const int button_width = 40; - int x_spacing = f_spacing.x(); - int x_mod_count = 0; - - const int button_height = 40; - int y_spacing = f_spacing.y(); - int y_mod_count = 0; - - emote_columns = ((ui_emotes->width() - button_width) / (x_spacing + button_width)) + 1; - emote_rows = ((ui_emotes->height() - button_height) / (y_spacing + button_height)) + 1; - - max_emotes_on_page = emote_columns * emote_rows; - - for (int n = 0 ; n < max_emotes_on_page ; ++n) - { - int x_pos = (button_width + x_spacing) * x_mod_count; - int y_pos = (button_height + y_spacing) * y_mod_count; - - AOEmoteButton *f_emote = new AOEmoteButton(ui_emotes, ao_app, x_pos, y_pos); - - ui_emote_list.append(f_emote); - - f_emote->set_id(n); - - connect(f_emote, SIGNAL(emote_clicked(int)), this, SLOT(on_emote_clicked(int))); - - ++x_mod_count; - - if (x_mod_count == emote_columns) - { - ++y_mod_count; - x_mod_count = 0; - } - } -} - -void Courtroom::set_emote_page() -{ - if (m_cid == -1) - return; - - int total_emotes = ao_app->get_emote_number(current_char); - - ui_emote_left->hide(); - ui_emote_right->hide(); - - for (AOEmoteButton *i_button : ui_emote_list) - { - i_button->hide(); - } - - int total_pages = total_emotes / max_emotes_on_page; - int emotes_on_page = 0; - - if (total_emotes % max_emotes_on_page != 0) - { - ++total_pages; - //i. e. not on the last page - if (total_pages > current_emote_page + 1) - emotes_on_page = max_emotes_on_page; - else - emotes_on_page = total_emotes % max_emotes_on_page; - - } - else - emotes_on_page = max_emotes_on_page; - - if (total_pages > current_emote_page + 1) - ui_emote_right->show(); - - if (current_emote_page > 0) - ui_emote_left->show(); - - for (int n_emote = 0 ; n_emote < emotes_on_page ; ++n_emote) - { - int n_real_emote = n_emote + current_emote_page * max_emotes_on_page; - AOEmoteButton *f_emote = ui_emote_list.at(n_emote); - - if (n_real_emote == current_emote) - f_emote->set_image(current_char, n_real_emote, "_on.png"); - else - f_emote->set_image(current_char, n_real_emote, "_off.png"); - - f_emote->show(); - } - -} - -void Courtroom::set_emote_dropdown() -{ - ui_emote_dropdown->clear(); - - int total_emotes = ao_app->get_emote_number(current_char); - QStringList emote_list; - - for (int n = 0 ; n < total_emotes ; ++n) - { - emote_list.append(ao_app->get_emote_comment(current_char, n)); - } - - ui_emote_dropdown->addItems(emote_list); -} - -void Courtroom::select_emote(int p_id) -{ - int min = current_emote_page * max_emotes_on_page; - int max = (max_emotes_on_page - 1) + current_emote_page * max_emotes_on_page; - - if (current_emote >= min && current_emote <= max) - ui_emote_list.at(current_emote % max_emotes_on_page)->set_image(current_char, current_emote, "_off.png"); - - int old_emote = current_emote; - - current_emote = p_id; - - if (current_emote >= min && current_emote <= max) - ui_emote_list.at(current_emote % max_emotes_on_page)->set_image(current_char, current_emote, "_on.png"); - - int emote_mod = ao_app->get_emote_mod(current_char, current_emote); - - if (old_emote == current_emote) // toggle - ui_pre->setChecked(!ui_pre->isChecked()); - else if (emote_mod == 1 || ao_app->read_config("always_pre") == "true") - ui_pre->setChecked(true); - else - ui_pre->setChecked(false); - - ui_emote_dropdown->setCurrentIndex(current_emote); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_emote_clicked(int p_id) -{ - select_emote(p_id + max_emotes_on_page * current_emote_page); -} - -void Courtroom::on_emote_left_clicked() -{ - --current_emote_page; - - set_emote_page(); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_emote_right_clicked() -{ - qDebug() << "emote right clicked"; - ++current_emote_page; - - set_emote_page(); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_emote_dropdown_changed(int p_index) -{ - select_emote(p_index); -} diff --git a/encryption_functions.cpp b/encryption_functions.cpp deleted file mode 100644 index 56b6e34cd..000000000 --- a/encryption_functions.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "encryption_functions.h" - -#include "hex_functions.h" - -#include -#include -#include -#include -#include - -QString fanta_encrypt(QString temp_input, unsigned int p_key) -{ - //using standard stdlib types is actually easier here because of implicit char<->int conversion - //which in turn makes encryption arithmetic easier - - unsigned int key = p_key; - unsigned int C1 = 53761; - unsigned int C2 = 32618; - - QVector temp_result; - std::string input = temp_input.toUtf8().constData(); - - for (unsigned int pos = 0 ; pos < input.size() ; ++pos) - { - uint_fast8_t output = input.at(pos) ^ (key >> 8) % 256; - temp_result.append(output); - key = (temp_result.at(pos) + key) * C1 + C2; - } - - std::string result = ""; - - for (uint_fast8_t i_int : temp_result) - { - result += omni::int_to_hex(i_int); - } - - QString final_result = QString::fromStdString(result); - - return final_result; -} - -QString fanta_decrypt(QString temp_input, unsigned int key) -{ - std::string input = temp_input.toUtf8().constData(); - - QVector unhexed_vector; - - for(unsigned int i=0; i< input.length(); i+=2) - { - std::string byte = input.substr(i,2); - unsigned int hex_int = strtoul(byte.c_str(), nullptr, 16); - unhexed_vector.append(hex_int); - } - - unsigned int C1 = 53761; - unsigned int C2 = 32618; - - std::string result = ""; - - for (int pos = 0 ; pos < unhexed_vector.size() ; ++pos) - { - unsigned char output = unhexed_vector.at(pos) ^ (key >> 8) % 256; - result += output; - key = (unhexed_vector.at(pos) + key) * C1 + C2; - } - - return QString::fromStdString(result); - -} diff --git a/encryption_functions.h b/encryption_functions.h deleted file mode 100644 index b6ea1d708..000000000 --- a/encryption_functions.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef ENCRYPTION_FUNCTIONS_H -#define ENCRYPTION_FUNCTIONS_H - -#include - -QString fanta_encrypt(QString p_input, unsigned int key); -QString fanta_decrypt(QString p_input, unsigned int key); - -#endif // ENCRYPTION_FUNCTIONS_H diff --git a/evidence.cpp b/evidence.cpp deleted file mode 100644 index 19ffecf22..000000000 --- a/evidence.cpp +++ /dev/null @@ -1,348 +0,0 @@ -#include "courtroom.h" - -#include -#include - -void Courtroom::construct_evidence() -{ - ui_evidence = new AOImage(this, ao_app); - - //ui_evidence_name = new QLabel(ui_evidence); - ui_evidence_name = new AOLineEdit(ui_evidence); - ui_evidence_name->setAlignment(Qt::AlignCenter); - ui_evidence_name->setFont(QFont("Arial", 14, QFont::Bold)); - ui_evidence_name->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: rgba(255, 128, 0, 255);"); - - ui_evidence_buttons = new QWidget(ui_evidence); - - ui_evidence_left = new AOButton(ui_evidence, ao_app); - ui_evidence_right = new AOButton(ui_evidence, ao_app); - ui_evidence_present = new AOButton(ui_evidence, ao_app); - - ui_evidence_overlay = new AOImage(ui_evidence, ao_app); - - ui_evidence_delete = new AOButton(ui_evidence_overlay, ao_app); - ui_evidence_image_name = new AOLineEdit(ui_evidence_overlay); - ui_evidence_image_button = new AOButton(ui_evidence_overlay, ao_app); - ui_evidence_image_button->setText("Choose.."); - ui_evidence_x = new AOButton(ui_evidence_overlay, ao_app); - - ui_evidence_description = new AOTextEdit(ui_evidence_overlay); - ui_evidence_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: white;"); - - set_size_and_pos(ui_evidence, "evidence_background"); - set_size_and_pos(ui_evidence_buttons, "evidence_buttons"); - - QPoint f_spacing = ao_app->get_button_spacing("evidence_button_spacing", "courtroom_design.ini"); - - const int button_width = 70; - int x_spacing = f_spacing.x(); - int x_mod_count = 0; - - const int button_height = 70; - int y_spacing = f_spacing.y(); - int y_mod_count = 0; - - evidence_columns = ((ui_evidence_buttons->width() - button_width) / (x_spacing + button_width)) + 1; - evidence_rows = ((ui_evidence_buttons->height() - button_height) / (y_spacing + button_height)) + 1; - - max_evidence_on_page = evidence_columns * evidence_rows; - - for (int n = 0 ; n < max_evidence_on_page ; ++n) - { - int x_pos = (button_width + x_spacing) * x_mod_count; - int y_pos = (button_height + y_spacing) * y_mod_count; - - AOEvidenceButton *f_evidence = new AOEvidenceButton(ui_evidence_buttons, ao_app, x_pos, y_pos); - - ui_evidence_list.append(f_evidence); - - f_evidence->set_id(n); - - connect(f_evidence, SIGNAL(evidence_clicked(int)), this, SLOT(on_evidence_clicked(int))); - connect(f_evidence, SIGNAL(evidence_double_clicked(int)), this, SLOT(on_evidence_double_clicked(int))); - connect(f_evidence, SIGNAL(on_hover(int, bool)), this, SLOT(on_evidence_hover(int, bool))); - - ++x_mod_count; - - if (x_mod_count == evidence_columns) - { - ++y_mod_count; - x_mod_count = 0; - } - } - - connect(ui_evidence_name, SIGNAL(returnPressed()), this, SLOT(on_evidence_name_edited())); - connect(ui_evidence_left, SIGNAL(clicked()), this, SLOT(on_evidence_left_clicked())); - connect(ui_evidence_right, SIGNAL(clicked()), this, SLOT(on_evidence_right_clicked())); - connect(ui_evidence_present, SIGNAL(clicked()), this, SLOT(on_evidence_present_clicked())); - connect(ui_evidence_delete, SIGNAL(clicked()), this, SLOT(on_evidence_delete_clicked())); - connect(ui_evidence_image_name, SIGNAL(returnPressed()), this, SLOT(on_evidence_image_name_edited())); - connect(ui_evidence_image_button, SIGNAL(clicked()), this, SLOT(on_evidence_image_button_clicked())); - connect(ui_evidence_x, SIGNAL(clicked()), this, SLOT(on_evidence_x_clicked())); - - ui_evidence->hide(); -} - -void Courtroom::set_evidence_list(QVector &p_evi_list) -{ - local_evidence_list.clear(); - local_evidence_list = p_evi_list; - - set_evidence_page(); -} - -void Courtroom::set_evidence_page() -{ - int total_evidence = local_evidence_list.size(); - - ui_evidence_left->hide(); - ui_evidence_right->hide(); - - for (AOEvidenceButton *i_button : ui_evidence_list) - { - i_button->reset(); - } - - //to account for the "add evidence" button - ++total_evidence; - - int total_pages = total_evidence / max_evidence_on_page; - int evidence_on_page = 0; - - if ((total_evidence % max_evidence_on_page) != 0) - { - ++total_pages; - //i. e. not on the last page - if (total_pages > current_evidence_page + 1) - evidence_on_page = max_evidence_on_page; - else - evidence_on_page = total_evidence % max_evidence_on_page; - - } - else - evidence_on_page = max_evidence_on_page; - - if (total_pages > current_evidence_page + 1) - ui_evidence_right->show(); - - if (current_evidence_page > 0) - ui_evidence_left->show(); - - for (int n_evidence_button = 0 ; n_evidence_button < evidence_on_page ; ++n_evidence_button) - { - int n_real_evidence = n_evidence_button + current_evidence_page * max_evidence_on_page; - AOEvidenceButton *f_evidence_button = ui_evidence_list.at(n_evidence_button); - - //ie. the add evidence button - if (n_real_evidence == (total_evidence - 1)) - f_evidence_button->set_theme_image("addevidence.png"); - else if (n_real_evidence < (total_evidence - 1)) - { - f_evidence_button->set_image(local_evidence_list.at(n_real_evidence).image); - - if (n_real_evidence == current_evidence) - f_evidence_button->set_selected(true); - else - f_evidence_button->set_selected(false); - } - else - f_evidence_button->set_image(""); - - f_evidence_button->show(); - } -} - -void Courtroom::on_evidence_name_edited() -{ - if (current_evidence >= local_evidence_list.size()) - return; - - QStringList f_contents; - - evi_type f_evi = local_evidence_list.at(current_evidence); - - f_contents.append(QString::number(current_evidence)); - f_contents.append(ui_evidence_name->text()); - f_contents.append(f_evi.description); - f_contents.append(f_evi.image); - - ao_app->send_server_packet(new AOPacket("EE", f_contents)); -} - -void Courtroom::on_evidence_image_name_edited() -{ - if (current_evidence >= local_evidence_list.size()) - return; - - QStringList f_contents; - - evi_type f_evi = local_evidence_list.at(current_evidence); - - f_contents.append(QString::number(current_evidence)); - f_contents.append(f_evi.name); - f_contents.append(f_evi.description); - f_contents.append(ui_evidence_image_name->text()); - - ao_app->send_server_packet(new AOPacket("EE", f_contents)); -} - -void Courtroom::on_evidence_image_button_clicked() -{ - QFileDialog dialog(this); - dialog.setFileMode(QFileDialog::ExistingFile); - dialog.setNameFilter(tr("Images (*.png)")); - dialog.setViewMode(QFileDialog::List); - dialog.setDirectory(ao_app->get_evidence_path()); - - QStringList filenames; - - if (dialog.exec()) - filenames = dialog.selectedFiles(); - - if (filenames.size() != 1) - return; - - QString filename = filenames.at(0); - - QStringList split_filename = filename.split("/"); - - filename = split_filename.at(split_filename.size() - 1); - - ui_evidence_image_name->setText(filename); - - on_evidence_image_name_edited(); -} - -void Courtroom::on_evidence_clicked(int p_id) -{ - ui_evidence_name->setReadOnly(true); - - int f_real_id = p_id + max_evidence_on_page * current_evidence_page; - - if (f_real_id == local_evidence_list.size()) - { - ao_app->send_server_packet(new AOPacket("PE###empty.png#%")); - return; - } - else if (f_real_id > local_evidence_list.size()) - return; - - ui_evidence_name->setText(local_evidence_list.at(f_real_id).name); - - for (AOEvidenceButton *i_button : ui_evidence_list) - i_button->set_selected(false); - - ui_evidence_list.at(p_id)->set_selected(true); - - current_evidence = f_real_id; - - ui_ic_chat_message->setFocus(); - -} - -void Courtroom::on_evidence_double_clicked(int p_id) -{ - int f_real_id = p_id + max_evidence_on_page * current_evidence_page; - - if (f_real_id >= local_evidence_list.size()) - return; - - current_evidence = f_real_id; - - evi_type f_evi = local_evidence_list.at(f_real_id); - - ui_evidence_description->clear(); - ui_evidence_description->appendPlainText(f_evi.description); - - ui_evidence_image_name->setText(f_evi.image); - - ui_evidence_overlay->show(); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_evidence_hover(int p_id, bool p_state) -{ - ui_evidence_name->setReadOnly(true); - int final_id = p_id + max_evidence_on_page * current_evidence_page; - - if (p_state) - { - if (final_id == local_evidence_list.size()) - ui_evidence_name->setText("Add new evidence..."); - else if (final_id < local_evidence_list.size()) - ui_evidence_name->setText(local_evidence_list.at(final_id).name); - } - else if (current_evidence < local_evidence_list.size()) - ui_evidence_name->setText(local_evidence_list.at(current_evidence).name); - else - ui_evidence_name->setText(""); -} - -void Courtroom::on_evidence_left_clicked() -{ - --current_evidence_page; - - set_evidence_page(); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_evidence_right_clicked() -{ - ++current_evidence_page; - - set_evidence_page(); - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_evidence_present_clicked() -{ - if (is_presenting_evidence) - ui_evidence_present->set_image("present_disabled.png"); - else - ui_evidence_present->set_image("present.png"); - - is_presenting_evidence = !is_presenting_evidence; - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_evidence_delete_clicked() -{ - ui_evidence_description->setReadOnly(true); - ui_evidence_overlay->hide(); - - ao_app->send_server_packet(new AOPacket("DE#" + QString::number(current_evidence) + "#%")); - - current_evidence = 0; - - ui_ic_chat_message->setFocus(); -} - -void Courtroom::on_evidence_x_clicked() -{ - ui_evidence_description->setReadOnly(true); - ui_evidence_overlay->hide(); - - if (current_evidence >= local_evidence_list.size()) - return; - - QStringList f_contents; - - evi_type f_evi = local_evidence_list.at(current_evidence); - - f_contents.append(QString::number(current_evidence)); - f_contents.append(f_evi.name); - f_contents.append(ui_evidence_description->toPlainText()); - f_contents.append(f_evi.image); - - ao_app->send_server_packet(new AOPacket("EE", f_contents)); - - ui_ic_chat_message->setFocus(); -} - diff --git a/file_functions.cpp b/file_functions.cpp deleted file mode 100644 index f3a634fa5..000000000 --- a/file_functions.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include - -#include "file_functions.h" - -bool file_exists(QString file_path) -{ - QFileInfo check_file(file_path); - - return check_file.exists() && check_file.isFile(); -} - -QString file_exists(QString file_path, QVector p_exts) -{ - for(auto &ext : p_exts) - { - if(file_exists(file_path + ext)) - return ext; - } - return ""; -} - -bool dir_exists(QString dir_path) -{ - QDir check_dir(dir_path); - - return check_dir.exists(); -} diff --git a/file_functions.h b/file_functions.h deleted file mode 100644 index 97b4da7a8..000000000 --- a/file_functions.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef FILE_FUNCTIONS_H -#define FILE_FUNCTIONS_H - -#include -#include - -bool file_exists(QString file_path); -QString file_exists(QString file_path, QVector p_exts); -bool dir_exists(QString file_path); - -#endif // FILE_FUNCTIONS_H diff --git a/hardware_functions.cpp b/hardware_functions.cpp deleted file mode 100644 index 29823cdfb..000000000 --- a/hardware_functions.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include "hardware_functions.h" - -#include - -#if (defined (_WIN32) || defined (_WIN64)) -#include - -DWORD dwVolSerial; -BOOL bIsRetrieved; - -QString get_hdid() -{ - bIsRetrieved = GetVolumeInformation(TEXT("C:\\"), NULL, NULL, &dwVolSerial, NULL, NULL, NULL, NULL); - - if (bIsRetrieved) - return QString::number(dwVolSerial, 16); - else - //a totally random string - //what could possibly go wrong - return "gxsps32sa9fnwic92mfbs0"; - -} - -#elif (defined (LINUX) || defined (__linux__)) - -#include -#include - -QString get_hdid() -{ - QFile fstab_file("/etc/fstab"); - if (!fstab_file.open(QIODevice::ReadOnly)) - return "gxcps32sa9fnwic92mfbs0"; - - QTextStream in(&fstab_file); - - while(!in.atEnd()) - { - QString line = in.readLine(); - - if (line.startsWith("UUID")) - { - QStringList line_elements = line.split("="); - - if (line_elements.size() > 1) - return line_elements.at(1).left(23).trimmed(); - } - } - - return "gxcpz32sa9fnwic92mfbs0"; -} - -#else - -#error This operating system is unsupported for hardware functions. - -#endif diff --git a/hex_functions.cpp b/hex_functions.cpp deleted file mode 100644 index 9db2b0a73..000000000 --- a/hex_functions.cpp +++ /dev/null @@ -1,83 +0,0 @@ -//WELCOME TO THE EXTREMELY GHETTO HEX CONVERSION KLUDGE BECAUSE I COULDNT MAKE ANYTHING ELSE WORK - -#include "hex_functions.h" - -namespace omni -{ - char halfword_to_hex_char(unsigned int input) - { - if (input > 127) - return 'F'; - - switch (input) - { - case 0: - return '0'; - case 1: - return '1'; - case 2: - return '2'; - case 3: - return '3'; - case 4: - return '4'; - case 5: - return '5'; - case 6: - return '6'; - case 7: - return '7'; - case 8: - return '8'; - case 9: - return '9'; - case 10: - return 'A'; - case 11: - return 'B'; - case 12: - return 'C'; - case 13: - return 'D'; - case 14: - return 'E'; - case 15: - return 'F'; - default: - return 'F'; - } - } - - std::string int_to_hex(unsigned int input) - { - if (input > 255) - return "FF"; - - std::bitset<8> whole_byte(input); - //240 represents 11110000, our needed bitmask - uint8_t left_mask_int = 240; - std::bitset<8> left_mask(left_mask_int); - std::bitset<8> left_halfword((whole_byte & left_mask) >> 4); - //likewise, 15 represents 00001111 - uint8_t right_mask_int = 15; - std::bitset<8> right_mask(right_mask_int); - std::bitset<8> right_halfword((whole_byte & right_mask)); - - unsigned int left = left_halfword.to_ulong(); - unsigned int right = right_halfword.to_ulong(); - - //std::cout << "now have have " << left << " and " << right << '\n'; - - char a = halfword_to_hex_char(left); - char b = halfword_to_hex_char(right); - - std::string left_string(1, a); - std::string right_string(1, b); - - std::string final_byte = left_string + right_string; - - //std::string final_byte = halfword_to_hex_char(left) + "" + halfword_to_hex_char(right); - - return final_byte; - } -} //namespace omni diff --git a/hex_functions.h b/hex_functions.h deleted file mode 100644 index 47d9466b7..000000000 --- a/hex_functions.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef HEX_OPERATIONS_H -#define HEX_OPERATIONS_H - -#include -#include -#include - -namespace omni -{ - char halfword_to_hex_char(unsigned int input); - std::string int_to_hex(unsigned int input); -} - -#endif //HEX_OPERATIONS_H diff --git a/icon-2x.png b/icon-2x.png new file mode 100644 index 000000000..9c46fb462 Binary files /dev/null and b/icon-2x.png differ diff --git a/icon-filled.png b/icon-filled.png new file mode 100644 index 000000000..40119dddf Binary files /dev/null and b/icon-filled.png differ diff --git a/logo.ico b/icon-transparent.ico similarity index 100% rename from logo.ico rename to icon-transparent.ico diff --git a/logo.png b/icon-transparent.png similarity index 100% rename from logo.png rename to icon-transparent.png diff --git a/icon.icns b/icon.icns new file mode 100644 index 000000000..c03b205f7 Binary files /dev/null and b/icon.icns differ diff --git a/icon.ico b/icon.ico new file mode 100644 index 000000000..cd9d4b7d4 Binary files /dev/null and b/icon.ico differ diff --git a/icon.png b/icon.png new file mode 100644 index 000000000..3e423a018 Binary files /dev/null and b/icon.png differ diff --git a/lobby.cpp b/lobby.cpp deleted file mode 100644 index bba8dddee..000000000 --- a/lobby.cpp +++ /dev/null @@ -1,455 +0,0 @@ -#include "lobby.h" - -#include "debug_functions.h" -#include "aoapplication.h" -#include "networkmanager.h" -#include "aosfxplayer.h" - -#include -#include - -Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() -{ - ao_app = p_ao_app; - - this->setWindowTitle("Danganronpa Online"); - - ui_background = new AOImage(this, ao_app); - ui_public_servers = new AOButton(this, ao_app); - ui_favorites = new AOButton(this, ao_app); - ui_refresh = new AOButton(this, ao_app); - ui_add_to_fav = new AOButton(this, ao_app); - ui_connect = new AOButton(this, ao_app); - ui_version = new QLabel(this); - ui_about = new AOButton(this, ao_app); - ui_server_list = new QListWidget(this); - ui_player_count = new QLabel(this); - ui_description = new AOTextArea(this); - ui_chatbox = new AOTextArea(this); - ui_chatbox->setOpenExternalLinks(true); - ui_chatname = new QLineEdit(this); - ui_chatname->setPlaceholderText("Name"); - ui_chatmessage = new QLineEdit(this); - ui_loading_background = new AOImage(this, ao_app); - ui_loading_text = new QTextEdit(ui_loading_background); - ui_progress_bar = new QProgressBar(ui_loading_background); - ui_progress_bar->setMinimum(0); - ui_progress_bar->setMaximum(100); - ui_progress_bar->setStyleSheet("QProgressBar{ color: white; }"); - ui_cancel = new AOButton(ui_loading_background, ao_app); - - connect(ui_public_servers, SIGNAL(clicked()), this, SLOT(on_public_servers_clicked())); - connect(ui_favorites, SIGNAL(clicked()), this, SLOT(on_favorites_clicked())); - connect(ui_refresh, SIGNAL(pressed()), this, SLOT(on_refresh_pressed())); - connect(ui_refresh, SIGNAL(released()), this, SLOT(on_refresh_released())); - connect(ui_add_to_fav, SIGNAL(pressed()), this, SLOT(on_add_to_fav_pressed())); - connect(ui_add_to_fav, SIGNAL(released()), this, SLOT(on_add_to_fav_released())); - connect(ui_connect, SIGNAL(pressed()), this, SLOT(on_connect_pressed())); - connect(ui_connect, SIGNAL(released()), this, SLOT(on_connect_released())); - connect(ui_about, SIGNAL(clicked()), this, SLOT(on_about_clicked())); - connect(ui_server_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_server_list_clicked(QModelIndex))); - connect(ui_chatmessage, SIGNAL(returnPressed()), this, SLOT(on_chatfield_return_pressed())); - connect(ui_cancel, SIGNAL(clicked()), ao_app, SLOT(loading_cancelled())); - - set_widgets(); -} - -//sets images, position and size -void Lobby::set_widgets() -{ - ao_app->reload_theme(); - - QString filename = "lobby_design.ini"; - - pos_size_type f_lobby = ao_app->get_element_dimensions("lobby", filename); - - if (f_lobby.width < 0 || f_lobby.height < 0) - { - qDebug() << "W: did not find lobby width or height in " << filename; - - // Most common symptom of bad config files and missing assets. - call_notice("It doesn't look like your client is set up correctly.\n" - "Did you download all resources correctly from tiny.cc/getao, " - "including the large 'base' folder?"); - - this->resize(517, 666); - } - else - { - this->resize(f_lobby.width, f_lobby.height); - } - - set_size_and_pos(ui_background, "lobby"); - ui_background->set_image("lobbybackground.png"); - - set_size_and_pos(ui_public_servers, "public_servers"); - ui_public_servers->set_image("publicservers_selected.png"); - - set_size_and_pos(ui_favorites, "favorites"); - ui_favorites->set_image("favorites.png"); - - set_size_and_pos(ui_refresh, "refresh"); - ui_refresh->set_image("refresh.png"); - - set_size_and_pos(ui_add_to_fav, "add_to_fav"); - ui_add_to_fav->set_image("addtofav.png"); - - set_size_and_pos(ui_connect, "connect"); - ui_connect->set_image("connect.png"); - - set_size_and_pos(ui_version, "version"); - ui_version->setText("Version: " + ao_app->get_version_string()); - - set_size_and_pos(ui_about, "about"); - ui_about->set_image("about.png"); - - set_size_and_pos(ui_server_list, "server_list"); - ui_server_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "font: bold;"); - - set_size_and_pos(ui_player_count, "player_count"); - ui_player_count->setText("Offline"); - ui_player_count->setStyleSheet("font: bold;" - "color: white;" - "qproperty-alignment: AlignCenter;"); - - set_size_and_pos(ui_description, "description"); - ui_description->setReadOnly(true); - ui_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: white;"); - - set_size_and_pos(ui_chatbox, "chatbox"); - ui_chatbox->setReadOnly(true); - ui_chatbox->setStyleSheet("QTextBrowser{background-color: rgba(0, 0, 0, 0);}"); - - set_size_and_pos(ui_chatname, "chatname"); - ui_chatname->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "selection-background-color: rgba(0, 0, 0, 0);"); - - set_size_and_pos(ui_chatmessage, "chatmessage"); - ui_chatmessage->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "selection-background-color: rgba(0, 0, 0, 0);"); - - ui_loading_background->resize(this->width(), this->height()); - ui_loading_background->set_image("loadingbackground.png"); - - - set_size_and_pos(ui_loading_text, "loading_label"); - ui_loading_text->setFont(QFont("Arial", 20, QFont::Bold)); - ui_loading_text->setReadOnly(true); - ui_loading_text->setAlignment(Qt::AlignCenter); - ui_loading_text->setFrameStyle(QFrame::NoFrame); - ui_loading_text->setStyleSheet("background-color: rgba(0, 0, 0, 0);" - "color: rgba(255, 128, 0, 255);"); - ui_loading_text->append("Loading"); - - set_size_and_pos(ui_progress_bar, "progress_bar"); - set_size_and_pos(ui_cancel, "cancel"); - ui_cancel->setText("Cancel"); - - ui_loading_background->hide(); - - set_fonts(); - set_stylesheets(); -} - -void Lobby::set_size_and_pos(QWidget *p_widget, QString p_identifier) -{ - QString filename = "lobby_design.ini"; - - pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); - - if (design_ini_result.width < 0 || design_ini_result.height < 0) - { - qDebug() << "W: could not find " << p_identifier << " in " << filename; - p_widget->hide(); - } - else - { - p_widget->move(design_ini_result.x, design_ini_result.y); - p_widget->resize(design_ini_result.width, design_ini_result.height); - } -} - -void Lobby::set_fonts() -{ - set_font(ui_player_count, "player_count"); - set_font(ui_description, "description"); - set_font(ui_chatbox, "chatbox"); - set_font(ui_chatname, "chatname"); - set_font(ui_chatmessage, "chatmessage"); - set_font(ui_loading_text, "loading_text"); - set_font(ui_server_list, "server_list"); -} - -void Lobby::set_stylesheet(QWidget *widget, QString target_tag) -{ - QString f_file = "lobby_stylesheets.css"; - QString style_sheet_string = ao_app->get_stylesheet(target_tag, f_file); - if (style_sheet_string != "") - widget->setStyleSheet(style_sheet_string); -} - -void Lobby::set_stylesheets() -{ - set_stylesheet(ui_player_count, "[PLAYER COUNT]"); - set_stylesheet(ui_description, "[DESCRIPTION]"); - set_stylesheet(ui_chatbox, "[CHAT BOX]"); - set_stylesheet(ui_chatname, "[CHAT NAME]"); - set_stylesheet(ui_chatmessage, "[CHAT MESSAGE]"); - set_stylesheet(ui_loading_text, "[LOADING TEXT]"); - set_stylesheet(ui_server_list, "[SERVER LIST]"); -} - -void Lobby::set_font(QWidget *widget, QString p_identifier) -{ - QString design_file = "lobby_fonts.ini"; - int f_weight = ao_app->get_font_size(p_identifier, design_file); - QString class_name = widget->metaObject()->className(); - - QString font_name = ao_app->get_font_name("font_" + p_identifier, design_file); - - QFont font(font_name, f_weight); - - bool use = (bool)ao_app->get_font_size("use_custom_fonts", design_file); - - if(use) - { - widget->setFont(font); - - QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); - - bool bold = (bool)ao_app->get_font_size(p_identifier + "_bold", design_file); // is the font bold or not? - bool center = (bool)ao_app->get_font_size(p_identifier + "_center", design_file); // should it be centered? - - - QString is_bold = ""; - if(bold) is_bold = "bold"; - - QString is_center = ""; - if(center) is_center = "qproperty-alignment: AlignCenter;"; - - QString style_sheet_string = class_name + " { background-color: rgba(0, 0, 0, 0);\n" + - "color: rgba(" + - QString::number(f_color.red()) + ", " + - QString::number(f_color.green()) + ", " + - QString::number(f_color.blue()) + ", 255);\n" + - is_center + "\n" + - "font: " + is_bold + "; }"; - - widget->setStyleSheet(style_sheet_string); - } - - - return; -} - -void Lobby::set_loading_text(QString p_text) -{ - ui_loading_text->clear(); - ui_loading_text->setAlignment(Qt::AlignCenter); - ui_loading_text->append(p_text); -} - -QString Lobby::get_chatlog() -{ - QString return_value = ui_chatbox->toPlainText(); - - return return_value; -} - -int Lobby::get_selected_server() -{ - return ui_server_list->currentRow(); -} - -void Lobby::set_loading_value(int p_value) -{ - ui_progress_bar->setValue(p_value); -} - -void Lobby::on_public_servers_clicked() -{ - ui_public_servers->set_image("publicservers_selected.png"); - ui_favorites->set_image("favorites.png"); - - list_servers(); - - public_servers_selected = true; -} - -void Lobby::on_favorites_clicked() -{ - ui_favorites->set_image("favorites_selected.png"); - ui_public_servers->set_image("publicservers.png"); - - ao_app->set_favorite_list(); - //ao_app->favorite_list = read_serverlist_txt(); - - list_favorites(); - - public_servers_selected = false; -} - -void Lobby::on_refresh_pressed() -{ - ui_refresh->set_image("refresh_pressed.png"); -} - -void Lobby::on_refresh_released() -{ - ui_refresh->set_image("refresh.png"); - - AOPacket *f_packet = new AOPacket("ALL#%"); - - ao_app->send_ms_packet(f_packet); -} - -void Lobby::on_add_to_fav_pressed() -{ - ui_add_to_fav->set_image("addtofav_pressed.png"); -} - -void Lobby::on_add_to_fav_released() -{ - ui_add_to_fav->set_image("addtofav.png"); - - //you cant add favorites from favorites m8 - if (!public_servers_selected) - return; - - ao_app->add_favorite_server(ui_server_list->currentRow()); -} - -void Lobby::on_connect_pressed() -{ - ui_connect->set_image("connect_pressed.png"); -} - -void Lobby::on_connect_released() -{ - ui_connect->set_image("connect.png"); - - AOPacket *f_packet; - - f_packet = new AOPacket("askchaa#%"); - - ao_app->send_server_packet(f_packet); -} - -void Lobby::on_about_clicked() -{ - call_notice("Attorney Online 2 is built using Qt 5.7\n\n" - "Lead development:\n" - "OmniTroid\n\n" - "stonedDiscord\n" - "longbyte1\n" - "Supporting development:\n" - "Fiercy\n\n" - "UI design:\n" - "Ruekasu\n" - "Draxirch\n\n" - "Special thanks:\n" - "Unishred\n" - "Argoneus\n" - "Noevain\n" - "Cronnicossy"); -} - -void Lobby::on_server_list_clicked(QModelIndex p_model) -{ - server_type f_server; - int n_server = p_model.row(); - - if (n_server < 0) - return; - - if (public_servers_selected) - { - QVector f_server_list = ao_app->get_server_list(); - - if (n_server >= f_server_list.size()) - return; - - f_server = f_server_list.at(p_model.row()); - } - else - { - if (n_server >= ao_app->get_favorite_list().size()) - return; - - f_server = ao_app->get_favorite_list().at(p_model.row()); - } - - ui_description->clear(); - ui_description->append(f_server.desc); - - ui_description->moveCursor(QTextCursor::Start); - ui_description->ensureCursorVisible(); - - ui_player_count->setText("Offline"); - - ao_app->net_manager->connect_to_server(f_server); -} - -void Lobby::on_chatfield_return_pressed() -{ - //no you can't send empty messages - if (ui_chatname->text() == "" || ui_chatmessage->text() == "") - return; - - - QString f_header = "CT"; - QStringList f_contents{ui_chatname->text(), ui_chatmessage->text()}; - - AOPacket *f_packet = new AOPacket(f_header, f_contents); - - ao_app->send_ms_packet(f_packet); - - ui_chatmessage->clear(); -} - -void Lobby::list_servers() -{ - public_servers_selected = true; - ui_favorites->set_image("favorites.png"); - ui_public_servers->set_image("publicservers_selected.png"); - - ui_server_list->clear(); - - for (server_type i_server : ao_app->get_server_list()) - { - ui_server_list->addItem(i_server.name); - } -} - -void Lobby::list_favorites() -{ - ui_server_list->clear(); - - for (server_type i_server : ao_app->get_favorite_list()) - { - ui_server_list->addItem(i_server.name); - } -} - -void Lobby::append_chatmessage(QString f_name, QString f_message) -{ - ui_chatbox->append_chatmessage(f_name, f_message); -} - -void Lobby::append_error(QString f_message) -{ - ui_chatbox->append_error(f_message); -} - -void Lobby::set_player_count(int players_online, int max_players) -{ - QString f_string = "Online: " + QString::number(players_online) + "/" + QString::number(max_players); - ui_player_count->setText(f_string); -} - -Lobby::~Lobby() -{ - -} diff --git a/lobby.h b/lobby.h deleted file mode 100644 index e0eb0bfa0..000000000 --- a/lobby.h +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef LOBBY_H -#define LOBBY_H - -#include "aoimage.h" -#include "aobutton.h" -#include "aopacket.h" -#include "aotextarea.h" - -#include -#include -#include -#include -#include -#include -#include - -class AOApplication; - -class Lobby : public QMainWindow -{ - Q_OBJECT - -public: - Lobby(AOApplication *p_ao_app); - - void set_widgets(); - void list_servers(); - void list_favorites(); - void append_chatmessage(QString f_name, QString f_message); - void append_error(QString f_message); - void set_player_count(int players_online, int max_players); - void set_loading_text(QString p_text); - void set_stylesheet(QWidget *widget, QString target_tag); - void set_stylesheets(); - void set_fonts(); - void set_font(QWidget *widget, QString p_identifier); - void show_loading_overlay(){ui_loading_background->show();} - void hide_loading_overlay(){ui_loading_background->hide();} - QString get_chatlog(); - int get_selected_server(); - - void set_loading_value(int p_value); - - bool public_servers_selected = true; - - ~Lobby(); - -private: - AOApplication *ao_app = nullptr; - - AOImage *ui_background; - - AOButton *ui_public_servers; - AOButton *ui_favorites; - - AOButton *ui_refresh; - AOButton *ui_add_to_fav; - AOButton *ui_connect; - - QLabel *ui_version; - AOButton *ui_about; - - QListWidget *ui_server_list; - - QLabel *ui_player_count; - AOTextArea *ui_description; - - AOTextArea *ui_chatbox; - - QLineEdit *ui_chatname; - QLineEdit *ui_chatmessage; - - AOImage *ui_loading_background; - QTextEdit *ui_loading_text; - QProgressBar *ui_progress_bar; - AOButton *ui_cancel; - - void set_size_and_pos(QWidget *p_widget, QString p_identifier); - -private slots: - void on_public_servers_clicked(); - void on_favorites_clicked(); - - void on_refresh_pressed(); - void on_refresh_released(); - void on_add_to_fav_pressed(); - void on_add_to_fav_released(); - void on_connect_pressed(); - void on_connect_released(); - void on_about_clicked(); - void on_server_list_clicked(QModelIndex p_model); - void on_chatfield_return_pressed(); -}; - -#endif // LOBBY_H diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 46a870339..000000000 --- a/main.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "aoapplication.h" - -#include "datatypes.h" -#include "networkmanager.h" -#include "lobby.h" -#include "courtroom.h" -#include -#include -#include - -int main(int argc, char *argv[]) -{ -#if QT_VERSION > QT_VERSION_CHECK(5, 6, 0) - // High-DPI support is for Qt version >=5.6. - // However, many Linux distros still haven't brought their stable/LTS - // packages up to Qt 5.6, so this is conditional. - AOApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -#endif - - QPluginLoader apng("imageformats/qapng.dll"); - if (!apng.load()) - { - qDebug() << apng.errorString(); - } - - AOApplication main_app(argc, argv); - main_app.construct_lobby(); -#ifndef INDEV - main_app.net_manager->connect_to_master(); -#endif - main_app.w_lobby->show(); - - return main_app.exec(); -} diff --git a/networkmanager.cpp b/networkmanager.cpp deleted file mode 100644 index 0c97cef2b..000000000 --- a/networkmanager.cpp +++ /dev/null @@ -1,244 +0,0 @@ -#include "networkmanager.h" - -#include "datatypes.h" -#include "debug_functions.h" -#include "lobby.h" - -#include - -NetworkManager::NetworkManager(AOApplication *parent) : QObject(parent) -{ - ao_app = parent; - - ms_socket = new QTcpSocket(this); - server_socket = new QTcpSocket(this); - - ms_reconnect_timer = new QTimer(this); - ms_reconnect_timer->setSingleShot(true); - QObject::connect(ms_reconnect_timer, SIGNAL(timeout()), this, SLOT(retry_ms_connect())); - - QObject::connect(ms_socket, SIGNAL(readyRead()), this, SLOT(handle_ms_packet())); - QObject::connect(server_socket, SIGNAL(readyRead()), this, SLOT(handle_server_packet())); - QObject::connect(server_socket, SIGNAL(disconnected()), ao_app, SLOT(server_disconnected())); -} - -NetworkManager::~NetworkManager() -{ - -} - -void NetworkManager::connect_to_master() -{ - ms_socket->close(); - ms_socket->abort(); - -#ifdef MS_FAILOVER_SUPPORTED - perform_srv_lookup(); -#else - connect_to_master_nosrv(); -#endif -} - -void NetworkManager::connect_to_master_nosrv() -{ - QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), - this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); - - QObject::connect(ms_socket, SIGNAL(connected()), - this, SLOT(on_ms_nosrv_connect_success())); - ms_socket->connectToHost(ms_nosrv_hostname, ms_port); -} - -void NetworkManager::connect_to_server(server_type p_server) -{ - server_socket->close(); - server_socket->abort(); - - server_socket->connectToHost(p_server.ip, p_server.port); -} - -void NetworkManager::ship_ms_packet(QString p_packet) -{ - if (!ms_socket->isOpen()) - { - retry_ms_connect(); - } - else - { - ms_socket->write(p_packet.toUtf8()); - } -} - -void NetworkManager::ship_server_packet(QString p_packet) -{ - server_socket->write(p_packet.toUtf8()); -} - -void NetworkManager::handle_ms_packet() -{ - QByteArray buffer = ms_socket->readAll(); - QString in_data = QString::fromUtf8(buffer, buffer.size()); - - if (!in_data.endsWith("%")) - { - ms_partial_packet = true; - ms_temp_packet += in_data; - return; - } - - else - { - if (ms_partial_packet) - { - in_data = ms_temp_packet + in_data; - ms_temp_packet = ""; - ms_partial_packet = false; - } - } - - QStringList packet_list = in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts)); - - for (QString packet : packet_list) - { - AOPacket *f_packet = new AOPacket(packet); - - ao_app->ms_packet_received(f_packet); - } -} - - -void NetworkManager::perform_srv_lookup() -{ - #ifdef MS_FAILOVER_SUPPORTED - ms_dns = new QDnsLookup(QDnsLookup::SRV, ms_srv_hostname, this); - - connect(ms_dns, SIGNAL(finished()), this, SLOT(on_srv_lookup())); - ms_dns->lookup(); - #endif -} - -void NetworkManager::on_srv_lookup() -{ - #ifdef MS_FAILOVER_SUPPORTED - bool connected = false; - if (ms_dns->error() != QDnsLookup::NoError) - { - qWarning("SRV lookup of the master server DNS failed."); - ms_dns->deleteLater(); - } - else - { - const auto srv_records = ms_dns->serviceRecords(); - - for (const QDnsServiceRecord &record : srv_records) - { - qDebug() << "Connecting to " << record.target() << ":" << record.port(); - ms_socket->connectToHost(record.target(), record.port()); - QTime timer; - timer.start(); - do - { - ao_app->processEvents(); - if (ms_socket->state() == QAbstractSocket::ConnectedState) - { - connected = true; - break; - } - else if (ms_socket->state() != QAbstractSocket::ConnectingState - && ms_socket->state() != QAbstractSocket::HostLookupState - && ms_socket->error() != -1) - { - qDebug() << ms_socket->error(); - qWarning() << "Error connecting to master server:" << ms_socket->errorString(); - ms_socket->abort(); - ms_socket->close(); - break; - } - } while (timer.elapsed() < timeout_milliseconds); // Very expensive spin-wait loop - it will bring CPU to 100%! - if (connected) - { - // Connect a one-shot signal in case the master server disconnects randomly - QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), - this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); - break; - } - else - { - ms_socket->abort(); - ms_socket->close(); - } - } - } - - // Failover to non-SRV connection - if (!connected) - connect_to_master_nosrv(); - else - emit ms_connect_finished(connected, false); - #endif -} - -void NetworkManager::on_ms_nosrv_connect_success() -{ - emit ms_connect_finished(true, false); - - QObject::disconnect(ms_socket, SIGNAL(connected()), - this, SLOT(on_ms_nosrv_connect_success())); - - QObject::connect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), - this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); -} - -void NetworkManager::on_ms_socket_error(QAbstractSocket::SocketError error) -{ - qWarning() << "Master server socket error:" << ms_socket->errorString() - << "(" << error << ")"; - - // Disconnect the one-shot signal - this way, failover connect attempts - // don't trigger a full retry - QObject::disconnect(ms_socket, SIGNAL(error(QAbstractSocket::SocketError)), - this, SLOT(on_ms_socket_error(QAbstractSocket::SocketError))); - - emit ms_connect_finished(false, true); - - ms_reconnect_timer->start(ms_reconnect_delay_ms); -} - -void NetworkManager::retry_ms_connect() -{ - if (!ms_reconnect_timer->isActive() && ms_socket->state() != QAbstractSocket::ConnectingState) - connect_to_master(); -} - -void NetworkManager::handle_server_packet() -{ - QByteArray buffer = server_socket->readAll(); - QString in_data = QString::fromUtf8(buffer, buffer.size()); - - if (!in_data.endsWith("%")) - { - partial_packet = true; - temp_packet += in_data; - return; - } - - else - { - if (partial_packet) - { - in_data = temp_packet + in_data; - temp_packet = ""; - partial_packet = false; - } - } - - QStringList packet_list = in_data.split("%", QString::SplitBehavior(QString::SkipEmptyParts)); - - for (QString packet : packet_list) - { - AOPacket *f_packet = new AOPacket(packet); - - ao_app->server_packet_received(f_packet); - } -} - diff --git a/networkmanager.h b/networkmanager.h deleted file mode 100644 index 5003883a4..000000000 --- a/networkmanager.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef NETWORKMANAGER_H -#define NETWORKMANAGER_H - -// Qt for Android has stubbed QDnsLookup. This is not documented in any part of their wiki. -// This prevents SRV lookup/failover behavior from functioning. -// https://bugreports.qt.io/browse/QTBUG-56143 -#ifndef ANDROID -#define MS_FAILOVER_SUPPORTED -#endif - -//#define LOCAL_MS - -#ifdef LOCAL_MS -#undef MS_FAILOVER_SUPPORTED -#endif - -#include "aopacket.h" -#include "aoapplication.h" - -#include -#include -#include -#include - -class NetworkManager : public QObject -{ - Q_OBJECT - -public: - NetworkManager(AOApplication *parent); - ~NetworkManager(); - - AOApplication *ao_app = nullptr; - QTcpSocket *ms_socket; - QTcpSocket *server_socket; - QDnsLookup *ms_dns; - QTimer *ms_reconnect_timer; - - const QString ms_srv_hostname = "_aoms._tcp.aceattorneyonline.com"; -#ifdef LOCAL_MS - const QString ms_nosrv_hostname = "localhost"; -#else - const QString ms_nosrv_hostname = "master.aceattorneyonline.com"; -#endif - - static const int ms_port = 27016; - static const int timeout_milliseconds = 2000; - - static const int ms_reconnect_delay_ms = 7000; - - bool ms_partial_packet = false; - QString ms_temp_packet = ""; - - bool partial_packet = false; - QString temp_packet = ""; - - unsigned int s_decryptor = 5; - - void connect_to_master(); - void connect_to_master_nosrv(); - void connect_to_server(server_type p_server); - -public slots: - void ship_ms_packet(QString p_packet); - void ship_server_packet(QString p_packet); - -signals: - void ms_connect_finished(bool success, bool will_retry); - -private: - void perform_srv_lookup(); - -private slots: - void on_srv_lookup(); - void handle_ms_packet(); - void handle_server_packet(); - void on_ms_nosrv_connect_success(); - void on_ms_socket_error(QAbstractSocket::SocketError error); - void retry_ms_connect(); -}; - -#endif // NETWORKMANAGER_H diff --git a/packet_distribution.cpp b/packet_distribution.cpp deleted file mode 100644 index 2f030918f..000000000 --- a/packet_distribution.cpp +++ /dev/null @@ -1,707 +0,0 @@ -#include "aoapplication.h" - -#include "lobby.h" -#include "courtroom.h" -#include "networkmanager.h" -#include "encryption_functions.h" -#include "hardware_functions.h" -#include "debug_functions.h" - -#include -#include - -void AOApplication::ms_packet_received(AOPacket *p_packet) -{ - p_packet->net_decode(); - - QString header = p_packet->get_header(); - QStringList f_contents = p_packet->get_contents(); - - if (header != "CHECK") - qDebug() << "R(ms):" << p_packet->to_string(); - - if (header == "ALL") - { - server_list.clear(); - - for (QString i_string : p_packet->get_contents()) - { - server_type f_server; - QStringList sub_contents = i_string.split("&"); - - if (sub_contents.size() < 4) - { - qDebug() << "W: malformed packet"; - continue; - } - - f_server.name = sub_contents.at(0); - f_server.desc = sub_contents.at(1); - f_server.ip = sub_contents.at(2); - f_server.port = sub_contents.at(3).toInt(); - - server_list.append(f_server); - } - - if (lobby_constructed) - { - w_lobby->list_servers(); - } - } - else if (header == "CT") - { - QString f_name, f_message; - - if (f_contents.size() == 1) - { - f_name = ""; - f_message = f_contents.at(0); - } - else if (f_contents.size() >= 2) - { - f_name = f_contents.at(0); - f_message = f_contents.at(1); - } - else - goto end; - - if (lobby_constructed) - { - w_lobby->append_chatmessage(f_name, f_message); - } - if (courtroom_constructed && courtroom_loaded) - { - w_courtroom->append_ms_chatmessage(f_name, f_message); - } - } - else if (header == "AO2CHECK") - { - send_ms_packet(new AOPacket("ID#AO2#" + get_version_string() + "#%")); - send_ms_packet(new AOPacket("HI#" + get_hdid() + "#%")); - - if (f_contents.size() < 1) - goto end; - - QStringList version_contents = f_contents.at(0).split("."); - - if (version_contents.size() < 3) - goto end; - - int f_release = version_contents.at(0).toInt(); - int f_major = version_contents.at(1).toInt(); - int f_minor = version_contents.at(2).toInt(); - - if (get_release() > f_release) - goto end; - else if (get_release() == f_release) - { - if (get_major_version() > f_major) - goto end; - else if (get_major_version() == f_major) - { - if (get_minor_version() >= f_minor) - goto end; - } - } - - call_notice("Outdated version! Your version: " + get_version_string() - + "\nPlease go to aceattorneyonline.com to update."); - destruct_courtroom(); - destruct_lobby(); - } - else if (header == "DOOM") - { - call_notice("You have been exiled from AO." - "Have a nice day."); - destruct_courtroom(); - destruct_lobby(); - } - - end: - - delete p_packet; -} - -void AOApplication::server_packet_received(AOPacket *p_packet) -{ - p_packet->net_decode(); - - QString header = p_packet->get_header(); - QStringList f_contents = p_packet->get_contents(); - QString f_packet = p_packet->to_string(); - - if (header != "checkconnection") - qDebug() << "R:" << f_packet; - - if (header == "decryptor") - { - if (f_contents.size() == 0) - goto end; - - //you may ask where 322 comes from. that would be a good question. - s_decryptor = fanta_decrypt(f_contents.at(0), 322).toUInt(); - - //default(legacy) values - encryption_needed = true; - yellow_text_enabled = false; - prezoom_enabled = false; - flipping_enabled = false; - custom_objection_enabled = false; - improved_loading_enabled = false; - desk_mod_enabled = false; - evidence_enabled = false; - - //workaround for tsuserver4 - if (f_contents.at(0) == "NOENCRYPT") - encryption_needed = false; - - QString f_hdid; - f_hdid = get_hdid(); - - AOPacket *hi_packet = new AOPacket("HI#" + f_hdid + "#%"); - send_server_packet(hi_packet); - } - else if (header == "ID") - { - if (f_contents.size() < 2) - goto end; - - s_pv = f_contents.at(0).toInt(); - server_software = f_contents.at(1); - - send_server_packet(new AOPacket("ID#AO2#" + get_version_string() + "#%")); - } - else if (header == "CT") - { - if (f_contents.size() < 2) - goto end; - - if (courtroom_constructed) - w_courtroom->append_server_chatmessage(f_contents.at(0), f_contents.at(1)); - } - else if (header == "FL") - { - if (f_packet.contains("yellowtext",Qt::CaseInsensitive)) - yellow_text_enabled = true; - if (f_packet.contains("flipping",Qt::CaseInsensitive)) - flipping_enabled = true; - if (f_packet.contains("customobjections",Qt::CaseInsensitive)) - custom_objection_enabled = true; - if (f_packet.contains("fastloading",Qt::CaseInsensitive)) - improved_loading_enabled = true; - if (f_packet.contains("noencryption",Qt::CaseInsensitive)) - encryption_needed = false; - if (f_packet.contains("deskmod",Qt::CaseInsensitive)) - desk_mod_enabled = true; - if (f_packet.contains("evidence",Qt::CaseInsensitive)) - evidence_enabled = true; - } - else if (header == "PN") - { - if (f_contents.size() < 2) - goto end; - - w_lobby->set_player_count(f_contents.at(0).toInt(), f_contents.at(1).toInt()); - } - else if (header == "SI") - { - if (f_contents.size() != 3) - goto end; - - char_list_size = f_contents.at(0).toInt(); - evidence_list_size = f_contents.at(1).toInt(); - music_list_size = f_contents.at(2).toInt(); - - if (char_list_size < 1 || evidence_list_size < 0 || music_list_size < 0) - goto end; - - loaded_chars = 0; - loaded_evidence = 0; - loaded_music = 0; - - destruct_courtroom(); - construct_courtroom(); - - courtroom_loaded = false; - - QString window_title = "Danganronpa Online"; - int selected_server = w_lobby->get_selected_server(); - - QString server_address = "", server_name = ""; - if (w_lobby->public_servers_selected) - { - if (selected_server >= 0 && selected_server < server_list.size()) { - auto info = server_list.at(selected_server); - server_name = info.name; - server_address = info.ip + info.port; - window_title += ": " + server_name; - } - } - else - { - if (selected_server >= 0 && selected_server < favorite_list.size()) { - auto info = favorite_list.at(selected_server); - server_name = info.name; - server_address = info.ip + info.port; - window_title += ": " + server_name; - } - } - - w_courtroom->set_window_title(window_title); - - w_lobby->show_loading_overlay(); - w_lobby->set_loading_text("Loading"); - w_lobby->set_loading_value(0); - - AOPacket *f_packet; - - if(improved_loading_enabled) - f_packet = new AOPacket("RC#%"); - else - f_packet = new AOPacket("askchar2#%"); - - send_server_packet(f_packet); - - QCryptographicHash hash(QCryptographicHash::Algorithm::Sha256); - hash.addData(server_address.toUtf8()); - discord->state_server(server_name.toStdString(), hash.result().toBase64().toStdString()); - } - else if (header == "CI") - { - if (!courtroom_constructed) - goto end; - - for (int n_element = 0 ; n_element < f_contents.size() ; n_element += 2) - { - if (f_contents.at(n_element).toInt() != loaded_chars) - break; - - //this means we are on the last element and checking n + 1 element will be game over so - if (n_element == f_contents.size() - 1) - break; - - QStringList sub_elements = f_contents.at(n_element + 1).split("&"); - if (sub_elements.size() < 2) - break; - - char_type f_char; - f_char.name = sub_elements.at(0); - f_char.description = sub_elements.at(1); - f_char.evidence_string = sub_elements.at(3); - //temporary. the CharsCheck packet sets this properly - f_char.taken = false; - - ++loaded_chars; - - w_lobby->set_loading_text("Loading chars:\n" + QString::number(loaded_chars) + "/" + QString::number(char_list_size)); - - w_courtroom->append_char(f_char); - } - - int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = (loaded_chars / static_cast(total_loading_size)) * 100; - w_lobby->set_loading_value(loading_value); - - if (improved_loading_enabled) - send_server_packet(new AOPacket("RE#%")); - else - { - QString next_packet_number = QString::number(((loaded_chars - 1) / 10) + 1); - send_server_packet(new AOPacket("AN#" + next_packet_number + "#%")); - } - - } - else if (header == "EI") - { - if (!courtroom_constructed) - goto end; - - - // +1 because evidence starts at 1 rather than 0 for whatever reason - //enjoy fanta - if (f_contents.at(0).toInt() != loaded_evidence + 1) - goto end; - - if (f_contents.size() < 2) - goto end; - - QStringList sub_elements = f_contents.at(1).split("&"); - if (sub_elements.size() < 4) - goto end; - - evi_type f_evi; - f_evi.name = sub_elements.at(0); - f_evi.description = sub_elements.at(1); - //no idea what the number at position 2 is. probably an identifier? - f_evi.image = sub_elements.at(3); - - ++loaded_evidence; - - w_lobby->set_loading_text("Loading evidence:\n" + QString::number(loaded_evidence) + "/" + QString::number(evidence_list_size)); - - w_courtroom->append_evidence(f_evi); - - int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = ((loaded_chars + loaded_evidence) / static_cast(total_loading_size)) * 100; - w_lobby->set_loading_value(loading_value); - - QString next_packet_number = QString::number(loaded_evidence); - send_server_packet(new AOPacket("AE#" + next_packet_number + "#%")); - - } - else if (header == "EM") - { - if (!courtroom_constructed) - goto end; - - bool music_turn = false; - int areas = 0; - - for (int n_element = 0 ; n_element < f_contents.size() ; n_element += 2) - { - if (f_contents.at(n_element).toInt() != loaded_music) - break; - - if (n_element == f_contents.size() - 1) - break; - - QString f_music = f_contents.at(n_element + 1); - - ++loaded_music; - - w_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + QString::number(music_list_size)); - - if (music_turn) - { - w_courtroom->append_music(f_music); - } - else - { - if (f_music.endsWith(".wav") || - f_music.endsWith(".mp3") || - f_music.endsWith(".mp4") || - f_music.endsWith(".ogg") || - f_music.endsWith(".opus")) - { - music_turn = true; - areas--; - w_courtroom->fix_last_area(); - w_courtroom->append_music(f_music); - } - else - { - w_courtroom->append_area(f_music); - areas++; - } - } - qDebug() << f_music; - - int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = ((loaded_chars + loaded_evidence + loaded_music) / static_cast(total_loading_size)) * 100; - w_lobby->set_loading_value(loading_value); - - } - - QString next_packet_number = QString::number(((loaded_music - 1) / 10) + 1); - send_server_packet(new AOPacket("AM#" + next_packet_number + "#%")); - } - else if (header == "CharsCheck") - { - if (!courtroom_constructed) - goto end; - - for (int n_char = 0 ; n_char < f_contents.size() ; ++n_char) - { - if (f_contents.at(n_char) == "-1") - w_courtroom->set_taken(n_char, true); - else - w_courtroom->set_taken(n_char, false); - } - } - - else if (header == "SC") - { - if (!courtroom_constructed) - goto end; - - for (int n_element = 0 ; n_element < f_contents.size() ; ++n_element) - { - QStringList sub_elements = f_contents.at(n_element).split("&"); - - char_type f_char; - f_char.name = sub_elements.at(0); - if (sub_elements.size() >= 2) - f_char.description = sub_elements.at(1); - - //temporary. the CharsCheck packet sets this properly - f_char.taken = false; - - ++loaded_chars; - - w_lobby->set_loading_text("Loading chars:\n" + QString::number(loaded_chars) + "/" + QString::number(char_list_size)); - - w_courtroom->append_char(f_char); - } - - int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = (loaded_chars / static_cast(total_loading_size)) * 100; - w_lobby->set_loading_value(loading_value); - - send_server_packet(new AOPacket("RM#%")); - } - else if (header == "SM") - { - if (!courtroom_constructed) - goto end; - - bool musics_time = false; - int areas = 0; - - for (int n_element = 0 ; n_element < f_contents.size() ; ++n_element) - { - ++loaded_music; - - w_lobby->set_loading_text("Loading music:\n" + QString::number(loaded_music) + "/" + QString::number(music_list_size)); - - if (musics_time) - { - w_courtroom->append_music(f_contents.at(n_element)); - } - else - { - if (f_contents.at(n_element).endsWith(".wav") || - f_contents.at(n_element).endsWith(".mp3") || - f_contents.at(n_element).endsWith(".mp4") || - f_contents.at(n_element).endsWith(".ogg") || - f_contents.at(n_element).endsWith(".opus")) - { - musics_time = true; - w_courtroom->fix_last_area(); - w_courtroom->append_music(f_contents.at(n_element)); - areas--; - } - else - { - w_courtroom->append_area(f_contents.at(n_element)); - areas++; - } - } - - int total_loading_size = char_list_size + evidence_list_size + music_list_size; - int loading_value = (loaded_chars / static_cast(total_loading_size)) * 100; - w_lobby->set_loading_value(loading_value); - - } - - send_server_packet(new AOPacket("RD#%")); - } - else if (header == "FM") - { - if (!courtroom_constructed) - goto end; - - w_courtroom->clear_music(); - w_courtroom->clear_areas(); - - bool musics_time = false; - int areas = 0; - - for (int n_element = 0 ; n_element < f_contents.size() ; ++n_element) - { - if (musics_time) - { - w_courtroom->append_music(f_contents.at(n_element)); - } - else - { - if (f_contents.at(n_element).endsWith(".wav") || - f_contents.at(n_element).endsWith(".mp3") || - f_contents.at(n_element).endsWith(".mp4") || - f_contents.at(n_element).endsWith(".ogg") || - f_contents.at(n_element).endsWith(".opus")) - { - musics_time = true; - w_courtroom->fix_last_area(); - w_courtroom->append_music(f_contents.at(n_element)); - areas--; -// qDebug() << "wtf!!" << f_contents.at(n_element); - } - else - { - w_courtroom->append_area(f_contents.at(n_element)); - areas++; - } - } - } - - w_courtroom->list_music(); - w_courtroom->list_areas(); - } - else if (header == "DONE") - { - if (!courtroom_constructed) - goto end; - - if (lobby_constructed) - w_courtroom->append_ms_chatmessage("", w_lobby->get_chatlog()); - - w_courtroom->done_received(); - - courtroom_loaded = true; - - destruct_lobby(); - } - else if (header == "BN") - { - if (f_contents.size() < 1) - goto end; - - if (courtroom_constructed) - w_courtroom->set_background(f_contents.at(0)); - } - //server accepting char request(CC) packet - else if (header == "PV") - { - if (f_contents.size() < 3) - goto end; - - if (courtroom_constructed) - w_courtroom->enter_courtroom(f_contents.at(2).toInt()); - } - else if (header == "MS") - { - if (courtroom_constructed && courtroom_loaded) - w_courtroom->handle_chatmessage(&p_packet->get_contents()); - } - else if (header == "MC") - { - if (courtroom_constructed && courtroom_loaded) - w_courtroom->handle_song(&p_packet->get_contents()); - } - else if (header == "RT") - { - if (f_contents.size() < 1) - goto end; - if (courtroom_constructed) - w_courtroom->handle_wtce(f_contents.at(0)); - } - else if (header == "HP") - { - if (courtroom_constructed && f_contents.size() > 1) - w_courtroom->set_hp_bar(f_contents.at(0).toInt(), f_contents.at(1).toInt()); - } - else if (header == "LE") - { - if (courtroom_constructed) - { - QVector f_evi_list; - - for (QString f_string : f_contents) - { - QStringList sub_contents = f_string.split("&"); - - if (sub_contents.size() < 3) - continue; - - evi_type f_evi; - f_evi.name = sub_contents.at(0); - f_evi.description = sub_contents.at(1); - f_evi.image = sub_contents.at(2); - - f_evi_list.append(f_evi); - } - - w_courtroom->set_evidence_list(f_evi_list); - } - } - else if (header == "IL") - { - if (courtroom_constructed && f_contents.size() > 0) - w_courtroom->set_ip_list(f_contents.at(0)); - } - else if (header == "MU") - { - if (courtroom_constructed && f_contents.size() > 0) - w_courtroom->set_mute(true, f_contents.at(0).toInt()); - } - else if (header == "UM") - { - if (courtroom_constructed && f_contents.size() > 0) - w_courtroom->set_mute(false, f_contents.at(0).toInt()); - } - else if (header == "KK") - { - if (courtroom_constructed && f_contents.size() > 0) - { - int f_cid = w_courtroom->get_cid(); - int remote_cid = f_contents.at(0).toInt(); - - if (f_cid != remote_cid && remote_cid != -1) - goto end; - - call_notice("You have been kicked."); - construct_lobby(); - destruct_courtroom(); - } - - } - else if (header == "KB") - { - if (courtroom_constructed && f_contents.size() > 0) - w_courtroom->set_ban(f_contents.at(0).toInt()); - } - else if (header == "BD") - { - call_notice("You are banned on this server."); - } - else if (header == "ZZ") - { - if (courtroom_constructed && f_contents.size() > 0) - w_courtroom->mod_called(f_contents.at(0)); - } - else if (header == "CL") - { - w_courtroom->handle_clock(f_contents.at(1)); - } - - end: - - delete p_packet; -} - -void AOApplication::send_ms_packet(AOPacket *p_packet) -{ - p_packet->net_encode(); - - QString f_packet = p_packet->to_string(); - - net_manager->ship_ms_packet(f_packet); - - qDebug() << "S(ms):" << f_packet; - - delete p_packet; -} - -void AOApplication::send_server_packet(AOPacket *p_packet, bool encoded) -{ - if (encoded) - p_packet->net_encode(); - - QString f_packet = p_packet->to_string(); - - if (encryption_needed) - { - qDebug() << "S(e):" << f_packet; - - p_packet->encrypt_header(s_decryptor); - f_packet = p_packet->to_string(); - } - else - { - qDebug() << "S:" << f_packet; - } - - net_manager->ship_server_packet(f_packet); - - delete p_packet; -} diff --git a/path_functions.cpp b/path_functions.cpp deleted file mode 100644 index 6e772dbb7..000000000 --- a/path_functions.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include "aoapplication.h" -#include "courtroom.h" -#include "file_functions.h" -#include -#include -#include - -#ifdef BASE_OVERRIDE -#include "base_override.h" -#endif -QString base_path = ""; - -QString AOApplication::get_base_path() -{ - if (base_path == "") - { -#ifdef BASE_OVERRIDE - base_path = base_override; -#elif defined(ANDROID) - QString sdcard_storage = getenv("SECONDARY_STORAGE"); - if (dir_exists(sdcard_storage + "/AO2/")){ - base_path = sdcard_storage + "/AO2/"; - }else{ - QString external_storage = getenv("EXTERNAL_STORAGE"); - base_path = external_storage + "/AO2/"; - } -#else - base_path = QDir::currentPath() + "/base/"; -#endif -} - return base_path; - /* -#ifdef OMNI_DEBUG - return "/media/omnitroid/Data/winshare/AO/client/base/"; -#elif OMNI_DEBUG2 - return "/home/omnitroid/winshare/AO/client/base/"; -#elif defined(OMNI_WIN_DEBUG) - return "E:/AO/client/base/"; -#elif defined(OMNI_WIN_DEBUG2) - return "F:/winshare/AO/client/base/"; -#elif defined(ANDROID) - return "/storage/extSdCard/AO2/"; -#else - return QDir::currentPath() + "/base/"; -#endif -*/ -} - -QString AOApplication::get_data_path() -{ - return get_base_path() + "data/"; -} - -QString AOApplication::get_theme_path() -{ - return get_base_path() + "themes/" + current_theme.toLower() + "/"; -} - -QString AOApplication::get_default_theme_path() -{ - return get_base_path() + "themes/default/"; -} - -QString AOApplication::get_character_path(QString p_character) -{ - return get_base_path() + "characters/" + p_character.toLower() + "/"; -} - -QString AOApplication::get_demothings_path() -{ - QString default_path = "misc/demothings/"; - QString alt_path = "misc/RosterImages"; - if (dir_exists(default_path)) - return get_base_path() + default_path; - else if (dir_exists(alt_path)) - return get_base_path() + alt_path; - else - return get_base_path() + default_path; -} -QString AOApplication::get_sounds_path() -{ - return get_base_path() + "sounds/general/"; -} -QString AOApplication::get_music_path(QString p_song) -{ - return get_base_path() + "sounds/music/" + p_song.toLower(); -} - -QString AOApplication::get_background_path() -{ - if (courtroom_constructed) - return w_courtroom->get_background_path(); - //this function being called when the courtroom isn't constructed makes no sense - return ""; -} - -QString AOApplication::get_default_background_path() -{ - return get_base_path() + "background/gs4/"; -} - -QString AOApplication::get_evidence_path() -{ - QString default_path = "evidence/"; - QString alt_path = "items/"; - if (dir_exists(default_path)) - return get_base_path() + default_path; - else if (dir_exists(alt_path)) - return get_base_path() + alt_path; - else - return get_base_path() + default_path; -} - -QString Courtroom::get_background_path() -{ - return ao_app->get_base_path() + "background/" + current_background.toLower() + "/"; -} - -QString Courtroom::get_default_background_path() -{ - return ao_app->get_base_path() + "background/gs4/"; -} diff --git a/res.qrc b/res.qrc new file mode 100644 index 000000000..814586b27 --- /dev/null +++ b/res.qrc @@ -0,0 +1,10 @@ + + + res/fonts/Ace-Attorney.ttf + res/ui/config_panel.ui + res/git/git_branch.txt + res/git/git_hash.txt + src/drserverinfoeditor.ui + data/sample.avi + + diff --git a/resource/fonts/Ace-Attorney.ttf b/res/fonts/Ace-Attorney.ttf similarity index 100% rename from resource/fonts/Ace-Attorney.ttf rename to res/fonts/Ace-Attorney.ttf diff --git a/res/git/git_branch.txt b/res/git/git_branch.txt new file mode 100644 index 000000000..e69de29bb diff --git a/res/git/git_hash.txt b/res/git/git_hash.txt new file mode 100644 index 000000000..e69de29bb diff --git a/res/ui/config_panel.ui b/res/ui/config_panel.ui new file mode 100644 index 000000000..b78bf2e23 --- /dev/null +++ b/res/ui/config_panel.ui @@ -0,0 +1,1629 @@ + + + Form + + + + 0 + 0 + 487 + 514 + + + + + 0 + 0 + + + + + 487 + 0 + + + + Form + + + + QLayout::SetMaximumSize + + + + + 1 + + + + General + + + + + + User + + + + + + Name: + + + + + + + Your username + + + + + + + Callwords: + + + true + + + + + + + Separate words by space + + + + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Enable Discord presence.</p><p><span style=" color:#0000ff;">[NOTE] </span>Changes may take up to 15 seconds to be reflected in your Discord profile.</p></body></html> + + + Discord Presence + + + true + + + false + + + + + + Hide Server + + + + + + + Hide Character + + + + + + + + + + Server Advertiser + + + + + + Address: + + + + + + + https://serveraddress.addr/ + + + + + + + + + + + + + 0 + 0 + + + + Server alerts + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Reset Notifications + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Audio + + + + + + + + Device: + + + + + + + + + + Master: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + System: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Music: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Effects: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Blips: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Video: + + + + + + + + + + 0 + 0 + + + + + 0 + 20 + + + + 100 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + <html><head/><body><p>If enabled, audio will be temporarily suppressed when the client is not in focus.<br/><br/>This option is never applied to the System audio channel.</p></body></html> + + + Suppress Background Audio + + + true + + + false + + + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<span style=" color:#0000ff;">Suppress Background Audio</span>] is enabled and activated.</p></body></html> + + + Ignore Music + + + + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + + + Ignore Blips + + + + + + + false + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + + + Ignore System + + + true + + + true + + + + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<font style=" color:#0000ff;">Suppress Background Audio</font>] is enabled and activated.</p></body></html> + + + Ignore Effects + + + + + + + <html><head/><body><p>If enabled, the audio channel will not be suppressed when the option [<span style=" color:#0000ff;">Suppress Background Audio</span>] is enabled and activated.</p></body></html> + + + Ignore Video + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Reload + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Audiotracks: + + + + + + + + Performance + + + + + + Max Memory + + + + + + + Caching + + + + + + Characters + + + + + + + Backgrounds + + + + + + + Shouts + + + + + + + Effects + + + + + + + Graphical User Interface + + + GUI + + + + + + + Stickers + + + + + + + + + + Preload Threshold + + + + + + + + + <html><head/><body><p>Determines how much preloading progress must have reached before it is deemed acceptable to start playback of animated sprites.</p></body></html> + + + 100 + + + 50 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 25 + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 50% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Loading Bar Delay + + + + + + + + + <html><head/><body><p>Determines the delay before the client prompts the loading bar symbol.</p></body></html> + + + 2000 + + + 500 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 250 + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 500ms + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Vertical + + + + 444 + 163 + + + + + + + + + + <html><head/><body><p>Determines the maximum amount of system memory the client may use for caching. Before loading an animation, given that the animation would exceed the threshold provided the animation will instead preserve memory.<br/><br/><span style=" font-weight:700;">Example</span><br/>System memory is at 50%.<br/>Threshold is at 51%.<br/>The next animation if cached would bump total usage of system memory to 55%. The threshold being exceeded means the animation system will instead attempt to preserve memory. </p></body></html> + + + 10 + + + 80 + + + 50 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 10 + + + + + + + + 40 + 0 + + + + + 40 + 0 + + + + 50% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Theme + + + + + + + + + 0 + 0 + + + + Manual + + + + + + + + 0 + 0 + + + + Time of day: + + + + + + + + 0 + 0 + + + + Folder: + + + + + + + + 0 + 0 + + + + Gamemode: + + + + + + + + + false + + + + true + + + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> time of day.</p></body></html> + + + <default> + + + false + + + true + + + + + + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> time of day.</p></body></html> + + + + + + + + + + + false + + + + true + + + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">currently active</span> gamemode.</p></body></html> + + + <default> + + + false + + + true + + + + + + + true + + + <html><head/><body><p>The <span style=" font-weight:600; color:#0000ff;">current manually selected</span> gamemode.</p></body></html> + + + + + + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Allows you to manually switch to a gamemode interface.</p></body></html> + + + Manual + + + + + + + + + + 0 + 0 + + + + Switch + + + + + + + + 0 + 0 + + + + Reload + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Game + + + + + + Character + + + + + + Name: + + + + + + + The name of your character + + + + + + + + + Character options + + + + + + Searchable iniswap + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 0 + 121 + + + + Emote options + + + + + 12 + 87 + 78 + 22 + + + + Sticky SFX + + + + + + 12 + 31 + 141 + 22 + + + + Emote preview tooltip + + + + + + 12 + 59 + 113 + 22 + + + + + 0 + 0 + + + + Always-on Anim + + + false + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p>Reload the currently selected character. If a character is not selected, nothing happens.</p></body></html> + + + Reload + + + + + + + + + + + + Message + + + + + + Warning threshold: + + + + + + + + + <html><head/><body><p>When the currently inputted in-game message length reaches at or above the defined threshold a counter will be shown indicating the number of available characters left.</p></body></html> + + + 100 + + + 70 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 70% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + 0 + 101 + + + + Dialog + + + + + + Chat-tick interval: + + + + + + + ms + + + 0 + + + 999 + + + + + + + Blip Rate: + + + + + + + + + 1 + + + 1000000000 + + + 1000000000 + + + + + + + + 0 + 0 + + + + Blanks + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Log + + + + + + + + Max length: + + + + + + + lines + + + 1 + + + 10000 + + + 200 + + + + + + + + + Options + + + + + + + 0 + 0 + + + + Display timestamp + + + + + + + A client identifier allows you to identify users with identical names. + + + Display client identifier + + + + + + + + 0 + 0 + + + + Display empty messages + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Extra newlines are added between the name and message.</p></body></html> + + + Format use newline + + + + + + + + 0 + 0 + + + + Display music switch + + + + + + + Display self-identification highlight + + + + + + + + + + + 0 + 0 + + + + Orientation + + + + + + Top-down + + + + + + + Bottom-up + + + + + + + + + + + + Save to disk: + + + + + + + + 0 + 0 + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + About + + + + + 80 + 20 + 351 + 271 + + + + This is where the about text appears. If you see this message, something's gone wrong. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + + + + + + Autosave + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Save + + + + + + + &Close + + + + + + + + + + diff --git a/resources.qrc b/resources.qrc deleted file mode 100644 index d0c2f22b4..000000000 --- a/resources.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - resource/fonts/Ace-Attorney.ttf - - diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp new file mode 100644 index 000000000..1c2b322a1 --- /dev/null +++ b/src/aoapplication.cpp @@ -0,0 +1,484 @@ +#include "aoapplication.h" + +#include "aoconfig.h" +#include "aoconfigpanel.h" +#include "courtroom.h" +#include "debug_functions.h" +#include "drdiscord.h" +#include "drpacket.h" +#include "drpather.h" +#include "drserversocket.h" +#include "file_functions.h" +#include "lobby.h" +#include "theme.h" +#include "version.h" + +#include +#include +#include +#include + +AOApplication::AOApplication(int &argc, char **argv) + : QApplication(argc, argv) +{ + ao_config = new AOConfig(this); + + ao_config_panel = new AOConfigPanel(this); + + dr_discord = new DRDiscord(this); + + m_server_socket = new DRServerSocket(this); + + connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(gamemode_changed(QString)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(timeofday_changed(QString)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(manual_gamemode_changed(QString)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(manual_gamemode_selection_changed(bool)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(manual_timeofday_changed(QString)), this, SLOT(handle_theme_modification())); + connect(ao_config, SIGNAL(manual_timeofday_selection_changed(bool)), this, SLOT(handle_theme_modification())); + connect(ao_config_panel, SIGNAL(reload_theme()), this, SLOT(handle_theme_modification())); + connect(ao_config_panel, SIGNAL(reload_character()), this, SLOT(handle_character_reloading())); + connect(ao_config_panel, SIGNAL(reload_audiotracks()), this, SLOT(handle_audiotracks_reloading())); + ao_config_panel->hide(); + + dr_discord->set_presence(ao_config->discord_presence()); + dr_discord->set_hide_server(ao_config->discord_hide_server()); + dr_discord->set_hide_character(ao_config->discord_hide_character()); + connect(ao_config, SIGNAL(discord_presence_changed(bool)), dr_discord, SLOT(set_presence(bool))); + connect(ao_config, SIGNAL(discord_hide_server_changed(bool)), dr_discord, SLOT(set_hide_server(bool))); + connect(ao_config, SIGNAL(discord_hide_character_changed(bool)), dr_discord, SLOT(set_hide_character(bool))); + + connect(m_server_socket, &DRServerSocket::connection_state_changed, this, &AOApplication::_p_handle_server_state_update); + connect(m_server_socket, SIGNAL(packet_received(DRPacket)), this, SLOT(_p_handle_server_packet(DRPacket))); + + resolve_current_theme(); +} + +AOApplication::~AOApplication() +{ + qInfo() << "Closing Danganronpa Online..."; + destruct_lobby(); + destruct_courtroom(); +} + +int AOApplication::get_client_id() const +{ + return m_client_id; +} + +void AOApplication::set_client_id(int id) +{ + m_client_id = id; +} + +void AOApplication::leave_server() +{ + m_server_status = NotConnected; + m_server_socket->disconnect_from_server(); +} + +Lobby *AOApplication::get_lobby() const +{ + return m_lobby; +} + +void AOApplication::construct_lobby() +{ + if (is_lobby_constructed) + { + qDebug() << "W: lobby was attempted constructed when it already exists"; + return; + } + + m_lobby = new Lobby(this); + is_lobby_constructed = true; + center_widget_to_screen(m_lobby); + m_lobby->show(); + + dr_discord->set_state(DRDiscord::State::Idle); + dr_discord->clear_server_name(); +} + +void AOApplication::destruct_lobby() +{ + if (!is_lobby_constructed) + { + qDebug() << "W: lobby was attempted destructed when it did not exist"; + return; + } + + delete m_lobby; + is_lobby_constructed = false; +} + +Courtroom *AOApplication::get_courtroom() const +{ + return m_courtroom; +} + +void AOApplication::construct_courtroom() +{ + if (is_courtroom_constructed) + { + qDebug() << "W: courtroom was attempted constructed when it already exists"; + return; + } + + m_courtroom = new Courtroom(this); + connect(m_courtroom, SIGNAL(closing()), this, SLOT(on_courtroom_closing())); + connect(m_courtroom, SIGNAL(destroyed()), this, SLOT(on_courtroom_destroyed())); + is_courtroom_constructed = true; + center_widget_to_screen(m_courtroom); +} + +void AOApplication::destruct_courtroom() +{ + // destruct courtroom + if (is_courtroom_constructed) + { + delete m_courtroom; + is_courtroom_constructed = false; + ao_config->set_gamemode(nullptr); + ao_config->set_timeofday(nullptr); + } + else + { + qDebug() << "W: courtroom was attempted destructed when it did not exist"; + } +} + +DRDiscord *AOApplication::get_discord() const +{ + return dr_discord; +} + +VersionNumber AOApplication::get_server_client_version() const +{ + return m_server_client_version; +} + +VersionStatus AOApplication::get_server_client_version_status() const +{ + return m_server_client_version_status; +} + +bool AOApplication::is_server_client_version_compatible() const +{ + return m_server_client_version_status == VersionStatus::Ok; +} + +void AOApplication::handle_theme_modification() +{ + load_fonts(); + emit reload_theme(); +} + +void AOApplication::handle_character_reloading() +{ + emit reload_character(); +} + +void AOApplication::handle_audiotracks_reloading() +{ + emit reload_audiotracks(); +} + +QString AOApplication::get_sfx_dir_path() +{ + return get_base_path() + "sounds/general"; +} + +QString AOApplication::get_sfx_path(QString p_sfx) +{ + return find_asset_path(get_sfx_dir_path() + "/" + p_sfx); +} + +QString AOApplication::get_sfx_noext_path(QString p_file) +{ + return find_asset_path(get_sfx_dir_path() + "/" + p_file, audio_extensions()); +} + +QString AOApplication::get_ambient_sfx_path(QString p_file) +{ + return find_asset_path(get_base_path() + "sounds/ambient/" + p_file); +} + +QString AOApplication::get_character_sprite_path(QString p_character, QString p_emote, QString p_prefix, bool p_use_placeholder) +{ + bool l_valid = true; + const QStringList l_blacklist{ + "char_icon.png", + "showname.png", + "emotions", + }; + for (const QString &i_black : l_blacklist) + { + if (p_emote.startsWith(i_black, Qt::CaseInsensitive)) + { + l_valid = false; + break; + } + } + + QStringList l_file_name_list; + for (const QString &i_extension : animated_or_static_extensions()) + { + if (!p_prefix.isEmpty()) + { + l_file_name_list.append(p_prefix + p_emote + i_extension); + } + l_file_name_list.append(p_emote + i_extension); + } + + QString l_file_path; + if (l_valid) + { + QStringList l_file_path_list; + for (const QString &i_chr_name : get_char_include_tree(p_character)) + { + for (const QString &i_file_name : qAsConst(l_file_name_list)) + { + l_file_path_list.append(get_character_path(i_chr_name, i_file_name)); + } + } + + for (const QString &i_file_path : qAsConst(l_file_path_list)) + { + const QString l_resolved_file_path = find_asset_path(i_file_path); + if (!l_resolved_file_path.isEmpty()) + { + l_file_path = l_resolved_file_path; + break; + } + } + } + + if (l_file_path.isEmpty() && p_use_placeholder) + { + l_file_path = find_theme_asset_path("placeholder", animated_extensions()); + } + + if (l_file_path.isEmpty()) + { + qWarning() << "error: character animation not found" + << "character:" << p_character << "emote:" << p_emote << "prefix:" << p_prefix; + } + + return l_file_path; +} + +QString AOApplication::get_character_sprite_pre_path(QString character, QString emote) +{ + return get_character_sprite_path(character, emote, QString{}, false); +} + +QString AOApplication::get_character_sprite_idle_path(QString character, QString emote) +{ + return get_character_sprite_path(character, emote, "(a)", true); +} + +QString AOApplication::get_character_sprite_talk_path(QString character, QString emote) +{ + return get_character_sprite_path(character, emote, "(b)", true); +} + +QString AOApplication::get_background_sprite_path(QString p_background_name, QString p_image) +{ + const QString l_target_filename = find_asset_path(get_background_path(p_background_name) + "/" + p_image); + return l_target_filename; +} + +QString AOApplication::get_background_sprite_noext_path(QString background, QString image) +{ + return find_asset_path(get_background_path(background) + "/" + image, animated_or_static_extensions()); +} + +QString AOApplication::get_shout_sprite_path(QString p_character, QString p_shout) +{ + QStringList l_filepath_list{ + get_character_path(p_character, p_shout), + get_character_path(p_character, p_shout + "_bubble"), + }; + + QString l_filename = find_asset_path(l_filepath_list, animated_extensions()); + if (l_filename.isEmpty()) + { + l_filename = find_theme_asset_path(p_shout, animated_extensions()); + } + + if (l_filename.isEmpty()) + { + qWarning() << "error: shout not found" + << "character:" << p_character << "shout:" << p_shout; + } + + return l_filename; +} + +QString AOApplication::get_theme_sprite_path(QString p_file_name, QString p_character) +{ + QString l_file_path; + if (!p_character.isEmpty()) + { + QString l_character_file_name = p_file_name; + if (l_character_file_name == "custom") + { + l_character_file_name.append("_bubble"); + } + + QStringList l_path_list{ + get_character_path(p_character, l_character_file_name), + get_character_path(p_character, "overlay/" + l_character_file_name), + }; + l_file_path = find_asset_path(l_path_list, animated_or_static_extensions()); + } + + if (l_file_path.isEmpty()) + { + l_file_path = find_theme_asset_path(p_file_name, animated_or_static_extensions()); + if (l_file_path.isEmpty()) + { + l_file_path = find_theme_asset_path("placeholder", animated_or_static_extensions()); + } + } + + return l_file_path; +} + +QString AOApplication::get_theme_sprite_path(QString file_name) +{ + return get_theme_sprite_path(file_name, QString{}); +} + +QString AOApplication::get_current_char() +{ + if (!is_courtroom_constructed) + return nullptr; + return m_courtroom->get_character_ini(); +} + +/** + * @brief Check the path for various known exploits. + * + * In order: + * - Directory traversal (most commonly: "../" jumps) + * @param p_file The path to check. + * @return A sanitized path. If any check fails, the path returned is an empty string. The sanitized path does not + * necessarily exist. + */ +bool AOApplication::is_safe_path(QString p_file) +{ + if (!p_file.contains("..")) + return true; + const QStringList l_item_list = p_file.split(QRegularExpression("[\\/]")); + for (auto it = l_item_list.crbegin(); it != l_item_list.crend(); ++it) + if (*it == "..") + return false; + return true; +} + +void AOApplication::toggle_config_panel() +{ + ao_config_panel->setVisible(!ao_config_panel->isVisible()); + if (ao_config_panel->isVisible()) + { + ao_config_panel->setFocus(); + ao_config_panel->raise(); + center_widget_to_screen(ao_config_panel); + } +} + +bool AOApplication::get_first_person_enabled() +{ + return ao_config->get_bool("first_person", false); +} + +void AOApplication::load_fonts() +{ + QFontDatabase l_database; + for (const QFileInfo &fileInfo : QDir(get_case_sensitive_path(get_base_path() + "fonts")).entryInfoList()) + l_database.addApplicationFont(fileInfo.absoluteFilePath()); +} + +void AOApplication::loading_cancelled() +{ + destruct_courtroom(); + + m_lobby->hide_loading_overlay(); +} + +void AOApplication::on_courtroom_closing() +{ + ao_config_panel->hide(); +} + +void AOApplication::on_courtroom_destroyed() +{ + ao_config_panel->hide(); +} + +void AOApplication::resolve_current_theme() +{ + const QString l_theme_dir = get_case_sensitive_path(get_base_file_path("themes")); + if (l_theme_dir.isEmpty()) + { + call_warning("It doesn't look like your client is set up correctly. This can be " + "due to the following reasons: \n" + "1. Check you downloaded and extracted the resources correctly from " + "the DRO Discord including the large 'base' folder.\n" + "2. If you did, check that the base folder is in the same folder " + "where you launched Danganronpa Online from: " + + DRPather::get_application_path() + + "\n" + "3. If it is there, check that your current theme folder exists in " + "base/themes. "); + } + + const QString l_current_theme = ao_config->theme(); + std::optional l_target_theme; + const QList l_info_list = QDir(l_theme_dir).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QFileInfo &i_info : l_info_list) + { + const QString l_theme = i_info.fileName(); + if (l_theme == l_current_theme) + { + l_target_theme.reset(); + break; + } + + // target theme is always the first + if (!l_target_theme.has_value()) + { + l_target_theme = l_theme; + } + } + + if (l_target_theme.has_value()) + { + if (!ao_config->first_launch()) + { + call_warning(tr("Your previous theme [%1] is missing; the current theme has been reset.\n\nTo select a different theme, go to the config panel.").arg(l_current_theme)); + } + ao_config->set_theme(l_target_theme.value()); + } +} + +bool AOApplication::notify(QObject *receiver, QEvent *event) +{ + bool l_done = true; + + try + { + l_done = QApplication::notify(receiver, event); + } + catch (const std::exception &e) + { + qCritical() << "Caught exception:" << e.what(); + } + catch (...) + { + qFatal("Caught UNKNOWN exception. Cannot recover."); + } + + return l_done; +} diff --git a/src/aoapplication.h b/src/aoapplication.h new file mode 100644 index 000000000..25eb4c4c2 --- /dev/null +++ b/src/aoapplication.h @@ -0,0 +1,264 @@ +#ifndef AOAPPLICATION_H +#define AOAPPLICATION_H + +#include "datatypes.h" +#include "drpacket.h" +#include "drserversocket.h" + +class AOConfig; +class AOConfigPanel; +class Courtroom; +class DRDiscord; +class DRMasterClient; +class Lobby; + +#include +#include + +#include + +class AOApplication : public QApplication +{ + Q_OBJECT + +public: + enum ServerStatus + { + NotConnected, + Connecting, + Connected, + Joined, + TimedOut, + Disconnected, + }; + + AOApplication(int &argc, char **argv); + ~AOApplication(); + + int get_client_id() const; + void set_client_id(int id); + + void leave_server(); + void connect_to_server(DRServerInfo server); + void send_server_packet(DRPacket packet); + ServerStatus last_server_status(); + bool joined_server(); + + Lobby *get_lobby() const; + void construct_lobby(); + void destruct_lobby(); + + Courtroom *get_courtroom() const; + void construct_courtroom(); + void destruct_courtroom(); + + DRDiscord *get_discord() const; + + VersionNumber get_server_client_version() const; + VersionStatus get_server_client_version_status() const; + bool is_server_client_version_compatible() const; + + /////////////////////////////////////////// + + // Returns the character the player has currently selected + QString get_current_char(); + + // implementation in path_functions.cpp + QString get_base_path(); + QString get_base_file_path(QString file); + QString get_character_folder_path(QString character); + QString get_character_path(QString p_character, QString p_file); + // QString get_demothings_path(); + QString get_music_folder_path(); + QString get_music_path(QString p_song); + + QString get_background_path(QString p_background_name); + QString get_background_dir_path(QString p_identifier); + + bool is_safe_path(QString p_file); + + QString find_asset_path(QStringList file_list, QStringList extension_list); + QString find_asset_path(QStringList file_list); + QString find_asset_path(QString p_file, QStringList p_extension_list); + QString find_asset_path(QString p_file); + QString find_theme_asset_path(QString file, QStringList extension_list); + QString find_theme_asset_path(QString file); + + QString get_case_sensitive_path(QString p_file); + + ////// Functions for accessing the config panel ////// + void toggle_config_panel(); + + ////// Functions for reading and writing files ////// + // Implementations file_functions.cpp + + // Returns text from note file + QString read_note(QString filename); + + // returns a list of call words + QStringList get_callwords(); + + // returns whatever the client should simulate first person dialog + bool get_first_person_enabled(); + + // TODO document what this does + QStringList get_sfx_list(); + + // Writes to note file + void write_note(QString p_text, QString filename); + + // appends to note file + void append_note(QString p_line, QString filename); + + QString read_ini(QString p_identifier, QString p_path); + + QString read_theme_ini(QString p_identifier, QString p_file); + + bool read_theme_ini_bool(QString p_identifier, QString p_file); + + int read_theme_ini_int(QString p_identifier, QString p_file); + + // Returns the coordinates of widget with p_identifier from p_file + QPoint get_button_spacing(QString p_identifier, QString p_file); + + // Returns the dimensions of widget with specified identifier from p_file + pos_size_type get_element_dimensions(QString p_identifier, QString p_file); + + // Returns the value of font property p_identifier from p_file + int get_font_property(QString p_identifier, QString p_file); + + // Returns the name of the font with p_identifier from p_file + QString get_font_name(QString p_identifier, QString p_file); + + // Returns the color with p_identifier from p_file + std::optional maybe_color(QString p_identifier, QString p_file); + + // Returns the color with p_identifier from p_file + QColor get_color(QString p_identifier, QString p_file); + + // Returns the sfx with p_identifier from sounds.ini in the current theme path + QString get_sfx(QString p_identifier); + + // Returns the value of p_search_line within target_tag and terminator_tag + + QVariant read_char_ini(QString character, QString group, QString key, QVariant default_value); + QVariant read_char_ini(QString character, QString group, QString key); + + // Returns the text between target_tag and terminator_tag in p_file + QString get_stylesheet(QString target_tag, QString p_file); + + // Returns string list (color name to color HEX) + QMap get_chatmessage_colors(); + + // Returns string list (highlight character to color name) + QVector get_highlight_colors(); + + // Returns special button on cc_config according to index + QString get_spbutton(QString p_tag, int index); + + // Returns effect on cc_config according to index + QStringList get_effect(int index); + + // Returns the side of the p_char character from that characters ini file + QString get_char_side(QString p_char); + + // Returns the showname from the ini of p_char + QString get_showname(QString p_char); + + // Returns the value of chat from the specific p_char's ini file + QString get_chat(QString p_char); + + // Returns the name of p_char + QString get_char_name(QString p_char); + + QStringList get_char_include(QString character); + + QStringList get_char_include_tree(QString character); + + // Returns p_char's gender + QString get_gender(QString p_char); + + QVector get_emote_list(QString p_chr); + + // Returns x,y offset for effect p_effect + QStringList get_effect_offset(QString p_char, int p_effect); + + // Returns overlay at p_effect in char_path/overlay + QStringList get_overlay(QString p_char, int p_effect); + + ////// Functions for fonts handling ////// + void load_fonts(); + +public slots: + void loading_cancelled(); + +signals: + void reload_theme(); + void reload_character(); + void reload_audiotracks(); + void server_status_changed(ServerStatus); + +private: + AOConfig *ao_config = nullptr; + AOConfigPanel *ao_config_panel = nullptr; + DRDiscord *dr_discord = nullptr; + + DRServerSocket *m_server_socket = nullptr; + ServerStatus m_server_status = NotConnected; + + Lobby *m_lobby = nullptr; + bool is_lobby_constructed = false; + + Courtroom *m_courtroom = nullptr; + bool is_courtroom_constructed = false; + + ///////////////server metadata//////////////// + + VersionNumber m_server_client_version; + VersionStatus m_server_client_version_status = VersionStatus::Ok; + + ///////////////loading info/////////////////// + // player number, it's hardly used but might be needed for some old servers + int m_client_id = 0; + + QString m_server_software; + + int m_character_count = 0; + int m_loaded_characters = 0; + int m_evidence_count = 0; + int m_loaded_evidence = 0; + int m_music_count = 0; + int m_loaded_music = 0; + bool m_loaded_music_list = false; + bool m_loaded_area_list = false; + +private slots: + void _p_handle_server_state_update(DRServerSocket::ConnectionState); + void _p_handle_server_packet(DRPacket); + void on_courtroom_closing(); + void on_courtroom_destroyed(); + void resolve_current_theme(); + void handle_theme_modification(); + void handle_character_reloading(); + void handle_audiotracks_reloading(); + +public: + QString get_sfx_dir_path(); + QString get_sfx_path(QString sfx); + QString get_sfx_noext_path(QString p_file); + QString get_ambient_sfx_path(QString p_file); + QString get_character_sprite_path(QString character, QString emote, QString prefix, bool use_placeholder); + QString get_character_sprite_pre_path(QString character, QString emote); + QString get_character_sprite_idle_path(QString character, QString emote); + QString get_character_sprite_talk_path(QString character, QString emote); + QString get_background_sprite_path(QString background, QString image); + QString get_background_sprite_noext_path(QString background, QString image); + QString get_shout_sprite_path(QString character, QString shout); + QString get_theme_sprite_path(QString file_name, QString character); + QString get_theme_sprite_path(QString file_name); + +public: + bool notify(QObject *receiver, QEvent *event) override; +}; + +#endif // AOAPPLICATION_H diff --git a/src/aoblipplayer.cpp b/src/aoblipplayer.cpp new file mode 100644 index 000000000..397ccf12c --- /dev/null +++ b/src/aoblipplayer.cpp @@ -0,0 +1,28 @@ +#include "aoblipplayer.h" + +#include "aoapplication.h" + +const int AOBlipPlayer::BLIP_COUNT = 5; + +AOBlipPlayer::AOBlipPlayer(AOApplication *p_ao_app, QObject *p_parent) + : AOObject(p_ao_app, p_parent) +{ + m_family = DRAudioEngine::get_family(DRAudio::Family::FBlip); + m_family->set_capacity(BLIP_COUNT); +} + +void AOBlipPlayer::set_blips(QString p_blip) +{ + if (m_name.has_value() && m_name.value() == p_blip) + return; + + m_name = p_blip; + m_file = ao_app->get_sfx_noext_path(m_name.value()); +} + +void AOBlipPlayer::blip_tick() +{ + if (!m_file.has_value()) + return; + m_family->play_stream(m_file.value()); +} diff --git a/src/aoblipplayer.h b/src/aoblipplayer.h new file mode 100644 index 000000000..584983558 --- /dev/null +++ b/src/aoblipplayer.h @@ -0,0 +1,29 @@ +#pragma once + +#include "aoobject.h" +#include "draudioengine.h" + +// 3rd +#include + +// std +#include + +class AOBlipPlayer : public AOObject +{ + Q_OBJECT + +public: + static const int BLIP_COUNT; + + AOBlipPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); + +public slots: + void set_blips(QString p_sfx); + void blip_tick(); + +private: + DRAudioStreamFamily::ptr m_family; + std::optional m_name; + std::optional m_file; +}; diff --git a/src/aobutton.cpp b/src/aobutton.cpp new file mode 100644 index 000000000..d50d3f65a --- /dev/null +++ b/src/aobutton.cpp @@ -0,0 +1,54 @@ +#include "aobutton.h" + +#include "aoapplication.h" +#include "debug_functions.h" +#include "file_functions.h" + +#include + +AOButton::AOButton(QWidget *parent, AOApplication *p_ao_app) + : QPushButton(parent) +{ + ao_app = p_ao_app; +} + +QString AOButton::get_image() +{ + return m_image; +} + +bool AOButton::has_image() +{ + return (!m_image.isEmpty()); +} + +void AOButton::set_image(QString p_image) +{ + m_image_stem = p_image; + m_image = ao_app->find_theme_asset_path(p_image); + + // Get the path of the found image without the extension + const QString l_image_name = p_image.left(p_image.lastIndexOf(QChar('.'))); + QString l_hover_image = ao_app->find_theme_asset_path(l_image_name + "_hover.png"); + if (l_hover_image.isEmpty()) + { + l_hover_image = m_image; + } + + setStyleSheet("QPushButton {border-image:url(\"" + m_image + + "\");}" + "QPushButton:hover {border-image:url(\"" + + l_hover_image + "\");}"); + setText(m_image.isEmpty() ? m_text : nullptr); +} + +void AOButton::set_image_and_text(QString p_image, QString p_text) +{ + m_text = p_text; + set_image(p_image); +} + +void AOButton::refresh_image() +{ + set_image(m_image_stem); +} diff --git a/aobutton.h b/src/aobutton.h similarity index 55% rename from aobutton.h rename to src/aobutton.h index 23e6311ca..6b6443512 100644 --- a/aobutton.h +++ b/src/aobutton.h @@ -1,7 +1,7 @@ #ifndef AOBUTTON_H #define AOBUTTON_H -#include "aoapplication.h" +class AOApplication; #include @@ -11,11 +11,18 @@ class AOButton : public QPushButton public: AOButton(QWidget *parent, AOApplication *p_ao_app); - ~AOButton(); - - AOApplication *ao_app = nullptr; + QString get_image(); + bool has_image(); void set_image(QString p_image); + void set_image_and_text(QString p_image, QString p_text); + void refresh_image(); + +private: + AOApplication *ao_app = nullptr; + QString m_image; + QString m_image_stem; + QString m_text; }; #endif // AOBUTTON_H diff --git a/src/aocharbutton.cpp b/src/aocharbutton.cpp new file mode 100644 index 000000000..4d0e83698 --- /dev/null +++ b/src/aocharbutton.cpp @@ -0,0 +1,69 @@ +#include "aocharbutton.h" + +#include "aoapplication.h" +#include "aoimagedisplay.h" +#include "file_functions.h" + +#include +#include + +AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos) + : QPushButton(parent) +{ + ao_app = p_ao_app; + + this->resize(60, 60); + this->move(x_pos, y_pos); + + ui_character = new QLabel(this); + ui_character->setAttribute(Qt::WA_TransparentForMouseEvents); + ui_character->resize(30, 30); + ui_character->move(28, 28); + + ui_taken = new AOImageDisplay(this, ao_app); + ui_taken->resize(60, 60); + ui_taken->set_theme_image("char_taken.png"); + ui_taken->setAttribute(Qt::WA_TransparentForMouseEvents); + ui_taken->hide(); +} + +QString AOCharButton::character() +{ + return m_character; +} + +void AOCharButton::set_character(QString p_character, QString p_character_ini) +{ + m_character = p_character; + const QString l_icon_path = ao_app->get_character_path(m_character, "char_icon.png"); + const bool l_file_exist = file_exists(l_icon_path); + setStyleSheet(l_file_exist ? QString("AOCharButton { border-image: url(\"%1\"); }").arg(l_icon_path) : nullptr); + const QString l_final_character = QString(m_character).replace("&", "&&"); + setText(l_file_exist ? nullptr : l_final_character); + + const bool l_is_different_chr = m_character != p_character_ini; + if (l_is_different_chr) + ui_character->setStyleSheet( + QString("border-image: url(\"%1\");").arg(ao_app->get_character_path(p_character_ini, "char_icon.png"))); + ui_character->setVisible(l_is_different_chr); + setToolTip(l_is_different_chr ? QString("%1 as %2").arg(m_character, QString(p_character_ini).replace("&", "&&")) + : l_final_character); +} + +void AOCharButton::set_taken(const bool p_enabled) +{ + ui_taken->setVisible(p_enabled); +} + +void AOCharButton::enterEvent(QEvent *e) +{ + setFlat(false); + QPushButton::enterEvent(e); + Q_EMIT mouse_entered(this); +} + +void AOCharButton::leaveEvent(QEvent *e) +{ + QPushButton::leaveEvent(e); + Q_EMIT mouse_left(); +} diff --git a/aocharbutton.h b/src/aocharbutton.h similarity index 51% rename from aocharbutton.h rename to src/aocharbutton.h index a7071cb7d..b491651c7 100644 --- a/aocharbutton.h +++ b/src/aocharbutton.h @@ -1,12 +1,12 @@ #ifndef AOCHARBUTTON_H #define AOCHARBUTTON_H -#include "aoapplication.h" +class AOApplication; +class AOImageDisplay; #include -#include -#include -#include "aoimage.h" + +class QLabel; class AOCharButton : public QPushButton { @@ -15,22 +15,25 @@ class AOCharButton : public QPushButton public: AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, int y_pos); - AOApplication *ao_app = nullptr; - - void reset(); - void set_taken(); - void set_passworded(); + QString character(); + void set_character(QString character, QString ini_character); + void set_taken(const bool); - void set_image(QString p_character); - -private: - AOImage *ui_taken; - AOImage *ui_passworded; - AOImage *ui_selector; +signals: + void mouse_entered(AOCharButton *p_caller); + void mouse_left(); protected: void enterEvent(QEvent *e); void leaveEvent(QEvent *e); + +private: + AOApplication *ao_app = nullptr; + + QString m_character; + + QLabel *ui_character = nullptr; + AOImageDisplay *ui_taken = nullptr; }; #endif // AOCHARBUTTON_H diff --git a/src/aoconfig.cpp b/src/aoconfig.cpp new file mode 100644 index 000000000..a2f8b85b1 --- /dev/null +++ b/src/aoconfig.cpp @@ -0,0 +1,1172 @@ +#include "aoconfig.h" + +#include "commondefs.h" +#include "datatypes.h" +#include "draudioengine.h" +#include "drpather.h" +#include "mk2/spritedynamicreader.h" + +// qt +#include +#include +#include +#include +#include +#include +#include +#include + +/*! + We have to suffer through a lot of boilerplate code + but hey, when has ao2 ever cared? + Wait, am I using the term wrong? Nice. +*/ +class AOConfigPrivate : public QObject +{ + Q_OBJECT + +public: + AOConfigPrivate(); + ~AOConfigPrivate(); + + // setters +public slots: + void load_file(); + void save_file(); + +private: + void invoke_signal(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr), QGenericArgument p_arg2 = QGenericArgument()); + void update_favorite_device(); + +private slots: + void on_application_state_changed(Qt::ApplicationState p_state); + +private: + friend class AOConfig; + + QSettings cfg; + // hate me more + QVector children; + + // data + bool first_launch; + bool autosave; + QStringList notification_filter; + QString username; + QString callwords; + QString server_advertiser; + bool server_alerts; + bool discord_presence = false; + bool discord_hide_server = false; + bool discord_hide_character = false; + QString theme; + QString gamemode; + QString manual_gamemode; + bool manual_gamemode_selection; + QString timeofday; + QString manual_timeofday; + bool manual_timeofday_selection; + QString showname; + QString showname_placeholder; + QMap ini_map; + bool searchable_iniswap; + bool always_pre; + int chat_tick_interval; + bool emote_preview; + bool sticky_sfx; + int message_length_threshold; + int log_max_lines; + bool log_display_timestamp; + bool log_display_client_id; + bool log_display_self_highlight; + bool log_display_empty_messages; + bool log_is_topdown; + bool log_format_use_newline; + bool log_display_music_switch; + bool log_is_recording; + + // performance + int system_memory_threshold; + QMap sprite_caching; + int loading_bar_delay; + int caching_threshold; + + // audio + std::optional favorite_device_driver; + int master_volume; + bool suppress_background_audio; + int system_volume; + int effect_volume; + bool effect_ignore_suppression; + int music_volume; + bool music_ignore_suppression; + int video_volume; + bool video_ignore_suppression; + int blip_volume; + bool blip_ignore_suppression; + int blip_rate; + bool blank_blips; + + // audio sync + DRAudioEngine *audio_engine = nullptr; +}; + +AOConfigPrivate::AOConfigPrivate() + : QObject(nullptr) + , cfg(DRPather::get_application_path() + BASE_CONFIG_INI, QSettings::IniFormat) + , audio_engine(new DRAudioEngine(this)) +{ + Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); + connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(on_application_state_changed(Qt::ApplicationState))); + + load_file(); +} + +AOConfigPrivate::~AOConfigPrivate() +{} + +void AOConfigPrivate::load_file() +{ + first_launch = cfg.value("first_launch", true).toBool(); + + if (first_launch) + { + cfg.setValue("first_launch", false); + cfg.sync(); + } + + autosave = cfg.value("autosave", true).toBool(); + + { // notifications + notification_filter.clear(); + cfg.beginGroup("notifications"); + for (const QString &i_key : cfg.childKeys()) + { + const QString l_message = cfg.value(i_key).toString(); + if (l_message.isEmpty()) + continue; + notification_filter.append(l_message); + } + cfg.endGroup(); + } + + username = cfg.value("username").toString(); + showname = cfg.value("showname").toString(); + callwords = cfg.value("callwords").toString(); + server_advertiser = cfg.value("server_advertiser", "https://servers.aceattorneyonline.com").toString(); + server_alerts = cfg.value("server_alerts", true).toBool(); + + discord_presence = cfg.value("discord_presence", true).toBool(); + discord_hide_server = cfg.value("discord_hide_server", false).toBool(); + discord_hide_character = cfg.value("discord_hide_character", false).toBool(); + + theme = cfg.value("theme").toString(); + if (theme.trimmed().isEmpty()) + theme = "default"; + + manual_gamemode = cfg.value("gamemode").toString(); + manual_gamemode_selection = cfg.value("manual_gamemode", false).toBool(); + manual_timeofday = cfg.value("timeofday").toString(); + manual_timeofday_selection = cfg.value("manual_timeofday", false).toBool(); + searchable_iniswap = cfg.value("searchable_iniswap", true).toBool(); + always_pre = cfg.value("always_pre", true).toBool(); + chat_tick_interval = cfg.value("chat_tick_interval", 60).toInt(); + emote_preview = cfg.value("emote_preview", true).toBool(); + sticky_sfx = cfg.value("sticky_sfx", false).toBool(); + message_length_threshold = cfg.value("message_length_threshold", 70).toInt(); + log_max_lines = cfg.value("chatlog_limit", 100).toInt(); + log_is_topdown = cfg.value("chatlog_scrolldown", true).toBool(); + log_display_timestamp = cfg.value("chatlog_display_timestamp", true).toBool(); + log_display_client_id = cfg.value("chatlog_display_client_id", false).toBool(); + log_display_self_highlight = cfg.value("chatlog_display_self_highlight", true).toBool(); + log_display_empty_messages = cfg.value("chatlog_display_empty_messages", false).toBool(); + log_format_use_newline = cfg.value("chatlog_newline", false).toBool(); + log_display_music_switch = cfg.value("music_change_log", true).toBool(); + log_is_recording = cfg.value("enable_logging", true).toBool(); + + // performance + { + sprite_caching.clear(); + cfg.beginGroup("sprite_caching"); + const QStringList l_key_list = sprite_category_string_list(); + for (const QString &i_key : l_key_list) + { + const SpriteCategory l_category = string_to_sprite_category(i_key); + sprite_caching.insert(l_category, cfg.value(i_key, true).toBool()); + } + cfg.endGroup(); + } + system_memory_threshold = qBound(10, cfg.value("system_memory_threshold", 50).toInt(), 80); + loading_bar_delay = qBound(0, cfg.value("loading_bar_delay", 500).toInt(), 2000); + caching_threshold = qBound(0, cfg.value("caching_threshold", 50).toInt(), 100); + mk2::SpriteDynamicReader::set_system_memory_threshold(system_memory_threshold); + + // audio + if (cfg.contains("favorite_device_driver")) + favorite_device_driver = cfg.value("favorite_device_driver").toString(); + + suppress_background_audio = cfg.value("suppress_background_audio").toBool(); + master_volume = cfg.value("default_master", 50).toInt(); + system_volume = cfg.value("default_system", 50).toInt(); + effect_volume = cfg.value("default_sfx", 50).toInt(); + effect_ignore_suppression = cfg.value("effect_ignore_suppression", false).toBool(); + music_volume = cfg.value("default_music", 50).toInt(); + music_ignore_suppression = cfg.value("music_ignore_suppression", false).toBool(); + video_volume = cfg.value("default_video", 50).toInt(); + video_ignore_suppression = cfg.value("video_ignore_suppression", false).toBool(); + blip_volume = cfg.value("default_blip", 50).toInt(); + blip_ignore_suppression = cfg.value("blip_ignore_suppression", false).toBool(); + blip_rate = cfg.value("blip_rate", 1000000000).toInt(); + blank_blips = cfg.value("blank_blips").toBool(); + + // audio update + audio_engine->set_volume(master_volume); + audio_engine->get_family(DRAudio::Family::FSystem)->set_volume(system_volume); + audio_engine->get_family(DRAudio::Family::FEffect)->set_volume(effect_volume); + audio_engine->get_family(DRAudio::Family::FEffect)->set_ignore_suppression(effect_ignore_suppression); + audio_engine->get_family(DRAudio::Family::FMusic)->set_volume(music_volume); + audio_engine->get_family(DRAudio::Family::FMusic)->set_ignore_suppression(effect_ignore_suppression); + audio_engine->get_family(DRAudio::Family::FVideo)->set_volume(video_volume); + audio_engine->get_family(DRAudio::Family::FVideo)->set_ignore_suppression(effect_ignore_suppression); + audio_engine->get_family(DRAudio::Family::FBlip)->set_volume(blip_volume); + audio_engine->get_family(DRAudio::Family::FBlip)->set_ignore_suppression(effect_ignore_suppression); + + { // ini swap + cfg.beginGroup("character_ini"); + + ini_map.clear(); + for (const QString &i_key : cfg.childKeys()) + { + const QString i_value = cfg.value(i_key).toString(); + if (i_key == i_value || i_value.trimmed().isEmpty()) + continue; + ini_map.insert(i_key, i_value); + } + + cfg.endGroup(); + } + + // audio device + update_favorite_device(); +} + +void AOConfigPrivate::save_file() +{ + cfg.setValue("autosave", autosave); + + { // notifications + cfg.remove("notifications"); + cfg.beginGroup("notifications"); + for (int i = 0; i < notification_filter.length(); ++i) + cfg.setValue(QString::number(i), notification_filter[i]); + cfg.endGroup(); + } + + cfg.setValue("username", username); + cfg.setValue("showname", showname); + cfg.setValue("callwords", callwords); + cfg.setValue("server_advertiser", server_advertiser); + cfg.setValue("server_alerts", server_alerts); + + cfg.setValue("discord_presence", discord_presence); + cfg.setValue("discord_hide_server", discord_hide_server); + cfg.setValue("discord_hide_character", discord_hide_character); + + cfg.setValue("theme", theme); + cfg.setValue("gamemode", manual_gamemode); + cfg.setValue("manual_gamemode", manual_gamemode_selection); + cfg.setValue("timeofday", manual_timeofday); + cfg.setValue("manual_timeofday", manual_timeofday_selection); + cfg.setValue("searchable_iniswap", searchable_iniswap); + cfg.setValue("always_pre", always_pre); + cfg.setValue("chat_tick_interval", chat_tick_interval); + cfg.setValue("emote_preview", emote_preview); + cfg.setValue("sticky_sfx", sticky_sfx); + cfg.setValue("message_length_threshold", message_length_threshold); + cfg.setValue("chatlog_limit", log_max_lines); + cfg.setValue("chatlog_display_timestamp", log_display_timestamp); + cfg.setValue("chatlog_display_client_id", log_display_client_id); + cfg.setValue("chatlog_display_self_highlight", log_display_self_highlight); + cfg.setValue("chatlog_newline", log_format_use_newline); + cfg.setValue("chatlog_display_empty_messages", log_display_empty_messages); + cfg.setValue("music_change_log", log_display_music_switch); + cfg.setValue("chatlog_scrolldown", log_is_topdown); + cfg.setValue("enable_logging", log_is_recording); + + // performance + { + cfg.remove("sprite_caching"); + cfg.beginGroup("sprite_caching"); + for (auto it = sprite_caching.cbegin(); it != sprite_caching.cend(); ++it) + { + const QString l_category_str = sprite_category_to_string(SpriteCategory(it.key())); + cfg.setValue(l_category_str, it.value()); + } + cfg.endGroup(); + } + cfg.setValue("system_memory_threshold", system_memory_threshold); + cfg.setValue("loading_bar_delay", loading_bar_delay); + cfg.setValue("caching_threshold", caching_threshold); + + // audio + if (favorite_device_driver.has_value()) + cfg.setValue("favorite_device_driver", favorite_device_driver.value()); + + cfg.setValue("suppress_background_audio", suppress_background_audio); + cfg.setValue("default_master", master_volume); + cfg.setValue("default_system", system_volume); + cfg.setValue("default_sfx", effect_volume); + cfg.setValue("effect_ignore_suppression", effect_ignore_suppression); + cfg.setValue("default_music", music_volume); + cfg.setValue("music_ignore_suppression", music_ignore_suppression); + cfg.setValue("default_video", video_volume); + cfg.setValue("video_ignore_suppression", video_ignore_suppression); + cfg.setValue("default_blip", blip_volume); + cfg.setValue("blip_ignore_suppression", blip_ignore_suppression); + cfg.setValue("blip_rate", blip_rate); + cfg.setValue("blank_blips", blank_blips); + + cfg.remove("character_ini"); + { // ini swap + cfg.beginGroup("character_ini"); + + for (auto it = ini_map.cbegin(); it != ini_map.cend(); ++it) + cfg.setValue(it.key(), it.value()); + + cfg.endGroup(); + } + + cfg.sync(); +} + +void AOConfigPrivate::invoke_signal(QString p_method_name, QGenericArgument p_arg1, QGenericArgument p_arg2) +{ + for (QObject *i_child : qAsConst(children)) + { + QMetaObject::invokeMethod(i_child, p_method_name.toStdString().c_str(), p_arg1, p_arg2); + } +} + +void AOConfigPrivate::update_favorite_device() +{ + if (!favorite_device_driver.has_value()) + return; + audio_engine->set_favorite_device_driver(favorite_device_driver.value()); +} + +void AOConfigPrivate::on_application_state_changed(Qt::ApplicationState p_state) +{ + audio_engine->set_suppressed(suppress_background_audio && p_state != Qt::ApplicationActive); +} + +// AOConfig //////////////////////////////////////////////////////////////////// + +/*! + * private classes are cool + */ +namespace +{ +static QSharedPointer d; +} + +AOConfig::AOConfig(QObject *p_parent) + : QObject(p_parent) +{ + // init if not created yet + if (d == nullptr) + { + Q_ASSERT_X(qApp, "initialization", "QGuiApplication is required"); + d = QSharedPointer(new AOConfigPrivate); + } + + // ao2 is the pinnacle of thread security + d->children.append(this); +} + +AOConfig::~AOConfig() +{ + // totally safe! + d->children.removeAll(this); +} + +QString AOConfig::get_string(QString p_name, QString p_default) const +{ + return d->cfg.value(p_name, p_default).toString(); +} + +bool AOConfig::get_bool(QString p_name, bool p_default) const +{ + return d->cfg.value(p_name, p_default).toBool(); +} + +int AOConfig::get_number(QString p_name, int p_default) const +{ + return d->cfg.value(p_name, p_default).toInt(); +} + +bool AOConfig::first_launch() const +{ + return d->first_launch; +} + +bool AOConfig::autosave() const +{ + return d->autosave; +} + +bool AOConfig::display_notification(QString p_message) const +{ + return !d->notification_filter.contains(p_message, Qt::CaseInsensitive); +} + +QString AOConfig::username() const +{ + return d->username; +} + +QString AOConfig::showname() const +{ + return d->showname; +} + +QString AOConfig::showname_placeholder() const +{ + return d->showname_placeholder; +} + +QString AOConfig::character_ini(QString p_base_chr) const +{ + if (d->ini_map.contains(p_base_chr)) + return d->ini_map[p_base_chr]; + return p_base_chr; +} + +QString AOConfig::callwords() const +{ + return d->callwords; +} + +QString AOConfig::server_advertiser() const +{ + return d->server_advertiser; +} + +bool AOConfig::server_alerts_enabled() const +{ + return d->server_alerts; +} + +bool AOConfig::discord_presence() const +{ + return d->discord_presence; +} + +bool AOConfig::discord_hide_server() const +{ + return d->discord_hide_server; +} + +bool AOConfig::discord_hide_character() const +{ + return d->discord_hide_character; +} + +/** + * @brief Return the current theme name. + * @return Name of current theme. + */ +QString AOConfig::theme() const +{ + return d->theme; +} + +QString AOConfig::gamemode() const +{ + return d->gamemode; +} + +/** + * @brief Return the current gamemode. If no gamemode is set, return the + * empty string. + * + * @return Current gamemode, or empty string if not set. + */ +QString AOConfig::manual_gamemode() const +{ + return d->manual_gamemode; +} + +/** + * @brief Returns the current manual gamemode status. + * + * @details If true, a player can change gamemodes manually and their client + * will ignore orders to change gamemode from the server. If false, neither is + * possible and the client will follow orders from the server to change + * gamemode. + * + * @return Current manual gamemode status. + */ +bool AOConfig::is_manual_gamemode_selection_enabled() const +{ + return d->manual_gamemode_selection; +} + +QString AOConfig::timeofday() const +{ + return d->timeofday; +} + +/** + * @brief Returns the current manual time of day. If no time of day is set, return + * the empty string. + * + * @return Current manual time of day, or empty string if not set. + */ +QString AOConfig::manual_timeofday() const +{ + return d->manual_timeofday; +} + +/** + * @brief Returns the current manual time of day status. + * + * @details If true, a player can change time of day manually and their client + * will ignore orders to change time of day from the server. If false, neither + * is possible and the client will follow orders from the server to change + * time of day. + * + * @return Current manual time of day status. + */ +bool AOConfig::is_manual_timeofday_selection_enabled() const +{ + return d->manual_timeofday_selection; +} + +bool AOConfig::searchable_iniswap_enabled() const +{ + return d->searchable_iniswap; +} + +bool AOConfig::always_pre_enabled() const +{ + return d->always_pre; +} + +int AOConfig::chat_tick_interval() const +{ + return d->chat_tick_interval; +} + +bool AOConfig::emote_preview_enabled() const +{ + return d->emote_preview; +} + +bool AOConfig::sticky_sfx_enabled() const +{ + return d->sticky_sfx; +} + +int AOConfig::message_length_threshold() const +{ + return d->message_length_threshold; +} + +int AOConfig::log_max_lines() const +{ + return d->log_max_lines; +} + +bool AOConfig::log_display_timestamp_enabled() const +{ + return d->log_display_timestamp; +} + +bool AOConfig::log_display_client_id_enabled() const +{ + return d->log_display_client_id; +} + +bool AOConfig::log_display_self_highlight_enabled() const +{ + return d->log_display_self_highlight; +} + +bool AOConfig::log_display_empty_messages_enabled() const +{ + return d->log_display_empty_messages; +} + +bool AOConfig::log_is_topdown_enabled() const +{ + return d->log_is_topdown; +} + +bool AOConfig::log_format_use_newline_enabled() const +{ + return d->log_format_use_newline; +} + +bool AOConfig::log_display_music_switch_enabled() const +{ + return d->log_display_music_switch; +} + +bool AOConfig::log_is_recording_enabled() const +{ + return d->log_is_recording; +} + +int AOConfig::system_memory_threshold() const +{ + return d->system_memory_threshold; +} + +bool AOConfig::sprite_caching_enabled(int type) const +{ + return d->sprite_caching[type]; +} + +int AOConfig::loading_bar_delay() const +{ + return d->loading_bar_delay; +} + +int AOConfig::caching_threshold() const +{ + return d->caching_threshold; +} + +std::optional AOConfig::favorite_device_driver() const +{ + return d->favorite_device_driver; +} + +int AOConfig::master_volume() const +{ + return d->master_volume; +} + +bool AOConfig::suppress_background_audio() const +{ + return d->suppress_background_audio; +} + +int AOConfig::system_volume() const +{ + return d->system_volume; +} +int AOConfig::effect_volume() const +{ + return d->effect_volume; +} + +bool AOConfig::effect_ignore_suppression() const +{ + return d->effect_ignore_suppression; +} + +int AOConfig::music_volume() const +{ + return d->music_volume; +} + +bool AOConfig::music_ignore_suppression() const +{ + return d->music_ignore_suppression; +} + +int AOConfig::video_volume() const +{ + return d->video_volume; +} + +bool AOConfig::video_ignore_suppression() const +{ + return d->video_ignore_suppression; +} + +int AOConfig::blip_volume() const +{ + return d->blip_volume; +} + +bool AOConfig::blip_ignore_suppression() const +{ + return d->blip_ignore_suppression; +} + +int AOConfig::blip_rate() const +{ + return d->blip_rate; +} + +bool AOConfig::blank_blips_enabled() const +{ + return d->blank_blips; +} + +void AOConfig::load_file() +{ + d->load_file(); +} + +void AOConfig::save_file() +{ + d->save_file(); +} + +void AOConfig::set_autosave(bool p_enabled) +{ + if (d->autosave == p_enabled) + return; + d->autosave = p_enabled; + d->invoke_signal("autosave_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::clear_notification_filter() +{ + d->notification_filter.clear(); +} + +void AOConfig::filter_notification(QString p_message) +{ + d->notification_filter.append(p_message); +} + +void AOConfig::set_username(QString p_value) +{ + const QString l_simplified_value = p_value.simplified(); + if (d->username == l_simplified_value) + return; + d->username = l_simplified_value; + d->invoke_signal("username_changed", Q_ARG(QString, d->username)); +} + +void AOConfig::set_showname(QString p_value) +{ + const QString l_simplified_value = p_value.simplified(); + if (d->showname == l_simplified_value && !l_simplified_value.isEmpty()) + return; + d->showname = l_simplified_value; + d->invoke_signal("showname_changed", Q_ARG(QString, d->showname)); +} + +void AOConfig::set_showname_placeholder(QString p_string) +{ + if (d->showname_placeholder == p_string) + return; + d->showname_placeholder = p_string; + d->invoke_signal("showname_placeholder_changed", Q_ARG(QString, p_string)); +} + +void AOConfig::clear_showname_placeholder() +{ + set_showname_placeholder(nullptr); +} + +void AOConfig::set_character_ini(QString p_base_chr, QString p_target_chr) +{ + if (d->ini_map.contains(p_base_chr)) + { + if (d->ini_map[p_base_chr] == p_target_chr) + return; + } + else if (p_base_chr == p_target_chr) + return; + if (p_base_chr == p_target_chr) + d->ini_map.remove(p_base_chr); + else + d->ini_map.insert(p_base_chr, p_target_chr); + d->invoke_signal("character_ini_changed", Q_ARG(QString, p_base_chr)); +} + +void AOConfig::set_callwords(QString p_string) +{ + if (d->callwords == p_string) + return; + d->callwords = p_string; + d->invoke_signal("callwords_changed", Q_ARG(QString, p_string)); +} + +void AOConfig::set_server_advertiser(QString p_address) +{ + if (d->server_advertiser == p_address) + return; + d->server_advertiser = p_address; + d->invoke_signal("server_advertiser_changed", Q_ARG(QString, p_address)); +} + +void AOConfig::set_server_alerts(bool p_enabled) +{ + if (d->server_alerts == p_enabled) + return; + d->server_alerts = p_enabled; + d->invoke_signal("server_alerts_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_discord_presence(const bool p_enabled) +{ + if (d->discord_presence == p_enabled) + return; + d->discord_presence = p_enabled; + d->invoke_signal("discord_presence_changed", Q_ARG(bool, d->discord_presence)); +} + +void AOConfig::set_discord_hide_server(const bool p_enabled) +{ + if (d->discord_hide_server == p_enabled) + return; + d->discord_hide_server = p_enabled; + d->invoke_signal("discord_hide_server_changed", Q_ARG(bool, d->discord_hide_server)); +} + +void AOConfig::set_discord_hide_character(const bool p_enabled) +{ + if (d->discord_hide_character == p_enabled) + return; + d->discord_hide_character = p_enabled; + d->invoke_signal("discord_hide_character_changed", Q_ARG(bool, d->discord_hide_character)); +} + +void AOConfig::set_theme(QString p_string) +{ + if (d->theme == p_string) + return; + d->theme = p_string; + d->manual_gamemode.clear(); + d->manual_timeofday.clear(); + d->invoke_signal("theme_changed", Q_ARG(QString, p_string)); +} + +void AOConfig::set_gamemode(QString p_string) +{ + if (d->gamemode == p_string) + return; + d->gamemode = p_string; + d->invoke_signal("gamemode_changed", Q_ARG(QString, p_string)); +} + +void AOConfig::set_manual_gamemode(QString p_string) +{ + if (d->manual_gamemode == p_string) + return; + d->manual_gamemode = p_string; + d->manual_timeofday.clear(); + d->invoke_signal("manual_gamemode_changed", Q_ARG(QString, p_string)); +} + +void AOConfig::set_manual_gamemode_selection_enabled(bool p_enabled) +{ + if (d->manual_gamemode_selection == p_enabled) + return; + d->manual_gamemode_selection = p_enabled; + d->invoke_signal("manual_gamemode_selection_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_timeofday(QString p_string) +{ + if (d->timeofday == p_string) + return; + d->timeofday = p_string; + d->invoke_signal("timeofday_changed", Q_ARG(QString, p_string)); +} + +void AOConfig::set_manual_timeofday(QString p_string) +{ + if (d->manual_timeofday == p_string) + return; + d->manual_timeofday = p_string; + d->invoke_signal("manual_timeofday_changed", Q_ARG(QString, p_string)); +} + +void AOConfig::set_manual_timeofday_selection_enabled(bool p_enabled) +{ + if (d->manual_timeofday_selection == p_enabled) + return; + d->manual_timeofday_selection = p_enabled; + d->invoke_signal("manual_timeofday_selection_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_searchable_iniswap(bool p_enabled) +{ + if (d->searchable_iniswap == p_enabled) + return; + d->searchable_iniswap = p_enabled; + d->invoke_signal("searchable_iniswap_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_always_pre(bool p_enabled) +{ + if (d->always_pre == p_enabled) + return; + d->always_pre = p_enabled; + d->invoke_signal("always_pre_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_chat_tick_interval(int p_number) +{ + if (d->chat_tick_interval == p_number) + return; + d->chat_tick_interval = p_number; + d->invoke_signal("chat_tick_interval_changed", Q_ARG(int, p_number)); +} + +void AOConfig::set_emote_preview(bool p_enabled) +{ + if (d->emote_preview == p_enabled) + return; + d->emote_preview = p_enabled; + d->invoke_signal("emote_preview_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_sticky_sfx(bool p_enabled) +{ + if (d->sticky_sfx == p_enabled) + return; + d->sticky_sfx = p_enabled; + d->invoke_signal("sticky_sfx_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_message_length_threshold(int p_number) +{ + if (d->message_length_threshold == p_number) + return; + d->message_length_threshold = p_number; + d->invoke_signal("message_length_threshold_changed", Q_ARG(int, p_number)); +} + +void AOConfig::set_log_max_lines(int p_number) +{ + if (d->log_max_lines == p_number) + return; + d->log_max_lines = p_number; + d->invoke_signal("log_max_lines_changed", Q_ARG(int, p_number)); +} + +void AOConfig::set_log_display_timestamp(bool p_enabled) +{ + if (d->log_display_timestamp == p_enabled) + return; + d->log_display_timestamp = p_enabled; + d->invoke_signal("log_display_timestamp_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_log_display_client_id(bool p_enabled) +{ + if (d->log_display_client_id == p_enabled) + return; + d->log_display_client_id = p_enabled; + d->invoke_signal("log_display_client_id_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_log_display_self_highlight(bool p_enabled) +{ + if (d->log_display_self_highlight == p_enabled) + return; + d->log_display_self_highlight = p_enabled; + d->invoke_signal("log_display_self_highlight_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_log_display_empty_messages(bool p_enabled) +{ + if (d->log_display_empty_messages == p_enabled) + return; + d->log_display_empty_messages = p_enabled; + d->invoke_signal("log_display_empty_messages_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_log_format_use_newline(bool p_enabled) +{ + if (d->log_format_use_newline == p_enabled) + return; + d->log_format_use_newline = p_enabled; + d->invoke_signal("log_format_use_newline_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_log_is_topdown(bool p_enabled) +{ + if (d->log_is_topdown == p_enabled) + return; + d->log_is_topdown = p_enabled; + d->invoke_signal("log_is_topdown_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_log_display_music_switch(bool p_enabled) +{ + if (d->log_display_music_switch == p_enabled) + return; + d->log_display_music_switch = p_enabled; + d->invoke_signal("log_display_music_switch_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_log_is_recording(bool p_enabled) +{ + if (d->log_is_recording == p_enabled) + return; + d->log_is_recording = p_enabled; + d->invoke_signal("log_is_recording_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_master_volume(int p_number) +{ + if (d->master_volume == p_number) + return; + d->master_volume = p_number; + d->audio_engine->set_volume(p_number); + d->invoke_signal("master_volume_changed", Q_ARG(int, p_number)); +} + +void AOConfig::set_suppress_background_audio(bool p_enabled) +{ + if (d->suppress_background_audio == p_enabled) + return; + d->suppress_background_audio = p_enabled; + d->invoke_signal("suppress_background_audio_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_system_memory_threshold(int p_percent) +{ + p_percent = qBound(10, p_percent, 80); + if (d->system_memory_threshold == p_percent) + return; + d->system_memory_threshold = p_percent; + mk2::SpriteDynamicReader::set_system_memory_threshold(p_percent); + d->invoke_signal("system_memory_threshold_changed", Q_ARG(int, p_percent)); +} + +void AOConfig::set_sprite_caching(int p_type, bool p_enabled) +{ + if (d->sprite_caching[p_type] == p_enabled) + return; + d->sprite_caching[p_type] = p_enabled; + d->invoke_signal("sprite_caching_toggled", Q_ARG(int, p_type), Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_loading_bar_delay(int p_delay) +{ + p_delay = qBound(0, p_delay, 2000); + if (d->loading_bar_delay == p_delay) + return; + d->loading_bar_delay = p_delay; + d->invoke_signal("loading_bar_delay_changed", Q_ARG(int, p_delay)); +} + +void AOConfig::set_caching_threshold(int p_percent) +{ + p_percent = qBound(0, p_percent, 100); + if (d->caching_threshold == p_percent) + return; + d->caching_threshold = p_percent; + d->invoke_signal("caching_threshold_changed", Q_ARG(int, p_percent)); +} + +void AOConfig::set_favorite_device_driver(QString p_device_driver) +{ + if (d->favorite_device_driver.has_value() && d->favorite_device_driver.value() == p_device_driver) + return; + d->favorite_device_driver = p_device_driver; + d->update_favorite_device(); + d->invoke_signal("favorite_device_changed", Q_ARG(QString, p_device_driver)); +} + +void AOConfig::set_system_volume(int p_number) +{ + if (d->system_volume == p_number) + return; + d->system_volume = p_number; + d->audio_engine->get_family(DRAudio::Family::FSystem)->set_volume(p_number); + d->invoke_signal("system_volume_changed", Q_ARG(int, p_number)); +} + +void AOConfig::set_effect_volume(int p_number) +{ + if (d->effect_volume == p_number) + return; + d->effect_volume = p_number; + d->audio_engine->get_family(DRAudio::Family::FEffect)->set_volume(p_number); + d->invoke_signal("effect_volume_changed", Q_ARG(int, p_number)); +} + +void AOConfig::set_effect_ignore_suppression(bool p_enabled) +{ + if (d->effect_ignore_suppression == p_enabled) + return; + d->effect_ignore_suppression = p_enabled; + d->audio_engine->get_family(DRAudio::Family::FEffect)->set_ignore_suppression(p_enabled); + d->invoke_signal("effect_ignore_suppression_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_music_volume(int p_number) +{ + if (d->music_volume == p_number) + return; + d->music_volume = p_number; + d->audio_engine->get_family(DRAudio::Family::FMusic)->set_volume(p_number); + d->invoke_signal("music_volume_changed", Q_ARG(int, p_number)); +} + +void AOConfig::set_music_ignore_suppression(bool p_enabled) +{ + if (d->music_ignore_suppression == p_enabled) + return; + d->music_ignore_suppression = p_enabled; + d->audio_engine->get_family(DRAudio::Family::FMusic)->set_ignore_suppression(p_enabled); + d->invoke_signal("music_ignore_suppression_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_video_volume(int p_number) +{ + if (d->video_volume == p_number) + return; + d->video_volume = p_number; + d->audio_engine->get_family(DRAudio::Family::FVideo)->set_volume(p_number); + d->invoke_signal("video_volume_changed", Q_ARG(int, p_number)); +} + +void AOConfig::set_video_ignore_suppression(bool p_enabled) +{ + if (d->video_ignore_suppression == p_enabled) + return; + d->video_ignore_suppression = p_enabled; + d->audio_engine->get_family(DRAudio::Family::FVideo)->set_ignore_suppression(p_enabled); + d->invoke_signal("video_ignore_suppression_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_blip_volume(int p_number) +{ + if (d->blip_volume == p_number) + return; + d->blip_volume = p_number; + d->audio_engine->get_family(DRAudio::Family::FBlip)->set_volume(p_number); + d->invoke_signal("blip_volume_changed", Q_ARG(int, p_number)); +} + +void AOConfig::set_blip_ignore_suppression(bool p_enabled) +{ + if (d->blip_ignore_suppression == p_enabled) + return; + d->blip_ignore_suppression = p_enabled; + d->audio_engine->get_family(DRAudio::Family::FBlip)->set_ignore_suppression(p_enabled); + d->invoke_signal("blip_ignore_suppression_changed", Q_ARG(bool, p_enabled)); +} + +void AOConfig::set_blip_rate(int p_number) +{ + if (d->blip_rate == p_number) + return; + d->blip_rate = p_number; + d->invoke_signal("blip_rate_changed", Q_ARG(int, p_number)); +} + +void AOConfig::set_blank_blips(bool p_enabled) +{ + if (d->blank_blips == p_enabled) + return; + d->blank_blips = p_enabled; + d->audio_engine->get_family(DRAudio::Family::FBlip)->set_ignore_suppression(p_enabled); + d->invoke_signal("blank_blips_changed", Q_ARG(bool, p_enabled)); +} + +// moc +#include "aoconfig.moc" diff --git a/src/aoconfig.h b/src/aoconfig.h new file mode 100644 index 000000000..591e9ab39 --- /dev/null +++ b/src/aoconfig.h @@ -0,0 +1,219 @@ +#ifndef AOCONFIG_H +#define AOCONFIG_H + +#include "datatypes.h" + +#include + +#include + +class AOConfig : public QObject +{ + Q_OBJECT + +public: + AOConfig(QObject *p_parent = nullptr); + ~AOConfig(); + + // generic getters + QString get_string(QString p_name, QString p_default = nullptr) const; + bool get_bool(QString p_name, bool p_default = false) const; + int get_number(QString p_name, int p_default = 0) const; + + bool first_launch() const; + + // getters + bool autosave() const; + bool display_notification(QString message) const; + QString username() const; + QString showname() const; + QString showname_placeholder() const; + QString character_ini(QString base_character) const; + QString callwords() const; + QString server_advertiser() const; + bool server_alerts_enabled() const; + bool discord_presence() const; + bool discord_hide_server() const; + bool discord_hide_character() const; + QString theme() const; + QString gamemode() const; + QString manual_gamemode() const; + bool is_manual_gamemode_selection_enabled() const; + QString timeofday() const; + QString manual_timeofday() const; + bool is_manual_timeofday_selection_enabled() const; + bool searchable_iniswap_enabled() const; + bool always_pre_enabled() const; + int chat_tick_interval() const; + bool emote_preview_enabled() const; + bool sticky_sfx_enabled() const; + int message_length_threshold() const; + int log_max_lines() const; + bool log_display_timestamp_enabled() const; + bool log_display_client_id_enabled() const; + bool log_display_self_highlight_enabled() const; + bool log_display_empty_messages_enabled() const; + bool log_is_topdown_enabled() const; + bool log_format_use_newline_enabled() const; + bool log_display_music_switch_enabled() const; + bool log_is_recording_enabled() const; + + // performance + bool sprite_caching_enabled(int type) const; + int system_memory_threshold() const; + int loading_bar_delay() const; + int caching_threshold() const; + + // audio + std::optional favorite_device_driver() const; + int master_volume() const; + bool suppress_background_audio() const; + int system_volume() const; + int effect_volume() const; + bool effect_ignore_suppression() const; + int music_volume() const; + bool music_ignore_suppression() const; + int video_volume() const; + bool video_ignore_suppression() const; + int blip_volume() const; + bool blip_ignore_suppression() const; + int blip_rate() const; + bool blank_blips_enabled() const; + + // io +public slots: + void load_file(); + void save_file(); + + // setters +public slots: + void set_autosave(bool p_enabled); + void clear_notification_filter(); + void filter_notification(QString message); + void set_username(QString p_string); + void set_showname(QString p_string); + void set_showname_placeholder(QString p_string); + void clear_showname_placeholder(); + void set_character_ini(QString base_character, QString target_character); + void set_callwords(QString p_string); + void set_server_advertiser(QString address); + void set_server_alerts(bool p_enabled); + void set_discord_presence(const bool p_enabled); + void set_discord_hide_server(const bool p_enabled); + void set_discord_hide_character(const bool p_enabled); + void set_theme(QString p_string); + void set_gamemode(QString p_string); + void set_manual_gamemode(QString p_string); + void set_manual_gamemode_selection_enabled(bool p_enabled); + void set_timeofday(QString p_string); + void set_manual_timeofday(QString p_string); + void set_manual_timeofday_selection_enabled(bool p_enabled); + void set_searchable_iniswap(bool); + void set_always_pre(bool p_enabled); + void set_chat_tick_interval(int p_number); + void set_emote_preview(bool p_enabled); + void set_sticky_sfx(bool p_enabled); + void set_message_length_threshold(int percent); + void set_log_max_lines(int p_number); + void set_log_display_timestamp(bool p_enabled); + void set_log_display_client_id(bool p_enabled); + void set_log_display_self_highlight(bool p_enabled); + void set_log_display_empty_messages(bool p_enabled); + void set_log_is_topdown(bool p_enabled); + void set_log_format_use_newline(bool p_enabled); + void set_log_display_music_switch(bool p_enabled); + void set_log_is_recording(bool p_enabled); + void set_suppress_background_audio(bool p_enabled); + + // performance + void set_sprite_caching(int type, bool enabled); + void set_system_memory_threshold(int percent); + void set_loading_bar_delay(int delay); + void set_caching_threshold(int percent); + + // audio + void set_favorite_device_driver(QString p_device_driver); + void set_master_volume(int p_number); + void set_system_volume(int p_number); + void set_effect_volume(int p_number); + void set_effect_ignore_suppression(bool p_enabled); + void set_music_volume(int p_number); + void set_music_ignore_suppression(bool p_enabled); + void set_video_volume(int p_number); + void set_video_ignore_suppression(bool p_enabled); + void set_blip_volume(int p_number); + void set_blip_ignore_suppression(bool p_enabled); + void set_blip_rate(int p_number); + void set_blank_blips(bool p_enabled); + + // signals +signals: + // meta + void autosave_changed(bool); + + // general + void username_changed(QString); + void callwords_changed(QString); + void server_advertiser_changed(QString); + void server_alerts_changed(bool); + void discord_presence_changed(bool); + void discord_hide_server_changed(bool); + void discord_hide_character_changed(bool); + + // game + void showname_changed(QString); + void showname_placeholder_changed(QString); + void character_ini_changed(QString base_character); + void searchable_iniswap_changed(bool); + void always_pre_changed(bool); + void chat_tick_interval_changed(int); + void emote_preview_changed(bool); + void sticky_sfx_changed(bool); + + // theme + void theme_changed(QString); + void gamemode_changed(QString); + void manual_gamemode_changed(QString); + void manual_gamemode_selection_changed(bool); + void timeofday_changed(QString); + void manual_timeofday_changed(QString); + void manual_timeofday_selection_changed(bool); + + // ic + void message_length_threshold_changed(int); + + // log + void log_max_lines_changed(int); + void log_display_timestamp_changed(bool); + void log_display_client_id_changed(bool); + void log_display_self_highlight_changed(bool); + void log_display_empty_messages_changed(bool); + void log_is_topdown_changed(bool); + void log_format_use_newline_changed(bool); + void log_display_music_switch_changed(bool); + void log_is_recording_changed(bool); + + // performance + void sprite_caching_toggled(int, bool); + void system_memory_threshold_changed(int); + void loading_bar_delay_changed(int); + void caching_threshold_changed(int); + + // audio + void favorite_device_changed(QString); + void master_volume_changed(int); + void suppress_background_audio_changed(bool); + void system_volume_changed(int); + void effect_volume_changed(int); + void effect_ignore_suppression_changed(bool); + void music_volume_changed(int); + void music_ignore_suppression_changed(bool); + void video_volume_changed(int); + void video_ignore_suppression_changed(bool); + void blip_volume_changed(int); + void blip_ignore_suppression_changed(bool); + void blip_rate_changed(int); + void blank_blips_changed(bool); +}; + +#endif // AOCONFIG_H diff --git a/src/aoconfigpanel.cpp b/src/aoconfigpanel.cpp new file mode 100644 index 000000000..99945b520 --- /dev/null +++ b/src/aoconfigpanel.cpp @@ -0,0 +1,726 @@ +#include "aoconfigpanel.h" + +#include "aoapplication.h" +#include "aoconfig.h" +#include "aoguiloader.h" +#include "datatypes.h" +#include "drpather.h" +#include "mk2/spritedynamicreader.h" +#include "version.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +AOConfigPanel::AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent) + : QWidget(p_parent) + , m_config(new AOConfig(this)) + , m_engine(new DRAudioEngine(this)) +{ + ao_app = p_ao_app; + + setWindowTitle(tr("Config")); + setWindowFlag(Qt::WindowMinMaxButtonsHint, false); + + AOGuiLoader loader; + loader.load_from_file(":res/ui/config_panel.ui", this); + + // tab + QTabWidget *tab_widget = AO_GUI_WIDGET(QTabWidget, "tab_widget"); + setFocusProxy(tab_widget); + tab_widget->setCurrentIndex(0); + + // behaviour + ui_save = AO_GUI_WIDGET(QPushButton, "save"); + ui_close = AO_GUI_WIDGET(QPushButton, "close"); + ui_autosave = AO_GUI_WIDGET(QCheckBox, "autosave"); + + // notifications + ui_clear_notifications = AO_GUI_WIDGET(QPushButton, "clear_notifications"); + + // general + ui_username = AO_GUI_WIDGET(QLineEdit, "username"); + ui_callwords = AO_GUI_WIDGET(QLineEdit, "callwords"); + ui_advertiser = AO_GUI_WIDGET(QLineEdit, "advertiser"); + ui_server_alerts = AO_GUI_WIDGET(QCheckBox, "server_alerts"); + ui_discord_presence = AO_GUI_WIDGET(QGroupBox, "discord_presence"); + ui_discord_hide_server = AO_GUI_WIDGET(QCheckBox, "discord_hide_server"); + ui_discord_hide_character = AO_GUI_WIDGET(QCheckBox, "discord_hide_character"); + + // game + ui_theme = AO_GUI_WIDGET(QComboBox, "theme"); + ui_switch_theme = AO_GUI_WIDGET(QPushButton, "switch_theme"); + ui_reload_theme = AO_GUI_WIDGET(QPushButton, "reload_theme"); + ui_gamemode = AO_GUI_WIDGET(QLineEdit, "gamemode"); + ui_manual_gamemode = AO_GUI_WIDGET(QComboBox, "manual_gamemode"); + ui_manual_gamemode_selection = AO_GUI_WIDGET(QCheckBox, "manual_gamemode_selection"); + ui_timeofday = AO_GUI_WIDGET(QLineEdit, "timeofday"); + ui_manual_timeofday = AO_GUI_WIDGET(QComboBox, "manual_timeofday"); + ui_manual_timeofday_selection = AO_GUI_WIDGET(QCheckBox, "manual_timeofday_selection"); + ui_showname = AO_GUI_WIDGET(QLineEdit, "showname"); + ui_reload_character = AO_GUI_WIDGET(QPushButton, "reload_character"); + ui_searchable_iniswap = AO_GUI_WIDGET(QCheckBox, "searchable_iniswap"); + ui_always_pre = AO_GUI_WIDGET(QCheckBox, "always_pre"); + ui_chat_tick_interval = AO_GUI_WIDGET(QSpinBox, "chat_tick_interval"); + ui_emote_preview = AO_GUI_WIDGET(QCheckBox, "emote_preview"); + ui_sticky_sfx = AO_GUI_WIDGET(QCheckBox, "sticky_sfx"); + + // IC message + ui_length_threshold = AO_GUI_WIDGET(QSlider, "length_threshold"); + ui_length_threshold_label = AO_GUI_WIDGET(QLabel, "length_threshold_label"); + + // IC Chatlog + ui_log_max_lines = AO_GUI_WIDGET(QSpinBox, "log_length"); + ui_log_display_timestamp = AO_GUI_WIDGET(QCheckBox, "log_display_timestamp"); + ui_log_display_client_id = AO_GUI_WIDGET(QCheckBox, "log_display_client_id"); + ui_log_display_self_highlight = AO_GUI_WIDGET(QCheckBox, "log_display_self_highlight"); + ui_log_format_use_newline = AO_GUI_WIDGET(QCheckBox, "log_format_use_newline"); + ui_log_display_empty_messages = AO_GUI_WIDGET(QCheckBox, "log_display_empty_messages"); + ui_log_display_music_switch = AO_GUI_WIDGET(QCheckBox, "log_display_music_switch"); + ui_log_orientation_top_down = AO_GUI_WIDGET(QRadioButton, "log_orientation_top_down"); + ui_log_orientation_bottom_up = AO_GUI_WIDGET(QRadioButton, "log_orientation_bottom_up"); + ui_log_is_recording = AO_GUI_WIDGET(QCheckBox, "log_recording"); + + // performance + ui_cache_backgrounds = AO_GUI_WIDGET(QCheckBox, "cache_backgrounds"); + ui_cache_characters = AO_GUI_WIDGET(QCheckBox, "cache_characters"); + ui_cache_effects = AO_GUI_WIDGET(QCheckBox, "cache_effects"); + ui_cache_shouts = AO_GUI_WIDGET(QCheckBox, "cache_shouts"); + ui_cache_gui = AO_GUI_WIDGET(QCheckBox, "cache_gui"); + ui_cache_stickers = AO_GUI_WIDGET(QCheckBox, "cache_stickers"); + ui_system_memory_threshold = AO_GUI_WIDGET(QSlider, "system_memory_threshold"); + ui_system_memory_threshold_label = AO_GUI_WIDGET(QLabel, "system_memory_threshold_label"); + ui_caching_threshold = AO_GUI_WIDGET(QSlider, "caching_threshold"); + ui_caching_threshold_label = AO_GUI_WIDGET(QLabel, "caching_threshold_label"); + ui_loading_bar_delay = AO_GUI_WIDGET(QSlider, "loading_bar_delay"); + ui_loading_bar_delay_label = AO_GUI_WIDGET(QLabel, "loading_bar_delay_label"); + + m_cache_checkbox_map.insert(SpriteStage, ui_cache_backgrounds); + m_cache_checkbox_map.insert(SpriteCharacter, ui_cache_characters); + m_cache_checkbox_map.insert(SpriteEffect, ui_cache_effects); + m_cache_checkbox_map.insert(SpriteShout, ui_cache_shouts); + m_cache_checkbox_map.insert(SpriteGUI, ui_cache_gui); + m_cache_checkbox_map.insert(SpriteSticker, ui_cache_stickers); + + // audio + ui_device = AO_GUI_WIDGET(QComboBox, "device"); + ui_favorite_device = AO_GUI_WIDGET(QCheckBox, "favorite_device"); + ui_master = AO_GUI_WIDGET(QSlider, "master"); + ui_master_value = AO_GUI_WIDGET(QLabel, "master_value"); + ui_suppress_background_audio = AO_GUI_WIDGET(QGroupBox, "suppress_background_audio"); + ui_system = AO_GUI_WIDGET(QSlider, "system"); + ui_system_value = AO_GUI_WIDGET(QLabel, "system_value"); + ui_effect = AO_GUI_WIDGET(QSlider, "effect"); + ui_effect_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "effect_ignore_suppression"); + ui_effect_value = AO_GUI_WIDGET(QLabel, "effect_value"); + ui_music = AO_GUI_WIDGET(QSlider, "music"); + ui_music_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "music_ignore_suppression"); + ui_music_value = AO_GUI_WIDGET(QLabel, "music_value"); + ui_video = AO_GUI_WIDGET(QSlider, "video"); + ui_video_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "video_ignore_suppression"); + ui_video_value = AO_GUI_WIDGET(QLabel, "video_value"); + ui_blip = AO_GUI_WIDGET(QSlider, "blip"); + ui_blip_ignore_suppression = AO_GUI_WIDGET(QCheckBox, "blip_ignore_suppression"); + ui_blip_value = AO_GUI_WIDGET(QLabel, "blip_value"); + ui_blip_rate = AO_GUI_WIDGET(QSpinBox, "blip_rate"); + ui_blank_blips = AO_GUI_WIDGET(QCheckBox, "blank_blips"); + ui_reload_audiotracks = AO_GUI_WIDGET(QPushButton, "reload_audiotracks"); + + // about + ui_about = AO_GUI_WIDGET(QLabel, "about_label"); + + // themes + refresh_theme_list(); + refresh_gamemode_list(); + refresh_timeofday_list(); + + // input + // meta + connect(m_config, SIGNAL(autosave_changed(bool)), ui_autosave, SLOT(setChecked(bool))); + + // notifications + connect(ui_clear_notifications, SIGNAL(clicked()), m_config, SLOT(clear_notification_filter())); + + // general + connect(m_config, SIGNAL(username_changed(QString)), ui_username, SLOT(setText(QString))); + connect(m_config, SIGNAL(callwords_changed(QString)), ui_callwords, SLOT(setText(QString))); + connect(m_config, SIGNAL(server_advertiser_changed(QString)), ui_advertiser, SLOT(setText(QString))); + connect(m_config, SIGNAL(server_alerts_changed(bool)), ui_server_alerts, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(discord_presence_changed(bool)), ui_discord_presence, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(discord_hide_server_changed(bool)), ui_discord_hide_server, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(discord_hide_character_changed(bool)), ui_discord_hide_character, SLOT(setChecked(bool))); + + // game + connect(m_config, SIGNAL(theme_changed(QString)), this, SLOT(on_theme_changed(QString))); + connect(m_config, SIGNAL(gamemode_changed(QString)), this, SLOT(on_gamemode_changed(QString))); + connect(m_config, SIGNAL(manual_gamemode_changed(QString)), this, SLOT(on_manual_gamemode_changed(QString))); + connect(m_config, SIGNAL(manual_gamemode_selection_changed(bool)), this, SLOT(on_manual_gamemode_selection_changed(bool))); + connect(m_config, SIGNAL(timeofday_changed(QString)), this, SLOT(on_timeofday_changed(QString))); + connect(m_config, SIGNAL(manual_timeofday_changed(QString)), this, SLOT(on_manual_timeofday_changed(QString))); + connect(m_config, SIGNAL(manual_timeofday_selection_changed(bool)), this, SLOT(on_manual_timeofday_selection_changed(bool))); + connect(m_config, SIGNAL(showname_changed(QString)), ui_showname, SLOT(setText(QString))); + connect(m_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); + connect(m_config, SIGNAL(searchable_iniswap_changed(bool)), ui_searchable_iniswap, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(always_pre_changed(bool)), ui_always_pre, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(chat_tick_interval_changed(int)), ui_chat_tick_interval, SLOT(setValue(int))); + connect(m_config, SIGNAL(emote_preview_changed(bool)), ui_emote_preview, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(sticky_sfx_changed(bool)), ui_sticky_sfx, SLOT(setChecked(bool))); + + // log + connect(m_config, SIGNAL(log_max_lines_changed(int)), ui_log_max_lines, SLOT(setValue(int))); + connect(m_config, SIGNAL(log_display_timestamp_changed(bool)), ui_log_display_timestamp, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_client_id_changed(bool)), ui_log_display_client_id, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_self_highlight_changed(bool)), ui_log_display_self_highlight, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_format_use_newline_changed(bool)), ui_log_format_use_newline, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_empty_messages_changed(bool)), ui_log_display_empty_messages, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_display_music_switch_changed(bool)), ui_log_display_music_switch, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_log_is_topdown_changed(bool))); + connect(m_config, SIGNAL(log_is_recording_changed(bool)), ui_log_is_recording, SLOT(setChecked(bool))); + + // audio + connect(m_config, SIGNAL(master_volume_changed(int)), ui_master, SLOT(setValue(int))); + connect(m_config, SIGNAL(suppress_background_audio_changed(bool)), ui_suppress_background_audio, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(system_volume_changed(int)), ui_system, SLOT(setValue(int))); + connect(m_config, SIGNAL(effect_volume_changed(int)), ui_effect, SLOT(setValue(int))); + connect(m_config, SIGNAL(effect_ignore_suppression_changed(bool)), ui_effect_ignore_suppression, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(music_volume_changed(int)), ui_music, SLOT(setValue(int))); + connect(m_config, SIGNAL(music_ignore_suppression_changed(bool)), ui_music_ignore_suppression, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(video_volume_changed(int)), ui_video, SLOT(setValue(int))); + connect(m_config, SIGNAL(video_ignore_suppression_changed(bool)), ui_video_ignore_suppression, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(blip_volume_changed(int)), ui_blip, SLOT(setValue(int))); + connect(m_config, SIGNAL(blip_ignore_suppression_changed(bool)), ui_blip_ignore_suppression, SLOT(setChecked(bool))); + connect(m_config, SIGNAL(blip_rate_changed(int)), ui_blip_rate, SLOT(setValue(int))); + connect(m_config, SIGNAL(blank_blips_changed(bool)), ui_blank_blips, SLOT(setChecked(bool))); + + connect(m_engine, SIGNAL(current_device_changed(DRAudioDevice)), this, SLOT(on_audio_device_changed(DRAudioDevice))); + connect(m_engine, SIGNAL(device_list_changed(QVector)), this, SLOT(on_audio_device_list_changed(QVector))); + connect(m_engine, SIGNAL(favorite_device_changed(DRAudioDevice)), this, SLOT(on_favorite_audio_device_changed(DRAudioDevice))); + + // meta + connect(ui_close, SIGNAL(clicked()), this, SLOT(close())); + connect(ui_save, SIGNAL(clicked()), m_config, SLOT(save_file())); + connect(ui_autosave, SIGNAL(toggled(bool)), m_config, SLOT(set_autosave(bool))); + + // general + connect(ui_username, SIGNAL(editingFinished()), this, SLOT(username_editing_finished())); + connect(ui_callwords, SIGNAL(editingFinished()), this, SLOT(callwords_editing_finished())); + connect(ui_advertiser, SIGNAL(editingFinished()), this, SLOT(advertiser_editing_finished())); + connect(ui_server_alerts, SIGNAL(toggled(bool)), m_config, SLOT(set_server_alerts(bool))); + connect(ui_discord_presence, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_presence(bool))); + connect(ui_discord_hide_server, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_hide_server(const bool))); + connect(ui_discord_hide_character, SIGNAL(toggled(bool)), m_config, SLOT(set_discord_hide_character(const bool))); + + // game + connect(ui_theme, SIGNAL(currentTextChanged(QString)), this, SLOT(update_theme_controls())); + connect(ui_switch_theme, SIGNAL(clicked()), this, SLOT(on_switch_theme_clicked())); + connect(ui_reload_theme, SIGNAL(clicked()), this, SLOT(on_reload_theme_clicked())); + connect(ui_reload_character, SIGNAL(clicked()), this, SLOT(on_reload_character_clicked())); + connect(ui_reload_audiotracks, SIGNAL(clicked()), this, SLOT(on_reload_audiotracks_clicked())); + connect(ui_manual_gamemode, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_manual_gamemode_index_changed(QString))); + connect(ui_manual_gamemode_selection, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_gamemode_selection_enabled(bool))); + connect(ui_manual_timeofday, SIGNAL(currentIndexChanged(QString)), this, SLOT(on_manual_timeofday_index_changed(QString))); + connect(ui_manual_timeofday_selection, SIGNAL(toggled(bool)), m_config, SLOT(set_manual_timeofday_selection_enabled(bool))); + connect(ui_showname, SIGNAL(editingFinished()), this, SLOT(showname_editing_finished())); + connect(ui_searchable_iniswap, SIGNAL(toggled(bool)), m_config, SLOT(set_searchable_iniswap(bool))); + connect(ui_always_pre, SIGNAL(toggled(bool)), m_config, SLOT(set_always_pre(bool))); + connect(ui_chat_tick_interval, SIGNAL(valueChanged(int)), m_config, SLOT(set_chat_tick_interval(int))); + connect(ui_emote_preview, SIGNAL(toggled(bool)), m_config, SLOT(set_emote_preview(bool))); + connect(ui_sticky_sfx, SIGNAL(toggled(bool)), m_config, SLOT(set_sticky_sfx(bool))); + + // ic message + connect(m_config, SIGNAL(message_length_threshold_changed(int)), ui_length_threshold, SLOT(setValue(int))); + connect(ui_length_threshold, SIGNAL(valueChanged(int)), m_config, SLOT(set_message_length_threshold(int))); + connect(ui_length_threshold, SIGNAL(valueChanged(int)), this, SLOT(on_length_threshold_value_changed(int))); + + // out, log + connect(ui_log_max_lines, SIGNAL(valueChanged(int)), m_config, SLOT(set_log_max_lines(int))); + connect(ui_log_display_timestamp, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_timestamp(bool))); + connect(ui_log_display_client_id, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_client_id(bool))); + connect(ui_log_display_self_highlight, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_self_highlight(bool))); + connect(ui_log_format_use_newline, SIGNAL(toggled(bool)), m_config, SLOT(set_log_format_use_newline(bool))); + connect(ui_log_display_empty_messages, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_empty_messages(bool))); + connect(ui_log_display_music_switch, SIGNAL(toggled(bool)), m_config, SLOT(set_log_display_music_switch(bool))); + connect(ui_log_orientation_top_down, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_topdown(bool))); + connect(ui_log_is_recording, SIGNAL(toggled(bool)), m_config, SLOT(set_log_is_recording(bool))); + connect(ui_suppress_background_audio, SIGNAL(toggled(bool)), m_config, SLOT(set_suppress_background_audio(bool))); + connect(ui_device, SIGNAL(currentIndexChanged(int)), this, SLOT(on_device_current_index_changed(int))); + connect(ui_master, SIGNAL(valueChanged(int)), m_config, SLOT(set_master_volume(int))); + connect(ui_master, SIGNAL(valueChanged(int)), this, SLOT(on_master_value_changed(int))); + connect(ui_system, SIGNAL(valueChanged(int)), m_config, SLOT(set_system_volume(int))); + connect(ui_system, SIGNAL(valueChanged(int)), this, SLOT(on_system_value_changed(int))); + connect(ui_effect, SIGNAL(valueChanged(int)), m_config, SLOT(set_effect_volume(int))); + connect(ui_effect, SIGNAL(valueChanged(int)), this, SLOT(on_effect_value_changed(int))); + connect(ui_effect_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_effect_ignore_suppression(bool))); + connect(ui_music, SIGNAL(valueChanged(int)), m_config, SLOT(set_music_volume(int))); + connect(ui_music, SIGNAL(valueChanged(int)), this, SLOT(on_music_value_changed(int))); + connect(ui_music_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_music_ignore_suppression(bool))); + connect(ui_video, SIGNAL(valueChanged(int)), m_config, SLOT(set_video_volume(int))); + connect(ui_video, SIGNAL(valueChanged(int)), this, SLOT(on_video_value_changed(int))); + connect(ui_video_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_video_ignore_suppression(bool))); + connect(ui_blip, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_volume(int))); + connect(ui_blip, SIGNAL(valueChanged(int)), this, SLOT(on_blip_value_changed(int))); + connect(ui_blip_ignore_suppression, SIGNAL(toggled(bool)), m_config, SLOT(set_blip_ignore_suppression(bool))); + connect(ui_blip_rate, SIGNAL(valueChanged(int)), m_config, SLOT(set_blip_rate(int))); + connect(ui_blank_blips, SIGNAL(toggled(bool)), m_config, SLOT(set_blank_blips(bool))); + + // set values + // meta + ui_autosave->setChecked(m_config->autosave()); + + // general + ui_username->setText(m_config->username()); + ui_callwords->setText(m_config->callwords()); + ui_advertiser->setText(m_config->server_advertiser()); + ui_server_alerts->setChecked(m_config->server_alerts_enabled()); + + // game + ui_theme->setCurrentText(m_config->theme()); + ui_manual_gamemode->setCurrentText(m_config->manual_gamemode()); + ui_manual_gamemode_selection->setChecked(m_config->is_manual_gamemode_selection_enabled()); + ui_manual_timeofday->setCurrentText(m_config->manual_timeofday()); + ui_manual_timeofday_selection->setChecked(m_config->is_manual_timeofday_selection_enabled()); + ui_showname->setText(m_config->showname()); + on_showname_placeholder_changed(m_config->showname_placeholder()); + ui_searchable_iniswap->setChecked(m_config->searchable_iniswap_enabled()); + ui_always_pre->setChecked(m_config->always_pre_enabled()); + ui_chat_tick_interval->setValue(m_config->chat_tick_interval()); + ui_emote_preview->setChecked(m_config->emote_preview_enabled()); + ui_sticky_sfx->setChecked(m_config->sticky_sfx_enabled()); + + // ic message + ui_length_threshold->setValue(m_config->message_length_threshold()); + + // log + ui_log_max_lines->setValue(m_config->log_max_lines()); + + if (m_config->log_is_topdown_enabled()) + { + ui_log_orientation_top_down->setChecked(true); + } + else + { + ui_log_orientation_bottom_up->setChecked(true); + } + + ui_log_display_timestamp->setChecked(m_config->log_display_timestamp_enabled()); + ui_log_display_client_id->setChecked(m_config->log_display_client_id_enabled()); + ui_log_display_self_highlight->setChecked(m_config->log_display_self_highlight_enabled()); + ui_log_format_use_newline->setChecked(m_config->log_format_use_newline_enabled()); + ui_log_display_empty_messages->setChecked(m_config->log_display_empty_messages_enabled()); + ui_log_display_music_switch->setChecked(m_config->log_display_music_switch_enabled()); + ui_log_is_recording->setChecked(m_config->log_is_recording_enabled()); + + ui_discord_presence->setChecked(m_config->discord_presence()); + ui_discord_hide_server->setChecked(m_config->discord_hide_server()); + ui_discord_hide_character->setChecked(m_config->discord_hide_character()); + + // performance + connect(m_config, SIGNAL(sprite_caching_toggled(int, bool)), this, SLOT(set_sprite_caching_toggled(int, bool))); + connect(this, SIGNAL(emit_sprite_caching_toggled(int, bool)), m_config, SLOT(set_sprite_caching(int, bool))); + for (auto it = m_cache_checkbox_map.cbegin(); it != m_cache_checkbox_map.cend(); ++it) + { + QCheckBox *l_checkbox = it.value(); + connect(l_checkbox, SIGNAL(toggled(bool)), this, SLOT(handle_sprite_caching_toggled(bool))); + l_checkbox->setChecked(m_config->sprite_caching_enabled(it.key())); + } + + connect(m_config, SIGNAL(system_memory_threshold_changed(int)), this, SLOT(set_system_memory_threshold(int))); + connect(ui_system_memory_threshold, SIGNAL(valueChanged(int)), m_config, SLOT(set_system_memory_threshold(int))); + set_system_memory_threshold(m_config->system_memory_threshold()); + + connect(m_config, SIGNAL(loading_bar_delay_changed(int)), this, SLOT(set_loading_bar_delay(int))); + connect(ui_loading_bar_delay, SIGNAL(valueChanged(int)), m_config, SLOT(set_loading_bar_delay(int))); + set_loading_bar_delay(m_config->loading_bar_delay()); + + connect(m_config, SIGNAL(caching_threshold_changed(int)), this, SLOT(set_caching_threshold(int))); + connect(ui_caching_threshold, SIGNAL(valueChanged(int)), m_config, SLOT(set_caching_threshold(int))); + set_caching_threshold(m_config->caching_threshold()); + + // audio + update_audio_device_list(); + ui_master->setValue(m_config->master_volume()); + ui_suppress_background_audio->setChecked(m_config->suppress_background_audio()); + ui_system->setValue(m_config->system_volume()); + ui_effect->setValue(m_config->effect_volume()); + ui_effect_ignore_suppression->setChecked(m_config->effect_ignore_suppression()); + ui_music->setValue(m_config->music_volume()); + ui_music_ignore_suppression->setChecked(m_config->music_ignore_suppression()); + ui_video->setValue(m_config->video_volume()); + ui_video_ignore_suppression->setChecked(m_config->video_ignore_suppression()); + ui_blip->setValue(m_config->blip_volume()); + ui_blip_ignore_suppression->setChecked(m_config->blip_ignore_suppression()); + ui_blip_rate->setValue(m_config->blip_rate()); + ui_blank_blips->setChecked(m_config->blank_blips_enabled()); + + on_manual_gamemode_selection_changed(m_config->is_manual_gamemode_selection_enabled()); + on_manual_timeofday_selection_changed(m_config->is_manual_timeofday_selection_enabled()); + + ui_about->setText(get_about_message()); +} + +void AOConfigPanel::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + + if (isVisible()) + { + refresh_theme_list(); + } +} + +void AOConfigPanel::refresh_theme_list() +{ + const QString l_current_theme = ui_theme->currentText(); + + ui_theme->clear(); + std::optional l_theme_index; + const QString l_theme_dir = DRPather::get_application_path() + "/base/themes"; + for (const QFileInfo &i_info : QDir(ao_app->get_case_sensitive_path(l_theme_dir)).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) + { + const QString l_theme = i_info.fileName(); + if (l_theme == l_current_theme) + { + l_theme_index = ui_theme->count(); + } + ui_theme->addItem(l_theme); + } + + if (l_theme_index.has_value()) + { + ui_theme->setCurrentIndex(l_theme_index.value()); + } + + update_theme_controls(); +} + +void AOConfigPanel::refresh_gamemode_list() +{ + ui_manual_gamemode->blockSignals(true); + ui_manual_gamemode->clear(); + + // add empty entry indicating no gamemode chosen + ui_manual_gamemode->addItem(""); + // gamemodes + QString path = DRPather::get_application_path() + "/base/themes/" + m_config->theme() + "/gamemodes/"; + for (const QString &i_folder : QDir(ao_app->get_case_sensitive_path(path)).entryList(QDir::Dirs)) + { + if (i_folder == "." || i_folder == "..") + continue; + ui_manual_gamemode->addItem(i_folder, i_folder); + } + + ui_manual_gamemode->setCurrentText(m_config->manual_gamemode()); + ui_manual_gamemode->blockSignals(false); +} + +void AOConfigPanel::refresh_timeofday_list() +{ + ui_manual_timeofday->blockSignals(true); + ui_manual_timeofday->clear(); + + // add empty entry indicating no time of day chosen + ui_manual_timeofday->addItem(""); + + const QString l_theme = m_config->theme(); + const QString l_gamemode = + m_config->is_manual_gamemode_selection_enabled() ? m_config->manual_gamemode() : m_config->gamemode(); + + // decide path to look for times of day. This differs whether there is a + // gamemode chosen or not + QString l_timeofday_path; + + if (l_gamemode.isEmpty()) + l_timeofday_path = DRPather::get_application_path() + "/base/themes/" + l_theme + "/times/"; + else + l_timeofday_path = + DRPather::get_application_path() + "/base/themes/" + l_theme + "/gamemodes/" + l_gamemode + "/times/"; + + // times of day + for (const QString &i_folder : QDir(ao_app->get_case_sensitive_path(l_timeofday_path)).entryList(QDir::Dirs)) + { + if (i_folder == "." || i_folder == "..") + continue; + ui_manual_timeofday->addItem(i_folder, i_folder); + } + + ui_manual_timeofday->setCurrentText(m_config->manual_timeofday()); + ui_manual_timeofday->blockSignals(false); +} + +void AOConfigPanel::update_audio_device_list() +{ + const std::optional l_current_device = m_engine->get_current_device(); + const std::optional l_favorite_device = m_engine->get_favorite_device(); + + std::optional l_prev_driver; + std::optional l_prev_driver_index; + if (ui_device->currentIndex() != -1) + l_prev_driver = ui_device->currentData().toString(); + QSignalBlocker l_blocker(ui_device); + ui_device->clear(); + + std::optional l_current_driver_index; + for (const DRAudioDevice &i_device : m_engine->get_device_list()) + { + if (!i_device.is_enabled()) + continue; + ui_device->addItem(i_device.get_name(), i_device.get_driver()); + int l_item_index = ui_device->count() - 1; + + const QString l_device_driver = i_device.get_driver(); + if (l_prev_driver.has_value() && l_prev_driver == l_device_driver) + l_prev_driver_index = l_item_index; + + if (l_current_device.has_value() && l_current_device->get_driver() == l_device_driver) + { + ui_device->setItemData(l_item_index, QColor(Qt::lightGray), Qt::BackgroundRole); + l_current_driver_index = l_item_index; + } + + if (l_favorite_device.has_value() && l_favorite_device->get_driver() == l_device_driver) + ui_device->setItemData(l_item_index, QColor(Qt::green), Qt::BackgroundRole); + } + ui_device->setCurrentIndex(l_prev_driver_index.value_or(l_current_driver_index.value_or(0))); +} + +void AOConfigPanel::update_theme_controls() +{ + const bool l_different_theme = ui_theme->currentText() != m_config->theme(); + ui_switch_theme->setVisible(l_different_theme); + ui_reload_theme->setHidden(l_different_theme); +} + +void AOConfigPanel::on_switch_theme_clicked() +{ + m_config->set_theme(ui_theme->currentText()); +} + +void AOConfigPanel::on_reload_theme_clicked() +{ + Q_EMIT reload_theme(); +} + +void AOConfigPanel::on_reload_character_clicked() +{ + Q_EMIT reload_character(); +} + +void AOConfigPanel::on_reload_audiotracks_clicked() +{ + Q_EMIT reload_audiotracks(); +} + +void AOConfigPanel::on_theme_changed(QString p_name) +{ + Q_UNUSED(p_name); + refresh_theme_list(); + refresh_gamemode_list(); + refresh_timeofday_list(); +} + +void AOConfigPanel::on_gamemode_changed(QString p_text) +{ + ui_gamemode->setText(p_text.isEmpty() ? "" : p_text); +} + +void AOConfigPanel::on_manual_gamemode_selection_changed(bool p_enabled) +{ + ui_gamemode->setHidden(p_enabled); + ui_manual_gamemode->setVisible(p_enabled); + ui_manual_gamemode_selection->setChecked(p_enabled); + refresh_gamemode_list(); + refresh_timeofday_list(); +} + +void AOConfigPanel::on_manual_gamemode_changed(QString p_name) +{ + Q_UNUSED(p_name); + refresh_gamemode_list(); + refresh_timeofday_list(); +} + +void AOConfigPanel::on_manual_gamemode_index_changed(QString p_text) +{ + Q_UNUSED(p_text); + m_config->set_manual_gamemode(ui_manual_gamemode->currentData().toString()); +} + +void AOConfigPanel::on_timeofday_changed(QString p_text) +{ + ui_timeofday->setText(p_text.isEmpty() ? "" : p_text); +} + +void AOConfigPanel::on_manual_timeofday_selection_changed(bool p_enabled) +{ + ui_timeofday->setHidden(p_enabled); + ui_manual_timeofday->setVisible(p_enabled); + ui_manual_timeofday_selection->setChecked(p_enabled); + refresh_timeofday_list(); +} + +void AOConfigPanel::on_manual_timeofday_changed(QString p_name) +{ + Q_UNUSED(p_name); + refresh_timeofday_list(); +} + +void AOConfigPanel::on_manual_timeofday_index_changed(QString p_text) +{ + Q_UNUSED(p_text); + m_config->set_manual_timeofday(ui_manual_timeofday->currentData().toString()); +} + +void AOConfigPanel::on_showname_placeholder_changed(QString p_text) +{ + const QString l_showname(p_text.trimmed().isEmpty() ? "Showname" : p_text); + ui_showname->setPlaceholderText(l_showname); + ui_showname->setToolTip(l_showname); +} + +void AOConfigPanel::on_log_is_topdown_changed(bool p_enabled) +{ + ui_log_orientation_top_down->setChecked(p_enabled); + ui_log_orientation_bottom_up->setChecked(!p_enabled); +} + +void AOConfigPanel::on_device_current_index_changed(int p_index) +{ + if (p_index == -1 || p_index >= ui_device->count()) + return; + + const QString target_device_driver = ui_device->itemData(p_index).toString(); + for (DRAudioDevice &i_device : m_engine->get_device_list()) + { + if (target_device_driver == i_device.get_driver()) + { + m_config->set_favorite_device_driver(i_device.get_driver()); + break; + } + } +} + +void AOConfigPanel::on_audio_device_changed(DRAudioDevice p_device) +{ + Q_UNUSED(p_device) + update_audio_device_list(); +} + +void AOConfigPanel::on_favorite_audio_device_changed(DRAudioDevice p_device) +{ + Q_UNUSED(p_device) + update_audio_device_list(); +} + +void AOConfigPanel::on_audio_device_list_changed(QVector p_device_list) +{ + Q_UNUSED(p_device_list); + update_audio_device_list(); +} + +void AOConfigPanel::on_master_value_changed(int p_num) +{ + ui_master_value->setText(QString::number(p_num) + "%"); +} + +void AOConfigPanel::on_system_value_changed(int p_num) +{ + ui_system_value->setText(QString::number(p_num) + "%"); +} + +void AOConfigPanel::on_effect_value_changed(int p_num) +{ + ui_effect_value->setText(QString::number(p_num) + "%"); +} + +void AOConfigPanel::on_music_value_changed(int p_num) +{ + ui_music_value->setText(QString::number(p_num) + "%"); +} + +void AOConfigPanel::on_video_value_changed(int p_num) +{ + ui_video_value->setText(QString::number(p_num) + "%"); +} + +void AOConfigPanel::on_blip_value_changed(int p_num) +{ + ui_blip_value->setText(QString::number(p_num) + "%"); +} + +void AOConfigPanel::on_length_threshold_value_changed(int p_number) +{ + ui_length_threshold_label->setText(QString::number(p_number) + "%"); +} + +void AOConfigPanel::set_sprite_caching_toggled(int p_category, bool p_enabled) +{ + QCheckBox *l_checkbox = m_cache_checkbox_map[SpriteCategory(p_category)]; + l_checkbox->setChecked(p_enabled); +} + +void AOConfigPanel::handle_sprite_caching_toggled(bool p_enabled) +{ + QObject *l_sender = sender(); + for (auto it = m_cache_checkbox_map.cbegin(); it != m_cache_checkbox_map.cend(); ++it) + { + if (l_sender == it.value()) + { + emit emit_sprite_caching_toggled(it.key(), p_enabled); + break; + } + } +} + +void AOConfigPanel::set_system_memory_threshold(int p_percent) +{ + ui_system_memory_threshold->setValue(p_percent); + ui_system_memory_threshold_label->setText(QString::number(p_percent) + "%"); +} + +void AOConfigPanel::set_loading_bar_delay(int p_number) +{ + ui_loading_bar_delay->setValue(p_number); + ui_loading_bar_delay_label->setText(QString::number(p_number) + "ms"); +} + +void AOConfigPanel::set_caching_threshold(int p_number) +{ + ui_caching_threshold->setValue(p_number); + ui_caching_threshold_label->setText(QString::number(p_number) + "%"); +} + +void AOConfigPanel::username_editing_finished() +{ + m_config->set_username(ui_username->text()); +} + +void AOConfigPanel::showname_editing_finished() +{ + m_config->set_showname(ui_showname->text()); +} + +void AOConfigPanel::advertiser_editing_finished() +{ + m_config->set_server_advertiser(ui_advertiser->text()); +} + +void AOConfigPanel::callwords_editing_finished() +{ + m_config->set_callwords(ui_callwords->text()); +} + +void AOConfigPanel::on_config_reload_theme_requested() +{ + refresh_theme_list(); + refresh_gamemode_list(); + refresh_timeofday_list(); +} diff --git a/src/aoconfigpanel.h b/src/aoconfigpanel.h new file mode 100644 index 000000000..15aba5f82 --- /dev/null +++ b/src/aoconfigpanel.h @@ -0,0 +1,199 @@ +#ifndef AOCONFIGPANEL_H +#define AOCONFIGPANEL_H + +#include "draudioengine.h" + +class AOApplication; +class AOConfig; + +#include +#include + +class QCheckBox; +class QComboBox; +class QGroupBox; +class QLabel; +class QLineEdit; +class QPushButton; +class QRadioButton; +class QSlider; +class QSpinBox; + +class AOConfigPanel : public QWidget +{ + Q_OBJECT + +public: + AOConfigPanel(AOApplication *p_ao_app, QWidget *p_parent = nullptr); + +public slots: + void on_config_reload_theme_requested(); + +signals: + void reload_theme(); + void reload_character(); + void reload_audiotracks(); + + void emit_sprite_caching_toggled(int type, bool enabled); + +protected: + void showEvent(QShowEvent *event) override; + +private: + void refresh_theme_list(); + void refresh_gamemode_list(); + void refresh_timeofday_list(); + void update_audio_device_list(); + +private slots: + void update_theme_controls(); + void on_switch_theme_clicked(); + void on_reload_theme_clicked(); + void on_reload_character_clicked(); + void on_reload_audiotracks_clicked(); + void on_theme_changed(QString); + void on_gamemode_changed(QString); + void on_manual_gamemode_selection_changed(bool); + void on_manual_gamemode_changed(QString); + void on_manual_gamemode_index_changed(QString p_text); + void on_timeofday_changed(QString); + void on_manual_timeofday_selection_changed(bool); + void on_manual_timeofday_changed(QString); + void on_manual_timeofday_index_changed(QString p_text); + void on_showname_placeholder_changed(QString p_text); + void on_log_is_topdown_changed(bool p_enabled); + void on_device_current_index_changed(int p_index); + void on_audio_device_changed(DRAudioDevice p_device); + void on_favorite_audio_device_changed(DRAudioDevice p_device); + void on_audio_device_list_changed(QVector p_device_list); + void on_master_value_changed(int p_num); + void on_system_value_changed(int p_num); + void on_effect_value_changed(int p_num); + void on_music_value_changed(int p_num); + void on_video_value_changed(int p_num); + void on_blip_value_changed(int p_num); + + void on_length_threshold_value_changed(int p_number); + + // performance + void set_sprite_caching_toggled(int type, bool enabled); + void handle_sprite_caching_toggled(bool enabled); + void set_system_memory_threshold(int megabytes); + void set_loading_bar_delay(int percent); + void set_caching_threshold(int msecs); + +private: + // FIXME This dependency shouldn't have come to exist. + AOApplication *ao_app = nullptr; + // driver + AOConfig *m_config = nullptr; + DRAudioEngine *m_engine = nullptr; + + // behaviour + QPushButton *ui_save = nullptr; + QPushButton *ui_close = nullptr; + QCheckBox *ui_autosave = nullptr; + + // notifications + QPushButton *ui_clear_notifications = nullptr; + + // general + QLineEdit *ui_username = nullptr; + QLineEdit *ui_callwords = nullptr; + QLineEdit *ui_advertiser = nullptr; + QCheckBox *ui_server_alerts = nullptr; + QGroupBox *ui_discord_presence = nullptr; + QCheckBox *ui_discord_hide_server = nullptr; + QCheckBox *ui_discord_hide_character = nullptr; + + // character + QLineEdit *ui_showname = nullptr; + QPushButton *ui_reload_character = nullptr; + QCheckBox *ui_searchable_iniswap = nullptr; + + // emotes + QCheckBox *ui_emote_preview = nullptr; + QCheckBox *ui_always_pre = nullptr; + QCheckBox *ui_sticky_sfx = nullptr; + + // dialog + QSpinBox *ui_chat_tick_interval = nullptr; + QSpinBox *ui_blip_rate = nullptr; + QCheckBox *ui_blank_blips = nullptr; + + // game + QComboBox *ui_theme = nullptr; + QPushButton *ui_switch_theme = nullptr; + QPushButton *ui_reload_theme = nullptr; + QLineEdit *ui_gamemode = nullptr; + QComboBox *ui_manual_gamemode = nullptr; + QCheckBox *ui_manual_gamemode_selection = nullptr; + QLineEdit *ui_timeofday = nullptr; + QComboBox *ui_manual_timeofday = nullptr; + QCheckBox *ui_manual_timeofday_selection = nullptr; + + // messages + QSlider *ui_length_threshold = nullptr; + QLabel *ui_length_threshold_label = nullptr; + + // chatlog + QSpinBox *ui_log_max_lines = nullptr; + QCheckBox *ui_log_display_timestamp = nullptr; + QCheckBox *ui_log_display_client_id = nullptr; + QCheckBox *ui_log_display_self_highlight = nullptr; + QCheckBox *ui_log_format_use_newline = nullptr; + QCheckBox *ui_log_display_empty_messages = nullptr; + QCheckBox *ui_log_display_music_switch = nullptr; + QRadioButton *ui_log_orientation_top_down = nullptr; + QRadioButton *ui_log_orientation_bottom_up = nullptr; + QCheckBox *ui_log_is_recording = nullptr; + + // performance + QCheckBox *ui_cache_backgrounds; + QCheckBox *ui_cache_characters; + QCheckBox *ui_cache_effects; + QCheckBox *ui_cache_shouts; + QCheckBox *ui_cache_gui; + QCheckBox *ui_cache_stickers; + QMap m_cache_checkbox_map; + + QSlider *ui_system_memory_threshold; + QLabel *ui_system_memory_threshold_label; + QSlider *ui_loading_bar_delay; + QLabel *ui_loading_bar_delay_label; + QSlider *ui_caching_threshold; + QLabel *ui_caching_threshold_label; + + // audio + QComboBox *ui_device = nullptr; + QCheckBox *ui_favorite_device = nullptr; + QSlider *ui_master = nullptr; + QLabel *ui_master_value = nullptr; + QGroupBox *ui_suppress_background_audio = nullptr; + QSlider *ui_system = nullptr; + QLabel *ui_system_value = nullptr; + QSlider *ui_effect = nullptr; + QCheckBox *ui_effect_ignore_suppression = nullptr; + QLabel *ui_effect_value = nullptr; + QSlider *ui_music = nullptr; + QCheckBox *ui_music_ignore_suppression = nullptr; + QLabel *ui_music_value = nullptr; + QSlider *ui_video = nullptr; + QCheckBox *ui_video_ignore_suppression = nullptr; + QLabel *ui_video_value = nullptr; + QSlider *ui_blip = nullptr; + QCheckBox *ui_blip_ignore_suppression = nullptr; + QLabel *ui_blip_value = nullptr; + QPushButton *ui_reload_audiotracks = nullptr; + + // about + QLabel *ui_about = nullptr; + +private slots: + void username_editing_finished(); + void showname_editing_finished(); + void advertiser_editing_finished(); + void callwords_editing_finished(); +}; + +#endif // AOCONFIGPANEL_H diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp new file mode 100644 index 000000000..249b64a6a --- /dev/null +++ b/src/aoemotebutton.cpp @@ -0,0 +1,114 @@ +#include "aoemotebutton.h" + +#include "aoapplication.h" +#include "file_functions.h" + +#include +#include +#include +#include +#include + +AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y) + : QPushButton(p_parent) +{ + ao_app = p_ao_app; + + this->move(p_x, p_y); + this->resize(40, 40); + + ui_selected = new QLabel(this); + ui_selected->resize(size()); + ui_selected->setAttribute(Qt::WA_TransparentForMouseEvents); + ui_selected->hide(); + + connect(this, SIGNAL(clicked()), this, SLOT(on_clicked())); +} + +void AOEmoteButton::set_emote_number(int p_emote_number) +{ + m_index = p_emote_number; +} + +int AOEmoteButton::get_emote_number() +{ + return m_index; +} + +void AOEmoteButton::set_image(DREmote p_emote, bool p_enabled) +{ + QString l_texture = + ao_app->get_character_path(p_emote.character, QString("emotions/button%1_off.png").arg(p_emote.key)); + + // reset states + ui_selected->hide(); + + // nested ifs are okay + if (p_enabled) + { + const QString l_enabled_texture = + ao_app->get_character_path(p_emote.character, QString("emotions/button%1_on.png").arg(p_emote.key)); + + if (file_exists(l_enabled_texture)) + { + l_texture = l_enabled_texture; + } + else + { + const QString l_selected_texture = ao_app->get_character_path(p_emote.character, "emotions/selected.png"); + + if (file_exists(l_selected_texture)) + { + ui_selected->setStyleSheet(QString("border-image: url(\"%1\")").arg(l_selected_texture)); + } + else + { + ui_selected->setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, " + "y2:1, stop:0 rgba(0, 0, 0, 0), stop:1 rgba(0, 0, 0, 127)); }"); + } + + ui_selected->show(); + } + } + + m_texture.load(l_texture); + m_comment = p_emote.comment; + setText(m_texture.isNull() ? p_emote.comment : nullptr); +} + +void AOEmoteButton::on_clicked() +{ + Q_EMIT emote_clicked(m_index); +} + +void AOEmoteButton::paintEvent(QPaintEvent *event) +{ + if (m_texture.isNull()) + { + QPushButton::paintEvent(event); + return; + } + + QPainter l_painter(this); + l_painter.drawImage(rect(), m_texture.scaled(size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + l_painter.end(); +} + +bool AOEmoteButton::event(QEvent *event) +{ + switch (event->type()) + { + case QEvent::ToolTip: + Q_EMIT tooltip_requested(m_index, dynamic_cast(event)->globalPos()); + break; + + case QEvent::Leave: + Q_EMIT mouse_left(m_index); + break; + + default: + break; + } + + return QPushButton::event(event); +} diff --git a/src/aoemotebutton.h b/src/aoemotebutton.h new file mode 100644 index 000000000..440e4ec3c --- /dev/null +++ b/src/aoemotebutton.h @@ -0,0 +1,51 @@ +#ifndef AOEMOTEBUTTON_H +#define AOEMOTEBUTTON_H + +// src +#include "datatypes.h" + +class AOApplication; + +// qt +#include + +class QLabel; + +class AOEmoteButton : public QPushButton +{ + Q_OBJECT + +public: + AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y); + + int get_emote_number(); + void set_emote_number(int emote_number); + void set_image(DREmote emote, bool enabled); + +signals: + void emote_clicked(int emote_number); + void tooltip_requested(int emote_number, QPoint global_pos); + void mouse_left(int emote_number); + + // QPushButton interface +protected: + bool event(QEvent *event) override; + +private: + AOApplication *ao_app = nullptr; + + int m_index = 0; + QImage m_texture; + QString m_comment; + + QLabel *ui_selected = nullptr; + +private slots: + void on_clicked(); + + // QWidget interface +protected: + void paintEvent(QPaintEvent *event) final; +}; + +#endif // AOEMOTEBUTTON_H diff --git a/src/aoguiloader.cpp b/src/aoguiloader.cpp new file mode 100644 index 000000000..cd5799dc6 --- /dev/null +++ b/src/aoguiloader.cpp @@ -0,0 +1,30 @@ +#include "aoguiloader.h" + +#include +#include +#include + +AOGuiLoader::AOGuiLoader(QObject *p_parent) + : QUiLoader(p_parent) +{} + +QWidget *AOGuiLoader::load_from_file(QString p_file_path, QWidget *p_parent) +{ + QWidget *r_widget = nullptr; + + QFile l_file(p_file_path); + if (l_file.open(QIODevice::ReadOnly)) + { + r_widget = load(&l_file, p_parent); + + // lazily replace the parent's layout with our own + if (p_parent != nullptr) + { + QVBoxLayout *l_parent_layout = new QVBoxLayout(p_parent); + l_parent_layout->addWidget(r_widget); + p_parent->setLayout(l_parent_layout); + } + } + + return r_widget; +} diff --git a/src/aoguiloader.h b/src/aoguiloader.h new file mode 100644 index 000000000..42b42c617 --- /dev/null +++ b/src/aoguiloader.h @@ -0,0 +1,20 @@ +#ifndef AOGUILOADER_H +#define AOGUILOADER_H + +// qt +#include + +// we could make a smarter system but let's keep it simple ;) +#define AO_GUI_WIDGET(p_type, p_name) findChild(p_name) + +class AOGuiLoader : public QUiLoader +{ + Q_OBJECT + +public: + AOGuiLoader(QObject *p_parent = nullptr); + + QWidget *load_from_file(QString p_file_path, QWidget *p_parent = nullptr); +}; + +#endif diff --git a/src/aoimagedisplay.cpp b/src/aoimagedisplay.cpp new file mode 100644 index 000000000..bab734d7b --- /dev/null +++ b/src/aoimagedisplay.cpp @@ -0,0 +1,58 @@ +#include "aoimagedisplay.h" + +#include + +#include "aoapplication.h" +#include "aopixmap.h" +#include "file_functions.h" + +/*! + * @class AOImageDisplay + * @brief Represents a static theme-dependent image. + */ +AOImageDisplay::AOImageDisplay(QWidget *parent, AOApplication *p_ao_app) + : QLabel(parent) +{ + ao_app = p_ao_app; +} + +QString AOImageDisplay::get_image() +{ + return m_image; +} + +void AOImageDisplay::set_image(QString p_image) +{ + m_image = p_image; + AOPixmap l_pixmap(p_image); + setPixmap(l_pixmap.scale(size())); +} + +void AOImageDisplay::set_theme_image(QString p_image) +{ + set_image(ao_app->find_theme_asset_path(p_image)); +} + +void AOImageDisplay::set_chatbox_image(QString p_chatbox_name, bool p_is_self) +{ + QString l_target_file = ao_app->find_asset_path(ao_app->get_base_path() + "misc/" + p_chatbox_name + ".png"); + if (l_target_file.isEmpty()) + { + l_target_file = ao_app->find_theme_asset_path("chatmed.png"); + + if (p_is_self) + { + const QString l_self_file = ao_app->find_theme_asset_path("chatmed_self.png"); + if (!l_self_file.isEmpty()) + { + l_target_file = l_self_file; + } + } + } + + if (l_target_file.isEmpty()) + { + qWarning() << "warning: could not retrieve any chatbox image, will display blank"; + } + set_image(l_target_file); +} diff --git a/src/aoimagedisplay.h b/src/aoimagedisplay.h new file mode 100644 index 000000000..abd991acf --- /dev/null +++ b/src/aoimagedisplay.h @@ -0,0 +1,24 @@ +#ifndef AOIMAGEDISPLAY_H +#define AOIMAGEDISPLAY_H + +class AOApplication; + +#include + +class AOImageDisplay : public QLabel +{ +public: + AOImageDisplay(QWidget *parent, AOApplication *p_ao_app); + + QString get_image(); + void set_image(QString p_image); + void set_theme_image(QString p_image); + void set_chatbox_image(QString p_chatbox_name, bool p_is_self); + +private: + AOApplication *ao_app = nullptr; + + QString m_image; +}; + +#endif // AOIMAGEDISPLAY_H diff --git a/src/aolabel.cpp b/src/aolabel.cpp new file mode 100644 index 000000000..8d01a2569 --- /dev/null +++ b/src/aolabel.cpp @@ -0,0 +1,15 @@ +#include "aolabel.h" + +#include "aoapplication.h" + +AOLabel::AOLabel(QWidget *parent, AOApplication *p_ao_app) + : QLabel(parent) +{ + ao_app = p_ao_app; +} + +void AOLabel::set_image(QString p_image) +{ + const QString l_image_path = ao_app->find_theme_asset_path(p_image); + setStyleSheet("border-image:url(\"" + l_image_path + "\")"); +} diff --git a/aolabel.hpp b/src/aolabel.h similarity index 68% rename from aolabel.hpp rename to src/aolabel.h index fef51e816..b1f97c421 100644 --- a/aolabel.hpp +++ b/src/aolabel.h @@ -1,11 +1,10 @@ -#ifndef AOLABEL_HPP -#define AOLABEL_HPP +#ifndef AOLABEL_H +#define AOLABEL_H -#include "aoapplication.h" +class AOApplication; #include - class AOLabel : public QLabel { public: @@ -17,4 +16,4 @@ class AOLabel : public QLabel AOApplication *ao_app = nullptr; }; -#endif // AOLABEL_HPP +#endif // AOLABEL_H diff --git a/aolineedit.cpp b/src/aolineedit.cpp similarity index 72% rename from aolineedit.cpp rename to src/aolineedit.cpp index f6026e142..cbc8efba7 100644 --- a/aolineedit.cpp +++ b/src/aolineedit.cpp @@ -1,9 +1,10 @@ #include "aolineedit.h" -AOLineEdit::AOLineEdit(QWidget *parent) : QLineEdit(parent) +AOLineEdit::AOLineEdit(QWidget *parent) + : QLineEdit(parent) { - this->setReadOnly(true); - this->setFrame(false); + setReadOnly(true); + setFrame(false); connect(this, SIGNAL(returnPressed()), this, SLOT(on_enter_pressed())); } @@ -11,7 +12,6 @@ AOLineEdit::AOLineEdit(QWidget *parent) : QLineEdit(parent) void AOLineEdit::mouseDoubleClickEvent(QMouseEvent *e) { QLineEdit::mouseDoubleClickEvent(e); - this->setReadOnly(false); } diff --git a/aolineedit.h b/src/aolineedit.h similarity index 92% rename from aolineedit.h rename to src/aolineedit.h index ce17680d5..4473577d9 100644 --- a/aolineedit.h +++ b/src/aolineedit.h @@ -2,7 +2,6 @@ #define AOLINEEDIT_H #include -#include class AOLineEdit : public QLineEdit { @@ -19,8 +18,6 @@ class AOLineEdit : public QLineEdit private slots: void on_enter_pressed(); - - }; #endif // AOLINEEDIT_H diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp new file mode 100644 index 000000000..e5c57a3eb --- /dev/null +++ b/src/aomusicplayer.cpp @@ -0,0 +1,44 @@ +#include "aomusicplayer.h" + +#include "aoapplication.h" + +#include "draudiostream.h" +#include "draudiotrackmetadata.h" +#include + +AOMusicPlayer::AOMusicPlayer(AOApplication *p_ao_app, QObject *p_parent) + : AOObject(p_ao_app, p_parent) +{ + m_family = DRAudioEngine::get_family(DRAudio::Family::FMusic); + m_family->set_capacity(1); // a single song is needed +} + +void AOMusicPlayer::play(QString p_song) +{ + stop(); + + m_filename = p_song; + + DRAudioStream::ptr l_stream = m_family->create_stream(ao_app->get_music_path(p_song)); + if (l_stream) + { + DRAudiotrackMetadata l_audiotrack(p_song); + if (!l_audiotrack.play_once()) + { + l_stream->set_repeatable(true); + l_stream->set_loop(l_audiotrack.loop_start(), l_audiotrack.loop_end()); + } + l_stream->play(); + + if (l_stream->is_playing()) + { + qDebug() << "playing" << l_stream->get_file_name(); + } + } +} + +void AOMusicPlayer::stop() +{ + for (auto &song : m_family->get_stream_list()) + song->stop(); +} diff --git a/src/aomusicplayer.h b/src/aomusicplayer.h new file mode 100644 index 000000000..3b24719d4 --- /dev/null +++ b/src/aomusicplayer.h @@ -0,0 +1,20 @@ +#pragma once + +#include "aoobject.h" +#include "draudioengine.h" + +class AOMusicPlayer : public AOObject +{ + Q_OBJECT + +public: + AOMusicPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); + +public slots: + void play(QString p_song); + void stop(); + +private: + DRAudioStreamFamily::ptr m_family; + QString m_filename; +}; diff --git a/src/aonotearea.cpp b/src/aonotearea.cpp new file mode 100644 index 000000000..253e9f59d --- /dev/null +++ b/src/aonotearea.cpp @@ -0,0 +1,106 @@ +#include "aonotearea.h" + +#include "aoapplication.h" +#include "aobutton.h" +#include "aonotepicker.h" +#include "commondefs.h" +#include "courtroom.h" +#include "theme.h" + +#include +#include +#include +#include +#include +#include + +AONoteArea::AONoteArea(QWidget *p_parent, AOApplication *p_ao_app) + : AOImageDisplay(p_parent, p_ao_app) +{ + ao_app = p_ao_app; +} + +void Courtroom::on_add_button_clicked() +{ + if (ui_note_area->m_layout->count() > 6) + return; + + AONotePicker *f_notepicker = new AONotePicker(ui_note_area, ao_app); + AOButton *f_button = new AOButton(f_notepicker, ao_app); + AOButton *f_delete = new AOButton(f_notepicker, ao_app); + QLineEdit *f_line = new QLineEdit(f_notepicker); + AOButton *f_hover = new AOButton(f_notepicker, ao_app); + QHBoxLayout *f_layout = new QHBoxLayout(f_notepicker); + + f_notepicker->ui_line = f_line; + f_notepicker->ui_button = f_button; + f_notepicker->ui_layout = f_layout; + f_notepicker->ui_delete_button = f_delete; + f_notepicker->ui_hover = f_hover; + f_notepicker->setProperty("index", ui_note_area->m_layout->count() - 1); + + f_button->set_image("note_edit.png"); + f_delete->set_image("note_delete.png"); + f_hover->set_image("note_select.png"); + + f_line->setReadOnly(true); + + f_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); + + f_layout->addWidget(f_hover); + f_layout->addWidget(f_line); + f_layout->addWidget(f_button); + f_layout->addWidget(f_delete); + f_notepicker->setLayout(f_layout); + ui_note_area->m_layout->addWidget(f_notepicker); + + if (contains_add_button) + { + ui_note_area->m_layout->removeWidget(ui_note_area->add_button); + ui_note_area->m_layout->addWidget(ui_note_area->add_button); + set_note_files(); + } + + set_stylesheet(f_line, "[LINE EDIT]", COURTROOM_STYLESHEETS_CSS, ao_app); + + connect(f_button, SIGNAL(clicked(bool)), this, SLOT(on_set_file_button_clicked())); + connect(f_delete, SIGNAL(clicked(bool)), this, SLOT(on_delete_button_clicked())); + connect(f_hover, SIGNAL(clicked(bool)), this, SLOT(on_file_selected())); +} + +void Courtroom::set_note_files() +{ + QString filename = ao_app->get_base_path() + CONFIG_FILESABSTRACT_INI; + QFile config_file(filename); + + if (!config_file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + qDebug() << "Couldn't open" << filename; + return; + } + + QTextStream in(&config_file); + + QByteArray t; + + for (int i = 0; i < ui_note_area->m_layout->count() - 1; ++i) + { + AONotePicker *f_notepicker = static_cast(ui_note_area->m_layout->itemAt(i)->widget()); + QString f_filestring = f_notepicker->m_file; + QString f_filename = f_notepicker->ui_line->text(); + + t += QString("%1 = %2 = %3\n\n").arg(i).arg(f_filestring).arg(f_filename).toUtf8(); + } + + config_file.close(); + + QFile ex(filename); + if (!ex.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) + { + qDebug() << "Couldn't open" << filename; + return; + } + + ex.write(t); + ex.close(); +} diff --git a/src/aonotearea.h b/src/aonotearea.h new file mode 100644 index 000000000..9a59d26b3 --- /dev/null +++ b/src/aonotearea.h @@ -0,0 +1,27 @@ +#ifndef AONOTEAREA_H +#define AONOTEAREA_H + +#include "aoimagedisplay.h" + +class AOApplication; +class AOButton; + +class QVBoxLayout; + +class AONoteArea : public AOImageDisplay +{ + Q_OBJECT + +public: + AOButton *add_button = nullptr; + QVBoxLayout *m_layout = nullptr; + + AONoteArea(QWidget *p_parent, AOApplication *p_ao_app); + +private: + AOApplication *ao_app = nullptr; + + void set_layout(); +}; + +#endif // AONOTEAREA_H diff --git a/src/aonotepad.cpp b/src/aonotepad.cpp new file mode 100644 index 000000000..63c0d5a7d --- /dev/null +++ b/src/aonotepad.cpp @@ -0,0 +1,7 @@ +#include "aonotepad.h" + +AONotepad::AONotepad(QWidget *p_parent, AOApplication *p_ao_app) + : DRTextEdit(p_parent) + , ao_app(p_ao_app) +{ +} diff --git a/src/aonotepad.h b/src/aonotepad.h new file mode 100644 index 000000000..1f9b62172 --- /dev/null +++ b/src/aonotepad.h @@ -0,0 +1,19 @@ +#ifndef AONOTEPAD_H +#define AONOTEPAD_H + +#include "drtextedit.h" + +class AOApplication; + +class AONotepad : public DRTextEdit +{ + Q_OBJECT + +public: + AONotepad(QWidget *p_parent, AOApplication *p_ao_app); + +private: + AOApplication *ao_app = nullptr; +}; + +#endif // AONOTEPAD_H diff --git a/src/aonotepicker.cpp b/src/aonotepicker.cpp new file mode 100644 index 000000000..4098072f2 --- /dev/null +++ b/src/aonotepicker.cpp @@ -0,0 +1,97 @@ +#include "aonotepicker.h" + +#include "aobutton.h" +#include "aonotearea.h" +#include "courtroom.h" +#include "debug_functions.h" +#include "drpather.h" + +#include +#include +#include + +AONotePicker::AONotePicker(QWidget *p_parent, AOApplication *p_ao_app) + : QLabel(p_parent) +{ + ao_app = p_ao_app; +} + +void Courtroom::on_file_selected() +{ + for (int i = 0; i < ui_note_area->m_layout->count() - 1; ++i) + { + AONotePicker *f_notepicker = static_cast(ui_note_area->m_layout->itemAt(i)->widget()); + f_notepicker->ui_hover->set_image("note_select.png"); + } + + AOButton *f_button = static_cast(sender()); + AONotePicker *f_notepicker = static_cast(f_button->parent()); + + if (f_notepicker->m_file.isEmpty()) + { + call_notice("You must give a filepath to load a note from!"); + return; + } + + if (current_file != f_notepicker->m_file) + { + current_file = f_notepicker->m_file; + load_note(); + f_notepicker->ui_hover->set_image("note_select_selected.png"); + f_notepicker->set_active(true); + } + else + { + current_file = ""; + f_notepicker->set_active(false); + } +} + +void Courtroom::on_set_file_button_clicked() +{ + AOButton *f_button = static_cast(sender()); + AONotePicker *f_notepicker = static_cast(f_button->parent()); + QString f_filename = + QFileDialog::getOpenFileName(this, "Open File", DRPather::get_application_path(), "Text files (*.txt)"); + + if (f_filename != "") + { + f_notepicker->ui_line->setText(f_filename); + + // If this notepicker is the currently selected slot, update the notepad as the file given changes. + if (f_notepicker->is_active()) + { + current_file = f_filename; + load_note(); + } + + f_notepicker->m_file = f_filename; + + set_note_files(); + } +} + +void Courtroom::on_delete_button_clicked() +{ + AOButton *f_button = static_cast(sender()); + AONotePicker *f_notepicker = static_cast(f_button->parent()); + + if (current_file == f_notepicker->m_file) + { + current_file = ""; + } + + ui_note_area->m_layout->removeWidget(f_notepicker); + delete f_notepicker; + set_note_files(); +} + +void AONotePicker::set_active(bool p_active) +{ + m_active = p_active; +} + +bool AONotePicker::is_active() +{ + return m_active; +} diff --git a/src/aonotepicker.h b/src/aonotepicker.h new file mode 100644 index 000000000..9f550303d --- /dev/null +++ b/src/aonotepicker.h @@ -0,0 +1,36 @@ +#ifndef AONOTEPICKER_H +#define AONOTEPICKER_H + +class AOApplication; +class AOButton; + +#include + +class QHBoxLayout; +class QLineEdit; + +class AONotePicker : public QLabel +{ + Q_OBJECT + +public: + AONotePicker(QWidget *p_parent, AOApplication *p_ao_app); + + QString m_file; + + QLineEdit *ui_line = nullptr; + AOButton *ui_button = nullptr; + AOButton *ui_delete_button = nullptr; + AOButton *ui_hover = nullptr; + QHBoxLayout *ui_layout = nullptr; + + void set_active(bool p_active); + bool is_active(); + +private: + AOApplication *ao_app = nullptr; + + bool m_active = false; +}; + +#endif // AONOTEPICKER_H diff --git a/src/aoobject.cpp b/src/aoobject.cpp new file mode 100644 index 000000000..2de19f263 --- /dev/null +++ b/src/aoobject.cpp @@ -0,0 +1,6 @@ +#include "aoobject.h" + +AOObject::AOObject(AOApplication *p_ao_app, QObject *p_parent) + : QObject(p_parent) + , ao_app(p_ao_app) +{} diff --git a/src/aoobject.h b/src/aoobject.h new file mode 100644 index 000000000..7838b2a30 --- /dev/null +++ b/src/aoobject.h @@ -0,0 +1,16 @@ +#pragma once + +class AOApplication; + +#include + +class AOObject : public QObject +{ + Q_OBJECT + +public: + AOObject(AOApplication *p_ao_app, QObject *p_parent = nullptr); + +protected: + AOApplication *ao_app = nullptr; +}; diff --git a/src/aopixmap.cpp b/src/aopixmap.cpp new file mode 100644 index 000000000..26c029c86 --- /dev/null +++ b/src/aopixmap.cpp @@ -0,0 +1,34 @@ +#include "aopixmap.h" + +AOPixmap::AOPixmap(QPixmap p_pixmap) + : m_pixmap(p_pixmap) +{ + if (m_pixmap.isNull()) + { + clear(); + } +} + +AOPixmap::AOPixmap(QString p_file_path) + : AOPixmap(QPixmap(p_file_path)) +{} + +void AOPixmap::clear() +{ + m_pixmap = QPixmap(1, 1); + m_pixmap.fill(Qt::transparent); +} + +QPixmap AOPixmap::scale(QSize p_size) +{ + const bool l_pixmap_is_larger = m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); + const Qt::TransformationMode f_mode = l_pixmap_is_larger ? Qt::SmoothTransformation : Qt::FastTransformation; + return m_pixmap.scaled(p_size, Qt::IgnoreAspectRatio, f_mode); +} + +QPixmap AOPixmap::scale_to_height(QSize p_size) +{ + const bool l_pixmap_is_larger = m_pixmap.width() > p_size.width() || m_pixmap.height() > p_size.height(); + return m_pixmap.scaledToHeight(p_size.height(), + l_pixmap_is_larger ? Qt::SmoothTransformation : Qt::FastTransformation); +} diff --git a/src/aopixmap.h b/src/aopixmap.h new file mode 100644 index 000000000..510abb91d --- /dev/null +++ b/src/aopixmap.h @@ -0,0 +1,21 @@ +#ifndef AOPIXMAP_H +#define AOPIXMAP_H + +#include + +class AOPixmap +{ +public: + AOPixmap(QPixmap p_pixmap = QPixmap()); + AOPixmap(QString p_file_path); + + void clear(); + + QPixmap scale(QSize p_size); + QPixmap scale_to_height(QSize p_size); + +private: + QPixmap m_pixmap; +}; + +#endif diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp new file mode 100644 index 000000000..618f348d1 --- /dev/null +++ b/src/aosfxplayer.cpp @@ -0,0 +1,157 @@ +#include "aosfxplayer.h" + +#include "aoapplication.h" +#include "draudioengine.h" +#include "draudiostream.h" +#include "file_functions.h" + +#include +#include + +AOSfxPlayer::AOSfxPlayer(AOApplication *p_ao_app, QObject *p_parent) + : AOObject(p_ao_app, p_parent) + , m_player(DRAudioEngine::get_family(DRAudio::Family::FEffect)) +{} + +void AOSfxPlayer::play(QString p_filename) +{ + auto l_stream = m_player->play_stream(p_filename); + if (l_stream) + { + qWarning() << "Playing effect" << p_filename; + m_stream_list.append(l_stream); + } +} + +void AOSfxPlayer::play_effect(QString p_effect) +{ + play(ao_app->find_asset_path({ao_app->get_sfx_noext_path(p_effect)}, audio_extensions())); +} + +void AOSfxPlayer::play_character_effect(QString p_chr, QString p_effect) +{ + QStringList l_file_list; + for (const QString &i_chr : ao_app->get_char_include_tree(p_chr)) + l_file_list.append(ao_app->get_character_path(i_chr, QString("sounds/%1").arg(p_effect))); + + const QString l_target_file = ao_app->find_asset_path(l_file_list, audio_extensions()); + if (l_target_file.isEmpty()) + { + play_effect(p_effect); + return; + } + + play(l_target_file); +} + +void AOSfxPlayer::stop_all() +{ + for (const DRAudioStream::ptr &i_stream : qAsConst(m_stream_list)) + { + i_stream->stop(); + } + m_stream_list.clear(); +} + +void AOSfxPlayer::play_ambient(QString p_filename) +{ + if (m_current_ambient) + { + if (m_current_ambient->get_file_name() == p_filename) + { + return; + } + + m_current_ambient->fadeOut(DEFAULT_FADE_DURATION); + } + + DRAudioStream::ptr l_ambient; + if (!m_ambient_map.contains(p_filename)) + { + l_ambient = m_player->create_stream(p_filename); + + if (l_ambient) + { + qInfo() << "Playing ambient" << p_filename; + m_ambient_map.insert(p_filename, l_ambient); + + connect(l_ambient.data(), SIGNAL(faded(DRAudioStream::Fade)), this, SLOT(handle_ambient_fade(DRAudioStream::Fade))); + connect(l_ambient.data(), SIGNAL(finished()), this, SLOT(remove_ambient())); + + l_ambient->set_repeatable(true); + } + else + { + return; + } + } + else + { + qInfo() << "Restoring ambient" << p_filename; + l_ambient = m_ambient_map[p_filename]; + } + m_current_ambient = l_ambient; + + if (m_current_ambient.isNull()) + { + return; + } + + m_current_ambient->fadeIn(DEFAULT_FADE_DURATION); + + if (!m_current_ambient->is_playing()) + { + m_current_ambient->play(); + } +} + +DRAudioStream::ptr AOSfxPlayer::get_stream_by_qobject(QObject *p_object) +{ + auto *l_stream_ptr = dynamic_cast(p_object); + if (!l_stream_ptr) + { + qCritical() << "error: object was not an audio stream" << p_object; + return nullptr; + } + + for (auto it = m_ambient_map.cbegin(); it != m_ambient_map.cend(); ++it) + { + const auto &i_stream = it.value(); + if (l_stream_ptr == i_stream) + { + return i_stream; + } + } + + return nullptr; +} + +void AOSfxPlayer::remove_ambient() +{ + auto l_stream = get_stream_by_qobject(sender()); + if (l_stream.isNull()) + { + return; + } + + qDebug() << "Removing ambient" << l_stream->get_file_name(); + m_ambient_map.remove(l_stream->get_file_name()); + if (m_current_ambient == l_stream) + { + m_current_ambient.reset(); + } +} + +void AOSfxPlayer::handle_ambient_fade(DRAudioStream::Fade p_fade) +{ + const auto l_stream = get_stream_by_qobject(sender()); + if (l_stream.isNull()) + { + return; + } + + if (p_fade == DRAudioStream::FadeOut) + { + l_stream->stop(); + } +} diff --git a/src/aosfxplayer.h b/src/aosfxplayer.h new file mode 100644 index 000000000..03ca99f8e --- /dev/null +++ b/src/aosfxplayer.h @@ -0,0 +1,37 @@ +#pragma once + +#include "aoobject.h" +#include "draudiostream.h" +#include "draudiostreamfamily.h" + +#include +#include + +class AOSfxPlayer : public AOObject +{ + Q_OBJECT + +public: + static const int DEFAULT_FADE_DURATION = 5000; + + AOSfxPlayer(AOApplication *ao_app, QObject *parent = nullptr); + + void play(QString filename); + void play_effect(QString effect); + void play_character_effect(QString character, QString effect); + void stop_all(); + + void play_ambient(QString filename); + +private: + DRAudioStreamFamily::ptr m_player; + QVector m_stream_list; + QMap m_ambient_map; + DRAudioStream::ptr m_current_ambient; + + DRAudioStream::ptr get_stream_by_qobject(QObject *object); + +private slots: + void remove_ambient(); + void handle_ambient_fade(DRAudioStream::Fade fade); +}; diff --git a/src/aoshoutplayer.cpp b/src/aoshoutplayer.cpp new file mode 100644 index 000000000..bdf3c7622 --- /dev/null +++ b/src/aoshoutplayer.cpp @@ -0,0 +1,24 @@ +#include "aoshoutplayer.h" + +#include "aoapplication.h" +#include "draudioengine.h" +#include "file_functions.h" + +AOShoutPlayer::AOShoutPlayer(AOApplication *p_ao_app, QObject *p_parent) + : AOObject(p_ao_app, p_parent) +{} + +void AOShoutPlayer::play(QString p_chr, QString p_shout) +{ + QStringList l_file_list; + for (const QString &i_chr : ao_app->get_char_include_tree(p_chr)) + l_file_list.append(ao_app->get_character_path(i_chr, p_shout)); + + QString l_file = ao_app->find_asset_path(l_file_list, audio_extensions()); + if (l_file.isEmpty()) + l_file = ao_app->find_theme_asset_path(p_shout, audio_extensions()); + + if (l_file.isEmpty()) + return; + DRAudioEngine::get_family(DRAudio::Family::FEffect)->play_stream(l_file); +} diff --git a/src/aoshoutplayer.h b/src/aoshoutplayer.h new file mode 100644 index 000000000..e04c5f16c --- /dev/null +++ b/src/aoshoutplayer.h @@ -0,0 +1,16 @@ +#ifndef AOSHOUTPLAYER_H +#define AOSHOUTPLAYER_H + +#include "aoobject.h" + +class AOShoutPlayer : public AOObject +{ + Q_OBJECT + +public: + AOShoutPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); + + void play(QString character, QString shout); +}; + +#endif // AOSHOUTPLAYER_H diff --git a/src/aosystemplayer.cpp b/src/aosystemplayer.cpp new file mode 100644 index 000000000..b65761a9b --- /dev/null +++ b/src/aosystemplayer.cpp @@ -0,0 +1,15 @@ +#include "aosystemplayer.h" + +#include "aoapplication.h" +#include "draudioengine.h" +#include "file_functions.h" + +AOSystemPlayer::AOSystemPlayer(AOApplication *p_ao_app, QObject *p_parent) + : AOObject(p_ao_app, p_parent) +{} + +void AOSystemPlayer::play(QString p_name) +{ + const QString file = ao_app->find_asset_path({ao_app->get_sfx_noext_path(p_name)}, audio_extensions()); + DRAudioEngine::get_family(DRAudio::Family::FSystem)->play_stream(file); +} diff --git a/src/aosystemplayer.h b/src/aosystemplayer.h new file mode 100644 index 000000000..cda463a52 --- /dev/null +++ b/src/aosystemplayer.h @@ -0,0 +1,13 @@ +#pragma once + +#include "aoobject.h" + +class AOSystemPlayer : public AOObject +{ + Q_OBJECT + +public: + AOSystemPlayer(AOApplication *p_ao_app, QObject *p_parent = nullptr); + + void play(QString p_file); +}; diff --git a/src/aotimer.cpp b/src/aotimer.cpp new file mode 100644 index 000000000..44511729c --- /dev/null +++ b/src/aotimer.cpp @@ -0,0 +1,162 @@ +#include "aotimer.h" + +#include +#include + +AOTimer::AOTimer(QWidget *p_parent) + : DRTextEdit(p_parent) +{ + // Adapted from: + // https://stackoverflow.com/questions/36679708/how-to-make-a-chronometer-in-qt-c + setStyleSheet("QLabel { color : white; }"); + setFrameStyle(QFrame::NoFrame); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setReadOnly(true); + + firing_timer = new QTimer(this); + firing_timer->setTimerType(Qt::PreciseTimer); + firing_timer->setInterval(firing_timer_length); + connect(firing_timer, SIGNAL(timeout()), this, SLOT(update_time())); + + set_time(start_time); + old_manual_timer.set_time(start_time); + set_timestep_length(manual_timer_timestep_length); + redraw(); +} + +void AOTimer::update_time() +{ + time_spent_in_timestep = 0; + manual_timer.perform_timestep(); + + // Check for underflow/overflow + + // This one checks overflows, as the updated manual timer would appear to have + // a past time compared to the manual timer a step ago (represented by + // old_manual_timer) + if (manual_timer.get_timestep_length() > 0) + { + if (manual_timer.get_time().operator<(old_manual_timer.get_time())) + { + set_time(QTime(0, 0)); + firing_timer->stop(); + redraw(); + return; + } + } + // This one checks underflows, as the updated manual timer would appear to + // have a future time compared to the manual timer a step ago (represented by + // old_manual_timer) + else if (manual_timer.get_timestep_length() < 0) + { + if (manual_timer.get_time().operator>(old_manual_timer.get_time())) + { + set_time(QTime(0, 0)); + firing_timer->stop(); + redraw(); + return; + } + } + + // If no overflow/underflow, now update the old manual timer so it matches the + // current timer Redraw and return + old_manual_timer.perform_timestep(); + redraw(); + // As the firing timer is not single shot, it will restart automatically once + // expired. However, it will do so with its firing interval, whatever it was + // (which could be less than firing_timer_length if, say, the timer was + // paused midstep). + // The next firing_timer should take firing_timer_length miliseconds no + // matter what. However, manually restarting a timer is a very expensive QT + // operation, while QT's built-in automatic restart is very efficient. + // Therefore, we only restart firing_timer if its length was not + // firing_timer_length already (see example above). + if (firing_timer->interval() != firing_timer_length) + firing_timer->start(firing_timer_length); +} + +void AOTimer::set() +{ + set_time(start_time); +} + +void AOTimer::resume() +{ + paused = false; + firing_timer->start(); +} + +void AOTimer::pause() +{ + paused = true; + int remaining = firing_timer->remainingTime(); + firing_timer->stop(); + firing_timer->setInterval(remaining); +} + +void AOTimer::redraw() +{ + setText(manual_timer.get_time().toString("mm:ss.zzz")); +} + +void AOTimer::set_time(QTime new_time) +{ + manual_timer.set_time(new_time); + old_manual_timer.set_time(new_time); + redraw(); +} + +void AOTimer::set_timestep_length(int new_timestep_length) +{ + manual_timer.set_timestep_length(new_timestep_length); + old_manual_timer.set_timestep_length(new_timestep_length); +} + +void AOTimer::set_firing_interval(int new_firing_interval) +{ + /* + * Update the firing interval of the steptimer for all future timesteps. If a + * timestep has started when this function is scheduled to run, the current + * timestep will be readapted to finish in this new firing interval as + * follows: + * - Assume length x of the 'current' timestep with orig has elapsed. + * - Then, the current timestep will be adapted to have length max(0, + * new_interval-x) + */ + + // Update time spent so far and new future firing interval + time_spent_in_timestep += (firing_timer_length - firing_timer->remainingTime()); + firing_timer_length = new_firing_interval; + // For this timestep however, the firing interval will be shorter than + // firing_timer_length to account for the fact the timer may have already been + // running a bit in this timestep + int this_step_firing_interval; + if (new_firing_interval < time_spent_in_timestep) + this_step_firing_interval = 0; + else + this_step_firing_interval = new_firing_interval - time_spent_in_timestep; + + firing_timer->setInterval(this_step_firing_interval); + + if (!paused) + firing_timer->start(); +} + +void AOTimer::set_concentrate_mode() +{ + set_timestep_length(-12); + set_firing_interval(120); +} + +void AOTimer::set_normal_mode() +{ + set_timestep_length(-12); + set_firing_interval(12); +} + +void AOTimer::set_fast_forward_mode() +{ + set_timestep_length(-24); + set_firing_interval(12); +} diff --git a/src/aotimer.h b/src/aotimer.h new file mode 100644 index 000000000..96c71d5d3 --- /dev/null +++ b/src/aotimer.h @@ -0,0 +1,72 @@ +#ifndef AOTIMER_H +#define AOTIMER_H + +#include "drtextedit.h" + +#include + +class QTimer; + +class ManualTimer +{ +public: + QTime get_time() + { + return current_time; + } + int get_timestep_length() + { + return timestep_length; + } + + void set_time(QTime new_time) + { + current_time = new_time; + } + void set_timestep_length(int new_timestep_length) + { + timestep_length = new_timestep_length; + } + void perform_timestep() + { + current_time = current_time.addMSecs(timestep_length); + } + +private: + QTime current_time; + int timestep_length; +}; + +class AOTimer : public DRTextEdit +{ + Q_OBJECT + +public: + AOTimer(QWidget *p_parent); + +public slots: + void update_time(); + void set(); + void resume(); + void pause(); + void redraw(); + void set_time(QTime new_time); + void set_timestep_length(int new_timestep_length); + void set_firing_interval(int new_firing_interval); + void set_concentrate_mode(); + void set_normal_mode(); + void set_fast_forward_mode(); + +private: + ManualTimer old_manual_timer; // Pre-update manual timer + ManualTimer manual_timer; + QTimer *firing_timer = nullptr; + QTime start_time = QTime(0, 0); + // All of this is in miliseconds + int manual_timer_timestep_length = -12; + int firing_timer_length = 12; + int time_spent_in_timestep = 0; + bool paused; +}; + +#endif // AOTIMER_H diff --git a/src/audio_functions.cpp b/src/audio_functions.cpp new file mode 100644 index 000000000..a9b633e35 --- /dev/null +++ b/src/audio_functions.cpp @@ -0,0 +1,26 @@ +#include "courtroom.h" + +#include "draudioengine.h" + +bool Courtroom::is_audio_suppressed() const +{ + return is_audio_muted; +} + +void Courtroom::suppress_audio(bool p_enabled) +{ + is_audio_muted = p_enabled; + + // suppress audio + for (auto &family : DRAudioEngine::get_family_list()) + { + family->set_suppressed(is_audio_muted); + } +} + +void Courtroom::stop_all_audio() +{ + for (auto &family : DRAudioEngine::get_family_list()) + for (auto &stream : family->get_stream_list()) + stream->stop(); +} diff --git a/src/charselect.cpp b/src/charselect.cpp new file mode 100644 index 000000000..e8e403df1 --- /dev/null +++ b/src/charselect.cpp @@ -0,0 +1,208 @@ +#include "courtroom.h" + +#include "aoapplication.h" +#include "aobutton.h" +#include "aocharbutton.h" +#include "aoconfig.h" +#include "aoimagedisplay.h" +#include "commondefs.h" +#include "debug_functions.h" +#include "drpacket.h" +#include "file_functions.h" +#include "hardware_functions.h" +#include "theme.h" + +#include +#include +#include + +void Courtroom::construct_char_select() +{ + ui_char_select_background = new AOImageDisplay(this, ao_app); + + ui_char_buttons = new QWidget(ui_char_select_background); + + ui_char_button_selector = new AOImageDisplay(ui_char_buttons, ao_app); + ui_char_button_selector->setAttribute(Qt::WA_TransparentForMouseEvents); + ui_char_button_selector->resize(62, 62); + + ui_back_to_lobby = new AOButton(ui_char_select_background, ao_app); + + ui_chr_select_left = new AOButton(ui_char_select_background, ao_app); + ui_chr_select_right = new AOButton(ui_char_select_background, ao_app); + + ui_spectator = new AOButton(ui_char_select_background, ao_app); + ui_spectator->setText("Spectator"); + + connect(char_button_mapper, SIGNAL(mapped(int)), this, SLOT(char_clicked(int))); + connect(ui_back_to_lobby, SIGNAL(clicked()), this, SLOT(on_back_to_lobby_clicked())); + + connect(ui_chr_select_left, SIGNAL(clicked()), this, SLOT(on_char_select_left_clicked())); + connect(ui_chr_select_right, SIGNAL(clicked()), this, SLOT(on_char_select_right_clicked())); + + connect(ao_config, SIGNAL(character_ini_changed(QString)), this, SLOT(update_character_icon(QString))); + + connect(ui_spectator, SIGNAL(clicked()), this, SLOT(on_spectator_clicked())); + + reconstruct_char_select(); +} + +void Courtroom::reconstruct_char_select() +{ + qDeleteAll(ui_char_button_list.begin(), ui_char_button_list.end()); + + QPoint f_spacing = ao_app->get_button_spacing("char_button_spacing", COURTROOM_DESIGN_INI); + + const int button_width = 60; + int x_spacing = f_spacing.x(); + int x_mod_count = 0; + + const int button_height = 60; + int y_spacing = f_spacing.y(); + int y_mod_count = 0; + + char_columns = ((ui_char_buttons->width() - button_width) / (x_spacing + button_width)) + 1; + char_rows = ((ui_char_buttons->height() - button_height) / (y_spacing + button_height)) + 1; + + m_page_max_chr_count = qMax(1, char_columns * char_rows); + + ui_char_button_list.clear(); + for (int n = 0; n < m_page_max_chr_count; ++n) + { + int x_pos = (button_width + x_spacing) * x_mod_count; + int y_pos = (button_height + y_spacing) * y_mod_count; + + AOCharButton *l_button = new AOCharButton(ui_char_buttons, ao_app, x_pos, y_pos); + ui_char_button_list.append(l_button); + + connect(l_button, SIGNAL(clicked()), char_button_mapper, SLOT(map())); + char_button_mapper->setMapping(l_button, n); + + // mouse events + connect(l_button, SIGNAL(mouse_entered(AOCharButton *)), this, SLOT(char_mouse_entered(AOCharButton *))); + connect(l_button, SIGNAL(mouse_left()), this, SLOT(char_mouse_left())); + + ++x_mod_count; + + if (x_mod_count == char_columns) + { + ++y_mod_count; + x_mod_count = 0; + } + } + + reset_char_select(); +} + +void Courtroom::reset_char_select() +{ + m_current_chr_page = 0; + + set_char_select(); + set_char_select_page(); +} + +void Courtroom::set_char_select() +{ + pos_size_type f_charselect = ao_app->get_element_dimensions("char_select", COURTROOM_DESIGN_INI); + if (f_charselect.width < 0 || f_charselect.height < 0) + qWarning() << "warning: char_select not found or invalid within courtroom_design.ini"; + + ui_char_select_background->resize(f_charselect.width, f_charselect.height); + ui_char_select_background->set_theme_image("charselect_background.png"); +} + +void Courtroom::set_char_select_page() +{ + ui_char_select_background->show(); + + ui_chr_select_left->hide(); + ui_chr_select_right->hide(); + + for (AOCharButton *button : qAsConst(ui_char_button_list)) + button->hide(); + + const int l_item_count = m_chr_list.length(); + const int l_page_count = qFloor(l_item_count / m_page_max_chr_count) + bool(l_item_count % m_page_max_chr_count); + m_current_chr_page = qBound(0, m_current_chr_page, l_page_count - 1); + const int l_current_page_emote_count = + qBound(0, l_item_count - m_current_chr_page * m_page_max_chr_count, m_page_max_chr_count); + + if (m_current_chr_page + 1 < l_page_count) + ui_chr_select_right->show(); + + if (m_current_chr_page > 0) + ui_chr_select_left->show(); + + // show all buttons for this page + for (int i = 0; i < l_current_page_emote_count; ++i) + { + int l_real_i = i + m_current_chr_page * m_page_max_chr_count; + AOCharButton *l_button = ui_char_button_list.at(i); + const QString l_base_chr = m_chr_list.at(l_real_i).name; + l_button->set_character(l_base_chr, ao_config->character_ini(l_base_chr)); + l_button->set_taken((m_chr_list.at(l_real_i).taken)); + l_button->show(); + } +} + +void Courtroom::on_char_select_left_clicked() +{ + --m_current_chr_page; + set_char_select_page(); +} + +void Courtroom::on_char_select_right_clicked() +{ + ++m_current_chr_page; + set_char_select_page(); +} + +void Courtroom::update_character_icon(QString p_character) +{ + for (AOCharButton *i_button : qAsConst(ui_char_button_list)) + { + if (i_button->character() != p_character) + continue; + i_button->set_character(p_character, ao_config->character_ini(p_character)); + break; + } +} + +void Courtroom::char_clicked(int n_char) +{ + if (get_character() == ui_char_button_list.at(n_char)->character()) + { + enter_courtroom(get_character_id()); + return; + } + + int n_real_char = n_char + m_current_chr_page * m_page_max_chr_count; + + QString char_ini_path = ao_app->get_character_path(m_chr_list.at(n_real_char).name, CHARACTER_CHAR_INI); + qDebug() << "char_ini_path" << char_ini_path; + + if (!file_exists(char_ini_path)) + { + qDebug() << "did not find " << char_ini_path; + call_notice("Could not find " + char_ini_path); + return; + } + + ao_app->send_server_packet( + DRPacket("CC", {QString::number(ao_app->get_client_id()), QString::number(n_real_char), "HDID"})); +} + +void Courtroom::char_mouse_entered(AOCharButton *p_caller) +{ + if (p_caller == nullptr) + return; + ui_char_button_selector->move(p_caller->x() - 1, p_caller->y() - 1); + ui_char_button_selector->raise(); + ui_char_button_selector->show(); +} + +void Courtroom::char_mouse_left() +{ + ui_char_button_selector->hide(); +} diff --git a/src/commondefs.cpp b/src/commondefs.cpp new file mode 100644 index 000000000..8a655353c --- /dev/null +++ b/src/commondefs.cpp @@ -0,0 +1,25 @@ +#include "commondefs.h" + +#include + +const QString BASE_CONFIG_INI = "/base/config.ini"; +const QString BASE_SERVER_BROWSER_INI = "server_browser.ini"; +const QString BASE_FAVORITE_SERVERS_INI = "favorite_servers.ini"; +const QString BASE_SERVERLIST_TXT = "serverlist.txt"; + +const QString CHARACTER_CHAR_INI = "char.ini"; +const QString CHARACTER_SOUNDS_INI = "sounds.ini"; + +const QString CONFIG_SOUNDS_INI = "configs/sounds.ini"; +const QString CONFIG_FILESABSTRACT_INI = "configs/filesabstract.ini"; + +const QString COURTROOM_CONFIG_INI = "courtroom_config.ini"; +const QString COURTROOM_DESIGN_INI = "courtroom_design.ini"; +const QString COURTROOM_FONTS_INI = "courtroom_fonts.ini"; +const QString COURTROOM_LAYERS_INI = "courtroom_layers.ini"; +const QString COURTROOM_SOUNDS_INI = "courtroom_sounds.ini"; +const QString COURTROOM_TEXT_COLOR_INI = "courtroom_text_colors.ini"; +const QString COURTROOM_STYLESHEETS_CSS = "courtroom_stylesheets.css"; + +const QString LOBBY_DESIGN_INI = "lobby_design.ini"; +const QString LOBBY_FONTS_INI = "lobby_fonts.ini"; diff --git a/src/commondefs.h b/src/commondefs.h new file mode 100644 index 000000000..0c63a9868 --- /dev/null +++ b/src/commondefs.h @@ -0,0 +1,25 @@ +#pragma once + +class QString; + +extern const QString BASE_CONFIG_INI; +extern const QString BASE_SERVER_BROWSER_INI; +extern const QString BASE_FAVORITE_SERVERS_INI; +extern const QString BASE_SERVERLIST_TXT; + +extern const QString CHARACTER_CHAR_INI; +extern const QString CHARACTER_SOUNDS_INI; + +extern const QString CONFIG_SOUNDS_INI; +extern const QString CONFIG_FILESABSTRACT_INI; + +extern const QString COURTROOM_CONFIG_INI; +extern const QString COURTROOM_DESIGN_INI; +extern const QString COURTROOM_FONTS_INI; +extern const QString COURTROOM_LAYERS_INI; +extern const QString COURTROOM_SOUNDS_INI; +extern const QString COURTROOM_TEXT_COLOR_INI; +extern const QString COURTROOM_STYLESHEETS_CSS; + +extern const QString LOBBY_DESIGN_INI; +extern const QString LOBBY_FONTS_INI; diff --git a/src/courtroom.cpp b/src/courtroom.cpp new file mode 100644 index 000000000..16bf06909 --- /dev/null +++ b/src/courtroom.cpp @@ -0,0 +1,2692 @@ +#include "courtroom.h" + +#include "aoapplication.h" +#include "aoblipplayer.h" +#include "aobutton.h" +#include "aoconfig.h" +#include "aoimagedisplay.h" +#include "aomusicplayer.h" +#include "aonotearea.h" +#include "aonotepicker.h" +#include "aosfxplayer.h" +#include "aoshoutplayer.h" +#include "aosystemplayer.h" +#include "aotimer.h" +#include "commondefs.h" +#include "debug_functions.h" +#include "draudiotrackmetadata.h" +#include "drcharactermovie.h" +#include "drchatlog.h" +#include "drdiscord.h" +#include "dreffectmovie.h" +#include "drpacket.h" +#include "drscenemovie.h" +#include "drshoutmovie.h" +#include "drsplashmovie.h" +#include "drstickerviewer.h" +#include "file_functions.h" +#include "mk2/graphicsvideoscreen.h" +#include "mk2/spritedynamicreader.h" +#include "mk2/spriteseekingreader.h" +#include "src/datatypes.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const int Courtroom::DEFAULT_WIDTH = 714; +const int Courtroom::DEFAULT_HEIGHT = 668; + +Courtroom::Courtroom(AOApplication *p_ao_app, QWidget *parent) + : QWidget(parent) +{ + ao_app = p_ao_app; + ao_config = new AOConfig(this); + + m_preloader_sync = new mk2::SpriteReaderSynchronizer(this); + m_preloader_sync->set_threshold(ao_config->caching_threshold()); + + connect(ao_app, SIGNAL(reload_theme()), this, SLOT(reload_theme())); + connect(ao_app, SIGNAL(reload_character()), this, SLOT(load_character())); + connect(ao_app, SIGNAL(reload_audiotracks()), this, SLOT(load_audiotracks())); + + create_widgets(); + connect_widgets(); + + while (m_chatmessage.length() < OPTIMAL_MESSAGE_SIZE) + { + m_chatmessage.append(QString{}); + } + m_chatmessage[CMPosition] = "wit"; + + setup_courtroom(); + map_viewport_viewers(); + map_viewport_readers(); + set_char_select(); + load_audiotracks(); + reset_viewport(); +} + +Courtroom::~Courtroom() +{ + cleanup_preload_readers(); + ao_config->set_gamemode(nullptr); + ao_config->set_timeofday(nullptr); + stop_all_audio(); +} + +QVector Courtroom::get_character_list() +{ + return m_chr_list; +} + +void Courtroom::set_character_list(QVector p_chr_list) +{ + m_chr_list = p_chr_list; + set_char_select_page(); +} + +void Courtroom::set_area_list(QStringList p_area_list) +{ + m_area_list = p_area_list; + list_areas(); +} + +void Courtroom::set_music_list(QStringList p_music_list) +{ + m_music_list = p_music_list; + list_music(); +} + +void Courtroom::setup_courtroom() +{ + load_shouts(); + load_effects(); + load_wtce(); + load_free_blocks(); + load_sfx_list_theme(); + + map_viewers(); + assign_readers_for_all_viewers(); + + m_shout_state = 0; + m_shout_current = 0; + check_shouts(); + if (m_shout_current < shouts_enabled.length() && !shouts_enabled[m_shout_current]) + cycle_shout(1); + + m_effect_state = 0; + m_effect_current = 0; + check_effects(); + if (m_effect_current < effects_enabled.length() && !effects_enabled[m_effect_current]) + cycle_effect(1); + + m_wtce_current = 0; + reset_wtce_buttons(); + check_wtce(); + if (is_judge && (m_wtce_current < wtce_enabled.length() && !wtce_enabled[m_wtce_current])) + cycle_wtce(1); + + ui_flip->show(); + + list_music(); + list_areas(); + + // Update widgets first, then check if everything is valid + // This will also handle showing the correct shouts, effects and wtce buttons, + // and cycling through them if the buttons that are supposed to be displayed + // do not exist It will also take care of free blocks + const bool l_chr_select_visible = ui_char_select_background->isVisible(); + + set_widget_names(); + set_widgets(); + set_widget_layers(); + + // char select + reconstruct_char_select(); + ui_char_select_background->setVisible(l_chr_select_visible); + + for (AOTimer *i_timer : qAsConst(ui_timers)) + { + i_timer->redraw(); + } +} + +void Courtroom::map_viewers() +{ + m_mapped_viewer_list.clear(); + + // general ui elements + m_mapped_viewer_list[SpriteGUI].append({ + ui_vp_chat_arrow->get_player(), + ui_vp_clock->get_player(), + }); + + // backgrounds + m_mapped_viewer_list[SpriteStage].append({ + ui_vp_background->get_player(), + ui_vp_desk->get_player(), + }); + + // characters + m_mapped_viewer_list[SpriteCharacter].append(ui_vp_player_char->get_player()); + + // shouts + m_mapped_viewer_list[SpriteShout].append(ui_vp_objection->get_player()); + + // effects + m_mapped_viewer_list[SpriteEffect].append(ui_vp_effect->get_player()); + + // stickers + for (DRStickerViewer *i_sticker : qAsConst(ui_free_blocks)) + { + m_mapped_viewer_list[SpriteSticker].append(i_sticker->get_player()); + } +} + +void Courtroom::map_viewport_viewers() +{ + m_viewport_viewer_map.clear(); + m_viewport_viewer_map.insert(ViewportStageBack, ui_vp_background->get_player()); + m_viewport_viewer_map.insert(ViewportStageFront, ui_vp_desk->get_player()); + m_viewport_viewer_map.insert(ViewportCharacterPre, ui_vp_player_char->get_player()); + m_viewport_viewer_map.insert(ViewportCharacterIdle, ui_vp_player_char->get_player()); + m_viewport_viewer_map.insert(ViewportCharacterTalk, ui_vp_player_char->get_player()); + m_viewport_viewer_map.insert(ViewportEffect, ui_vp_effect->get_player()); + m_viewport_viewer_map.insert(ViewportShout, ui_vp_objection->get_player()); +} + +void Courtroom::map_viewport_readers() +{ + const QList l_type_list = m_viewport_viewer_map.keys(); + for (const ViewportSprite i_type : qAsConst(l_type_list)) + { + m_reader_cache.insert(i_type, m_viewport_viewer_map[i_type]->get_reader()); + } +} + +mk2::SpriteReader::ptr Courtroom::get_viewport_reader(ViewportSprite p_type) const +{ + Q_ASSERT(m_viewport_viewer_map.contains(p_type)); + auto l_viewer = m_viewport_viewer_map[p_type]; + return l_viewer->get_reader(); +} + +void Courtroom::assign_readers_for_all_viewers() +{ + for (auto it = m_mapped_viewer_list.cbegin(); it != m_mapped_viewer_list.cend(); ++it) + { + const SpriteCategory l_category = it.key(); + const bool l_caching = ao_config->sprite_caching_enabled(l_category); + assign_readers_for_viewers(l_category, l_caching); + } +} + +void Courtroom::cleanup_preload_readers() +{ + m_preloader_sync->clear(); + m_preloader_cache.clear(); +} + +void Courtroom::swap_viewport_reader(DRMovie *p_viewer, ViewportSprite p_type) +{ + Q_ASSERT(m_reader_cache.contains(p_type)); + mk2::SpriteReader::ptr l_reader = m_reader_cache.value(p_type); + if (l_reader == p_viewer->get_reader()) + { + return; + } + p_viewer->set_reader(l_reader); +} + +void Courtroom::assign_readers_for_viewers(int p_category, bool p_caching) +{ + const SpriteCategory l_category = SpriteCategory(p_category); + + // viewport readers + const auto &l_viewer_list = m_mapped_viewer_list[l_category]; + for (auto i_viewer : qAsConst(l_viewer_list)) + { + mk2::SpriteReader::ptr l_new_reader; + if (p_caching) + { + l_new_reader = mk2::SpriteReader::ptr(new mk2::SpriteDynamicReader); + } + else + { + l_new_reader = mk2::SpriteReader::ptr(new mk2::SpriteSeekingReader); + } + l_new_reader->set_file_name(i_viewer->get_file_name()); + + const mk2::SpriteReader::ptr l_prev_reader = i_viewer->get_reader(); + const bool l_is_running = i_viewer->is_running(); + const bool l_is_visible = i_viewer->property("visible").toBool(); + + { // update reader cache + const QList l_type_list = m_reader_cache.keys(); + for (const ViewportSprite i_type : qAsConst(l_type_list)) + { + const mk2::SpriteReader::ptr l_current_reader = m_reader_cache.value(i_type); + if (l_prev_reader == l_current_reader) + { + m_reader_cache.insert(i_type, l_new_reader); + } + } + } + + i_viewer->set_reader(l_new_reader); + if (l_is_running) + { + i_viewer->start(); + } + i_viewer->setProperty("visible", l_is_visible); + } +} + +void Courtroom::enter_courtroom(int p_cid) +{ + qDebug() << "enter_courtroom"; + + // unmute audio + suppress_audio(false); + + const QString l_prev_chr_name = get_character_ini(); + + // character ================================================================= + const bool l_changed_chr_id = (m_chr_id != p_cid); + m_chr_id = p_cid; + + if (l_changed_chr_id) + update_default_iniswap_item(); + + QLineEdit *l_current_field = ui_ic_chat_message_field; + if (ui_ooc_chat_message->hasFocus()) + l_current_field = ui_ooc_chat_message; + const int l_current_cursor_pos = l_current_field->cursorPosition(); + + const QString l_chr_name = get_character_ini(); + if (is_spectating()) + { + ao_app->get_discord()->clear_character_name(); + ao_config->clear_showname_placeholder(); + } + else + { + const QString l_showname = ao_app->get_showname(l_chr_name); + const QString l_final_showname = l_showname.trimmed().isEmpty() ? l_chr_name : l_showname; + ao_app->get_discord()->set_character_name(l_final_showname); + ao_config->set_showname_placeholder(l_final_showname); + + // send the character declaration + QStringList l_content{l_chr_name, l_final_showname}; + ao_app->send_server_packet(DRPacket("chrini", l_content)); + } + const bool l_changed_chr = l_chr_name != l_prev_chr_name; + if (l_changed_chr) + set_character_position(ao_app->get_char_side(l_chr_name)); + select_base_character_iniswap(); + refresh_character_content_url(); + + const int l_prev_emote_count = m_emote_list.count(); + m_emote_list = ao_app->get_emote_list(l_chr_name); + + const QString l_prev_emote = ui_emote_dropdown->currentText(); + fill_emote_dropdown(); + + if (l_changed_chr || l_prev_emote_count != m_emote_list.count()) + { + m_emote_id = 0; + m_current_emote_page = 0; + ui_pre->setChecked(ui_pre->isChecked() || ao_config->always_pre_enabled()); + } + else + { + ui_emote_dropdown->setCurrentText(l_prev_emote); + } + refresh_emote_page(); + + load_current_character_sfx_list(); + select_default_sfx(); + + ui_emotes->setHidden(is_spectating()); + ui_emote_dropdown->setDisabled(is_spectating()); + ui_iniswap_dropdown->setDisabled(is_spectating()); + ui_ic_chat_message_field->setDisabled(is_spectating()); + set_character_position(ao_app->get_char_side(get_character_ini())); + + // restore line field focus + l_current_field->setFocus(); + l_current_field->setCursorPosition(l_current_cursor_pos); + + const QString l_showname = ao_config->showname(); + if (!l_showname.isEmpty() && !is_first_showname_sent) + send_showname_packet(l_showname); + + ui_char_select_background->hide(); +} + +void Courtroom::done_received() +{ + m_chr_id = SpectatorId; + + suppress_audio(true); + + set_char_select(); + set_char_select_page(); + + show(); + + ui_spectator->show(); +} + +void Courtroom::set_window_title(QString p_title) +{ + this->setWindowTitle(p_title); +} + +void Courtroom::set_ambient(QString p_ambient_sfx) +{ + m_ambient_sfx = p_ambient_sfx.trimmed(); + play_ambient(); +} + +void Courtroom::play_ambient() +{ + QString l_ambient = m_ambient_sfx; + if (l_ambient.isEmpty()) + { + DRPosition l_position = m_position_map.get_position(m_chatmessage[CMPosition]); + l_ambient = l_position.get_ambient_sfx(); + } + + QString l_filepath = ao_app->get_ambient_sfx_path(l_ambient); + m_effects_player->play_ambient(l_filepath); +} + +QString Courtroom::get_current_background() const +{ + QString l_tod = ao_config->timeofday(); + if (ao_config->is_manual_timeofday_selection_enabled()) + { + l_tod = ao_config->manual_timeofday(); + } + + return m_background.background_tod_map.value(l_tod, m_background.background); +} + +void Courtroom::update_background_scene() +{ + const QString l_prev_background_name = m_background_name; + m_background_name = get_current_background(); + + if (l_prev_background_name.isEmpty() || l_prev_background_name != m_background_name) + { + const QString l_positions_ini = ao_app->find_asset_path(ao_app->get_background_path(m_background_name) + "/" + "positions.ini"); + if (l_positions_ini.isEmpty() || !m_position_map.load_file(l_positions_ini)) + { + m_position_map = get_legacy_background(m_background_name); + } + } + + const QString l_position_id = m_chatmessage[CMPosition]; + DRPosition l_position = m_position_map.get_position(m_chatmessage[CMPosition]); + + { + const QString l_file_name = ao_app->get_background_sprite_path(m_background_name, l_position.get_back()); + ui_vp_background->set_file_name(l_file_name); + } + + { + const QString l_file_name = ao_app->get_background_sprite_path(m_background_name, l_position.get_front()); + ui_vp_desk->set_file_name(l_file_name); + } + + if (m_preloader_sync->is_waiting()) + { + preload_chatmessage(m_pre_chatmessage); + } + + display_background_scene(); +} + +void Courtroom::display_background_scene() +{ + ui_vp_background->show(); + ui_vp_background->set_play_once(false); + swap_viewport_reader(ui_vp_background, ViewportStageBack); + ui_vp_background->start(); + if (!ui_vp_background->is_valid()) + { + ui_vp_background->hide(); + } + + ui_vp_desk->show(); + ui_vp_desk->set_play_once(false); + swap_viewport_reader(ui_vp_desk, ViewportStageFront); + ui_vp_desk->start(); + if (!ui_vp_desk->is_valid() || m_chatmessage[CMDeskModifier] == "0") + { + ui_vp_desk->hide(); + } + + play_ambient(); +} + +DRPositionMap Courtroom::get_legacy_background(QString p_background) +{ + DRPositionMap l_position_map; + + const auto &l_legacy_map = DRPositionMap::LEGACY_POSITION_MAP; + for (auto it = l_legacy_map.cbegin(); it != l_legacy_map.cend(); ++it) + { + QString l_position_id = it.key(); + DRPosition l_position = it.value(); + + auto l_get_position_filename = [this, &p_background](QString p_filename) { + const QFileInfo l_filepath = ao_app->get_background_sprite_noext_path(p_background, p_filename); + return l_filepath.fileName(); + }; + l_position.set_back(l_get_position_filename(l_position.get_back())); + l_position.set_front(l_get_position_filename(l_position.get_front())); + l_position_map.set_position(l_position_id, l_position); + } + + return l_position_map; +} + +DRAreaBackground Courtroom::get_background() +{ + return m_background; +} + +void Courtroom::set_background(DRAreaBackground p_background) +{ + m_background = p_background; + + QStringList l_background_list{m_background.background}; + for (auto it = m_background.background_tod_map.cbegin(); it != m_background.background_tod_map.cend(); ++it) + { + l_background_list.append(it.value()); + } + + QStringList l_missing_backgrounds; + for (const QString &i_background : qAsConst(l_background_list)) + { + const QString l_background_path = ao_app->get_case_sensitive_path(ao_app->get_background_path(i_background)); + if (!dir_exists(l_background_path)) + { + l_missing_backgrounds.append(i_background); + } + } + + if (!l_missing_backgrounds.isEmpty()) + { + ui_ooc_chatlog->append_chatmessage("[WARNING]", "Missing background(s) detected: " + l_missing_backgrounds.join(", ")); + } + + update_background_scene(); +} + +void Courtroom::set_tick_rate(const int p_tick_rate) +{ + if (p_tick_rate < 0) + { + m_server_tick_rate.reset(); + return; + } + m_server_tick_rate = p_tick_rate; +} + +void Courtroom::set_music_text(QString p_text) +{ + ui_vp_music_name->setText(p_text); + update_music_text_anim(); +} + +void Courtroom::update_music_text_anim() +{ + pos_size_type res_a = ao_app->get_element_dimensions("music_name", COURTROOM_DESIGN_INI); + pos_size_type res_b = ao_app->get_element_dimensions("music_area", COURTROOM_DESIGN_INI); + float speed = static_cast(ao_app->get_font_property("music_name_speed", COURTROOM_FONTS_INI)); + + QFont f_font = ui_vp_music_name->font(); + QFontMetrics fm(f_font); + int dist; + if (ao_app->read_theme_ini_bool("enable_const_music_speed", COURTROOM_CONFIG_INI)) + dist = res_b.width; + else + dist = fm.horizontalAdvance(ui_vp_music_name->toPlainText()); + int time = static_cast(1000000 * dist / speed); + music_anim->setLoopCount(-1); + music_anim->setDuration(time); + music_anim->setStartValue(QRect(res_b.width + res_b.x, res_a.y, res_a.width, res_a.height)); + music_anim->setEndValue(QRect(-dist + res_a.x, res_a.y, res_a.width, res_a.height)); + music_anim->start(); +} + +void Courtroom::handle_clock(QString time) +{ + m_current_clock = time.toInt(); + if (m_current_clock < 0) + m_current_clock = -1; + qInfo() << QString("Clock time changed to %1").arg(m_current_clock); + + ui_vp_clock->hide(); + + if (m_current_clock == -1) + { + qInfo() << "Unknown time; no asset to be used."; + return; + } + + qDebug() << "Displaying clock asset..."; + QString clock_filename = "hours/" + QString::number(m_current_clock); + const QString asset_path = ao_app->find_theme_asset_path(clock_filename, animated_or_static_extensions()); + if (asset_path.isEmpty()) + { + qDebug() << "Asset not found; aborting."; + return; + } + ui_vp_clock->set_theme_image(clock_filename); + ui_vp_clock->start(); + ui_vp_clock->show(); +} + +void Courtroom::filter_list_widget(QListWidget *p_list_widget, QString p_filter) +{ + const QString l_final_filter = p_filter.simplified(); + for (int i = 0; i < p_list_widget->count(); i++) + { + QListWidgetItem *i_item = p_list_widget->item(i); + i_item->setHidden(!l_final_filter.isEmpty() && !i_item->text().contains(l_final_filter, Qt::CaseInsensitive)); + } +} + +bool Courtroom::is_area_music_list_separated() +{ + return ao_app->read_theme_ini_bool("enable_music_and_area_list_separation", COURTROOM_CONFIG_INI); +} + +void Courtroom::list_music() +{ + const QBrush l_song_brush(ao_app->get_color("found_song_color", COURTROOM_DESIGN_INI)); + const QBrush l_missing_song_brush(ao_app->get_color("missing_song_color", COURTROOM_DESIGN_INI)); + ui_music_list->clear(); + for (const QString &i_song : qAsConst(m_music_list)) + { + DRAudiotrackMetadata l_track(i_song); + QListWidgetItem *l_item = new QListWidgetItem(l_track.title(), ui_music_list); + l_item->setData(Qt::UserRole, l_track.filename()); + if (l_track.title() != l_track.filename()) + l_item->setToolTip(l_track.filename()); + const QString l_song_path = ao_app->find_asset_path({ao_app->get_music_path(i_song)}, audio_extensions()); + l_item->setBackground(l_song_path.isEmpty() ? l_missing_song_brush : l_song_brush); + } + filter_list_widget(ui_music_list, ui_music_search->text()); +} + +void Courtroom::list_areas() +{ + const QBrush l_area_brush(ao_app->get_color("area_free_color", COURTROOM_DESIGN_INI)); + ui_area_list->clear(); + for (const QString &i_item_name : qAsConst(m_area_list)) + { + QListWidgetItem *l_item = new QListWidgetItem(i_item_name, ui_area_list); + l_item->setBackground(l_area_brush); + } + filter_list_widget(ui_area_list, ui_area_search->text()); +} + +void Courtroom::list_note_files() +{ + QString f_config = ao_app->get_base_path() + CONFIG_FILESABSTRACT_INI; + QFile f_file(f_config); + if (!f_file.open(QIODevice::ReadOnly)) + { + qDebug() << "Couldn't open" << f_config; + return; + } + + QString f_filestring = ""; + QString f_filename = ""; + + QTextStream in(&f_file); + + QVBoxLayout *f_layout = ui_note_area->m_layout; + + while (!in.atEnd()) + { + QString line = in.readLine().trimmed(); + + QStringList f_contents = line.split("="); + if (f_contents.size() < 2) + continue; + + int f_index = f_contents.at(0).toInt(); + f_filestring = f_filename = f_contents.at(1).trimmed(); + + if (f_contents.size() > 2) + f_filename = f_contents.at(2).trimmed(); + + while (f_index >= f_layout->count()) + on_add_button_clicked(); + + AONotePicker *f_notepicker = static_cast(f_layout->itemAt(f_index)->widget()); + f_notepicker->ui_line->setText(f_filename); + f_notepicker->m_file = f_filestring; + } +} + +QString Courtroom::get_current_position() +{ + if (ui_pos_dropdown->currentIndex() == DefaultPositionIndex) + { + return ao_app->get_char_side(get_character_ini()); + } + return ui_pos_dropdown->currentData(Qt::UserRole).toString(); +} + +void Courtroom::load_note() +{ + // Do not attempt to load anything if no file was chosen. This makes it so + // that notepad text is kept in client if the user has decided not to choose a + // file to save to. Of course, this is ephimeral storage, it will be erased + // after the client closes or when the user decides to load a file. + if (current_file.isEmpty()) + return; + QString f_text = ao_app->read_note(current_file); + ui_vp_notepad->setText(f_text); +} + +void Courtroom::save_note() +{ + QString f_text = ui_vp_notepad->toPlainText(); + + ao_app->write_note(f_text, current_file); +} + +void Courtroom::save_textlog(QString p_text) +{ + QString f_file = ao_app->get_base_path() + icchatlogsfilename; + + ao_app->append_note("[" + QTime::currentTime().toString() + "]" + p_text, f_file); +} + +void Courtroom::append_server_chatmessage(QString p_name, QString p_message) +{ + ui_ooc_chatlog->append_chatmessage(p_name, p_message); + if (ao_config->log_is_recording_enabled()) + save_textlog("(OOC)" + p_name + ": " + p_message); +} + +void Courtroom::ignore_next_showname() +{ + is_next_showname_ignored = true; +} + +/** + * @brief Send a packet to set the showname of the user to + * the server. + * @param p_showname The showname. + */ +void Courtroom::send_showname_packet(QString p_showname) +{ + if (is_next_showname_ignored) + { + is_next_showname_ignored = false; + return; + } + + is_first_showname_sent = true; + + ao_app->send_server_packet(DRPacket("SN", {p_showname})); +} + +void Courtroom::on_showname_changed(QString p_showname) +{ + ui_ic_chat_showname->setText(p_showname); + send_showname_packet(p_showname); +} + +bool Courtroom::is_spectating() +{ + return m_chr_id == SpectatorId; +} + +void Courtroom::on_showname_placeholder_changed(QString p_showname_placeholder) +{ + const QString l_showname(p_showname_placeholder.trimmed().isEmpty() ? "Showname" : p_showname_placeholder); + ui_ic_chat_showname->setPlaceholderText(l_showname); + ui_ic_chat_showname->setToolTip(l_showname); +} + +void Courtroom::on_character_ini_changed() +{ + enter_courtroom(m_chr_id); +} + +void Courtroom::on_ic_message_return_pressed() +{ + if (ui_ic_chat_message_field->text() == "") + return; + + if ((anim_state < 3 || text_state < 2) && m_shout_state == 0) + return; + + // MS + // deskmod + // pre-emote + // character + // emote + // message + // side + // sfx-name + // emote_modifier + // char_id + // sfx_delay + // objection_modifier + // evidence + // placeholder + // realization + // text_color + // video_name + // show_emote + + QStringList packet_contents; + + const DREmote &l_emote = get_emote(m_emote_id); + + const QString l_desk_modifier = l_emote.desk_modifier == -1 ? QString("chat") : QString::number(l_emote.desk_modifier); + packet_contents.append(l_desk_modifier); + + packet_contents.append(l_emote.anim); + + packet_contents.append(get_character_ini()); + + if (ui_hide_character->isChecked()) + packet_contents.append("../../misc/blank"); + else + packet_contents.append(l_emote.dialog); + + packet_contents.append(ui_ic_chat_message_field->text()); + + packet_contents.append(get_current_position()); + + // sfx file + const QString l_sound_file = current_sfx_file(); + packet_contents.append(l_sound_file.isEmpty() ? "0" : l_sound_file); + + int l_emote_mod = l_emote.modifier; + + if (ui_pre->isChecked()) + { + if (l_emote_mod == ZoomEmoteMod) + l_emote_mod = PreZoomEmoteMod; + else + l_emote_mod = PreEmoteMod; + } + else + { + if (l_emote_mod == PreZoomEmoteMod) + l_emote_mod = ZoomEmoteMod; + else + l_emote_mod = IdleEmoteMod; + } + + if (m_shout_state != 0) + { + if (l_emote_mod == ZoomEmoteMod) + l_emote_mod = PreZoomEmoteMod; + else + l_emote_mod = PreEmoteMod; + } + + packet_contents.append(QString::number(l_emote_mod)); + packet_contents.append(QString::number(m_chr_id)); + + if (l_emote.sound_file == current_sfx_file()) + packet_contents.append(QString::number(l_emote.sound_delay)); + else + packet_contents.append("0"); + + packet_contents.append(QString::number(m_shout_state)); + + // evidence + packet_contents.append("0"); + + QString f_flip = ui_flip->isChecked() ? "1" : "0"; + packet_contents.append(f_flip); + + packet_contents.append(QString::number(m_effect_state)); + + QString f_text_color; + if (m_text_color < 0) + f_text_color = "0"; + else if (m_text_color > ui_text_color->count()) + f_text_color = "0"; + else + f_text_color = QString::number(m_text_color); + packet_contents.append(f_text_color); + + // showname + packet_contents.append(ao_config->showname()); + + // video name + packet_contents.append(!l_emote.video_file.isEmpty() ? l_emote.video_file : "0"); + + // hide character + packet_contents.append(QString::number(ui_hide_character->isChecked())); + + ao_app->send_server_packet(DRPacket("MS", packet_contents)); +} + +void Courtroom::handle_ic_message_length() +{ + const int l_length = ui_ic_chat_message_field->text().length(); + const int l_max_length = ui_ic_chat_message_field->maxLength(); + ui_ic_chat_message_counter->setText(QString::number(l_max_length - l_length)); + ui_ic_chat_message_counter->setVisible(l_length >= int(l_max_length * (ao_config->message_length_threshold() * 0.01))); + if (l_length == l_max_length) + { + QToolTip::showText(QCursor::pos(), QString(tr("Your message cannot be longer than %1 character(s).")).arg(ui_ic_chat_message_field->maxLength())); + } +} + +void Courtroom::handle_acknowledged_ms() +{ + ui_ic_chat_message_field->clear(); + + // reset states + ui_pre->setChecked(ao_config->always_pre_enabled()); + + reset_shout_buttons(); + reset_effect_buttons(); + clear_sfx_selection(); +} + +void Courtroom::next_chatmessage(QStringList p_chatmessage) +{ + if (p_chatmessage.length() < MINIMUM_MESSAGE_SIZE) + { + return; + } + + while (p_chatmessage.length() < OPTIMAL_MESSAGE_SIZE) + { + p_chatmessage.append(QString{}); + } + + const int l_message_chr_id = p_chatmessage[CMChrId].toInt(); + const bool l_system_speaking = l_message_chr_id == SpectatorId; + + QString l_showname = p_chatmessage[CMShowName]; + if (l_showname.isEmpty() && !l_system_speaking) + { + l_showname = ao_app->get_showname(p_chatmessage[CMChrName]); + } + + const QString l_message = QString(p_chatmessage[CMMessage]).remove(QRegularExpression("(?= 0 && l_message_chr_id < m_chr_list.length()) + { + const int l_client_id = p_chatmessage[CMClientId].toInt(); + append_ic_text(l_showname, l_message, false, false, l_client_id, m_chr_id == l_message_chr_id); + + if (ao_config->log_is_recording_enabled() && !l_message.isEmpty()) + { + save_textlog(l_showname + ": " + l_message); + } + } + + { // clear interface if required + bool l_ok; + const int l_client_id = p_chatmessage[CMClientId].toInt(&l_ok); + if (l_ok && l_client_id == ao_app->get_client_id()) + { + handle_acknowledged_ms(); + } + } + + preload_chatmessage(p_chatmessage); +} + +void Courtroom::reset_viewport() +{ + QStringList l_chatmessage; + + while (l_chatmessage.length() < OPTIMAL_MESSAGE_SIZE) + { + l_chatmessage.append(QString{}); + } + l_chatmessage[CMChrId] = QString::number(SpectatorId); + l_chatmessage[CMHideCharacter] = "1"; + l_chatmessage[CMClientId] = QString::number(SpectatorId); + l_chatmessage[CMPosition] = m_chatmessage.value(CMPosition, "wit"); + + next_chatmessage(l_chatmessage); +} + +void Courtroom::preload_chatmessage(QStringList p_contents) +{ + cleanup_preload_readers(); + m_loading_timer->stop(); + m_pre_chatmessage = p_contents; + m_game_state = GameState::Preloading; + + QMap l_file_list; + const QString l_position_id = m_pre_chatmessage[CMPosition]; + const QString l_character = m_pre_chatmessage[CMChrName]; + const QString l_emote_anim = m_pre_chatmessage[CMPreAnim]; + const QString l_emote = m_pre_chatmessage[CMEmote]; + const int l_effect_id = m_pre_chatmessage[CMEffectState].toInt(); + const int l_shout_id = m_pre_chatmessage[CMShoutModifier].toInt(); + + { // backgrounds + DRPosition l_position = m_position_map.get_position(l_position_id); + l_file_list.insert(ViewportStageBack, ao_app->get_background_sprite_path(m_background_name, l_position.get_back())); + l_file_list.insert(ViewportStageFront, ao_app->get_background_sprite_path(m_background_name, l_position.get_front())); + } + + // characters + l_file_list.insert(ViewportCharacterPre, ao_app->get_character_sprite_pre_path(l_character, l_emote_anim)); + l_file_list.insert(ViewportCharacterIdle, ao_app->get_character_sprite_idle_path(l_character, l_emote)); + l_file_list.insert(ViewportCharacterTalk, ao_app->get_character_sprite_talk_path(l_character, l_emote)); + + // shouts + l_file_list.insert(ViewportShout, ao_app->get_shout_sprite_path(l_character, get_shout_name(l_shout_id))); + + // effects + l_file_list.insert(ViewportEffect, ao_app->get_theme_sprite_path(get_effect_name(l_effect_id))); + + for (auto it = l_file_list.cbegin(); it != l_file_list.cend(); ++it) + { + const ViewportSprite l_type = it.key(); + const QString &l_file_name = it.value(); + + auto l_viewer = m_viewport_viewer_map.value(l_type); + const QString l_current_file_name = l_viewer->get_file_name(); + + // reuse readers when available + mk2::SpriteReader::ptr l_reader = l_viewer->get_reader(); + if (l_file_name != l_current_file_name) + { + const SpriteCategory l_category = viewport_sprite_to_sprite_category(l_type); + if (ao_config->sprite_caching_enabled(l_category)) + { + l_reader = mk2::SpriteReader::ptr(new mk2::SpriteDynamicReader); + } + else + { + l_reader = mk2::SpriteReader::ptr(new mk2::SpriteSeekingReader); + } + l_reader->set_file_name(l_file_name); + } + m_preloader_cache.insert(l_type, l_reader); + m_preloader_sync->add(l_reader); + } + m_loading_timer->start(); + m_preloader_sync->start(); +} + +void Courtroom::start_chatmessage() +{ + m_preloader_sync->clear(); + m_reader_cache = std::move(m_preloader_cache); + + m_loading_timer->stop(); + ui_vp_loading->hide(); + + m_tick_timer->stop(); + m_chatmessage = m_pre_chatmessage; + m_game_state = GameState::Processing; + + handle_chatmessage(); +} + +void Courtroom::handle_chatmessage() +{ + qDebug() << "handle_chatmessage"; + m_hide_character = m_chatmessage[CMHideCharacter].toInt(); + m_play_pre = false; + m_play_zoom = false; + const int l_emote_mod = m_chatmessage[CMEmoteModifier].toInt(); + switch (l_emote_mod) + { + case IdleEmoteMod: + default: + break; + case PreEmoteMod: + m_play_pre = true; + break; + case ZoomEmoteMod: + m_play_zoom = true; + break; + case PreZoomEmoteMod: + m_play_pre = true; + m_play_zoom = true; + break; + } + + m_speaker_chr_id = m_chatmessage[CMChrId].toInt(); + is_system_speaking = (m_speaker_chr_id == SpectatorId); + + if (m_speaker_chr_id != SpectatorId && (m_speaker_chr_id < 0 || m_speaker_chr_id >= m_chr_list.length())) + { + post_chatmessage(); + return; + } + + const QString l_message = QString(m_chatmessage[CMMessage]).remove(QRegularExpression("(?get_first_person_enabled(); + } + + qDebug() << "handle_chatmessage"; + + // We actually DO wanna fail here if the showname is empty but the system is speaking. + // Having an empty showname for system is actually what we expect. + + QString f_showname = m_chatmessage[CMShowName]; + if (f_showname.isEmpty() && !is_system_speaking) + { + f_showname = ao_app->get_showname(m_chatmessage[CMChrName]); + } + m_speaker_showname = f_showname; + + ui_vp_chat_arrow->hide(); + m_effects_player->stop_all(); + + text_state = 0; + anim_state = 0; + stop_chat_timer(); + ui_vp_objection->stop(); + + m_message_color_name = ""; + m_message_color_stack.clear(); + + // reset effect + ui_vp_effect->stop(); + ui_vp_effect->hide(); + + ui_vp_message->clear(); + ui_vp_chatbox->hide(); + ui_vp_showname->hide(); + ui_vp_showname_image->hide(); + ui_vp_showname->setText(m_speaker_showname); + + /** + * WARNING No check prior to changing will cause an unrecoverable + * exception. You have been warned! + * + * Qt Version: <= 5.15 + */ + if (!ui_video->isVisible()) + { + ui_video->show(); + } + ui_video->play_character_video(m_chatmessage[CMChrName], m_chatmessage[CMVideoName]); +} + +QString Courtroom::get_shout_name(int p_shout_index) +{ + if (p_shout_index < 1 || p_shout_index > ui_shouts.length()) + { + return QString{}; + } + + return shout_names.at(p_shout_index - 1); +} + +QString Courtroom::get_effect_name(int effect_index) +{ + if (effect_index < 1 || effect_index > ui_effects.length()) + { + return QString{}; + } + + return effect_names.at(effect_index - 1); +} + +void Courtroom::video_finished() +{ + /** + * WARNING No check prior to changing will cause an unrecoverable + * exception. You have been warned! + * + * Qt Version: <= 5.15 + */ + if (ui_video->isVisible()) + { + ui_video->hide(); + } + + const QString l_character = m_chatmessage[CMChrName]; + const int l_shout_index = m_chatmessage[CMShoutModifier].toInt(); + + const QString l_shout_name = get_shout_name(l_shout_index); + if (l_shout_name.isEmpty()) + { + handle_chatmessage_2(); + return; + } + + qDebug() << "[viewport] Starting shout..." << l_shout_name; + m_play_pre = true; + ui_vp_objection->set_play_once(true); + swap_viewport_reader(ui_vp_objection, ViewportShout); + ui_vp_objection->start(); + m_shouts_player->play(l_character, l_shout_name); +} + +void Courtroom::objection_done() +{ + if (anim_state != 0) + { + return; + } + handle_chatmessage_2(); +} + +void Courtroom::handle_chatmessage_2() // handles IC +{ + qDebug() << "handle_chatmessage_2"; + ui_vp_player_char->stop(); + + if (m_shout_reload_theme) + { + load_theme(); + } + m_shout_reload_theme = false; + + const QString l_chatbox_name = ao_app->get_chat(m_chatmessage[CMChrName]); + const bool l_is_self = (ao_config->log_display_self_highlight_enabled() && m_speaker_chr_id == m_chr_id); + ui_vp_chatbox->set_chatbox_image(l_chatbox_name, l_is_self); + + if (!m_msg_is_first_person) + { + display_background_scene(); + } + + if (m_chatmessage[CMFlipState].toInt() == 1) + ui_vp_player_char->set_mirrored(true); + else + ui_vp_player_char->set_mirrored(false); + + if (m_play_pre) + { + int sfx_delay = m_chatmessage[CMSoundDelay].toInt(); + m_sound_timer->start(sfx_delay); + play_preanim(); + return; + } + + handle_chatmessage_3(); +} + +void Courtroom::handle_chatmessage_3() +{ + qDebug() << "handle_chatmessage_3"; + + ui_vp_player_char->set_play_once(false); + + setup_chat(); + + int f_anim_state = 0; + // BLUE is from an enum in datatypes.h + bool text_is_blue = m_chatmessage[CMTextColor].toInt() == DR::CBlue; + + if (!text_is_blue && text_state == 1) + // talking + f_anim_state = 2; + else + // idle + f_anim_state = 3; + + if (f_anim_state <= anim_state) + return; + + ui_vp_player_char->stop(); + const QString f_char = m_chatmessage[CMChrName]; + const QString f_emote = m_chatmessage[CMEmote]; + + if (!chatmessage_is_empty) + { + QString l_showname_image; + if (ao_app->read_theme_ini_bool("enable_showname_image", COURTROOM_CONFIG_INI)) + { + l_showname_image = ao_app->find_theme_asset_path("characters/" + f_char + "/showname", static_extensions()); + if (l_showname_image.isEmpty()) + l_showname_image = ao_app->find_asset_path({ao_app->get_character_path(f_char, "showname")}, static_extensions()); + ui_vp_showname_image->set_image(l_showname_image); + ui_vp_showname_image->show(); + } + + if (!l_showname_image.isEmpty()) + { + ui_vp_showname_image->set_image(l_showname_image); + ui_vp_showname_image->show(); + } + else + { + ui_vp_showname->show(); + } + } + + // Path may be empty if + // 1. Chat message was empty + // 2. Enable showname images was false + // 3. No valid showname image was found + ui_vp_player_char->hide(); + if (ui_vp_player_char->is_running()) + { + ui_vp_player_char->stop(); + } + switch (f_anim_state) + { + case 2: + if (!m_hide_character && !m_msg_is_first_person) + { + swap_viewport_reader(ui_vp_player_char, ViewportCharacterTalk); + ui_vp_player_char->start(); + } + anim_state = 2; + break; + default: + qDebug() << "W: invalid anim_state: " << f_anim_state; + [[fallthrough]]; + case 3: + if (!m_hide_character && !m_msg_is_first_person) + { + swap_viewport_reader(ui_vp_player_char, ViewportCharacterIdle); + ui_vp_player_char->start(); + } + anim_state = 3; + break; + } + + { + bool l_effect_index_result; + const int l_effect_index = m_chatmessage[CMEffectState].toInt(&l_effect_index_result); + if (l_effect_index_result) + { + const QString l_effect_name = get_effect_name(l_effect_index); + if (!l_effect_name.isEmpty()) // check to prevent crashing + { + QStringList offset = ao_app->get_effect_offset(f_char, l_effect_index); + ui_vp_effect->setPos(ui_viewport->x() + offset.at(0).toInt(), ui_viewport->y() + offset.at(1).toInt()); + + QString s_eff = effect_names.at(l_effect_index - 1); + QStringList f_eff = ao_app->get_effect(l_effect_index); + + bool once = f_eff.at(1).trimmed().toInt(); + + QStringList overlay = ao_app->get_overlay(f_char, l_effect_index); + QString overlay_name = overlay.at(0); + QString overlay_sfx = overlay.at(1); + + if (overlay_sfx == "") + overlay_sfx = ao_app->get_sfx(s_eff); + m_effects_player->play_effect(overlay_sfx); + + if (overlay_name == "") + overlay_name = s_eff; + ui_vp_effect->set_play_once(once); + swap_viewport_reader(ui_vp_effect, ViewportEffect); + ui_vp_effect->start(); + } + } + } + + QString f_message = m_chatmessage[CMMessage]; + QStringList callwords = ao_app->get_callwords(); + + for (const QString &word : callwords) + { + if (f_message.contains(word, Qt::CaseInsensitive)) + { + m_system_player->play(ao_app->get_sfx("word_call")); + ao_app->alert(this); + const QString name = "CLIENT"; + const QString message = ui_vp_showname->toPlainText() + " has called you via your callword \"" + word + "\": \"" + f_message + "\""; + ui_ooc_chatlog->append_chatmessage(name, message); + if (ao_config->log_is_recording_enabled()) + save_textlog("(OOC)" + name + ": " + message); + break; + } + } + + calculate_chat_tick_interval(); + start_chat_timer(); +} + +void Courtroom::on_chat_config_changed() +{ + update_ic_log(true); +} + +void Courtroom::load_ic_text_format() +{ + ui_ic_chatlog->ensurePolished(); + m_ic_log_format.base = QTextCharFormat(); + m_ic_log_format.base.setFont(ui_ic_chatlog->font()); + m_ic_log_format.base.setForeground(ui_ic_chatlog->palette().color(ui_ic_chatlog->foregroundRole())); + + auto set_format_color = [this](const QString &f_identifier, QTextCharFormat &f_format) { + if (const std::optional l_color = ao_app->maybe_color(QString("ic_chatlog_%1_color").arg(f_identifier), COURTROOM_FONTS_INI); l_color.has_value()) + f_format.setForeground(l_color.value()); + + if (ao_app->get_font_property(QString("ic_chatlog_%1_bold").arg(f_identifier), COURTROOM_FONTS_INI)) + f_format.setFontWeight(QFont::Bold); + }; + + m_ic_log_format.name = m_ic_log_format.base; + set_format_color("showname", m_ic_log_format.name); + + m_ic_log_format.selfname = m_ic_log_format.name; + if (ao_config->log_display_self_highlight_enabled()) + set_format_color("selfname", m_ic_log_format.selfname); + + m_ic_log_format.message = m_ic_log_format.base; + set_format_color("message", m_ic_log_format.message); + + m_ic_log_format.system = m_ic_log_format.base; + set_format_color("system", m_ic_log_format.system); +} + +void Courtroom::update_ic_log(bool p_reset_log) +{ + if (const int l_record_count = m_ic_record_list.length() + m_ic_record_queue.length(); l_record_count > ao_config->log_max_lines()) + m_ic_record_list = m_ic_record_list.mid(l_record_count - ao_config->log_max_lines()); + + if (p_reset_log || ao_config->log_max_lines() == 1) + { + if (p_reset_log) + { + load_ic_text_format(); + QQueue l_new_queue; + dynamic_cast &>(l_new_queue) = std::move(m_ic_record_list); + l_new_queue.append(std::move(m_ic_record_queue)); + m_ic_record_queue = std::move(l_new_queue); + } + + ui_ic_chatlog->clear(); + ui_ic_chatlog->setAlignment(ui_ic_chatlog->get_text_alignment()); + } + bool l_log_is_empty = m_ic_record_list.length() == 0; + + const bool l_topdown_orientation = ao_config->log_is_topdown_enabled(); + const bool l_use_newline = ao_config->log_format_use_newline_enabled(); + QTextCursor l_cursor = ui_ic_chatlog->textCursor(); + const QTextCursor::MoveOperation move_type = l_topdown_orientation ? QTextCursor::End : QTextCursor::Start; + + const QTextCharFormat &l_name_format = m_ic_log_format.name; + const QTextCharFormat &l_selfname_format = m_ic_log_format.selfname; + const QTextCharFormat &l_message_format = m_ic_log_format.message; + const QTextCharFormat &l_system_format = m_ic_log_format.system; + + QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); + const int l_scroll_pos = l_scrollbar->value(); + const bool l_is_end_scroll_pos = p_reset_log || (l_topdown_orientation ? l_scroll_pos == l_scrollbar->maximum() : l_scroll_pos == l_scrollbar->minimum()); + + while (!m_ic_record_queue.isEmpty()) + { + const DRChatRecord l_record = m_ic_record_queue.takeFirst(); + m_ic_record_list.append(l_record); + + if (!ao_config->log_display_empty_messages_enabled() && l_record.get_message().trimmed().isEmpty()) + continue; + + if (!ao_config->log_display_music_switch_enabled() && l_record.is_music()) + continue; + + l_cursor.movePosition(move_type); + + const QString l_linefeed(QChar::LineFeed); + if (!l_log_is_empty) + l_cursor.insertText(l_linefeed + QString(l_use_newline ? l_linefeed : nullptr), l_message_format); + l_log_is_empty = false; + + if (!l_topdown_orientation) + l_cursor.movePosition(move_type); + + // self-highlight check + const QTextCharFormat &l_target_name_format = (l_record.is_self() && ao_config->log_display_self_highlight_enabled()) ? l_selfname_format : l_name_format; + + if (ao_config->log_display_timestamp_enabled()) + l_cursor.insertText(QString("[%1] ").arg(l_record.get_timestamp().toString("hh:mm")), l_target_name_format); + + if (l_record.is_system()) + { + l_cursor.insertText(l_record.get_message(), l_system_format); + } + else + { + QString l_separator; + if (l_use_newline) + l_separator = QString(QChar::LineFeed); + else if (!l_record.is_music()) + l_separator = ": "; + else + l_separator = " "; + + const int l_client_id = l_record.get_client_id(); + if (l_client_id != NoClientId && ao_config->log_display_client_id_enabled()) + l_cursor.insertText(QString::number(l_record.get_client_id()) + " | ", l_target_name_format); + + l_cursor.insertText(l_record.get_name() + l_separator, l_target_name_format); + l_cursor.insertText(l_record.get_message(), l_message_format); + } + } + + { // remove unneeded blocks + const int l_max_block_count = m_ic_record_list.length() * (1 + l_use_newline) + (l_use_newline * (m_ic_record_list.length() - 1)) + !l_topdown_orientation; + const QTextCursor::MoveOperation l_orientation = l_topdown_orientation ? QTextCursor::Start : QTextCursor::End; + const QTextCursor::MoveOperation l_block_orientation = l_topdown_orientation ? QTextCursor::NextBlock : QTextCursor::PreviousBlock; + + const int l_remove_block_count = ui_ic_chatlog->document()->blockCount() - l_max_block_count; + if (l_remove_block_count > 0) + { + l_cursor.movePosition(l_orientation); + for (int i = 0; i < l_remove_block_count; ++i) + l_cursor.movePosition(l_block_orientation, QTextCursor::KeepAnchor); + l_cursor.removeSelectedText(); + } + } + + if (l_is_end_scroll_pos) + { + l_scrollbar->setValue(l_topdown_orientation ? l_scrollbar->maximum() : l_scrollbar->minimum()); + } +} + +void Courtroom::on_ic_chatlog_scroll_changed() +{ + QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); + const int l_scroll_pos = l_scrollbar->value(); + const bool l_topdown_orientation = ao_config->log_is_topdown_enabled(); + const bool l_is_end_scroll_pos = (l_topdown_orientation ? l_scroll_pos == l_scrollbar->maximum() : l_scroll_pos == l_scrollbar->minimum()); + + if (l_is_end_scroll_pos) + { + ui_ic_chatlog_scroll_topdown->hide(); + ui_ic_chatlog_scroll_bottomup->hide(); + } + else + { + if (l_topdown_orientation) + ui_ic_chatlog_scroll_topdown->show(); + else + ui_ic_chatlog_scroll_bottomup->show(); + } +} + +void Courtroom::on_ic_chatlog_scroll_topdown_clicked() +{ + QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); + l_scrollbar->setValue(l_scrollbar->maximum()); +} + +void Courtroom::on_ic_chatlog_scroll_bottomup_clicked() +{ + QScrollBar *l_scrollbar = ui_ic_chatlog->verticalScrollBar(); + l_scrollbar->setValue(l_scrollbar->minimum()); +} + +void Courtroom::append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, int p_client_id, bool p_self) +{ + if (p_name.trimmed().isEmpty()) + p_name = "Anonymous"; + + if (p_line.trimmed().isEmpty()) + p_line = p_line.trimmed(); + + DRChatRecord new_record(p_name, p_line); + new_record.set_system(p_system); + new_record.set_client_id(p_client_id); + new_record.set_self(p_self); + new_record.set_music(p_music); + m_ic_record_queue.append(new_record); + update_ic_log(false); +} + +/** + * @brief Appends a message arriving from system to the IC chatlog. + * + * @param p_showname The showname used by the system. Can be an empty string. + * @param p_line The message that the system is sending. + */ +void Courtroom::append_system_text(QString p_showname, QString p_line) +{ + if (p_line.isEmpty()) + { + return; + } + append_ic_text(p_showname, p_line, true, false, NoClientId, false); +} + +void Courtroom::play_preanim() +{ + // set state + anim_state = 1; + + if (m_msg_is_first_person) + { + qDebug() << "[viewport] Skipping character animation as first person."; + preanim_done(); + return; + } + + const QString l_chr_name = m_chatmessage[CMChrName]; + const QString l_anim_name = m_chatmessage[CMPreAnim]; + qDebug() << "[viewport] Playing character animation; character:" << l_chr_name << "animation: " << l_anim_name << "file:" << ui_vp_player_char->file_name(); + ui_vp_player_char->set_play_once(true); + swap_viewport_reader(ui_vp_player_char, ViewportCharacterPre); + ui_vp_player_char->start(); +} + +void Courtroom::preanim_done() +{ + if (anim_state != 1) + { + return; + } + handle_chatmessage_3(); +} + +void Courtroom::realization_done() +{ + ui_vp_effect->stop(); +} + +void Courtroom::setup_chat() +{ + ui_vp_message->clear(); + + set_text_color(); + m_rainbow_step = 0; + // we need to ensure that the text isn't already ticking because this function + // can be called by two logic paths + if (text_state != 0) + return; + + if (chatmessage_is_empty) + { + // since the message is empty, it's technically done ticking + text_state = 2; + return; + } + + ui_vp_chatbox->show(); + + m_tick_speed = 0; + m_tick_step = 0; + is_ignore_next_letter = false; + m_blip_step = 0; + + // Cache these so chat_tick performs better + m_chatbox_message_outline = (ao_app->get_font_property("message_outline", COURTROOM_FONTS_INI) == 1); + m_chatbox_message_enable_highlighting = (ao_app->read_theme_ini_bool("enable_highlighting", COURTROOM_CONFIG_INI)); + m_chatbox_message_highlight_colors = ao_app->get_highlight_colors(); + + QString f_gender = ao_app->get_gender(m_chatmessage[CMChrName]); + + m_blips_player->set_blips("sfx-blip" + f_gender + ".wav"); + + // means text is currently ticking + text_state = 1; +} + +void Courtroom::start_chat_timer() +{ + if (m_tick_timer->isActive()) + { + return; + } + m_tick_timer->start(); +} + +void Courtroom::stop_chat_timer() +{ + m_tick_timer->stop(); +} + +void Courtroom::calculate_chat_tick_interval() +{ + double l_tick_rate = ao_config->chat_tick_interval(); + if (m_server_tick_rate.has_value()) + l_tick_rate = qMax(m_server_tick_rate.value(), 0); + l_tick_rate = qBound(0.0, l_tick_rate * (1.0 - qBound(-1.0, 0.4 * m_tick_speed, 1.0)), l_tick_rate * 2.0); + m_tick_timer->setInterval(l_tick_rate); +} + +void Courtroom::next_chat_letter() +{ + const QString &f_message = m_chatmessage[CMMessage]; + if (m_tick_step >= f_message.length() || ui_vp_chatbox->isHidden()) + { + post_chatmessage(); + return; + } + + // note: this is called fairly often(every 60 ms when char is talking) + // do not perform heavy operations here + QTextCharFormat vp_message_format = ui_vp_message->currentCharFormat(); + if (m_chatbox_message_outline) + vp_message_format.setTextOutline(QPen(Qt::black, 1)); + else + vp_message_format.setTextOutline(Qt::NoPen); + + const QChar f_character = f_message.at(m_tick_step); + if (!is_ignore_next_letter && f_character == Qt::Key_Backslash) + { + ++m_tick_step; + is_ignore_next_letter = true; + next_chat_letter(); + return; + } + else if (!is_ignore_next_letter && (f_character == Qt::Key_BraceLeft || f_character == Qt::Key_BraceRight)) // { or } + { + ++m_tick_step; + const bool is_positive = f_character == Qt::Key_BraceRight; + m_tick_speed = qBound(-3, m_tick_speed + (is_positive ? 1 : -1), 3); + calculate_chat_tick_interval(); + next_chat_letter(); + return; + } + else if (f_character == Qt::Key_Space) + { + ui_vp_message->insertPlainText(f_character); + } + else if (m_chatmessage[CMTextColor].toInt() == DR::CRainbow) + { + QString html_color; + + switch (m_rainbow_step) + { + case 0: + html_color = "#BA1518"; + break; + case 1: + html_color = "#D55900"; + break; + case 2: + html_color = "#E7CE4E"; + break; + case 3: + html_color = "#65C856"; + break; + default: + html_color = "#1596C8"; + m_rainbow_step = -1; + } + + ++m_rainbow_step; + // Apply color to the next character + QColor text_color; + text_color.setNamedColor(html_color); + vp_message_format.setForeground(text_color); + + ui_vp_message->textCursor().insertText(f_character, vp_message_format); + } + else if (m_chatbox_message_enable_highlighting) + { + bool highlight_found = false; + bool render_character = true; + // render_character should only be false if the character is a highlight + // character specifically marked as a character that should not be + // rendered. + if (m_message_color_stack.isEmpty()) + m_message_color_stack.push(""); + + if (!is_ignore_next_letter) + { + for (const auto &col : qAsConst(m_chatbox_message_highlight_colors)) + { + if (f_character == col[0][0] && m_message_color_name != col[1]) + { + m_message_color_stack.push(col[1]); + m_message_color_name = m_message_color_stack.top(); + highlight_found = true; + render_character = (col[2] != "0"); + break; + } + } + } + + // Apply color to the next character + if (m_message_color_name.isEmpty()) + vp_message_format.setForeground(m_message_color); + else + { + QColor textColor; + textColor.setNamedColor(m_message_color_name); + vp_message_format.setForeground(textColor); + } + + QString m_future_string_color = m_message_color_name; + + if (!highlight_found && !is_ignore_next_letter) + { + for (const auto &col : qAsConst(m_chatbox_message_highlight_colors)) + { + if (f_character == col[0][1]) + { + if (m_message_color_stack.size() > 1) + m_message_color_stack.pop(); + m_future_string_color = m_message_color_stack.top(); + render_character = (col[2] != "0"); + break; + } + } + } + + if (render_character) + ui_vp_message->textCursor().insertText(f_character, vp_message_format); + + m_message_color_name = m_future_string_color; + } + else + { + ui_vp_message->textCursor().insertText(f_character, vp_message_format); + } + + QScrollBar *scroll = ui_vp_message->verticalScrollBar(); + scroll->setValue(scroll->maximum()); + + if ((f_message.at(m_tick_step) != ' ' || ao_config->blank_blips_enabled())) + { + if (m_blip_step % ao_config->blip_rate() == 0) + { + m_blip_step = 0; + + // play blip + m_blips_player->blip_tick(); + } + + ++m_blip_step; + } + ui_vp_message->repaint(); + + ++m_tick_step; + is_ignore_next_letter = false; + m_tick_timer->start(); +} + +void Courtroom::post_chatmessage() +{ + m_tick_timer->stop(); + if (m_game_state != GameState::Preloading) + { + m_game_state = GameState::Finished; + } + text_state = 2; + anim_state = 3; + + if (!m_hide_character && !m_msg_is_first_person) + { + swap_viewport_reader(ui_vp_player_char, ViewportCharacterIdle); + ui_vp_player_char->start(); + } + + m_message_color_name = ""; + m_message_color_stack.clear(); + + if (ui_vp_chatbox->isVisible()) + { + ui_vp_chat_arrow->restart(); + ui_vp_chat_arrow->show(); + } +} + +void Courtroom::play_sfx() +{ + const QString l_effect = m_chatmessage[CMSoundName]; + if (l_effect.isEmpty() || l_effect == "0" || l_effect == "1") + return; + const QString l_chr = m_chatmessage[CMChrName]; + m_effects_player->play_character_effect(l_chr, l_effect); +} + +void Courtroom::on_loading_bar_delay_changed(int p_delay) +{ + m_loading_timer->setInterval(p_delay); +} + +void Courtroom::set_text_color() +{ + const QMap color_map = ao_app->get_chatmessage_colors(); + const DR::Color color = DR::Color(m_chatmessage[CMTextColor].toInt()); + const QString color_code = color_map[color_map.contains(color) ? color : DR::CDefault].code; + ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); + m_message_color.setNamedColor(color_code); +} + +void Courtroom::set_ban(int p_cid) +{ + if (p_cid != m_chr_id && p_cid != SpectatorId) + return; + + call_notice("You have been banned."); + + ao_app->construct_lobby(); + ao_app->destruct_courtroom(); +} + +void Courtroom::handle_song(QStringList p_contents) +{ + if (p_contents.size() < 4) + return; + + QString l_song = p_contents.at(0); + for (auto &i_extension : audio_extensions()) + { + const QString l_fetched_song = l_song + i_extension; + const QString l_path = ao_app->get_music_path(l_fetched_song); + if (file_exists(l_path)) + { + l_song = l_fetched_song; + break; + } + } + + const int l_chr_id = p_contents.at(1).toInt(); + + QString l_showname = p_contents.at(2); + + { + const bool l_restart = p_contents.at(3).toInt(); + if (m_current_song == l_song && !l_restart) + return; + } + m_current_song = l_song; + + m_music_player->play(l_song); + + DRAudiotrackMetadata l_song_meta(l_song); + if (l_chr_id >= 0 && l_chr_id < m_chr_list.length()) + { + if (l_showname.isEmpty()) + { + l_showname = ao_app->get_showname(m_chr_list.at(l_chr_id).name); + } + + append_ic_text(l_showname, "has played a song: " + l_song_meta.title(), false, true, NoClientId, l_chr_id == m_chr_id); + + if (ao_config->log_is_recording_enabled()) + { + save_textlog(l_showname + " has played a song: " + l_song_meta.filename()); + } + } + + set_music_text(l_song_meta.title()); +} + +void Courtroom::handle_wtce(QString p_wtce) +{ + int index = p_wtce.at(p_wtce.size() - 1).digitValue(); + if (index > 0 && index < wtce_names.size() + 1 && wtce_names.size() > 0) // check to prevent crash + { + p_wtce.chop(1); // looking for the 'testimony' part + if (p_wtce == "testimony") + { + m_effects_player->play_effect(ao_app->get_sfx(wtce_names[index - 1])); + ui_vp_wtce->play(wtce_names[index - 1]); + } + } +} + +void Courtroom::set_hp_bar(int p_bar, int p_state) +{ + if (p_state < 0 || p_state > 10) + return; + + if (p_bar == 1) + { + ui_defense_bar->set_theme_image("defensebar" + QString::number(p_state) + ".png"); + defense_bar_state = p_state; + } + else if (p_bar == 2) + { + ui_prosecution_bar->set_theme_image("prosecutionbar" + QString::number(p_state) + ".png"); + prosecution_bar_state = p_state; + } +} + +void Courtroom::set_character_position(QString p_pos) +{ + const bool l_is_default_pos = p_pos == ao_app->get_char_side(get_character_ini()); + + int l_pos_index = ui_pos_dropdown->currentIndex(); + if (!l_is_default_pos) + { + const int l_new_pos_index = ui_pos_dropdown->findData(p_pos); + if (l_new_pos_index != -1) + { + l_pos_index = l_new_pos_index; + } + } + ui_pos_dropdown->setCurrentIndex(l_pos_index); + + // enable judge mechanics if appropriate + set_judge_enabled(p_pos == "jud"); +} + +/** + * @brief Send a OOC packet (CT) out to the server. + * @param ooc_name The username. + * @param ooc_message The message. + */ +void Courtroom::send_ooc_packet(QString ooc_message) +{ + while (ao_config->username().isEmpty()) + { + QInputDialog l_dialog; + l_dialog.setWindowTitle(tr("Enter a name")); + l_dialog.setLabelText(tr("You must have a username to talk in OOC chat.")); + if (l_dialog.exec()) + { + ao_config->set_username(l_dialog.textValue()); + } + else + { + return; + } + } + + if (ooc_message.trimmed().isEmpty()) + { + append_server_chatmessage("CLIENT", "You cannot send empty messages."); + return; + } + QStringList l_content{ao_config->username(), ooc_message}; + ao_app->send_server_packet(DRPacket("CT", l_content)); +} + +void Courtroom::mod_called(QString p_ip) +{ + ui_ooc_chatlog->append(p_ip); + if (ao_config->server_alerts_enabled()) + { + m_system_player->play(ao_app->get_sfx("mod_call")); + ao_app->alert(this); + if (ao_config->log_is_recording_enabled()) + save_textlog("(OOC)(MOD CALL)" + p_ip); + } +} + +void Courtroom::on_ic_showname_editing_finished() +{ + const QString l_text = ui_ic_chat_showname->text().simplified(); + ui_ic_chat_showname->setText(l_text); + set_showname(l_text); +} + +void Courtroom::set_showname(QString p_showname) +{ + ao_config->set_showname(p_showname); +} + +void Courtroom::on_ooc_name_editing_finished() +{ + const QString l_text = ui_ooc_chat_name->text().simplified(); + ui_ooc_chat_name->setText(l_text); + ao_config->set_username(l_text); +} + +void Courtroom::on_ooc_message_return_pressed() +{ + const QString l_message = ui_ooc_chat_message->text(); + + if (l_message.startsWith("/rainbow") && !is_rainbow_enabled) + { + ui_text_color->addItem("Rainbow"); + ui_ooc_chat_message->clear(); + is_rainbow_enabled = true; + return; + } + else if (l_message.startsWith("/switch_am")) + { + on_switch_area_music_clicked(); + ui_ooc_chat_message->clear(); + return; + } + else if (l_message.startsWith("/rollp")) + { + m_effects_player->play_effect(ao_app->get_sfx("dice")); + } + else if (l_message.startsWith("/roll")) + { + m_effects_player->play_effect(ao_app->get_sfx("dice")); + } + else if (l_message.startsWith("/coinflip")) + { + m_effects_player->play_effect(ao_app->get_sfx("coinflip")); + } + else if (l_message.startsWith("/tr ")) + { + // Timer resume + int space_location = l_message.indexOf(" "); + + int timer_id; + if (space_location == -1) + timer_id = 0; + else + timer_id = l_message.mid(space_location + 1).toInt(); + resume_timer(timer_id); + } + else if (l_message.startsWith("/ts ")) + { + // Timer set + QStringList arguments = l_message.split(" "); + int size = arguments.size(); + + // Note arguments[0] == "/ts", so every index (and thus length) is off by + // one. + if (size > 5) + return; + + int timer_id = (size > 1 ? arguments[1].toInt() : 0); + int new_time = (size > 2 ? arguments[2].toInt() : 300) * 1000; + int timestep_length = (size > 3 ? arguments[3].toDouble() : -.016) * 1000; + int firing_interval = (size > 4 ? arguments[4].toDouble() : .016) * 1000; + set_timer_time(timer_id, new_time); + set_timer_timestep(timer_id, timestep_length); + set_timer_firing(timer_id, firing_interval); + } + else if (l_message.startsWith("/tp ")) + { + // Timer pause + int space_location = l_message.indexOf(" "); + + int timer_id; + if (space_location == -1) + timer_id = 0; + else + timer_id = l_message.mid(space_location + 1).toInt(); + pause_timer(timer_id); + } + + send_ooc_packet(l_message); + ui_ooc_chat_message->clear(); + + ui_ooc_chat_message->setFocus(); +} + +void Courtroom::on_pos_dropdown_changed() +{ + const QString l_pos = get_current_position(); + send_ooc_packet("/pos " + l_pos); + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_area_list_clicked() +{ + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_area_list_double_clicked(QModelIndex p_model) +{ + const QString l_area_name = ui_area_list->item(p_model.row())->text(); + ao_app->send_server_packet(DRPacket("MC", {l_area_name, QString::number(m_chr_id)})); + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_area_search_edited(QString p_filter) +{ + filter_list_widget(ui_area_list, p_filter); +} + +void Courtroom::on_area_search_edited() +{ + on_area_search_edited(ui_area_search->text()); +} + +void Courtroom::on_music_list_clicked() +{ + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_music_list_double_clicked(QModelIndex p_model) +{ + const QString l_song_name = ui_music_list->item(p_model.row())->data(Qt::UserRole).toString(); + send_mc_packet(l_song_name); + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_music_list_context_menu_requested(QPoint p_point) +{ + const QPoint l_global_point = ui_music_list->viewport()->mapToGlobal(p_point); + ui_music_menu->popup(l_global_point); +} + +void Courtroom::on_music_menu_play_triggered() +{ + QListWidgetItem *l_item = ui_music_list->currentItem(); + if (l_item) + { + const QString l_song = l_item->data(Qt::UserRole).toString(); + send_mc_packet(l_song); + } + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_music_menu_insert_ooc_triggered() +{ + QListWidgetItem *l_item = ui_music_list->currentItem(); + if (l_item) + { + const QString l_song = l_item->data(Qt::UserRole).toString(); + ui_ooc_chat_message->insert(l_song); + } + ui_ooc_chat_message->setFocus(); +} + +void Courtroom::on_music_search_edited(QString p_filter) +{ + filter_list_widget(ui_music_list, p_filter); +} + +void Courtroom::on_music_search_edited() +{ + on_music_search_edited(ui_music_search->text()); +} + +void Courtroom::send_mc_packet(QString p_song) +{ + if (is_client_muted) + return; + ao_app->send_server_packet(DRPacket("MC", {p_song, QString::number(m_chr_id)})); +} + +/** + * @brief Set the sprites of the shout buttons, and mark the currently + * selected shout as such. + * + * @details If a sprite cannot be found for a shout button, a regular + * push button is displayed for it with its shout name instead. + */ +void Courtroom::reset_shout_buttons() +{ + for (AOButton *i_button : qAsConst(ui_shouts)) + i_button->setChecked(false); + m_shout_state = 0; +} + +void Courtroom::on_shout_button_clicked(const bool p_checked) +{ + AOButton *l_button = dynamic_cast(sender()); + if (l_button == nullptr) + return; + + bool l_ok = false; + const int l_id = l_button->property("shout_id").toInt(&l_ok); + if (!l_ok) + return; + + // disable all other buttons + for (AOButton *i_button : qAsConst(ui_shouts)) + { + if (i_button == l_button) + continue; + i_button->setChecked(false); + } + m_shout_state = p_checked ? l_id : 0; + + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_shout_button_toggled(const bool p_checked) +{ + AOButton *l_button = dynamic_cast(sender()); + if (l_button == nullptr) + return; + + const QString l_name = l_button->property("shout_name").toString(); + if (l_name.isEmpty()) + return; + + const QString l_image_name(QString("%1%2.png").arg(l_name, QString(p_checked ? "_selected" : nullptr))); + l_button->set_image(l_image_name); + if (!l_button->has_image()) + l_button->setText(l_name); +} + +void Courtroom::on_cycle_clicked() +{ + AOButton *f_cycle_button = static_cast(sender()); + int f_cycle_id = f_cycle_button->property("cycle_id").toInt(); + + switch (f_cycle_id) + { + case 5: + cycle_wtce(-1); + break; + case 4: + cycle_wtce(1); + break; + case 3: + cycle_effect(-1); + break; + case 2: + cycle_effect(1); + break; + case 1: + cycle_shout(-1); + break; + case 0: + cycle_shout(1); + break; + default: + break; + } + + if (ao_app->read_theme_ini_bool("enable_cycle_ding", COURTROOM_CONFIG_INI)) + m_system_player->play(ao_app->get_sfx("cycle")); + + set_shouts(); + ui_ic_chat_message_field->setFocus(); +} + +/** + * @brief Selects the shout p_delta slots ahead of the current one, wrapping + * around if needed. If p_delta is negative, look -p_delta slots behind. + * @param Shout slots to advance. May be negative. + */ +void Courtroom::cycle_shout(int p_delta) +{ + int n = ui_shouts.size(); + m_shout_current = (m_shout_current - p_delta + n) % n; + set_shouts(); +} + +/** + * @brief Selects the effect p_delta slots ahead of the current one, wrapping + * around if needed. If p_delta is negative, look -p_delta slots behind. + * @param Shout slots to advance. May be negative. + */ +void Courtroom::cycle_effect(int p_delta) +{ + int n = ui_effects.size(); + m_effect_current = (m_effect_current - p_delta + n) % n; + set_effects(); +} + +/** + * @brief Selects the splash p_delta slots ahead of the current one, wrapping + * around if needed. If p_delta is negative, look -p_delta slots behind. + * @param Shout slots to advance. May be negative. + */ +void Courtroom::cycle_wtce(int p_delta) +{ + int n = ui_wtce.size(); + m_wtce_current = (m_wtce_current - p_delta + n) % n; + set_judge_wtce(); +} + +/** + * @brief Set the sprites of the effect buttons, and mark the currently + * selected effect as such. + * + * @details If a sprite cannot be found for an effect button, a regular + * push button is displayed for it with its effect name instead. + */ +void Courtroom::reset_effect_buttons() +{ + for (AOButton *i_button : qAsConst(ui_effects)) + i_button->setChecked(false); + m_effect_state = 0; +} + +void Courtroom::on_effect_button_clicked(const bool p_checked) +{ + AOButton *l_button = dynamic_cast(sender()); + if (l_button == nullptr) + return; + + bool l_ok = false; + const int l_id = l_button->property("effect_id").toInt(&l_ok); + if (!l_ok) + return; + + // disable all other buttons + for (AOButton *i_button : qAsConst(ui_effects)) + { + if (i_button == l_button) + continue; + i_button->setChecked(false); + } + + m_effect_state = p_checked ? l_id : 0; + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_effect_button_toggled(const bool p_checked) +{ + AOButton *l_button = dynamic_cast(sender()); + if (l_button == nullptr) + return; + + const QString l_name = l_button->property("effect_name").toString(); + if (l_name.isEmpty()) + return; + + const QString l_image_name(QString("%1%2.png").arg(l_name, QString(p_checked ? "_pressed" : nullptr))); + l_button->set_image(l_image_name); + if (!l_button->has_image()) + l_button->setText(l_name); +} + +void Courtroom::on_defense_minus_clicked() +{ + int f_state = defense_bar_state - 1; + + if (f_state >= 0) + ao_app->send_server_packet(DRPacket("HP", {QString::number(1), QString::number(f_state)})); +} + +void Courtroom::on_defense_plus_clicked() +{ + int f_state = defense_bar_state + 1; + + if (f_state <= 10) + ao_app->send_server_packet(DRPacket("HP", {QString::number(1), QString::number(f_state)})); +} + +void Courtroom::on_prosecution_minus_clicked() +{ + int f_state = prosecution_bar_state - 1; + + if (f_state >= 0) + ao_app->send_server_packet(DRPacket("HP", {QString::number(2), QString::number(f_state)})); +} + +void Courtroom::on_prosecution_plus_clicked() +{ + int f_state = prosecution_bar_state + 1; + + if (f_state <= 10) + ao_app->send_server_packet(DRPacket("HP", {QString::number(2), QString::number(f_state)})); +} + +void Courtroom::on_text_color_changed(int p_color) +{ + m_text_color = p_color; + ui_ic_chat_message_field->setFocus(); +} + +/** + * @brief Set the sprites of the splash buttons. + * + * @details If a sprite cannot be found for a shout button, a regular + * push button is displayed for it with its shout name instead. + */ +void Courtroom::reset_wtce_buttons() +{ + for (int i = 0; i < wtce_names.size(); ++i) + { + const QString l_name = wtce_names.at(i); + const QString l_file = l_name + ".png"; + AOButton *l_button = ui_wtce.at(i); + l_button->set_image(l_file); + l_button->setText(!l_button->has_image() ? l_name : nullptr); + } + + m_wtce_current = 0; + + // Unlike the other reset functions, the judge buttons are of immediate + // action and thus are immediately unpressed after being pressed. + // Therefore, we do not need to handle displaying a "_selected.png" + // when appropriate, because there is no appropriate situation +} + +void Courtroom::on_wtce_clicked() +{ + // qDebug() << "AA: wtce clicked!"; + if (is_client_muted) + return; + + AOButton *f_sig = static_cast(sender()); + QString id = f_sig->property("wtce_id").toString(); + + ao_app->send_server_packet(DRPacket("RT", {QString("testimony%1").arg(id)})); + + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_change_character_clicked() +{ + suppress_audio(true); + + set_char_select(); + set_char_select_page(); + + ui_spectator->show(); + + ao_app->send_server_packet(DRPacket("CharsCheck")); +} + +void Courtroom::load_theme() +{ + setup_courtroom(); + update_background_scene(); +} + +void Courtroom::reload_theme() +{ + if (m_game_state == GameState::Preloading || ui_vp_objection->is_running()) + { + m_shout_reload_theme = true; + return; + } + + load_theme(); +} + +void Courtroom::load_character() +{ + update_iniswap_list(); + enter_courtroom(get_character_id()); +} + +void Courtroom::load_audiotracks() +{ + DRAudiotrackMetadata::update_cache(); + list_music(); +} + +void Courtroom::on_back_to_lobby_clicked() +{ + if (m_back_to_lobby_clicked) + return; + m_back_to_lobby_clicked = true; + + // hide so we don't get the 'disconnected from server' prompt + hide(); + ao_app->leave_server(); + ao_app->construct_lobby(); + ao_app->destruct_courtroom(); +} + +void Courtroom::on_spectator_clicked() +{ + ao_app->send_server_packet(DRPacket("CC", {QString::number(ao_app->get_client_id()), "-1", "HDID"})); +} + +void Courtroom::on_call_mod_clicked() +{ + QMessageBox::StandardButton reply; + QString warning = "Are you sure you want to call a mod?\n" + "\n" + "Be prepared to explain what is happening and why they are needed when they answer."; + reply = QMessageBox::warning(this, "Warning", warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + + if (reply == QMessageBox::Yes) + { + ao_app->send_server_packet(DRPacket("ZZ")); + qDebug() << "Called mod"; + } + else + qDebug() << "Did not call mod"; + + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_switch_area_music_clicked() +{ + if (is_area_music_list_separated()) + return; + + if (ui_area_list->isHidden()) + { + ui_area_list->show(); + ui_area_search->show(); + ui_music_list->hide(); + ui_music_search->hide(); + } + else + { + ui_area_list->hide(); + ui_area_search->hide(); + ui_music_list->show(); + ui_music_search->show(); + } +} + +void Courtroom::on_pre_clicked() +{ + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_flip_clicked() +{ + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_hidden_clicked() +{ + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_config_panel_clicked() +{ + ao_app->toggle_config_panel(); + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_note_button_clicked() +{ + if (!is_note_shown) + { + load_note(); + ui_vp_notepad_image->show(); + ui_vp_notepad->show(); + ui_vp_notepad->setFocus(); + is_note_shown = true; + } + else + { + save_note(); + ui_vp_notepad_image->hide(); + ui_vp_notepad->hide(); + ui_ic_chat_message_field->setFocus(); + is_note_shown = false; + } +} + +void Courtroom::on_note_text_changed() +{ + ao_app->write_note(ui_vp_notepad->toPlainText(), current_file); +} + +void Courtroom::ping_server() +{ + ao_app->send_server_packet(DRPacket("CH", {QString::number(m_chr_id)})); +} + +void Courtroom::changeEvent(QEvent *event) +{ + QWidget::changeEvent(event); + if (event->type() == QEvent::WindowStateChange) + { + m_is_maximized = windowState().testFlag(Qt::WindowMaximized); + if (!m_is_maximized) + resize(m_default_size); + } +} + +void Courtroom::closeEvent(QCloseEvent *event) +{ + QWidget::closeEvent(event); + Q_EMIT closing(); +} + +void Courtroom::on_set_notes_clicked() +{ + if (ui_note_scroll_area->isHidden()) + ui_note_scroll_area->show(); + else + ui_note_scroll_area->hide(); +} + +void Courtroom::resume_timer(int p_id) +{ + if (p_id < 0 || p_id >= ui_timers.length()) + return; + AOTimer *l_timer = ui_timers.at(p_id); + l_timer->resume(); +} + +void Courtroom::set_timer_time(int p_id, int new_time) +{ + if (p_id < 0 || p_id >= ui_timers.length()) + return; + AOTimer *l_timer = ui_timers.at(p_id); + l_timer->set_time(QTime(0, 0).addMSecs(new_time)); +} + +void Courtroom::set_timer_timestep(int p_id, int timestep_length) +{ + if (p_id < 0 || p_id >= ui_timers.length()) + return; + AOTimer *l_timer = ui_timers.at(p_id); + l_timer->set_timestep_length(timestep_length); +} + +void Courtroom::set_timer_firing(int p_id, int firing_interval) +{ + if (p_id < 0 || p_id >= ui_timers.length()) + return; + AOTimer *l_timer = ui_timers.at(p_id); + l_timer->set_firing_interval(firing_interval); +} + +void Courtroom::pause_timer(int p_id) +{ + if (p_id < 0 || p_id >= ui_timers.length()) + return; + AOTimer *l_timer = ui_timers.at(p_id); + l_timer->pause(); +} diff --git a/src/courtroom.h b/src/courtroom.h new file mode 100644 index 000000000..293a3f1f6 --- /dev/null +++ b/src/courtroom.h @@ -0,0 +1,811 @@ +#ifndef COURTROOM_H +#define COURTROOM_H + +#include "datatypes.h" +#include "drgraphicscene.h" +#include "drposition.h" +#include "drthememovie.h" +#include "mk2/graphicsvideoscreen.h" +#include "mk2/spriteplayer.h" +#include "mk2/spritereadersynchronizer.h" + +class AOApplication; +class AOBlipPlayer; +class AOButton; +class AOCharButton; +class AOConfig; +class AOEmoteButton; +class AOImageDisplay; +class AOLabel; +class AOLineEdit; +class AOMovie; +class AOMusicPlayer; +class AONoteArea; +class AONotepad; +class AOSfxPlayer; +class AOShoutPlayer; +class AOSystemPlayer; +class AOTimer; +class DRCharacterMovie; +class DRChatLog; +class DRMovie; +class DREffectMovie; +class DRSceneMovie; +class DRShoutMovie; +class DRSplashMovie; +class DRStickerViewer; +class DRTextEdit; + +#include +#include +#include +#include +#include +#include +#include + +class QAction; +class QCheckBox; +class QComboBox; +class QLineEdit; +class QListWidget; +class QListWidgetItem; +class QMenu; +class QPropertyAnimation; +class QScrollArea; +class QSignalMapper; +class QLabel; + +#include + +class Courtroom : public QWidget +{ + Q_OBJECT + +public: + enum class GameState + { + Preloading, + Processing, + Finished, + }; + Q_ENUM(GameState) + + static const int DEFAULT_WIDTH; + static const int DEFAULT_HEIGHT; + + Courtroom(AOApplication *p_ao_app, QWidget *parent = nullptr); + ~Courtroom(); + + QVector get_character_list(); + void set_character_list(QVector character_list); + void set_area_list(QStringList area_list); + void set_music_list(QStringList music_list); + + // sets position of widgets based on theme ini files + void set_widgets(); + // helper function that calls above function on the relevant widgets + void set_fonts(); + + void set_window_title(QString p_title); + + // sets the current background to argument. also does some checks to see if + // it's a legacy bg + DRAreaBackground get_background(); + void set_background(DRAreaBackground p_area_bg); + + void set_tick_rate(const int tick_rate); + + // sets the character position + void set_character_position(QString p_pos); + + void send_ooc_packet(QString ooc_message); + + void ignore_next_showname(); + void send_showname_packet(QString p_showname); + + // called when a DONE#% from the server was received + void done_received(); + + void set_ambient(QString ambient_sfx); + void play_ambient(); + + QString get_current_background() const; + + // updates background based on the position given from the chatmessage; will reset preloading if active + void update_background_scene(); + + // displays the current background + void display_background_scene(); + + // returns a position map based on legacy background implementation + DRPositionMap get_legacy_background(QString background); + + // sets text color based on text color in chatmessage + void set_text_color(); + + // send a message that the player is banned and quits the server + void set_ban(int p_cid); + + // implementations in path_functions.cpp + QString get_background_path(QString p_file); + +public: + QString get_character(); + QString get_character_ini(); + QString get_character_content_url(); + void update_iniswap_list(); + void update_default_iniswap_item(); + void select_base_character_iniswap(); + void refresh_character_content_url(); + + // Set the showname of the client + void set_showname(QString p_showname); + + // sets up widgets + void setup_courtroom(); + + // properly sets up some varibles: resets user state + void enter_courtroom(int p_cid); + + // helper function that populates ui_music_list with the contents of + // music_list + void filter_list_widget(QListWidget *widget, QString filter); + bool is_area_music_list_separated(); + void list_music(); + void list_areas(); + + void list_note_files(); + void set_note_files(); + + void move_widget(QWidget *p_widget, QString p_identifier); + + void set_shouts(); + + void set_effects(); + + void set_judge_wtce(); + + void set_judge_enabled(bool p_enabled); + + // these are for OOC chat + void append_server_chatmessage(QString p_name, QString p_message); + + // handles resetting the UI after the server acknowledged the client sent an + // message. + void handle_acknowledged_ms(); + // these functions handle chatmessages sequentially. + // The process itself is very convoluted and merits separate documentation + // But the general idea is objection animation->pre animation->talking->idle + void next_chatmessage(QStringList p_contents); + void reset_viewport(); + void preload_chatmessage(QStringList p_contents); + void handle_chatmessage(); + void handle_chatmessage_2(); + void handle_chatmessage_3(); + + struct IcLogTextFormat + { + QTextCharFormat base; + QTextCharFormat name; + QTextCharFormat selfname; + QTextCharFormat message; + QTextCharFormat system; + }; + IcLogTextFormat m_ic_log_format; + void load_ic_text_format(); + + // adds text to the IC chatlog. p_name first as bold then p_text then a + // newlin this function keeps the chatlog scrolled to the top unless there's + // text selected or the user isn't already scrolled to the top + void update_ic_log(bool p_reset_log); + void append_ic_text(QString p_name, QString p_line, bool p_system, bool p_music, int p_client_id, bool p_self); + + void append_system_text(QString p_showname, QString p_line); + + // prints who played the song to IC chat and plays said song(if found on + // local filesystem) takes in a list where the first element is the song + // name and the second is the char id of who played it + void handle_song(QStringList p_contents); + + // animates music text + void set_music_text(QString p_text); + void update_music_text_anim(); + + // handle server-side clock animation and display + void handle_clock(QString time); + + void play_preanim(); + + // plays a splash animation based on the argument + void handle_wtce(QString p_wtce); + + // sets the hp bar of defense(p_bar 1) or pro(p_bar 2) + // state is an number between 0 and 10 inclusive + void set_hp_bar(int p_bar, int p_state); + + void check_connection_received(); + + // checks whether shout/effect/wtce/free block files are found + void check_shouts(); + void check_effects(); + void check_wtce(); + + void resume_timer(int timer_id); + void set_timer_time(int timer_id, int new_time); + void set_timer_timestep(int timer_id, int timestep_length); + void set_timer_firing(int timer_id, int firing_interval); + void pause_timer(int timer_id); + + template + int adapt_numbered_items(QVector &item_vector, QString config_item_number, QString item_name); + +signals: + void loaded_theme(); + void closing(); + +private: + bool m_first_theme_loading = true; + QSize m_default_size; + bool m_is_maximized = false; + + AOApplication *ao_app = nullptr; + AOConfig *ao_config = nullptr; + + QVector m_chr_list; + QStringList m_area_list; + QStringList m_music_list; + QString m_current_song; + + QSignalMapper *char_button_mapper = nullptr; + + // triggers ping_server() every 60 seconds + QTimer *m_keepalive_timer = nullptr; + + // maintains a timer for how fast messages tick onto screen + QTimer *m_tick_timer = nullptr; + std::optional m_server_tick_rate; + int m_tick_speed = 0; + // which tick position(character in chat message) we are at + int m_tick_step = 0; + bool is_ignore_next_letter = false; + // used to determine how often blips sound + int m_blip_step = 0; + int m_rainbow_step = 0; + bool is_first_showname_sent = false; + bool is_next_showname_ignored = false; + bool is_rainbow_enabled = false; + bool is_note_shown = false; + bool contains_add_button = false; + + ////////////// + QScrollArea *ui_note_scroll_area = nullptr; + + // delay before sfx plays + QTimer *m_sound_timer = nullptr; + + // keeps track of how long realization is visible(it's just a white square + // and should be visible less than a second) + QTimer *m_flash_timer = nullptr; + + // Generate a File Name based on the time you launched the client + QString icchatlogsfilename = QDateTime::currentDateTime().toString("'logs/'ddd MMMM dd yyyy hh.mm.ss.z'.txt'"); + + static const int MINIMUM_MESSAGE_SIZE = 15; + static const int OPTIMAL_MESSAGE_SIZE = 19; + QStringList m_pre_chatmessage; + GameState m_game_state = GameState::Finished; + QTimer *m_loading_timer; + mk2::SpriteReaderSynchronizer *m_preloader_sync; + QStringList m_chatmessage; + int m_speaker_chr_id = SpectatorId; + QString m_speaker_showname; + bool m_hide_character = false; + bool m_play_pre = false; + bool m_play_zoom = false; + bool chatmessage_is_empty = false; + + QString previous_ic_message; + + QColor m_message_color; + QString m_message_color_name; + QStack m_message_color_stack; + + bool is_client_muted = false; + + // state of animation, 0 = objecting, 1 = preanim, 2 = talking, 3 = idle + int anim_state = 3; + + // state of text ticking, 0 = not yet ticking, 1 = ticking in progress, 2 = + // ticking done + int text_state = 2; + + // if enabled, disable showing our own sprites when we talk in ic + bool m_msg_is_first_person = false; + + // Cached values for chat_tick + bool m_chatbox_message_outline = false; + bool m_chatbox_message_enable_highlighting = false; + QVector m_chatbox_message_highlight_colors; + + QString current_file; + + // if true, a reload theme order was delayed to be executed *after* a shout + // this allows reload theme orders that were received while a shout was + // playing to be executed only after the shout is done playing + bool m_shout_reload_theme = false; + + int m_shout_state = 0; + int m_effect_state = 0; + int m_text_color = 0; + int m_shout_current = 0; + int m_effect_current = 0; + int m_wtce_current = 0; + bool is_judge = false; + bool is_system_speaking = false; + + int defense_bar_state = 0; + int prosecution_bar_state = 0; + + int m_current_chr_page = 0; + int char_columns = 10; + int char_rows = 9; + int m_page_max_chr_count = 90; + + QString m_character_content_url; + QVector m_emote_list; + int m_emote_id = 0; + int m_current_emote_page = 0; + int emote_columns = 5; + int emote_rows = 2; + int m_page_max_emote_count = 10; + int m_emote_preview_id = -1; + + int m_current_clock = -1; + + QString m_ambient_sfx; + DRAreaBackground m_background; + QString m_background_name; + DRPositionMap m_position_map; + + AOImageDisplay *ui_background = nullptr; + + DRGraphicsView *ui_viewport = nullptr; + DRVideoScreen *ui_video = nullptr; + DRSceneMovie *ui_vp_background = nullptr; + DRCharacterMovie *ui_vp_player_char = nullptr; + DRSceneMovie *ui_vp_desk = nullptr; + + AONoteArea *ui_note_area = nullptr; + + AOImageDisplay *ui_vp_notepad_image = nullptr; + DRTextEdit *ui_vp_notepad = nullptr; + + AOImageDisplay *ui_vp_chatbox = nullptr; + DRTextEdit *ui_vp_showname = nullptr; + DRTextEdit *ui_vp_message = nullptr; + DREffectMovie *ui_vp_effect = nullptr; + DRSplashMovie *ui_vp_wtce = nullptr; + DRShoutMovie *ui_vp_objection = nullptr; + DRStickerViewer *ui_vp_chat_arrow = nullptr; + DRStickerViewer *ui_vp_loading = nullptr; + + QMap> m_mapped_viewer_list; + QMap m_viewport_viewer_map; + QMap m_preloader_cache; + QMap m_reader_cache; + + void map_viewers(); + void map_viewport_viewers(); + void map_viewport_readers(); + mk2::SpriteReader::ptr get_viewport_reader(ViewportSprite type) const; + void assign_readers_for_all_viewers(); + void swap_viewport_reader(DRMovie *viewer, ViewportSprite type); + void cleanup_preload_readers(); + + AOImageDisplay *ui_vp_music_display_a = nullptr; + AOImageDisplay *ui_vp_music_display_b = nullptr; + + AOImageDisplay *ui_vp_showname_image = nullptr; + + DRTextEdit *ui_vp_music_name = nullptr; + QPropertyAnimation *music_anim = nullptr; + + QWidget *ui_vp_music_area = nullptr; + + DRStickerViewer *ui_vp_clock = nullptr; + QVector ui_timers; + + DRTextEdit *ui_ic_chatlog = nullptr; + QList m_ic_record_list; + QQueue m_ic_record_queue; + AOButton *ui_ic_chatlog_scroll_topdown = nullptr; + AOButton *ui_ic_chatlog_scroll_bottomup = nullptr; + + DRChatLog *ui_ooc_chatlog = nullptr; + + QListWidget *ui_area_list = nullptr; + QLineEdit *ui_area_search = nullptr; + QListWidget *ui_music_list = nullptr; + QLineEdit *ui_music_search = nullptr; + QMenu *ui_music_menu = nullptr; + QAction *ui_music_menu_play = nullptr; + QAction *ui_music_menu_insert_ooc = nullptr; + + QListWidget *ui_sfx_list = nullptr; + QVector m_sfx_list; + const QString m_sfx_default_file = "__DEFAULT__"; + QColor m_sfx_color_found; + QColor m_sfx_color_missing; + QMenu *ui_sfx_menu = nullptr; + QAction *ui_sfx_menu_preview = nullptr; + QAction *ui_sfx_menu_insert_file_name = nullptr; + QAction *ui_sfx_menu_insert_caption = nullptr; + + QLineEdit *ui_ic_chat_showname = nullptr; + QWidget *ui_ic_chat_message = nullptr; + QLineEdit *ui_ic_chat_message_field = nullptr; + QLabel *ui_ic_chat_message_counter = nullptr; + + QLineEdit *ui_ooc_chat_name = nullptr; + QLineEdit *ui_ooc_chat_message = nullptr; + + QLineEdit *ui_sfx_search = nullptr; + + QWidget *ui_emotes = nullptr; + QVector ui_emote_list; + AOButton *ui_emote_left = nullptr; + AOButton *ui_emote_right = nullptr; + DRGraphicsView *ui_emote_preview; + DRThemeMovie *ui_emote_preview_background; + DRCharacterMovie *ui_emote_preview_character; + + QComboBox *ui_emote_dropdown = nullptr; + QComboBox *ui_iniswap_dropdown = nullptr; + + enum PositionIndex + { + DefaultPositionIndex, + }; + QComboBox *ui_pos_dropdown = nullptr; + + AOImageDisplay *ui_defense_bar = nullptr; + AOImageDisplay *ui_prosecution_bar = nullptr; + + // buttons to cycle through shouts + AOButton *ui_shout_up = nullptr; + AOButton *ui_shout_down = nullptr; + // buttons to cycle through effects + AOButton *ui_effect_up = nullptr; + AOButton *ui_effect_down = nullptr; + // buttons to cycle through wtce + AOButton *ui_wtce_up = nullptr; + AOButton *ui_wtce_down = nullptr; + + // holds all the shout button objects + QVector ui_shouts; + // holds all the effect button objects + QVector ui_effects; + // holds all the shout buttons objects + QVector ui_wtce; + // holds all the free block objects + QVector ui_free_blocks; + + // holds all the names for sound files for the shouts + QVector shout_names; + + // holds all the names for sound/anim files for the effects + QVector effect_names; + + // holds all the names for sound/anim files for the shouts + QVector wtce_names; + + // holds whether the animation file exists for a determined shout/effect + QVector shouts_enabled; + QVector effects_enabled; + QVector wtce_enabled; + QVector free_blocks_enabled; + + AOButton *ui_change_character = nullptr; + AOButton *ui_call_mod = nullptr; + AOButton *ui_switch_area_music = nullptr; + + AOButton *ui_config_panel = nullptr; + + AOButton *ui_set_notes = nullptr; + + QCheckBox *ui_pre = nullptr; + QCheckBox *ui_flip = nullptr; + QCheckBox *ui_hide_character = nullptr; + + QVector ui_checks; // 0 = pre, 1 = flip, 2 = hidden + QVector ui_labels; // 0 = music, 1 = sfx, 2 = blip + QVector ui_label_images; + QVector label_images = {"Pre", "Flip", "Hidden"}; + + AOButton *ui_effect_flash = nullptr; + AOButton *ui_effect_gloom = nullptr; + + AOButton *ui_defense_plus = nullptr; + AOButton *ui_defense_minus = nullptr; + + AOButton *ui_prosecution_plus = nullptr; + AOButton *ui_prosecution_minus = nullptr; + + QComboBox *ui_text_color = nullptr; + + AOButton *ui_note_button = nullptr; + + AOImageDisplay *ui_char_select_background = nullptr; + + // abstract widget to hold char buttons + QWidget *ui_char_buttons = nullptr; + AOImageDisplay *ui_char_button_selector = nullptr; + QVector ui_char_button_list; + + AOButton *ui_back_to_lobby = nullptr; + bool m_back_to_lobby_clicked = false; + + AOButton *ui_chr_select_left = nullptr; + AOButton *ui_chr_select_right = nullptr; + + AOButton *ui_spectator = nullptr; + + QHash widget_names; + + void create_widgets(); + void connect_widgets(); + void set_widget_names(); + void reset_widget_names(); + void insert_widget_name(QString p_widget_name, QWidget *p_widget); + void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); + template + void insert_widget_names(QVector &p_widget_names, QVector &p_widgets); + void set_widget_layers(); + + void construct_char_select(); + void reconstruct_char_select(); + void reset_char_select(); + void set_char_select(); + void set_char_select_page(); + + void construct_emotes(); + void construct_emote_page_layout(); + void reset_emote_page(); + void refresh_emote_page(const bool scroll_to_current_emote = false); + void fill_emote_dropdown(); + DREmote get_emote(const int id); + DREmote get_current_emote(); + QString get_current_position(); + + void load_note(); + void save_note(); + void save_textlog(QString p_text); + + bool is_spectating(); + + QString get_shout_name(int shout_index); + QString get_effect_name(int effect_index); + +public slots: + void video_finished(); + void objection_done(); + void preanim_done(); + + void realization_done(); + + void mod_called(QString p_ip); + +private slots: + void setup_chat(); + void play_sfx(); + + void on_loading_bar_delay_changed(int p_delay); + void start_chatmessage(); + + void start_chat_timer(); + void stop_chat_timer(); + void calculate_chat_tick_interval(); + void next_chat_letter(); + void post_chatmessage(); + + void on_showname_changed(QString); + void on_showname_placeholder_changed(QString); + void on_character_ini_changed(); + void on_ic_showname_editing_finished(); + void on_ic_message_return_pressed(); + void handle_ic_message_length(); + void on_chat_config_changed(); + + void on_ic_chatlog_scroll_changed(); + void on_ic_chatlog_scroll_topdown_clicked(); + void on_ic_chatlog_scroll_bottomup_clicked(); + + void on_ooc_name_editing_finished(); + void on_ooc_message_return_pressed(); + + void on_area_list_clicked(); + void on_area_list_double_clicked(QModelIndex p_model); + void on_area_search_edited(QString); + void on_area_search_edited(); + + void on_music_list_clicked(); + void on_music_list_double_clicked(QModelIndex p_model); + void on_music_list_context_menu_requested(QPoint p_point); + void on_music_menu_play_triggered(); + void on_music_menu_insert_ooc_triggered(); + void on_music_search_edited(QString); + void on_music_search_edited(); + void send_mc_packet(QString p_song); + + void select_emote(int p_id); + + void on_emote_clicked(int id); + void show_emote_tooltip(int id, QPoint global_pos); + void hide_emote_tooltip(int id); + void on_emote_preview_toggled(bool); + + void on_emote_left_clicked(); + void on_emote_right_clicked(); + + void on_emote_dropdown_changed(int p_index); + void on_iniswap_dropdown_changed(int p_index); + void update_iniswap_dropdown_searchable(); + void on_pos_dropdown_changed(); + + void on_cycle_clicked(); + + void cycle_shout(int p_delta); + void cycle_effect(int p_delta); + void cycle_wtce(int p_delta); + + void on_add_button_clicked(); + void on_delete_button_clicked(); + + void on_set_file_button_clicked(); + void on_file_selected(); + + void delete_widget(QWidget *p_widget); + void load_shouts(); + void load_effects(); + void load_wtce(); + void load_free_blocks(); + void reset_shout_buttons(); + + void on_shout_button_clicked(const bool); + void on_shout_button_toggled(const bool); + + void reset_effect_buttons(); + void on_effect_button_clicked(const bool); + void on_effect_button_toggled(const bool); + + void on_defense_minus_clicked(); + void on_defense_plus_clicked(); + void on_prosecution_minus_clicked(); + void on_prosecution_plus_clicked(); + + void on_text_color_changed(int p_color); + + void reset_wtce_buttons(); + void on_wtce_clicked(); + + void on_change_character_clicked(); + void load_theme(); + void reload_theme(); + void load_character(); + void load_audiotracks(); + void on_call_mod_clicked(); + + void on_switch_area_music_clicked(); + + void on_config_panel_clicked(); + void on_note_button_clicked(); + + void on_set_notes_clicked(); + + void on_note_text_changed(); + + void on_pre_clicked(); + void on_flip_clicked(); + void on_hidden_clicked(); + + void on_back_to_lobby_clicked(); + + void on_char_select_left_clicked(); + void on_char_select_right_clicked(); + void update_character_icon(QString character); + void char_clicked(int n_char); + void char_mouse_entered(AOCharButton *p_caller); + void char_mouse_left(); + + void on_spectator_clicked(); + + void ping_server(); + + // performance + void assign_readers_for_viewers(int p_type, bool p_caching); + + // character + // =========================================================================== + +public: + using CharacterId = int; + enum : CharacterId + { + SpectatorId = -1, + }; + + int get_character_id(); +public slots: + void set_character_id(const int); +signals: + void character_id_changed(int); + +private: + // character id, which index of the char_list the player is + CharacterId m_chr_id = SpectatorId; + + // sfx + +public: + std::optional current_sfx(); + QString current_sfx_file(); + void load_current_character_sfx_list(); + void load_sfx_list_theme(); + void select_default_sfx(); + void clear_sfx_selection(); + void update_all_sfx_item_color(); + +public slots: + void filter_sfx_list(QString); + void filter_sfx_list(); + +private: + void set_sfx_item_color(QListWidgetItem *item); + +private slots: + void on_sfx_list_current_item_changed(QListWidgetItem *current_item, QListWidgetItem *previous_item); + void on_sfx_list_context_menu_requested(QPoint point); + void on_sfx_menu_preview_triggered(); + void on_sfx_menu_insert_file_name_triggered(); + void on_sfx_menu_insert_caption_triggered(); + + /*! + * ============================================================================= + * AUDIO SYSTEM + */ + +public: + bool is_audio_suppressed() const; + +public slots: + void suppress_audio(bool p_enabled); + void stop_all_audio(); + +private: + AOSfxPlayer *m_effects_player = nullptr; + AOShoutPlayer *m_shouts_player = nullptr; + AOSystemPlayer *m_system_player = nullptr; + AOMusicPlayer *m_music_player = nullptr; + AOBlipPlayer *m_blips_player = nullptr; + bool is_audio_muted = false; + + // QWidget interface + +protected: + void changeEvent(QEvent *) override; + void closeEvent(QCloseEvent *event) override; +}; + +template +void Courtroom::insert_widget_names(QVector &p_widget_names, QVector &p_widgets) +{ + QVector widgets; + + for (QWidget *widget : p_widgets) + widgets.append(widget); + + insert_widget_names(p_widget_names, widgets); +} + +#endif // COURTROOM_H diff --git a/src/courtroom_character.cpp b/src/courtroom_character.cpp new file mode 100644 index 000000000..ccaaadda1 --- /dev/null +++ b/src/courtroom_character.cpp @@ -0,0 +1,151 @@ +#include "courtroom.h" + +#include "aoapplication.h" +#include "aoconfig.h" +#include "commondefs.h" +#include "drpacket.h" +#include "file_functions.h" +#include "theme.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int Courtroom::get_character_id() +{ + return m_chr_id; +} + +void Courtroom::set_character_id(const int p_chr_id) +{ + if (p_chr_id != SpectatorId && m_chr_id == p_chr_id) + return; + m_chr_id = p_chr_id; + load_character(); + Q_EMIT character_id_changed(m_chr_id); +} + +QString Courtroom::get_character() +{ + return is_spectating() ? nullptr : m_chr_list.at(m_chr_id).name; +} + +QString Courtroom::get_character_ini() +{ + return ao_config->character_ini(get_character()); +} + +QString Courtroom::get_character_content_url() +{ + QFile l_contentFile(ao_app->get_character_path(get_character_ini(), "CONTENT.txt")); + if (!l_contentFile.open(QIODevice::ReadOnly)) + return nullptr; + + const QUrl l_url(QString(l_contentFile.readAll()).simplified()); + if (l_url.isRelative() || l_url.isLocalFile()) + return nullptr; + + return l_url.toString(QUrl::FullyEncoded); +} + +namespace +{ +void drSetItemIcon(QComboBox *p_widget, const int p_index, const QString &p_chr_name, AOApplication *ao_app) +{ + static const QIcon s_blank_icon = []() -> QIcon { + QPixmap l_blank_texture(64, 64); + l_blank_texture.fill(Qt::transparent); + return QIcon(l_blank_texture); + }(); + + const QString l_icon_file = ao_app->get_character_path(p_chr_name, "char_icon.png"); + p_widget->setItemIcon(p_index, file_exists(l_icon_file) ? QIcon(l_icon_file) : s_blank_icon); +} +} // namespace + +void Courtroom::update_iniswap_list() +{ + ui_iniswap_dropdown->setEditable(false); + + { + QSignalBlocker b_ini_list(ui_iniswap_dropdown); + ui_iniswap_dropdown->clear(); + + QStringList l_name_list{"Default"}; + const QString l_path = ao_app->get_base_path() + "/characters"; + const QFileInfoList l_info_list = QDir(l_path).entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); + for (const QFileInfo &i_info : l_info_list) + { + const QString l_name = i_info.fileName(); + if (!file_exists(ao_app->get_character_path(l_name, CHARACTER_CHAR_INI))) + continue; + l_name_list.append(l_name); + } + + for (int i = 0; i < l_name_list.length(); ++i) + { + const QString &i_name = l_name_list.at(i); + ui_iniswap_dropdown->addItem(i_name); + if (i == 0) + continue; + drSetItemIcon(ui_iniswap_dropdown, i, i_name, ao_app); + } + update_default_iniswap_item(); + select_base_character_iniswap(); + } + + update_iniswap_dropdown_searchable(); +} + +void Courtroom::update_default_iniswap_item() +{ + drSetItemIcon(ui_iniswap_dropdown, 0, get_character(), ao_app); +} + +void Courtroom::select_base_character_iniswap() +{ + const QString l_current_chr = get_character_ini(); + if (get_character() == l_current_chr) + { + ui_iniswap_dropdown->setCurrentIndex(0); + return; + } + ui_iniswap_dropdown->setCurrentText(l_current_chr); +} + +void Courtroom::refresh_character_content_url() +{ + const QString l_new_content_url = get_character_content_url(); + if (m_character_content_url == l_new_content_url) + return; + m_character_content_url = l_new_content_url; + ao_app->send_server_packet(DRPacket("FS", {m_character_content_url})); +} + +void Courtroom::on_iniswap_dropdown_changed(int p_index) +{ + ao_config->set_character_ini(get_character(), + p_index == 0 ? get_character() : ui_iniswap_dropdown->itemText(p_index)); +} + +void Courtroom::update_iniswap_dropdown_searchable() +{ + const bool l_enabled = ao_config->searchable_iniswap_enabled(); + ui_iniswap_dropdown->setEditable(l_enabled); + if (l_enabled) + { + QCompleter *l_completer = ui_iniswap_dropdown->completer(); + l_completer->setCompletionMode(QCompleter::PopupCompletion); + l_completer->setFilterMode(Qt::MatchContains); + QAbstractItemView *l_list = l_completer->popup(); + l_list->setTextElideMode(Qt::TextElideMode::ElideNone); + l_list->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + } + set_stylesheet(ui_iniswap_dropdown, "[INISWAP DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); +} diff --git a/src/courtroom_sfx.cpp b/src/courtroom_sfx.cpp new file mode 100644 index 000000000..a78523ac5 --- /dev/null +++ b/src/courtroom_sfx.cpp @@ -0,0 +1,167 @@ +#include "courtroom.h" + +#include "aoapplication.h" +#include "aosfxplayer.h" +#include "commondefs.h" +#include "file_functions.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +std::optional Courtroom::current_sfx() +{ + QListWidgetItem *l_item = ui_sfx_list->currentItem(); + if (l_item == nullptr) + return std::nullopt; + return m_sfx_list.at(l_item->data(Qt::UserRole).toInt()); +} + +QString Courtroom::current_sfx_file() +{ + const QString l_current_emote_file = get_current_emote().sound_file; + const std::optional l_optional_sfx = current_sfx(); + if (!l_optional_sfx.has_value()) + return l_current_emote_file; + const QString l_file = l_optional_sfx.value().file; + return l_file == m_sfx_default_file ? l_current_emote_file : l_file; +} + +void Courtroom::load_sfx_list_theme() +{ + m_sfx_color_found = ao_app->get_color("found_song_color", COURTROOM_DESIGN_INI); + m_sfx_color_missing = ao_app->get_color("missing_song_color", COURTROOM_DESIGN_INI); + update_all_sfx_item_color(); +} + +void Courtroom::load_current_character_sfx_list() +{ + QSignalBlocker l_blocker(ui_sfx_list); + ui_sfx_list->clear(); + + // items + m_sfx_list.clear(); + m_sfx_list.append(DRSfx("Default", m_sfx_default_file)); + m_sfx_list.append(DRSfx("Silence", nullptr)); + + const QStringList l_sfx_list = ao_app->get_sfx_list(); + for (const QString &i_sfx_line : l_sfx_list) + { + const QStringList l_sfx_entry = i_sfx_line.split("=", DR::SkipEmptyParts); + + const QString l_name = l_sfx_entry.at(l_sfx_entry.size() - 1).trimmed(); + const QString l_file = QString(l_sfx_entry.size() >= 2 ? l_sfx_entry.at(0) : nullptr).trimmed(); + const bool l_is_found = !ao_app->find_asset_path({ao_app->get_sfx_noext_path(l_file)}, audio_extensions()).isEmpty(); + m_sfx_list.append(DRSfx(l_name, l_file, l_is_found)); + } + + // create items + QList l_item_list; + for (int i = 0; i < m_sfx_list.length(); ++i) + { + const DRSfx &i_sfx = m_sfx_list.at(i); + QListWidgetItem *l_item = new QListWidgetItem(ui_sfx_list); + l_item_list.append(l_item); + l_item->setText(i_sfx.name); + l_item->setData(Qt::UserRole, i); + set_sfx_item_color(l_item); + } + + filter_sfx_list(); +} + +void Courtroom::filter_sfx_list(QString p_filter) +{ + filter_list_widget(ui_sfx_list, p_filter); +} + +void Courtroom::filter_sfx_list() +{ + filter_sfx_list(ui_sfx_search->text()); +} + +void Courtroom::select_default_sfx() +{ + if (ui_sfx_list->count() == 0) + return; + ui_sfx_list->setCurrentRow(0); +} + +void Courtroom::clear_sfx_selection() +{ + ui_sfx_list->setCurrentRow(-1); +} + +void Courtroom::set_sfx_item_color(QListWidgetItem *p_item) +{ + if (p_item == nullptr) + return; + const bool l_is_found = m_sfx_list.at(p_item->data(Qt::UserRole).toInt()).is_found; + QColor l_color = l_is_found ? m_sfx_color_found : m_sfx_color_missing; + + if (p_item == ui_sfx_list->currentItem()) + { + // Calculate the amount of lightness it would take to light up the row. We + // also limit it to 1.0, as giving lightness values above 1.0 to QColor does + // nothing. +0.4 is just an arbitrarily chosen number. + const double l_final_lightness = qMin(1.0, l_color.lightnessF() + 0.4); + + // This is just the reverse of the above, basically. We set the colour, and we + // set the brush to have that colour. + l_color.setHslF(l_color.hueF(), l_color.saturationF(), l_final_lightness); + } + + p_item->setBackground(l_color); +} + +void Courtroom::update_all_sfx_item_color() +{ + for (int i = 0; i < m_sfx_list.length(); ++i) + set_sfx_item_color(ui_sfx_list->item(i)); +} + +void Courtroom::on_sfx_list_current_item_changed(QListWidgetItem *p_current_item, QListWidgetItem *p_previous_item) +{ + set_sfx_item_color(p_current_item); + set_sfx_item_color(p_previous_item); + ui_pre->setChecked(ui_pre->isChecked() || current_sfx().has_value()); + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_sfx_list_context_menu_requested(QPoint p_point) +{ + const QPoint l_global_point = ui_sfx_list->viewport()->mapToGlobal(p_point); + ui_sfx_menu->popup(l_global_point); +} + +void Courtroom::on_sfx_menu_preview_triggered() +{ + m_effects_player->play_effect(current_sfx_file()); +} + +void Courtroom::on_sfx_menu_insert_file_name_triggered() +{ + ui_ooc_chat_message->insert(current_sfx_file()); + ui_ooc_chat_message->setFocus(); +} + +void Courtroom::on_sfx_menu_insert_caption_triggered() +{ + const std::optional l_sfx = current_sfx(); + if (l_sfx.has_value()) + { + QString l_caption = l_sfx->name; + static const QRegularExpression l_regex("\"(.+)\""); + if (const auto l_match = l_regex.match(l_caption); l_match.hasMatch()) + { + l_caption = l_match.captured(1); + } + ui_ic_chat_message_field->insert(l_caption); + } +} diff --git a/src/courtroom_widgets.cpp b/src/courtroom_widgets.cpp new file mode 100644 index 000000000..456d3e9fe --- /dev/null +++ b/src/courtroom_widgets.cpp @@ -0,0 +1,1438 @@ +#include "courtroom.h" + +#include "aoapplication.h" +#include "aoblipplayer.h" +#include "aobutton.h" +#include "aoconfig.h" +#include "aoimagedisplay.h" +#include "aolabel.h" +#include "aolineedit.h" +#include "aomusicplayer.h" +#include "aonotearea.h" +#include "aonotepicker.h" +#include "aosfxplayer.h" +#include "aoshoutplayer.h" +#include "aosystemplayer.h" +#include "aotimer.h" +#include "commondefs.h" +#include "drcharactermovie.h" +#include "drchatlog.h" +#include "dreffectmovie.h" +#include "drscenemovie.h" +#include "drshoutmovie.h" +#include "drsplashmovie.h" +#include "drstickerviewer.h" +#include "drtextedit.h" +#include "file_functions.h" +#include "mk2/graphicsvideoscreen.h" +#include "theme.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void Courtroom::create_widgets() +{ + m_keepalive_timer = new QTimer(this); + m_keepalive_timer->start(60000); + + m_tick_timer = new QTimer(this); + m_tick_timer->setSingleShot(true); + m_tick_timer->setTimerType(Qt::PreciseTimer); + + m_sound_timer = new QTimer(this); + m_sound_timer->setSingleShot(true); + + m_flash_timer = new QTimer(this); + m_flash_timer->setSingleShot(true); + + char_button_mapper = new QSignalMapper(this); + + m_system_player = new AOSystemPlayer(ao_app, this); + m_effects_player = new AOSfxPlayer(ao_app, this); + m_shouts_player = new AOShoutPlayer(ao_app, this); + m_music_player = new AOMusicPlayer(ao_app, this); + m_blips_player = new AOBlipPlayer(ao_app, this); + + ui_background = new AOImageDisplay(this, ao_app); + + ui_viewport = new DRGraphicsView(this); + + { // populate scene + auto *l_scene = ui_viewport->scene(); + + ui_vp_background = new DRSceneMovie(ao_app); + l_scene->addItem(ui_vp_background); + + ui_vp_player_char = new DRCharacterMovie(ao_app); + l_scene->addItem(ui_vp_player_char); + + ui_vp_desk = new DRSceneMovie(ao_app); + l_scene->addItem(ui_vp_desk); + + ui_vp_effect = new DREffectMovie(ao_app); + l_scene->addItem(ui_vp_effect); + + ui_vp_wtce = new DRSplashMovie(ao_app); + l_scene->addItem(ui_vp_wtce); + + ui_vp_objection = new DRShoutMovie(ao_app); + l_scene->addItem(ui_vp_objection); + + ui_video = new DRVideoScreen(ao_app); + l_scene->addItem(ui_video); + + // move to the corner + for (auto i_item : l_scene->items()) + { + i_item->setPos(0, 0); + } + } + + ui_vp_music_display_a = new AOImageDisplay(this, ao_app); + ui_vp_music_display_b = new AOImageDisplay(this, ao_app); + ui_vp_music_area = new QWidget(ui_vp_music_display_a); + ui_vp_music_name = new DRTextEdit(ui_vp_music_area); + ui_vp_music_name->setFrameStyle(QFrame::NoFrame); + ui_vp_music_name->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_vp_music_name->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_vp_music_name->setReadOnly(true); + music_anim = new QPropertyAnimation(ui_vp_music_name, "geometry", this); + set_music_text("DANGANRONPA ONLINE"); + + ui_vp_clock = new DRStickerViewer(ao_app, this); + + ui_vp_chatbox = new AOImageDisplay(this, ao_app); + ui_vp_showname = new DRTextEdit(ui_vp_chatbox); + ui_vp_showname->setFrameStyle(QFrame::NoFrame); + ui_vp_showname->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_vp_showname->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_vp_showname->setReadOnly(true); + ui_vp_message = new DRTextEdit(ui_vp_chatbox); + ui_vp_message->setFrameStyle(QFrame::NoFrame); + ui_vp_message->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_vp_message->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_vp_message->setReadOnly(true); + + ui_vp_showname_image = new AOImageDisplay(this, ao_app); + + ui_vp_chat_arrow = new DRStickerViewer(ao_app, this); + ui_vp_loading = new DRStickerViewer(ao_app, this); + ui_vp_loading->hide(); + + m_loading_timer = new QTimer(this); + m_loading_timer->setSingleShot(true); + m_loading_timer->setInterval(ao_config->loading_bar_delay()); + + ui_iniswap_dropdown = new QComboBox(this); + ui_iniswap_dropdown->setInsertPolicy(QComboBox::NoInsert); + { + QListView *l_view = new QListView(ui_iniswap_dropdown); + ui_iniswap_dropdown->setView(l_view); + l_view->setTextElideMode(Qt::TextElideMode::ElideNone); + l_view->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + } + + ui_ic_chatlog = new DRTextEdit(this); + ui_ic_chatlog->setReadOnly(true); + ui_ic_chatlog->set_auto_align(false); + ui_ic_chatlog_scroll_topdown = new AOButton(this, ao_app); + ui_ic_chatlog_scroll_bottomup = new AOButton(this, ao_app); + + ui_ooc_chatlog = new DRChatLog(this); + ui_ooc_chatlog->setReadOnly(true); + ui_ooc_chatlog->setOpenExternalLinks(true); + + ui_area_list = new QListWidget(this); + ui_area_search = new QLineEdit(this); + ui_area_search->setFrame(false); + ui_area_search->setPlaceholderText("Area filter"); + + ui_music_list = new QListWidget(this); + ui_music_list->setContextMenuPolicy(Qt::CustomContextMenu); + ui_music_search = new QLineEdit(this); + ui_music_search->setFrame(false); + ui_music_search->setPlaceholderText("Music filter"); + ui_music_menu = new QMenu(this); + ui_music_menu_play = ui_music_menu->addAction(tr("Play")); + ui_music_menu_insert_ooc = ui_music_menu->addAction(tr("Insert to OOC")); + + ui_sfx_list = new QListWidget(this); + ui_sfx_list->setContextMenuPolicy(Qt::CustomContextMenu); + ui_sfx_search = new QLineEdit(this); + ui_sfx_search->setFrame(false); + ui_sfx_menu = new QMenu(this); + ui_sfx_menu_preview = ui_sfx_menu->addAction(tr("Preview")); + ui_sfx_menu_insert_file_name = ui_sfx_menu->addAction(tr("Insert filename")); + ui_sfx_menu_insert_caption = ui_sfx_menu->addAction("Insert caption"); + + ui_ic_chat_message = new QWidget(this); + + ui_ic_chat_message_field = new QLineEdit(ui_ic_chat_message); + ui_ic_chat_message_field->setFrame(false); + ui_ic_chat_message_field->setPlaceholderText(tr("Say something in-character.")); + ui_ic_chat_message_field->setMaxLength(255); + + ui_ooc_chat_message = new QLineEdit(this); + ui_ooc_chat_message->setFrame(false); + ui_ooc_chat_message->setPlaceholderText(tr("Say something out-of-character.")); + ui_ooc_chat_message->setMaxLength(1023); + + ui_ic_chat_message_counter = new QLabel(ui_ic_chat_message); + ui_ic_chat_message_counter->setAlignment(Qt::AlignVCenter | Qt::AlignRight); + ui_ic_chat_message_counter->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); + + { + auto l_layout = new QHBoxLayout(ui_ic_chat_message); + ui_ic_chat_message_counter->setIndent(l_layout->contentsMargins().right()); + l_layout->setContentsMargins(0, 0, 0, 0); + l_layout->addWidget(ui_ic_chat_message_field); + l_layout->addWidget(ui_ic_chat_message_counter); + } + + ui_ooc_chat_name = new QLineEdit(this); + ui_ooc_chat_name->setFrame(false); + ui_ooc_chat_name->setPlaceholderText("Name"); + ui_ooc_chat_name->setText(ao_config->username()); + + ui_ic_chat_showname = new QLineEdit(this); + ui_ic_chat_showname->setFrame(false); + ui_ic_chat_showname->setPlaceholderText("Showname"); + ui_ic_chat_showname->setText(ao_config->showname()); + + ui_note_area = new AONoteArea(this, ao_app); + ui_note_area->add_button = new AOButton(ui_note_area, ao_app); + ui_note_area->m_layout = new QVBoxLayout(ui_note_area); + + ui_note_scroll_area = new QScrollArea(this); + ui_note_scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_note_scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_note_scroll_area->setWidgetResizable(true); + + ui_set_notes = new AOButton(this, ao_app); + + construct_emotes(); + + ui_defense_bar = new AOImageDisplay(this, ao_app); + ui_prosecution_bar = new AOImageDisplay(this, ao_app); + + ui_shout_up = new AOButton(this, ao_app); + ui_shout_up->setProperty("cycle_id", 1); + ui_shout_down = new AOButton(this, ao_app); + ui_shout_down->setProperty("cycle_id", 0); + + ui_effect_down = new AOButton(this, ao_app); + ui_effect_down->setProperty("cycle_id", 2); + ui_effect_up = new AOButton(this, ao_app); + ui_effect_up->setProperty("cycle_id", 3); + + ui_wtce_up = new AOButton(this, ao_app); + ui_wtce_up->setProperty("cycle_id", 5); + ui_wtce_down = new AOButton(this, ao_app); + ui_wtce_down->setProperty("cycle_id", 4); + + ui_change_character = new AOButton(this, ao_app); + ui_call_mod = new AOButton(this, ao_app); + ui_switch_area_music = new AOButton(this, ao_app); + + ui_config_panel = new AOButton(this, ao_app); + ui_note_button = new AOButton(this, ao_app); + + ui_label_images.resize(label_images.size()); + for (int i = 0; i < ui_label_images.size(); ++i) + { + ui_label_images[i] = new AOImageDisplay(this, ao_app); + } + + ui_pre = new QCheckBox(this); + ui_pre->setText("Pre"); + ui_flip = new QCheckBox(this); + ui_flip->setText("Flip"); + ui_flip->hide(); + ui_hide_character = new QCheckBox(this); + ui_hide_character->setText("Hidden"); + + // filling vectors with existing label/checkbox pointers + ui_checks.push_back(ui_pre); + ui_checks.push_back(ui_flip); + ui_checks.push_back(ui_hide_character); + + ui_defense_plus = new AOButton(this, ao_app); + ui_defense_minus = new AOButton(this, ao_app); + + ui_prosecution_plus = new AOButton(this, ao_app); + ui_prosecution_minus = new AOButton(this, ao_app); + + ui_text_color = new QComboBox(this); + ui_text_color->addItem("White"); + ui_text_color->addItem("Green"); + ui_text_color->addItem("Red"); + ui_text_color->addItem("Orange"); + ui_text_color->addItem("Blue"); + ui_text_color->addItem("Yellow"); + ui_text_color->addItem("Purple"); + ui_text_color->addItem("Pink"); + ui_vp_notepad_image = new AOImageDisplay(this, ao_app); + ui_vp_notepad = new DRTextEdit(this); + ui_vp_notepad->setFrameStyle(QFrame::NoFrame); + + ui_timers.resize(1); + ui_timers[0] = new AOTimer(this); + + construct_char_select(); +} + +void Courtroom::connect_widgets() +{ + connect(m_keepalive_timer, SIGNAL(timeout()), this, SLOT(ping_server())); + + connect(ui_video, SIGNAL(finished()), this, SLOT(video_finished())); + connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); + connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); + + connect(m_sound_timer, SIGNAL(timeout()), this, SLOT(play_sfx())); + + connect(m_tick_timer, SIGNAL(timeout()), this, SLOT(next_chat_letter())); + + connect(m_flash_timer, SIGNAL(timeout()), this, SLOT(realization_done())); + + connect(ao_config, SIGNAL(searchable_iniswap_changed(bool)), this, SLOT(update_iniswap_dropdown_searchable())); + connect(ao_config, SIGNAL(emote_preview_changed(bool)), this, SLOT(on_emote_preview_toggled(bool))); + connect(ui_emote_left, SIGNAL(clicked()), this, SLOT(on_emote_left_clicked())); + connect(ui_emote_right, SIGNAL(clicked()), this, SLOT(on_emote_right_clicked())); + + connect(ui_emote_dropdown, SIGNAL(activated(int)), this, SLOT(on_emote_dropdown_changed(int))); + connect(ui_iniswap_dropdown, SIGNAL(activated(int)), this, SLOT(on_iniswap_dropdown_changed(int))); + connect(ui_pos_dropdown, SIGNAL(activated(int)), this, SLOT(on_pos_dropdown_changed())); + + connect(ao_config, SIGNAL(showname_changed(QString)), this, SLOT(on_showname_changed(QString))); + connect(ao_config, SIGNAL(showname_placeholder_changed(QString)), this, SLOT(on_showname_placeholder_changed(QString))); + connect(ao_config, SIGNAL(character_ini_changed(QString)), this, SLOT(on_character_ini_changed())); + connect(ui_ic_chat_showname, SIGNAL(editingFinished()), this, SLOT(on_ic_showname_editing_finished())); + connect(ui_ic_chat_message_field, SIGNAL(returnPressed()), this, SLOT(on_ic_message_return_pressed())); + connect(ao_config, SIGNAL(message_length_threshold_changed(int)), this, SLOT(handle_ic_message_length())); + connect(ui_ic_chat_message_field, SIGNAL(textChanged(QString)), this, SLOT(handle_ic_message_length())); + connect(ui_ic_chatlog->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(on_ic_chatlog_scroll_changed())); + connect(ui_ic_chatlog_scroll_topdown, SIGNAL(clicked()), this, SLOT(on_ic_chatlog_scroll_topdown_clicked())); + connect(ui_ic_chatlog_scroll_bottomup, SIGNAL(clicked()), this, SLOT(on_ic_chatlog_scroll_bottomup_clicked())); + connect(ao_config, SIGNAL(username_changed(QString)), ui_ooc_chat_name, SLOT(setText(QString))); + connect(ui_ooc_chat_name, SIGNAL(editingFinished()), this, SLOT(on_ooc_name_editing_finished())); + connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_message_return_pressed())); + + connect(ui_music_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_music_list_clicked())); + connect(ui_music_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_music_list_double_clicked(QModelIndex))); + connect(ui_music_list, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(on_music_list_context_menu_requested(QPoint))); + + connect(ui_music_menu_play, SIGNAL(triggered()), this, SLOT(on_music_menu_play_triggered())); + connect(ui_music_menu_insert_ooc, SIGNAL(triggered()), this, SLOT(on_music_menu_insert_ooc_triggered())); + + connect(ui_area_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_area_list_clicked())); + connect(ui_area_list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_area_list_double_clicked(QModelIndex))); + + // connect events for shout/effect/wtce buttons happen in load_shouts(), + // load_effects(), load_wtce() + connect(ui_shout_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); + connect(ui_shout_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); + + connect(ui_effect_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); + connect(ui_effect_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); + + connect(ui_wtce_up, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); + connect(ui_wtce_down, SIGNAL(clicked(bool)), this, SLOT(on_cycle_clicked())); + + connect(ui_defense_minus, SIGNAL(clicked()), this, SLOT(on_defense_minus_clicked())); + connect(ui_defense_plus, SIGNAL(clicked()), this, SLOT(on_defense_plus_clicked())); + connect(ui_prosecution_minus, SIGNAL(clicked()), this, SLOT(on_prosecution_minus_clicked())); + connect(ui_prosecution_plus, SIGNAL(clicked()), this, SLOT(on_prosecution_plus_clicked())); + + connect(ui_text_color, SIGNAL(currentIndexChanged(int)), this, SLOT(on_text_color_changed(int))); + + connect(this, SIGNAL(loaded_theme()), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_max_lines_changed(int)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_display_timestamp_changed(bool)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_display_client_id_changed(bool)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_display_self_highlight_changed(bool)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_format_use_newline_changed(bool)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_display_empty_messages_changed(bool)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_display_music_switch_changed(bool)), this, SLOT(on_chat_config_changed())); + connect(ao_config, SIGNAL(log_is_topdown_changed(bool)), this, SLOT(on_chat_config_changed())); + + connect(ui_area_search, SIGNAL(textChanged(QString)), this, SLOT(on_area_search_edited(QString))); + connect(ui_music_search, SIGNAL(textChanged(QString)), this, SLOT(on_music_search_edited(QString))); + connect(ui_sfx_search, SIGNAL(textChanged(QString)), this, SLOT(filter_sfx_list(QString))); + + connect(ui_change_character, SIGNAL(clicked()), this, SLOT(on_change_character_clicked())); + connect(ui_call_mod, SIGNAL(clicked()), this, SLOT(on_call_mod_clicked())); + + connect(ui_switch_area_music, SIGNAL(clicked()), this, SLOT(on_switch_area_music_clicked())); + + connect(ui_config_panel, SIGNAL(clicked()), this, SLOT(on_config_panel_clicked())); + connect(ui_note_button, SIGNAL(clicked()), this, SLOT(on_note_button_clicked())); + + connect(ui_vp_notepad, SIGNAL(textChanged()), this, SLOT(on_note_text_changed())); + + connect(ui_pre, SIGNAL(clicked()), this, SLOT(on_pre_clicked())); + connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); + connect(ui_hide_character, SIGNAL(clicked()), this, SLOT(on_hidden_clicked())); + + connect(ui_sfx_list, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this, SLOT(on_sfx_list_current_item_changed(QListWidgetItem *, QListWidgetItem *))); + connect(ui_sfx_list, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(on_sfx_list_context_menu_requested(QPoint))); + + connect(ui_sfx_menu_preview, SIGNAL(triggered()), this, SLOT(on_sfx_menu_preview_triggered())); + connect(ui_sfx_menu_insert_file_name, SIGNAL(triggered()), this, SLOT(on_sfx_menu_insert_file_name_triggered())); + connect(ui_sfx_menu_insert_caption, SIGNAL(triggered()), this, SLOT(on_sfx_menu_insert_caption_triggered())); + + connect(ui_note_area->add_button, SIGNAL(clicked(bool)), this, SLOT(on_add_button_clicked())); + connect(ui_set_notes, SIGNAL(clicked(bool)), this, SLOT(on_set_notes_clicked())); + + // performance + connect(ao_config, SIGNAL(sprite_caching_toggled(int, bool)), this, SLOT(assign_readers_for_viewers(int, bool))); + connect(ao_config, SIGNAL(caching_threshold_changed(int)), m_preloader_sync, SLOT(set_threshold(int))); + connect(m_preloader_sync, SIGNAL(finished()), this, SLOT(start_chatmessage())); + connect(ao_config, SIGNAL(loading_bar_delay_changed(int)), this, SLOT(on_loading_bar_delay_changed(int))); + connect(m_loading_timer, SIGNAL(timeout()), ui_vp_loading, SLOT(show())); +} + +void Courtroom::reset_widget_names() +{ + // Assign names to the default widgets + widget_names = { + {"courtroom", this}, + {"viewport", ui_viewport}, + {"music_display_a", ui_vp_music_display_a}, + {"music_display_b", ui_vp_music_display_b}, + {"music_area", ui_vp_music_area}, + {"music_name", ui_vp_music_name}, + // music_anim + {"clock", ui_vp_clock}, + {"ao2_chatbox", ui_vp_chatbox}, + {"showname", ui_vp_showname}, + {"message", ui_vp_message}, + {"showname_image", ui_vp_showname_image}, + {"chat_arrow", ui_vp_chat_arrow}, + {"loading", ui_vp_loading}, + {"ic_chatlog", ui_ic_chatlog}, + {"ic_chatlog_scroll_topdown", ui_ic_chatlog_scroll_topdown}, + {"ic_chatlog_scroll_bottomup", ui_ic_chatlog_scroll_bottomup}, + {"server_chatlog", ui_ooc_chatlog}, + {"area_list", ui_area_list}, + {"area_search", ui_area_search}, + {"music_list", ui_music_list}, + {"music_search", ui_music_search}, + {"sfx_list", ui_sfx_list}, + {"sfx_search", ui_sfx_search}, + {"ic_chat_name", ui_ic_chat_showname}, + {"ao2_ic_chat_message", ui_ic_chat_message}, + // ui_muted + {"ooc_chat_message", ui_ooc_chat_message}, + {"ooc_chat_name", ui_ooc_chat_name}, + {"note_scroll_area", ui_note_scroll_area}, + {"note_area", ui_note_area}, + // add_button + // m_layout + {"set_notes_button", ui_set_notes}, + {"emotes", ui_emotes}, + {"emote_left", ui_emote_left}, + {"emote_right", ui_emote_right}, + {"emote_dropdown", ui_emote_dropdown}, + {"iniswap_dropdown", ui_iniswap_dropdown}, + {"pos_dropdown", ui_pos_dropdown}, + {"defense_bar", ui_defense_bar}, + {"prosecution_bar", ui_prosecution_bar}, + // Each ui_shouts[i] + {"shout_up", ui_shout_up}, + {"shout_down", ui_shout_down}, + // Each ui_effects[i] + {"effect_down", ui_effect_down}, + {"effect_up", ui_effect_up}, + // Each ui_wtce[i] + {"wtce_up", ui_wtce_up}, + {"wtce_down", ui_wtce_down}, + {"change_character", ui_change_character}, + {"call_mod", ui_call_mod}, + {"switch_area_music", ui_switch_area_music}, + {"config_panel", ui_config_panel}, + {"note_button", ui_note_button}, + // Each ui_label_images[i] + {"pre", ui_pre}, + {"flip", ui_flip}, + {"hidden", ui_hide_character}, + {"defense_plus", ui_defense_plus}, + {"defense_minus", ui_defense_minus}, + {"prosecution_plus", ui_prosecution_plus}, + {"prosecution_minus", ui_prosecution_minus}, + {"text_color", ui_text_color}, + {"notepad_image", ui_vp_notepad_image}, + {"notepad", ui_vp_notepad}, + // Each ui_timers[i] + {"char_select", ui_char_select_background}, + {"back_to_lobby", ui_back_to_lobby}, + {"char_buttons", ui_char_buttons}, + {"char_select_left", ui_chr_select_left}, + {"char_select_right", ui_chr_select_right}, + {"spectator", ui_spectator}, + }; +} + +void Courtroom::insert_widget_name(QString p_widget_name, QWidget *p_widget) +{ + if (widget_names.contains(p_widget_name)) + qWarning() << QString("[WARNING] Widget <%1> is already defined").arg(p_widget_name); + widget_names.insert(p_widget_name, p_widget); + p_widget->setObjectName(p_widget_name); +} + +void Courtroom::insert_widget_names(QVector &p_name_list, QVector &p_widget_list) +{ + if (p_name_list.length() != p_widget_list.length()) + qFatal("[WARNING] Length of names and widgets differs!"); + for (int i = 0; i < p_widget_list.length(); ++i) + insert_widget_name(p_name_list[i], p_widget_list[i]); +} + +void Courtroom::set_widget_names() +{ + // Assign names to the default widgets + reset_widget_names(); + + // set existing widget names + for (const QString &widget_name : widget_names.keys()) + widget_names[widget_name]->setObjectName(widget_name); + + // setup table of widgets and names + insert_widget_names(effect_names, ui_effects); + insert_widget_names(shout_names, ui_shouts); + insert_widget_names(wtce_names, ui_wtce); + + for (auto *i_block : qAsConst(ui_free_blocks)) + { + widget_names.insert(i_block->objectName(), i_block); + } + + // timers are special children + QVector timer_names; + for (int i = 0; i < ui_timers.length(); ++i) + { + timer_names.append("timer_" + QString::number(i)); + } + insert_widget_names(timer_names, ui_timers); +} + +void Courtroom::set_widget_layers() +{ + QStringList l_widget_records; + + const QString l_ini_path = ao_app->find_theme_asset_path(COURTROOM_LAYERS_INI); + QFile l_layer_ini(l_ini_path); + if (l_layer_ini.open(QFile::ReadOnly)) + { + QTextStream in(&l_layer_ini); + + const QString l_default_parent_name = objectName(); + + l_widget_records.append(l_default_parent_name); + + QString l_parent_name = l_default_parent_name; + while (!in.atEnd()) + { + QString l_line = in.readLine().trimmed(); + + // skip if line is empty + if (l_line.isEmpty()) + { + continue; + } + + // revert to default parent if we encounter an end scope + if (l_line.startsWith("[\\")) + { + l_parent_name = l_default_parent_name; + } + // is this a parent? + else if (l_line.startsWith("[")) + { + // update the current parent + l_parent_name = l_line.remove(0, 1).chopped(1).trimmed(); + } + // if this is not a parent, it's a child + else + { + // if the child is already known, skip + if (l_widget_records.contains(l_line)) + { + qWarning() << "error: widget already recorded:" << l_line; + continue; + } + l_widget_records.append(l_line); + + // attach the children to the parents' + if (!widget_names.contains(l_line)) + { + qWarning() << "widget does not exist:" << l_line; + continue; + } + QWidget *l_widget = widget_names.value(l_line); + QWidget *l_parent = widget_names.value(l_parent_name, this); + + // set child to parent + qDebug() << "attaching widget" << l_widget->objectName() << "to parent" << l_parent->objectName(); + const bool l_visible = l_widget->isVisible(); + l_widget->setParent(l_parent); + if (l_visible) + { + l_widget->setVisible(l_visible); + } + l_widget->raise(); + } + } + } + + // do special logic if config panel was not found in courtroom_layers. In + // particular, make it visible and raise it in front of all assets. This can + // help assist a theme designer who accidentally missed config_panel and would + // have become unable to reload themes had they closed the config panel + if (!l_widget_records.contains("config_panel")) + { + ui_config_panel->setParent(this); + ui_config_panel->setVisible(true); + ui_config_panel->raise(); + } +} + +void Courtroom::set_widgets() +{ + pos_size_type f_courtroom = ao_app->get_element_dimensions("courtroom", COURTROOM_DESIGN_INI); + if (f_courtroom.width < 0 || f_courtroom.height < 0) + { + qWarning() << "W: did not find courtroom width or height in " << COURTROOM_DESIGN_INI; + f_courtroom.width = DEFAULT_WIDTH; + f_courtroom.height = DEFAULT_HEIGHT; + } + + m_default_size = QSize(f_courtroom.width, f_courtroom.height); + if (!m_is_maximized) + { + resize(m_default_size); + } + + if (m_first_theme_loading) + { + m_first_theme_loading = false; + center_widget_to_screen(this); + } + + ui_background->move(0, 0); + ui_background->resize(m_default_size); + ui_background->set_theme_image("courtroombackground.png"); + + set_size_and_pos(ui_viewport, "viewport", COURTROOM_DESIGN_INI, ao_app); + + set_size_and_pos(ui_vp_notepad_image, "notepad_image", COURTROOM_DESIGN_INI, ao_app); + ui_vp_notepad_image->set_theme_image("notepad_image.png"); + ui_vp_notepad_image->hide(); + + set_size_and_pos(ui_vp_notepad, "notepad", COURTROOM_DESIGN_INI, ao_app); + ui_vp_notepad->hide(); + + set_size_and_pos(ui_vp_showname, "showname", COURTROOM_DESIGN_INI, ao_app); + + set_size_and_pos(ui_vp_showname_image, "showname_image", COURTROOM_DESIGN_INI, ao_app); + ui_vp_showname_image->hide(); + + set_size_and_pos(ui_vp_message, "message", COURTROOM_DESIGN_INI, ao_app); + ui_vp_message->setTextInteractionFlags(Qt::NoTextInteraction); + ui_vp_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + "color: white"); + + set_size_and_pos(ui_vp_chat_arrow, "chat_arrow", COURTROOM_DESIGN_INI, ao_app); + if (!ao_app->find_theme_asset_path("chat_arrow", animated_or_static_extensions()).isEmpty()) + { + ui_vp_chat_arrow->set_theme_image("chat_arrow"); + } + set_sticker_play_once(ui_vp_chat_arrow, "chat_arrow", COURTROOM_CONFIG_INI, ao_app); + ui_vp_chat_arrow->hide(); + + { + const bool l_visible = ui_vp_loading->isVisible(); + set_size_and_pos(ui_vp_loading, "loading", COURTROOM_DESIGN_INI, ao_app); + ui_vp_loading->set_theme_image("loading"); + ui_vp_loading->start(); + ui_vp_loading->setVisible(l_visible); + } + + set_size_and_pos(ui_ic_chatlog, "ic_chatlog", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_ic_chatlog_scroll_topdown, "ic_chatlog_scroll_topdown", COURTROOM_DESIGN_INI, ao_app); + ui_ic_chatlog_scroll_topdown->set_image("ic_chatlog_scroll_topdown.png"); + ui_ic_chatlog_scroll_topdown->hide(); + set_size_and_pos(ui_ic_chatlog_scroll_bottomup, "ic_chatlog_scroll_bottomup", COURTROOM_DESIGN_INI, ao_app); + ui_ic_chatlog_scroll_bottomup->set_image("ic_chatlog_scroll_bottomup.png"); + ui_ic_chatlog_scroll_bottomup->hide(); + + set_size_and_pos(ui_ooc_chatlog, "server_chatlog", COURTROOM_DESIGN_INI, ao_app); + + set_size_and_pos(ui_sfx_list, "sfx_list", COURTROOM_DESIGN_INI, ao_app); + + set_size_and_pos(ui_ic_chat_showname, "ic_chat_name", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_ic_chat_showname, "ic_chat_name", COURTROOM_FONTS_INI, ao_app); + if (!set_stylesheet(ui_ic_chat_showname, "[IC NAME LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) + { + ui_ic_chat_showname->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); + } + + set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_ic_chat_message_field, "ao2_ic_chat_message", COURTROOM_FONTS_INI, ao_app); + if (!set_stylesheet(ui_ic_chat_message, "[IC LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) + { + ui_ic_chat_message->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); + } + ui_ic_chat_message_field->setStyleSheet(ui_ic_chat_message->styleSheet()); + ui_ic_chat_message_counter->setStyleSheet(ui_ic_chat_message->styleSheet()); + + set_size_and_pos(ui_vp_chatbox, "ao2_chatbox", COURTROOM_DESIGN_INI, ao_app); + + set_size_and_pos(ui_vp_music_area, "music_area", COURTROOM_DESIGN_INI, ao_app); + ui_vp_music_area->show(); + set_size_and_pos(ui_vp_music_name, "music_name", COURTROOM_DESIGN_INI, ao_app); + + set_size_and_pos(ui_vp_music_display_a, "music_display_a", COURTROOM_DESIGN_INI, ao_app); + ui_vp_music_display_a->set_theme_image("music_display_a.png"); + ui_vp_music_display_a->show(); + + set_size_and_pos(ui_vp_music_display_b, "music_display_b", COURTROOM_DESIGN_INI, ao_app); + ui_vp_music_display_b->set_theme_image("music_display_b.png"); + ui_vp_music_display_b->show(); + + set_size_and_pos(ui_vp_clock, "clock", COURTROOM_DESIGN_INI, ao_app); + if (m_current_clock == -1) + ui_vp_clock->hide(); + set_sticker_play_once(ui_vp_clock, "clock", COURTROOM_CONFIG_INI, ao_app); + + ui_vp_chatbox->set_theme_image("chatmed.png"); + ui_vp_chatbox->hide(); + + set_size_and_pos(ui_ooc_chat_name, "ooc_chat_name", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_ooc_chat_name, "ooc_chat_name", COURTROOM_FONTS_INI, ao_app); + if (!set_stylesheet(ui_ooc_chat_name, "[OOC NAME LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) + { + ui_ooc_chat_name->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); + } + + set_size_and_pos(ui_ooc_chat_message, "ooc_chat_message", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_ooc_chat_message, "ooc_chat_message", COURTROOM_FONTS_INI, ao_app); + if (!set_stylesheet(ui_ooc_chat_message, "[OOC LINE]", COURTROOM_STYLESHEETS_CSS, ao_app)) + { + ui_ooc_chat_message->setStyleSheet("background-color: rgba(100, 100, 100, 255);"); + } + + set_size_and_pos(ui_sfx_search, "sfx_search", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_sfx_search, "sfx_search", COURTROOM_FONTS_INI, ao_app); + + set_size_and_pos(ui_music_list, "music_list", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_music_search, "music_search", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_music_search, "music_search", COURTROOM_FONTS_INI, ao_app); + ui_music_list->show(); + ui_music_search->show(); + + { // area separation logic + const bool l_is_area_music_list_separated = is_area_music_list_separated(); + const QString p_area_identifier = l_is_area_music_list_separated ? "area" : "music"; + + set_size_and_pos(ui_area_list, p_area_identifier + "_list", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_area_search, p_area_identifier + "_search", COURTROOM_DESIGN_INI, ao_app); + set_text_alignment(ui_area_search, p_area_identifier + "_search", COURTROOM_FONTS_INI, ao_app); + + ui_area_list->setVisible(l_is_area_music_list_separated); + ui_area_search->setVisible(l_is_area_music_list_separated); + ui_switch_area_music->setHidden(l_is_area_music_list_separated); + } + + // emotes + set_size_and_pos(ui_emotes, "emotes", COURTROOM_DESIGN_INI, ao_app); + construct_emote_page_layout(); + + set_size_and_pos(ui_emote_left, "emote_left", COURTROOM_DESIGN_INI, ao_app); + ui_emote_left->set_image("arrow_left.png"); + + set_size_and_pos(ui_emote_right, "emote_right", COURTROOM_DESIGN_INI, ao_app); + ui_emote_right->set_image("arrow_right.png"); + + { // emote preview + pos_size_type l_emote_preview_size = ao_app->get_element_dimensions("emote_preview", COURTROOM_DESIGN_INI); + if (l_emote_preview_size.width <= 0 || l_emote_preview_size.height <= 0) + { + l_emote_preview_size.width = 320; + l_emote_preview_size.height = 192; + } + ui_emote_preview->resize(l_emote_preview_size.width, l_emote_preview_size.height); + ui_emote_preview_background->set_theme_image("emote_preview.png"); + ui_emote_preview_character->set_size(QSizeF(l_emote_preview_size.width, l_emote_preview_size.height)); + } + + set_size_and_pos(ui_emote_dropdown, "emote_dropdown", COURTROOM_DESIGN_INI, ao_app); + set_stylesheet(ui_emote_dropdown, "[EMOTE DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); + + set_size_and_pos(ui_iniswap_dropdown, "iniswap_dropdown", COURTROOM_DESIGN_INI, ao_app); + update_iniswap_dropdown_searchable(); + + set_size_and_pos(ui_pos_dropdown, "pos_dropdown", COURTROOM_DESIGN_INI, ao_app); + set_stylesheet(ui_pos_dropdown, "[POS DROPDOWN]", COURTROOM_STYLESHEETS_CSS, ao_app); + + set_size_and_pos(ui_defense_bar, "defense_bar", COURTROOM_DESIGN_INI, ao_app); + ui_defense_bar->set_theme_image("defensebar" + QString::number(defense_bar_state) + ".png"); + + set_size_and_pos(ui_prosecution_bar, "prosecution_bar", COURTROOM_DESIGN_INI, ao_app); + ui_prosecution_bar->set_theme_image("prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); + + for (int i = 0; i < shout_names.size(); ++i) + { + set_size_and_pos(ui_shouts[i], shout_names[i], COURTROOM_DESIGN_INI, ao_app); + } + reset_shout_buttons(); + + set_size_and_pos(ui_shout_up, "shout_up", COURTROOM_DESIGN_INI, ao_app); + ui_shout_up->set_image("shoutup.png"); + ui_shout_up->hide(); + set_size_and_pos(ui_shout_down, "shout_down", COURTROOM_DESIGN_INI, ao_app); + ui_shout_down->set_image("shoutdown.png"); + ui_shout_down->hide(); + + // courtroom_config.ini necessary + check for crash + if (ao_app->read_theme_ini_bool("enable_single_shout", COURTROOM_CONFIG_INI) && ui_shouts.size() > 0) + { + for (auto &shout : ui_shouts) + move_widget(shout, "bullet"); + + set_shouts(); + + ui_shout_up->show(); + ui_shout_down->show(); + } + + for (int i = 0; i < effect_names.size(); ++i) + { + set_size_and_pos(ui_effects[i], effect_names[i], COURTROOM_DESIGN_INI, ao_app); + } + reset_effect_buttons(); + + set_size_and_pos(ui_effect_up, "effect_up", COURTROOM_DESIGN_INI, ao_app); + ui_effect_up->set_image("effectup.png"); + ui_effect_up->hide(); + set_size_and_pos(ui_effect_down, "effect_down", COURTROOM_DESIGN_INI, ao_app); + ui_effect_down->set_image("effectdown.png"); + ui_effect_down->hide(); + + if (ao_app->read_theme_ini_bool("enable_single_effect", COURTROOM_CONFIG_INI) && ui_effects.size() > 0) // check to prevent crashing + { + for (auto &effect : ui_effects) + move_widget(effect, "effect"); + + set_effects(); + + ui_effect_up->show(); + ui_effect_down->show(); + } + + set_size_and_pos(ui_wtce_up, "wtce_up", COURTROOM_DESIGN_INI, ao_app); + ui_wtce_up->set_image("wtceup.png"); + ui_wtce_up->hide(); + set_size_and_pos(ui_wtce_down, "wtce_down", COURTROOM_DESIGN_INI, ao_app); + ui_wtce_down->set_image("wtcedown.png"); + ui_wtce_down->hide(); + + for (int i = 0; i < wtce_names.size(); ++i) + { + set_size_and_pos(ui_wtce[i], wtce_names[i], COURTROOM_DESIGN_INI, ao_app); + } + + if (ao_app->read_theme_ini_bool("enable_single_wtce", COURTROOM_CONFIG_INI)) // courtroom_config.ini necessary + { + for (auto &wtce : ui_wtce) + move_widget(wtce, "wtce"); + qDebug() << "AA: single wtce"; + } + set_judge_wtce(); + reset_wtce_buttons(); + + for (DRStickerViewer *i_sticker : ui_free_blocks) + { + const QString l_name = i_sticker->objectName(); + set_size_and_pos(i_sticker, l_name, COURTROOM_DESIGN_INI, ao_app); + i_sticker->set_theme_image(l_name); + set_sticker_play_once(i_sticker, l_name, COURTROOM_CONFIG_INI, ao_app); + i_sticker->show(); + } + + // Set the default values for the buttons, then determine if they should be + // replaced by images + set_size_and_pos(ui_change_character, "change_character", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_call_mod, "call_mod", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_note_button, "note_button", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_switch_area_music, "switch_area_music", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_config_panel, "config_panel", COURTROOM_DESIGN_INI, ao_app); + + ui_change_character->setText(""); + ui_call_mod->setText(""); + ui_switch_area_music->setText(""); + ui_config_panel->setText(""); + ui_note_button->setText(""); + + ui_change_character->setStyleSheet(""); + ui_call_mod->setStyleSheet(""); + ui_switch_area_music->setStyleSheet(""); + ui_config_panel->setStyleSheet(""); + ui_note_button->setStyleSheet(""); + + if (ao_app->read_theme_ini_bool("enable_button_images", COURTROOM_CONFIG_INI)) + { + // Set files, ask questions later + // set_image first tries the gamemode-timeofday folder, then the theme + // folder, then falls back to the default theme + ui_change_character->set_image("changecharacter.png"); + if (ui_change_character->get_image().isEmpty()) + ui_change_character->setText("Change Character"); + + ui_call_mod->set_image("callmod.png"); + if (ui_call_mod->get_image().isEmpty()) + ui_call_mod->setText("Call Mod"); + + ui_switch_area_music->set_image("switch_area_music.png"); + if (ui_switch_area_music->get_image().isEmpty()) + ui_switch_area_music->setText("A/M"); + + ui_config_panel->set_image("config_panel.png"); + if (ui_config_panel->get_image().isEmpty()) + ui_config_panel->setText("Config"); + + ui_note_button->set_image("notebutton.png"); + if (ui_note_button->get_image().isEmpty()) + ui_note_button->setText("Notes"); + } + + // The config panel has a special property. If it is displayed beyond the right or lower limit of the window, it will + // be moved to 0, 0 A similar behavior will occur if the button is resized to 0, 0 due to 'config_panel' not being + // found in courtroom_design.ini This is to assist with people who switch to incompatible and/or smaller themes and + // have the button disappear + if (ui_config_panel->x() > width() || ui_config_panel->y() > height() || ui_config_panel->width() <= 0 || ui_config_panel->height() <= 0) + { + ui_config_panel->setVisible(true); + ui_config_panel->move(0, 0); + // Moreover, if the width or height is invalid, change it to some fixed + // values + if (ui_config_panel->width() <= 0 || ui_config_panel->height() <= 0) + ui_config_panel->resize(64, 64); + } + + set_size_and_pos(ui_pre, "pre", COURTROOM_DESIGN_INI, ao_app); + ui_pre->setText("Pre"); + + set_size_and_pos(ui_flip, "flip", COURTROOM_DESIGN_INI, ao_app); + + set_size_and_pos(ui_hide_character, "hidden", COURTROOM_DESIGN_INI, ao_app); + + for (int i = 0; i < ui_label_images.size(); ++i) + { + set_size_and_pos(ui_label_images[i], label_images[i].toLower() + "_image", COURTROOM_DESIGN_INI, ao_app); + } + + if (ao_app->read_theme_ini_bool("enable_label_images", COURTROOM_CONFIG_INI)) + { + for (int i = 0; i < ui_checks.size(); ++i) // loop through checks + { + QString image = label_images[i].toLower() + ".png"; + ui_label_images[i]->set_theme_image(image); + + if (!ui_label_images[i]->get_image().isEmpty()) + ui_checks[i]->setText(""); + else + ui_checks[i]->setText(label_images[i]); + } + + for (int i = 0; i < ui_labels.size(); ++i) // now through labels.......... + { + int j = i + ui_checks.size(); + QString image = label_images[j].toLower() + ".png"; + ui_label_images[j]->set_theme_image(image); + + if (!ui_label_images[j]->get_image().isEmpty()) + ui_labels[i]->setText(""); + else + ui_labels[i]->setText(label_images[j]); + } + } + else + { + for (int i = 0; i < ui_checks.size(); ++i) // same thing + { + ui_checks[i]->setText(label_images[i]); + ui_label_images[i]->set_theme_image(""); + } + + for (int i = 0; i < ui_labels.size(); ++i) // same thing + { + int j = i + ui_checks.size(); + ui_labels[i]->setText(label_images[j]); + ui_label_images[j]->set_theme_image(""); + } + } + + set_size_and_pos(ui_defense_plus, "defense_plus", COURTROOM_DESIGN_INI, ao_app); + ui_defense_plus->set_image("defplus.png"); + + set_size_and_pos(ui_defense_minus, "defense_minus", COURTROOM_DESIGN_INI, ao_app); + ui_defense_minus->set_image("defminus.png"); + + set_size_and_pos(ui_prosecution_plus, "prosecution_plus", COURTROOM_DESIGN_INI, ao_app); + ui_prosecution_plus->set_image("proplus.png"); + + set_size_and_pos(ui_prosecution_minus, "prosecution_minus", COURTROOM_DESIGN_INI, ao_app); + ui_prosecution_minus->set_image("prominus.png"); + + set_size_and_pos(ui_text_color, "text_color", COURTROOM_DESIGN_INI, ao_app); + set_stylesheet(ui_text_color, "[TEXT COLOR]", COURTROOM_STYLESHEETS_CSS, ao_app); + + ui_char_button_selector->set_theme_image("char_selector.png"); + ui_char_button_selector->hide(); + + set_size_and_pos(ui_char_buttons, "char_buttons", COURTROOM_DESIGN_INI, ao_app); + + set_size_and_pos(ui_back_to_lobby, "back_to_lobby", COURTROOM_DESIGN_INI, ao_app); + ui_back_to_lobby->setText("Back to Lobby"); + + set_size_and_pos(ui_chr_select_left, "char_select_left", COURTROOM_DESIGN_INI, ao_app); + ui_chr_select_left->set_image("arrow_left.png"); + + set_size_and_pos(ui_chr_select_right, "char_select_right", COURTROOM_DESIGN_INI, ao_app); + ui_chr_select_right->set_image("arrow_right.png"); + + set_size_and_pos(ui_spectator, "spectator", COURTROOM_DESIGN_INI, ao_app); + + update_music_text_anim(); + + set_size_and_pos(ui_set_notes, "set_notes_button", COURTROOM_DESIGN_INI, ao_app); + ui_set_notes->set_image("set_notes.png"); + ui_note_area->m_layout->setSpacing(10); + set_size_and_pos(ui_note_area, "note_area", COURTROOM_DESIGN_INI, ao_app); + set_size_and_pos(ui_note_scroll_area, "note_area", COURTROOM_DESIGN_INI, ao_app); + ui_note_scroll_area->setWidget(ui_note_area); + + ui_note_area->set_theme_image("note_area.png"); + ui_note_area->add_button->set_image("add_button.png"); + ui_note_area->add_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + ui_note_area->setLayout(ui_note_area->m_layout); + ui_note_area->show(); + ui_note_scroll_area->hide(); + + list_note_files(); + + if (!contains_add_button) + { + ui_note_area->m_layout->addWidget(ui_note_area->add_button); + contains_add_button = true; + } + + // This is used to force already existing notepicker elements to reset their image and theme setting + for (AONotePicker *notepicker : ui_note_area->findChildren()) + { + for (AOButton *button : notepicker->findChildren()) + { + button->refresh_image(); + } + QLineEdit *f_line = notepicker->findChild(); + set_stylesheet(f_line, "[LINE EDIT]", COURTROOM_STYLESHEETS_CSS, ao_app); + } + + adapt_numbered_items(ui_timers, "timer_number", "timer"); + set_fonts(); + + Q_EMIT loaded_theme(); +} + +void Courtroom::move_widget(QWidget *p_widget, QString p_identifier) +{ + QString filename = COURTROOM_DESIGN_INI; + + pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, filename); + + if (design_ini_result.width < 0 || design_ini_result.height < 0) + { + qDebug() << "W: could not find \"" << p_identifier << "\" in " << filename; + // Don't hide, as some widgets don't have a built-in way of reappearing again. + p_widget->move(0, 0); + p_widget->resize(0, 0); + } + else + { + p_widget->move(design_ini_result.x, design_ini_result.y); + } +} + +template +int Courtroom::adapt_numbered_items(QVector &item_vector, QString config_item_number, QString item_name) +{ + // &item_vector must be a vector of size at least 1! + + // Redraw the new correct number of items. + int new_item_number = ao_app->read_theme_ini_int(config_item_number, COURTROOM_CONFIG_INI); + int current_item_number = item_vector.size(); + // Note we use the fact that, if config_item_number is not there, + // read_theme_ini returns an empty string, which .toInt() would fail to + // convert and by documentation transform to 0. + + // Decide what to do if the new theme has a different amount of items than the + // old one + if (new_item_number < current_item_number) + { + // Hide old items if there are any. + for (int i = new_item_number; i < current_item_number; i++) + { + item_vector[i]->hide(); + } + } + else if (current_item_number < new_item_number) + { + // Create new items + item_vector.resize(new_item_number); + for (int i = current_item_number; i < new_item_number; i++) + { + item_vector[i] = new T(this); + item_vector[i]->stackUnder(item_vector[i - 1]); + // index i-1 exists as i >= current_item_number == item_vector.size() >= 1 + } + } + // Note that by design we do *not* destroy items (or similarly, the size of + // item_vector is non-decreasing. This is because we want to allow for items + // to, say, run in the background as invisible. With that said, we can now + // properly format our new items + for (int i = 0; i < new_item_number; i++) + { + item_vector[i]->show(); + set_size_and_pos(item_vector[i], item_name + "_" + QString::number(i), COURTROOM_DESIGN_INI, ao_app); + // Note that show is deliberately placed before set_size_and_pos + // That is because we want to retain the functionality set_size_and_pos + // includes where hides a widget if it is unable to find a position for it, + // and does not change its visibility otherwise. + } + return new_item_number; +} + +void Courtroom::check_effects() +{ + // Asset lookup order + // 1. In the character folder, look for + // `effect_names.at(i)` + extensions in `exts` in order + // 2. In the theme folder (gamemode-timeofday/main/default), look for + // `effect_names.at(i)` + extensions in `exts` in order + // Only enable buttons where a file was found + + for (int i = 0; i < ui_effects.size(); ++i) + { + QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), effect_names.at(i))}, animated_extensions()); + if (path.isEmpty()) + path = ao_app->find_theme_asset_path(effect_names.at(i), animated_extensions()); + effects_enabled[i] = (!path.isEmpty()); + } +} + +void Courtroom::check_shouts() +{ + // Asset lookup order + // 1. In the character folder, look for + // `shout_names.at(i)` + extensions in `exts` in order + // 2. In the theme folder (gamemode-timeofday/main/default), look for + // `shout_names.at(i)` + extensions in `exts` in order + // Only enable buttons where a file was found + + for (int i = 0; i < ui_shouts.size(); ++i) + { + QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), shout_names.at(i))}, animated_extensions()); + + if (path.isEmpty()) + path = ao_app->find_theme_asset_path(shout_names.at(i), animated_extensions()); + + shouts_enabled[i] = (!path.isEmpty()); + } +} + +void Courtroom::check_wtce() +{ + // Asset lookup order + // 1. In the character folder, look for + // `wtce_names.at(i)` + extensions in `exts` in order + // 2. In the theme folder (gamemode-timeofday/main/default), look for + // `wtce_names.at(i)` + extensions in `exts` in order + // Only enable buttons where a file was found + + for (int i = 0; i < ui_wtce.size(); ++i) + { + QString path = ao_app->find_asset_path({ao_app->get_character_path(get_character_ini(), wtce_names.at(i))}, animated_extensions()); + if (path.isEmpty()) + path = ao_app->find_theme_asset_path(wtce_names.at(i), animated_extensions()); + wtce_enabled[i] = (!path.isEmpty()); + } +} + +void Courtroom::delete_widget(QWidget *p_widget) +{ + // remove the widget from recorded names + widget_names.remove(p_widget->objectName()); + + // transfer the children to our grandparent since our parent is about to be deleted + QWidget *grand_parent = p_widget->parentWidget(); + // if we don't have a grand parent, attach ourselves to courtroom + if (!grand_parent) + grand_parent = this; + + // set new parent + for (QWidget *child : p_widget->findChildren(nullptr, Qt::FindDirectChildrenOnly)) + child->setParent(grand_parent); + + // delete widget + delete p_widget; +} + +void Courtroom::load_effects() +{ + // Close any existing effects to prevent memory leaks + for (QWidget *widget : qAsConst(ui_effects)) + delete_widget(widget); + + // And create new effects + int effect_number = ao_app->read_theme_ini_int("effect_number", COURTROOM_CONFIG_INI); + effects_enabled.resize(effect_number); + ui_effects.resize(effect_number); + + for (int i = 0; i < ui_effects.size(); ++i) + { + AOButton *l_button = new AOButton(this, ao_app); + ui_effects.replace(i, l_button); + l_button->setCheckable(true); + l_button->setProperty("effect_id", i + 1); + l_button->stackUnder(ui_effect_up); + l_button->stackUnder(ui_effect_down); + + connect(l_button, SIGNAL(clicked(bool)), this, SLOT(on_effect_button_clicked(bool))); + connect(l_button, SIGNAL(toggled(bool)), this, SLOT(on_effect_button_toggled(bool))); + } + + // And add names + effect_names.clear(); + for (int i = 1; i <= ui_effects.size(); ++i) + { + QStringList names = ao_app->get_effect(i); + if (!names.isEmpty()) + { + const QString l_name = names.at(0).trimmed(); + effect_names.append(l_name); + AOButton *l_button = ui_effects.at(i - 1); + l_button->setProperty("effect_name", l_name); + Q_EMIT l_button->toggled(l_button->isChecked()); + } + } +} + +void Courtroom::load_free_blocks() +{ + for (QWidget *widget : qAsConst(ui_free_blocks)) + delete_widget(widget); + + ui_free_blocks.clear(); + const int l_block_count = ao_app->read_theme_ini_int("free_block_number", COURTROOM_CONFIG_INI); + for (int i = 0; i < l_block_count; ++i) + { + const QString l_name = ao_app->get_spbutton("[FREE BLOCKS]", i + 1).trimmed(); + if (l_name.isEmpty()) + { + qWarning() << "error: block index" << i << "has no block name."; + continue; + } + const QString l_block_name = "free_block_" + l_name; + auto *l_block = new DRStickerViewer(ao_app, this); + l_block->setObjectName(l_block_name); + ui_free_blocks.append(l_block); + widget_names.insert(l_block_name, l_block); + } +} + +void Courtroom::load_shouts() +{ + for (QWidget *widget : qAsConst(ui_shouts)) + delete_widget(widget); + + // And create new shouts + int shout_number = ao_app->read_theme_ini_int("shout_number", COURTROOM_CONFIG_INI); + shouts_enabled.resize(shout_number); + ui_shouts.resize(shout_number); + + for (int i = 0; i < ui_shouts.size(); ++i) + { + AOButton *l_button = new AOButton(this, ao_app); + ui_shouts.replace(i, l_button); + l_button->setCheckable(true); + l_button->setProperty("shout_id", i + 1); + l_button->stackUnder(ui_shout_up); + l_button->stackUnder(ui_shout_down); + + connect(l_button, SIGNAL(clicked(bool)), this, SLOT(on_shout_button_clicked(bool))); + connect(l_button, SIGNAL(toggled(bool)), this, SLOT(on_shout_button_toggled(bool))); + } + + // And add names + shout_names.clear(); + for (int i = 1; i <= ui_shouts.size(); ++i) + { + QString name = ao_app->get_spbutton("[SHOUTS]", i).trimmed(); + if (!name.isEmpty()) + { + shout_names.append(name); + AOButton *l_button = ui_shouts.at(i - 1); + widget_names.insert(name, l_button); + l_button->setObjectName(name); + l_button->setProperty("shout_name", name); + Q_EMIT l_button->toggled(l_button->isChecked()); + } + } +} + +void Courtroom::load_wtce() +{ + for (QWidget *widget : qAsConst(ui_wtce)) + delete_widget(widget); + + // And create new wtce buttons + const int l_wtce_count = ao_app->read_theme_ini_int("wtce_number", COURTROOM_CONFIG_INI); + wtce_enabled.resize(l_wtce_count); + + ui_wtce.clear(); + for (int i = 0; i < l_wtce_count; ++i) + { + AOButton *l_button = new AOButton(this, ao_app); + ui_wtce.append(l_button); + l_button->setProperty("wtce_id", i + 1); + l_button->stackUnder(ui_wtce_up); + l_button->stackUnder(ui_wtce_down); + connect(l_button, SIGNAL(clicked(bool)), this, SLOT(on_wtce_clicked())); + } + + // And add names + wtce_names.clear(); + for (int i = 1; i <= ui_wtce.size(); ++i) + { + QString name = ao_app->get_spbutton("[WTCE]", i).trimmed(); + if (!name.isEmpty()) + { + wtce_names.append(name); + widget_names[name] = ui_wtce[i - 1]; + ui_wtce[i - 1]->setObjectName(name); + } + } +} + +/** + * @brief Show the currently selected shout button, hide the remaining ones. + * If no shouts exist, this method does nothing. + */ +void Courtroom::set_shouts() +{ + for (auto &shout : ui_shouts) + shout->hide(); + if (ui_shouts.size() > 0) + ui_shouts[m_shout_current]->show(); // check to prevent crashing +} + +/** + * @brief Show the currently selected effect button, hide the remaining ones. + * If no effects exist, this method does nothing. + */ +void Courtroom::set_effects() +{ + for (auto &effect : ui_effects) + effect->hide(); + + // check to prevent crashing + if (ui_effects.size() > 0) + ui_effects[m_effect_current]->show(); +} + +void Courtroom::set_judge_enabled(bool p_enabled) +{ + is_judge = p_enabled; + + // set judge button visibility + ui_defense_plus->setVisible(is_judge); + ui_defense_minus->setVisible(is_judge); + ui_prosecution_plus->setVisible(is_judge); + ui_prosecution_minus->setVisible(is_judge); + + set_judge_wtce(); +} + +/** + * @brief Show the currently selected splash button, hide the remaining ones. + * If no splashes exist, this method does nothing. + */ +void Courtroom::set_judge_wtce() +{ + // hide all wtce before enabling visibility + for (auto &wtce : ui_wtce) + wtce->hide(); + + // check if we use a single wtce or multiple + const bool is_single_wtce = ao_app->read_theme_ini_bool("enable_single_wtce", COURTROOM_CONFIG_INI); + + // update visibility for next/previous + ui_wtce_up->setVisible(is_judge && is_single_wtce); + ui_wtce_down->setVisible(is_judge && is_single_wtce); + + // prevent going ahead if we have no wtce + if (!is_judge || ui_wtce.length() == 0) + return; + + // set visibility based off parameter + if (is_single_wtce == true) + { + ui_wtce[m_wtce_current]->show(); + } + else + { + for (AOButton *i_wtce : qAsConst(ui_wtce)) + i_wtce->show(); + } +} + +void Courtroom::set_fonts() +{ + set_drtextedit_font(ui_vp_showname, "showname", COURTROOM_FONTS_INI, ao_app); + ui_vp_showname->setPlainText(ui_vp_showname->toPlainText()); + set_drtextedit_font(ui_vp_message, "message", COURTROOM_FONTS_INI, ao_app); + ui_vp_message->setPlainText(ui_vp_message->toPlainText()); + set_drtextedit_font(ui_ic_chatlog, "ic_chatlog", COURTROOM_FONTS_INI, ao_app); + + // Chatlog does not support drtextedit because html + set_font(ui_ooc_chatlog, "server_chatlog", COURTROOM_FONTS_INI, ao_app); + ui_ooc_chatlog->reset_message_format(); + + set_font(ui_music_list, "music_list", COURTROOM_FONTS_INI, ao_app); + set_font(ui_area_list, "area_list", COURTROOM_FONTS_INI, ao_app); + set_font(ui_sfx_list, "sfx_list", COURTROOM_FONTS_INI, ao_app); + set_drtextedit_font(ui_vp_music_name, "music_name", COURTROOM_FONTS_INI, ao_app); + ui_vp_music_name->setPlainText(ui_vp_music_name->toPlainText()); + set_drtextedit_font(ui_vp_notepad, "notepad", COURTROOM_FONTS_INI, ao_app); + ui_vp_notepad->setPlainText(ui_vp_notepad->toPlainText()); + + for (int i = 0; i < ui_timers.length(); ++i) + { + AOTimer *i_timer = ui_timers.at(i); + set_drtextedit_font(i_timer, QString("timer_%1").arg(i), COURTROOM_FONTS_INI, ao_app); + } +} diff --git a/src/datatypes.cpp b/src/datatypes.cpp new file mode 100644 index 000000000..1fdaf61f2 --- /dev/null +++ b/src/datatypes.cpp @@ -0,0 +1,117 @@ +#include "datatypes.h" + +#include + +QMap DR::get_default_color_map() +{ + QMap default_color_map; + + default_color_map[DR::CWhite] = ColorInfo("White", "#d5d5d5"); + default_color_map[DR::CGreen] = ColorInfo("Green", "#65c856"); + default_color_map[DR::CRed] = ColorInfo("Red", "#ba1518"); + default_color_map[DR::COrange] = ColorInfo("Orange", "#d55900"); + default_color_map[DR::CBlue] = ColorInfo("Blue", "#1588c8"); + default_color_map[DR::CYellow] = ColorInfo("Yellow", "#e7ce4e"); + default_color_map[DR::CPurple] = ColorInfo("Purple", "#f776fd"); + default_color_map[DR::CPink] = ColorInfo("Pink", "#da7c80"); + + return default_color_map; +} + +QString DRServerInfo::to_info() const +{ + QString r_info = "Unnamed Server"; + + if (!name.isEmpty()) + { + r_info = name; + } + else if (!address.isEmpty()) + { + r_info = to_address(); + } + + return r_info; +} + +QString DRServerInfo::to_address() const +{ + return address + ":" + QString::number(port); +} + +bool DRServerInfo::operator==(const DRServerInfo &o) const +{ + return address == o.address && port == o.port; +} + +bool DRServerInfo::operator!=(const DRServerInfo &other) const +{ + return !operator==(other); +} + +VersionNumber::VersionNumber() +{} + +VersionNumber::VersionNumber(int p_release, int p_major, int p_minor) + : release(p_release) + , major(p_major) + , minor(p_minor) +{} + +QString VersionNumber::to_string() const +{ + return QString("%1.%2.%3").arg(release).arg(major).arg(minor); +} + +bool VersionNumber::operator==(const VersionNumber &other) const +{ + return release == other.release && major == other.major; +} + +bool VersionNumber::operator<(const VersionNumber &other) const +{ + return release < other.release || (release == other.release && major < other.major); +} + +bool VersionNumber::operator>(const VersionNumber &other) const +{ + return release > other.release || (release == other.release && major > other.major); +} + +const QMap s_sprite_category_string_map{ + {SpriteGUI, "gui"}, + {SpriteStage, "background"}, + {SpriteCharacter, "character"}, + {SpriteEffect, "effect"}, + {SpriteShout, "shout"}, + {SpriteSticker, "sticker"}, +}; + +SpriteCategory string_to_sprite_category(QString p_category) +{ + return s_sprite_category_string_map.key(p_category, SpriteGUI); +} + +QString sprite_category_to_string(SpriteCategory p_category) +{ + return s_sprite_category_string_map.value(p_category, "gui"); +} + +QStringList sprite_category_string_list() +{ + return s_sprite_category_string_map.values(); +} + +SpriteCategory viewport_sprite_to_sprite_category(ViewportSprite p_type) +{ + return QMap{ + {ViewportStageBack, SpriteStage}, + {ViewportStageFront, SpriteStage}, + {ViewportCharacterPre, SpriteCharacter}, + {ViewportCharacterIdle, SpriteCharacter}, + {ViewportCharacterTalk, SpriteCharacter}, + {ViewportEffect, SpriteEffect}, + {ViewportShout, SpriteShout}, + } + .value(p_type, SpriteGUI); +} diff --git a/src/datatypes.h b/src/datatypes.h new file mode 100644 index 000000000..76118331a --- /dev/null +++ b/src/datatypes.h @@ -0,0 +1,286 @@ +#ifndef DATATYPES_H +#define DATATYPES_H + +#include +#include +#include + +class DREmote +{ +public: + QString character; + QString key; + QString comment; + QString anim; + QString dialog; + int modifier = 0; + int desk_modifier = -1; + QString sound_file; + int sound_delay = 0; + QString video_file; +}; + +class DRAreaBackground +{ +public: + QString background; + QMap background_tod_map; +}; + +enum ClientId +{ + NoClientId = -1, +}; + +class DRChatRecord +{ +public: + DRChatRecord(QString p_name, QString p_message) + : name(p_name) + , message(p_message) + {} + + QDateTime get_timestamp() const + { + return timestamp; + } + QString get_name() const + { + return name; + } + QString get_message() const + { + return message; + } + int get_client_id() const + { + return client_id; + } + bool is_self() const + { + return self; + } + bool is_system() const + { + return system; + } + bool is_music() const + { + return music; + } + + // set + void set_client_id(const int p_client_id) + { + client_id = p_client_id; + } + void set_self(const bool p_enabled) + { + self = p_enabled; + } + void set_system(bool p_enabled) + { + system = p_enabled; + } + void set_music(bool p_enabled) + { + music = p_enabled; + } + +private: + QDateTime timestamp = QDateTime::currentDateTime(); + QString name; + QString message; + int client_id = NoClientId; + bool self = false; + bool system = false; + bool music = false; +}; + +struct DRSfx +{ +public: + DRSfx() = default; + DRSfx(QString p_name, QString p_file, bool p_is_found = false) + : name(p_name.trimmed()) + , file(p_file.trimmed()) + , is_found(p_is_found) + {} + + QString name; + QString file; + bool is_found; +}; + +class DRServerInfo +{ +public: + QString name; + QString description; + QString address; + int port; + + QString to_info() const; + QString to_address() const; + + bool operator==(const DRServerInfo &other) const; + bool operator!=(const DRServerInfo &other) const; +}; +using DRServerInfoList = QVector; + +class VersionNumber +{ +public: + int release = 0; + int major = 0; + int minor = 0; + + VersionNumber(); + VersionNumber(int release, int major, int minor); + + QString to_string() const; + + bool operator==(const VersionNumber &other) const; + bool operator>(const VersionNumber &other) const; + bool operator<(const VersionNumber &other) const; +}; + +enum class VersionStatus +{ + Ok, + NotCompatible, + ClientOutdated, + ServerOutdated, +}; + +struct char_type +{ + QString name; + bool taken = false; +}; + +struct pos_size_type +{ + int x = 0; + int y = 0; + int width = 0; + int height = 0; +}; + +enum SpriteCategory +{ + SpriteGUI, + SpriteStage, + SpriteCharacter, + SpriteEffect, + SpriteShout, + SpriteSticker, +}; + +SpriteCategory string_to_sprite_category(QString p_category); +QString sprite_category_to_string(SpriteCategory p_category); +QStringList sprite_category_string_list(); + +enum ViewportSprite +{ + ViewportStageBack, + ViewportStageFront, + ViewportCharacterPre, + ViewportCharacterIdle, + ViewportCharacterTalk, + ViewportEffect, + ViewportShout, +}; + +SpriteCategory viewport_sprite_to_sprite_category(ViewportSprite p_type); + +enum ChatMessage : int32_t +{ + CMDeskModifier = 0, + CMPreAnim, + CMChrName, + CMEmote, + CMMessage, + CMPosition, + CMSoundName, + CMEmoteModifier, + CMChrId, + CMSoundDelay, + CMShoutModifier, + CMEvidenceId, + CMFlipState, + CMEffectState, + CMTextColor, + CMShowName, + CMVideoName, + CMHideCharacter, + CMClientId, +}; + +enum EmoteMod +{ + IdleEmoteMod = 0, + PreEmoteMod = 1, + ZoomEmoteMod = 5, + PreZoomEmoteMod = 6, +}; + +namespace DR +{ +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) +using SplitBehavior = QString::SplitBehavior; +#else +using SplitBehavior = Qt::SplitBehaviorFlags; +#endif +const SplitBehavior KeepEmptyParts = SplitBehavior::KeepEmptyParts; +const SplitBehavior SkipEmptyParts = SplitBehavior::SkipEmptyParts; + +enum VAlign : int32_t +{ + Top, + Middle, + Bottom, +}; + +enum HAlign : int32_t +{ + Left, + Center, + Right, +}; + +enum Color : int32_t +{ + CDefault, + CGreen, + CRed, + COrange, + CBlue, + CYellow, + CPurple, + CPink, + CRainbow, + + // aliases + CWhite = CDefault, +}; + +struct ColorInfo +{ +public: + ColorInfo() = default; + ColorInfo(QString p_showname, QString p_code) + : name(p_showname.toLower()) + , showname(p_showname) + , code(p_code) + {} + + QString name; + QString showname; + QString code; +}; + +QMap get_default_color_map(); +} // namespace DR + +#endif // DATATYPES_H diff --git a/src/debug_functions.cpp b/src/debug_functions.cpp new file mode 100644 index 000000000..74b7b8b13 --- /dev/null +++ b/src/debug_functions.cpp @@ -0,0 +1,58 @@ +#include "debug_functions.h" + +#include +#include +#include + +#include "aoconfig.h" + +enum class MessageType +{ + Notice, + Warning, + Prompt, +}; + +bool drMessageBox(QString p_message, MessageType p_message_type) +{ + AOConfig config; + if (!config.display_notification(p_message)) + return QMessageBox::Accepted; + + QMessageBox message; + const bool l_is_notice = p_message_type == MessageType::Notice; + message.setWindowTitle(l_is_notice ? "Notice" : "Warning"); + message.setIcon(l_is_notice ? QMessageBox::Information : QMessageBox::Warning); + message.setText(p_message); + if (p_message_type == MessageType::Prompt) + { + message.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + } + QCheckBox *show_again = new QCheckBox(&message); + show_again->setText(QObject::tr("&Show this message again")); + show_again->setChecked(true); + message.setCheckBox(show_again); + message.exec(); + + if (!show_again->isChecked()) + config.filter_notification(p_message); + return message.result() == QMessageBox::Ok; +} + +void call_notice(QString p_message) +{ + qInfo() << p_message; + drMessageBox(p_message, MessageType::Notice); +} + +void call_warning(QString p_message) +{ + qWarning() << "error:" << p_message; + drMessageBox(p_message, MessageType::Warning); +} + +bool prompt_warning(QString p_message) +{ + qDebug() << "prompt:" << p_message; + return drMessageBox(p_message, MessageType::Prompt); +} diff --git a/debug_functions.h b/src/debug_functions.h similarity index 57% rename from debug_functions.h rename to src/debug_functions.h index 6feaf90a5..24cf65280 100644 --- a/debug_functions.h +++ b/src/debug_functions.h @@ -1,9 +1,10 @@ #ifndef DEBUG_FUNCTIONS_H #define DEBUG_FUNCTIONS_H -#include +class QString; -void call_error(QString message); void call_notice(QString message); +void call_warning(QString message); +bool prompt_warning(QString message); #endif // DEBUG_FUNCTIONS_H diff --git a/src/draudio.cpp b/src/draudio.cpp new file mode 100644 index 000000000..44f95b4b3 --- /dev/null +++ b/src/draudio.cpp @@ -0,0 +1,97 @@ +#include "draudio.h" + +#include + +#include + +QString DRAudio::get_bass_error(const int32_t p_error_code) +{ + switch (p_error_code) + { + case BASS_OK: + return "all is OK"; + case BASS_ERROR_MEM: + return "memory error"; + case BASS_ERROR_FILEOPEN: + return "can't open the file"; + case BASS_ERROR_DRIVER: + return "can't find a free/valid driver"; + case BASS_ERROR_BUFLOST: + return "the sample buffer was lost"; + case BASS_ERROR_HANDLE: + return "invalid handle"; + case BASS_ERROR_FORMAT: + return "unsupported sample format"; + case BASS_ERROR_POSITION: + return "invalid position"; + case BASS_ERROR_INIT: + return "BASS_Init has not been successfully called"; + case BASS_ERROR_START: + return "BASS_Start has not been successfully called"; + case BASS_ERROR_SSL: + return "SSL/HTTPS support isn't available"; + case BASS_ERROR_ALREADY: + return "already initialized/paused/whatever"; + case BASS_ERROR_NOTAUDIO: + return "file does not contain audio"; + case BASS_ERROR_NOCHAN: + return "can't get a free channel"; + case BASS_ERROR_ILLTYPE: + return "an illegal type was specified"; + case BASS_ERROR_ILLPARAM: + return "an illegal parameter was specified"; + case BASS_ERROR_NO3D: + return "no 3D support"; + case BASS_ERROR_NOEAX: + return "no EAX support"; + case BASS_ERROR_DEVICE: + return "illegal device number"; + case BASS_ERROR_NOPLAY: + return "not playing"; + case BASS_ERROR_FREQ: + return "illegal sample rate"; + case BASS_ERROR_NOTFILE: + return "the stream is not a file stream"; + case BASS_ERROR_NOHW: + return "no hardware voices available"; + case BASS_ERROR_EMPTY: + return "the MOD music has no sequence data"; + case BASS_ERROR_NONET: + return "no internet connection could be opened"; + case BASS_ERROR_CREATE: + return "couldn't create the file"; + case BASS_ERROR_NOFX: + return "effects are not available"; + case BASS_ERROR_NOTAVAIL: + return "requested data/action is not available"; + case BASS_ERROR_DECODE: + return "the channel is/isn't a \"decoding channel\""; + case BASS_ERROR_DX: + return "a sufficient DirectX version is not installed"; + case BASS_ERROR_TIMEOUT: + return "connection timedout"; + case BASS_ERROR_FILEFORM: + return "unsupported file format"; + case BASS_ERROR_SPEAKER: + return "unavailable speaker"; + case BASS_ERROR_VERSION: + return "invalid BASS version (used by add-ons)"; + case BASS_ERROR_CODEC: + return "codec is not available/supported"; + case BASS_ERROR_ENDED: + return "the channel/file has ended"; + case BASS_ERROR_BUSY: + return "the device is busy"; + case BASS_ERROR_UNSTREAMABLE: + return "unstreamable file"; + case BASS_ERROR_UNKNOWN: + return "some other mystery problem"; + default: + return "unknown error"; + } +} + +QString DRAudio::get_last_bass_error() +{ + return get_bass_error(BASS_ErrorGetCode()); +} diff --git a/src/draudio.h b/src/draudio.h new file mode 100644 index 000000000..530330d7c --- /dev/null +++ b/src/draudio.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +namespace DRAudio +{ +enum class Family +{ + FSystem, + FEffect, + FMusic, + FVideo, + FBlip, +}; + +enum Option +{ + OSuppressed = 0x1, + OIgnoreSuppression = 0x2, + + // engine + + /** + * If enabled, the engine will suppress all audio when the application is inactive. + */ + OEngineSuppressBackgroundAudio = 0x4, +}; +Q_DECLARE_FLAGS(Options, Option) + +QString get_bass_error(const int32_t p_error_code); +QString get_last_bass_error(); +} // namespace DRAudio +Q_DECLARE_METATYPE(DRAudio::Options) diff --git a/src/draudiodevice.cpp b/src/draudiodevice.cpp new file mode 100644 index 000000000..dc0c468c7 --- /dev/null +++ b/src/draudiodevice.cpp @@ -0,0 +1,81 @@ +#include "draudiodevice.h" + +#include + +#include + +QVector DRAudioDevice::get_device_list() +{ + QVector r_device_list; + + BASS_DEVICEINFO l_device_info; + for (int i = 0; BASS_GetDeviceInfo(i, &l_device_info); ++i) + { + DRAudioDevice l_device; + l_device.m_id = i; + l_device.m_name = l_device_info.name; + l_device.m_driver = l_device_info.driver; + l_device.m_states = State(l_device_info.flags); + r_device_list.append(l_device); + } + + return r_device_list; +} + +DRAudioDevice::DRAudioDevice() + : m_id(0) + , m_name("") +{} + +DRAudioDevice::~DRAudioDevice() +{} + +DWORD DRAudioDevice::get_id() const +{ + return m_id; +} + +QString DRAudioDevice::get_name() const +{ + return m_name; +} + +QString DRAudioDevice::get_driver() const +{ + return m_driver.isEmpty() ? "NOSOUND" : m_driver; +} + +DRAudioDevice::States DRAudioDevice::get_states() const +{ + return m_states; +} + +bool DRAudioDevice::is_state(DRAudioDevice::State p_state) const +{ + return m_states.testFlag(p_state); +} + +bool DRAudioDevice::is_enabled() const +{ + return is_state(SEnabled); +} + +bool DRAudioDevice::is_default() const +{ + return is_state(SDefault); +} + +bool DRAudioDevice::is_init() const +{ + return is_state(SInit); +} + +bool DRAudioDevice::operator==(const DRAudioDevice &other) const +{ + return m_id == other.m_id && m_name == other.m_name && m_driver == other.m_driver && m_states == other.m_states; +} + +bool DRAudioDevice::operator!=(const DRAudioDevice &other) const +{ + return !operator==(other); +} diff --git a/src/draudiodevice.h b/src/draudiodevice.h new file mode 100644 index 000000000..1e5ecb5ca --- /dev/null +++ b/src/draudiodevice.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +#include + +#include + +class DRAudioDevice +{ +public: + static QVector get_device_list(); + + enum State : DWORD + { + SEnabled = BASS_DEVICE_ENABLED, + SDefault = BASS_DEVICE_DEFAULT, + SInit = BASS_DEVICE_INIT, + }; + Q_DECLARE_FLAGS(States, State) + + DRAudioDevice(); + ~DRAudioDevice(); + + DWORD get_id() const; + QString get_name() const; + QString get_driver() const; + States get_states() const; + bool is_state(State state) const; + bool is_enabled() const; + bool is_default() const; + bool is_init() const; + + bool operator==(const DRAudioDevice &other) const; + bool operator!=(const DRAudioDevice &other) const; + +private: + DWORD m_id; + QString m_name; + QString m_driver; + States m_states; +}; +Q_DECLARE_METATYPE(DRAudioDevice) diff --git a/src/draudioengine.cpp b/src/draudioengine.cpp new file mode 100644 index 000000000..9c50dfbf1 --- /dev/null +++ b/src/draudioengine.cpp @@ -0,0 +1,160 @@ +#include "draudioengine.h" + +#include "draudioengine_p.h" + +#include + +static const int UPDATE_TIMER_INTERVAL = 100; + +static class DRAudioEngineData +{ +public: + using ptr = DRAudioEnginePrivate::ptr; + + ptr &operator->() + { + if (d == nullptr) + { + d = ptr(new DRAudioEnginePrivate); + + // create engine shortcut + d->engine = new DRAudioEngine; + + // create families + d->family_map.insert(DRAudio::Family::FSystem, + DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FSystem))); + d->family_map.insert(DRAudio::Family::FEffect, + DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FEffect))); + d->family_map.insert(DRAudio::Family::FMusic, + DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FMusic))); + d->family_map.insert(DRAudio::Family::FVideo, + DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FVideo))); + d->family_map.insert(DRAudio::Family::FBlip, + DRAudioStreamFamily::ptr(new DRAudioStreamFamily(DRAudio::Family::FBlip))); + + // set family-specific options + d->family_map.value(DRAudio::Family::FSystem)->set_ignore_suppression(true); + + // create and init event loop + d->update_timer->setInterval(UPDATE_TIMER_INTERVAL); + d->update_timer->setSingleShot(false); + + QObject::connect(d->update_timer, SIGNAL(timeout()), d, SLOT(update_current_device())); + d->update_timer->start(); + d->update_current_device(); + } + + return d; + } + +private: + ptr d; +} d; + +DRAudioEngine::DRAudioEngine(QObject *p_parent) + : QObject(p_parent) +{ + d->children.append(this); +} + +DRAudioEngine::~DRAudioEngine() +{ + d->children.removeAll(this); +} + +std::optional DRAudioEngine::get_current_device() +{ + return d->device; +} + +QString DRAudioEngine::get_favorite_device_driver() +{ + return d->favorite_device_driver; +} + +std::optional DRAudioEngine::get_favorite_device() +{ + return d->favorite_device; +} + +QVector DRAudioEngine::get_device_list() +{ + return d->device_list; +} + +DRAudioStreamFamily::ptr DRAudioEngine::get_family(DRAudio::Family p_family) +{ + return d->family_map.value(p_family); +} + +QList DRAudioEngine::get_family_list() +{ + return d->family_map.values(); +} + +int32_t DRAudioEngine::get_volume() +{ + return d->volume; +} + +DRAudio::Options DRAudioEngine::get_options() +{ + return d->options; +} + +bool DRAudioEngine::is_option(DRAudio::Option p_option) +{ + return d->options.testFlag(p_option); +} + +bool DRAudioEngine::is_suppressed() +{ + return d->options.testFlag(DRAudio::OSuppressed); +} + +bool DRAudioEngine::is_suppress_background_audio() +{ + return is_option(DRAudio::OEngineSuppressBackgroundAudio); +} + +void DRAudioEngine::set_favorite_device_driver(QString p_driver) +{ + d->favorite_device_driver = p_driver; + d->update_current_device(); +} + +void DRAudioEngine::set_volume(int32_t p_volume) +{ + p_volume = std::clamp(p_volume, 0, 100); + if (d->volume == p_volume) + return; + d->volume = p_volume; + d->update_volume(); + Q_EMIT d->invoke_signal("volume_changed", Q_ARG(int32_t, d->volume)); +} + +void DRAudioEngine::set_options(DRAudio::Options p_options) +{ + if (d->options == p_options) + return; + d->options = p_options; + d->update_options(); + Q_EMIT d->invoke_signal("options_changed", Q_ARG(DRAudio::Options, d->options)); +} + +void DRAudioEngine::set_option(DRAudio::Option p_option, bool p_enabled) +{ + DRAudio::Options new_options = d->options; + new_options.setFlag(p_option, p_enabled); + set_options(new_options); +} + +void DRAudioEngine::set_suppressed(bool p_enabled) +{ + set_option(DRAudio::OSuppressed, p_enabled); +} + +void DRAudioEngine::set_suppress_background_audio(bool p_enabled) +{ + set_option(DRAudio::OEngineSuppressBackgroundAudio, p_enabled); +} diff --git a/src/draudioengine.h b/src/draudioengine.h new file mode 100644 index 000000000..a930cc9be --- /dev/null +++ b/src/draudioengine.h @@ -0,0 +1,41 @@ +#pragma once + +#include "draudiostreamfamily.h" + +class DRAudioEngine : public QObject +{ + Q_OBJECT + +public: + static QVector get_device_list(); + static std::optional get_current_device(); + static QString get_favorite_device_driver(); + static std::optional get_favorite_device(); + static DRAudioStreamFamily::ptr get_family(DRAudio::Family p_family); + static QList get_family_list(); + static int32_t get_volume(); + static DRAudio::Options get_options(); + // option get + static bool is_option(DRAudio::Option p_option); + static bool is_suppressed(); + static bool is_suppress_background_audio(); + + DRAudioEngine(QObject *p_parent = nullptr); + ~DRAudioEngine(); + +public slots: + void set_favorite_device_driver(QString driver); + void set_volume(int32_t p_volume); + void set_options(DRAudio::Options p_options); + // option set + void set_option(DRAudio::Option p_option, bool p_enabled); + void set_suppressed(bool p_enabled); + void set_suppress_background_audio(bool p_enabled); + +signals: + void device_list_changed(QVector); + void current_device_changed(DRAudioDevice); + void favorite_device_changed(DRAudioDevice); + void volume_changed(int32_t); + void options_changed(DRAudio::Options); +}; diff --git a/src/draudioengine_p.cpp b/src/draudioengine_p.cpp new file mode 100644 index 000000000..1a63c15a1 --- /dev/null +++ b/src/draudioengine_p.cpp @@ -0,0 +1,117 @@ +#include "draudioengine_p.h" + +/*! + I could've avoided making this necessary but I didn't + want to make the code extra complex. + + So here, instead have this very convuloted workaround + instead. +*/ +#include "draudioengine.h" + +#include +#include +#include + +DRAudioEnginePrivate::DRAudioEnginePrivate() + : QObject(nullptr) + , update_timer(new QTimer(this)) +{ + BASS_SetConfig(BASS_CONFIG_DEV_DEFAULT, FALSE); +} + +DRAudioEnginePrivate::~DRAudioEnginePrivate() +{ + update_timer->stop(); + + BASS_Free(); +} + +void DRAudioEnginePrivate::invoke_signal(QString p_method_name, QGenericArgument p_arg1) +{ + for (QObject *i_child : qAsConst(children)) + { + QMetaObject::invokeMethod(i_child, p_method_name.toStdString().c_str(), p_arg1); + } +} + +void DRAudioEnginePrivate::update_current_device() +{ + update_device_list(); + + DRAudioDevice l_target_device; + for (const DRAudioDevice &i_device : qAsConst(device_list)) + { + if (!favorite_device_driver.isEmpty() && i_device.get_driver() == favorite_device_driver) + { + if (!favorite_device.has_value() || favorite_device.value() != i_device) + { + favorite_device = i_device; + invoke_signal("favorite_device_changed", Q_ARG(DRAudioDevice, favorite_device.value())); + } + + if (i_device.is_enabled()) + { + l_target_device = i_device; + break; + } + } + + if (i_device.is_default()) + { + l_target_device = i_device; + } + } + + if (device.has_value() && device.value() == l_target_device) + return; + const std::optional l_prev_device = device; + device = l_target_device; + + if (l_prev_device.has_value() && l_prev_device->get_id() == device->get_id()) + return; + + if (!BASS_IsStarted()) + BASS_Start(); + + if (!device->is_init()) + { + if (!BASS_Init(device->get_id(), 44100, 0, 0, NULL)) + { + qWarning() << "Error: failed to initialize audio device:" << DRAudio::get_last_bass_error() + << "(device:" << device->get_name() << ")"; + return; + } + } + qInfo() << "Audio device changed to" << device->get_name(); + invoke_signal("current_device_changed", Q_ARG(DRAudioDevice, device.value())); + + if (l_prev_device.has_value()) + { + if (BASS_SetDevice(l_prev_device->get_id())) + { + BASS_Free(); + } + } +} + +void DRAudioEnginePrivate::update_device_list() +{ + const QVector l_new_device_list = DRAudioDevice::get_device_list(); + if (l_new_device_list != device_list) + { + device_list = std::move(l_new_device_list); + invoke_signal("device_list_changed", Q_ARG(QVector, device_list)); + } +} + +void DRAudioEnginePrivate::update_options() +{ + update_volume(); +} + +void DRAudioEnginePrivate::update_volume() +{ + for (auto &i_group : family_map.values()) + i_group->update_volume(); +} diff --git a/src/draudioengine_p.h b/src/draudioengine_p.h new file mode 100644 index 000000000..2420efbbd --- /dev/null +++ b/src/draudioengine_p.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +#include "draudio.h" +#include "draudiostreamfamily.h" + +class QTimer; + +#include + +class DRAudioEnginePrivate : public QObject +{ + Q_OBJECT + +public: + using ptr = QPointer; + + QObjectList children; + QPointer engine; + + QPointer update_timer; + QVector device_list; + std::optional device; + QString favorite_device_driver; + std::optional favorite_device; + + int32_t volume = 0; + DRAudio::Options options; + QMap family_map; + + DRAudioEnginePrivate(); + ~DRAudioEnginePrivate(); + + void invoke_signal(QString p_method_name, QGenericArgument p_arg1 = QGenericArgument(nullptr)); + +public slots: + void update_device_list(); + void update_current_device(); + void update_volume(); + void update_options(); +}; diff --git a/src/draudioerror.cpp b/src/draudioerror.cpp new file mode 100644 index 000000000..9e116f1a7 --- /dev/null +++ b/src/draudioerror.cpp @@ -0,0 +1,13 @@ +#include "draudioerror.h" + +DRAudioError::DRAudioError() +{} + +DRAudioError::DRAudioError(QString p_error) + : m_error(QString("[bass] %1").arg(p_error)) +{} + +QString DRAudioError::what() +{ + return m_error; +} diff --git a/src/draudioerror.h b/src/draudioerror.h new file mode 100644 index 000000000..b6b955643 --- /dev/null +++ b/src/draudioerror.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +class DRAudioError +{ +public: + DRAudioError(); + DRAudioError(QString p_error); + + QString what(); + +private: + QString m_error; +}; diff --git a/src/draudiostream.cpp b/src/draudiostream.cpp new file mode 100644 index 000000000..4821cbd91 --- /dev/null +++ b/src/draudiostream.cpp @@ -0,0 +1,277 @@ +#include "draudiostream.h" + +#include +#include +#include + +#include +#include + +#include "draudioengine.h" + +DRAudioStream::DRAudioStream(DRAudio::Family p_family) + : m_engine(new DRAudioEngine(this)) + , m_family(p_family) + , m_fade(NoFade) +{ + registerMetatypes(); + + connect(m_engine, &DRAudioEngine::current_device_changed, this, &DRAudioStream::update_device); +} + +void DRAudioStream::registerMetatypes() +{ + qRegisterMetaType(); +} + +DRAudioStream::~DRAudioStream() +{ + if (m_hstream) + { + BASS_StreamFree(m_hstream); + } +} + +DRAudio::Family DRAudioStream::get_family() const +{ + return m_family; +} + +QString DRAudioStream::get_file_name() const +{ + return m_filename; +} + +bool DRAudioStream::is_playing() const +{ + if (!ensure_init()) + return false; + return BASS_ChannelIsActive(m_hstream) == BASS_ACTIVE_PLAYING; +} + +bool DRAudioStream::is_repeatable() const +{ + return m_repeatable; +} + +void DRAudioStream::play() +{ + if (!ensure_init()) + return; + if (!BASS_ChannelPlay(m_hstream, FALSE)) + { + qWarning() << QString("error: failed to play file: %1 (file: \"%1\")").arg(DRAudio::get_last_bass_error(), m_filename); + Q_EMIT finished(); + } +} + +void DRAudioStream::stop() +{ + if (!ensure_init()) + return; + BASS_ChannelStop(m_hstream); + Q_EMIT finished(); +} + +std::optional DRAudioStream::set_file_name(QString p_file_name) +{ + m_filename = p_file_name; + m_init_state = InitNotDone; + if (!ensure_init()) + { + return DRAudioError("failed to set file: " + p_file_name); + } + emit file_name_changed(m_filename); + return std::nullopt; +} + +void DRAudioStream::set_volume(float p_volume) +{ + if (!ensure_init()) + return; + m_volume = p_volume; + if (m_fade_running) + { + const Fade l_prev_fade = m_fade; + m_fade = NoFade; + fade(l_prev_fade, m_fade_duration); + } + else + { + update_volume(); + } +} + +void DRAudioStream::set_repeatable(bool p_enabled) +{ + if (m_repeatable == p_enabled) + return; + m_repeatable = p_enabled; + init_loop(); +} + +void DRAudioStream::set_loop(quint64 p_start, quint64 p_end) +{ + if (!ensure_init()) + return; + + if (m_loop_start == p_start && m_loop_end == p_end) + return; + m_loop_start = p_start; + m_loop_end = p_end; + init_loop(); +} + +void DRAudioStream::fade(Fade p_fade, int p_duration) +{ + if (p_fade == NoFade || p_fade == m_fade || !ensure_init()) + { + return; + } + + float l_volume = m_volume * 0.01f; + if (m_fade == NoFade) + { + BASS_ChannelSetAttribute(m_hstream, BASS_ATTRIB_VOL, (p_fade == FadeOut ? l_volume : 0)); + } + m_fade = p_fade; + m_fade_duration = p_duration; + m_fade_running = true; + BASS_ChannelSlideAttribute(m_hstream, BASS_ATTRIB_VOL, (p_fade == FadeOut ? 0 : l_volume), qMax(0, p_duration)); +} + +void DRAudioStream::fadeIn(int p_duration) +{ + fade(FadeIn, p_duration); +} + +void DRAudioStream::fadeOut(int p_duration) +{ + fade(FadeOut, p_duration); +} + +void DRAudioStream::end_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata) +{ + Q_UNUSED(hsync); + Q_UNUSED(ch); + Q_UNUSED(data); + + DRAudioStream *l_stream = static_cast(userdata); + if (!l_stream->is_repeatable()) + { + emit l_stream->finished(); + } +} + +void DRAudioStream::loop_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata) +{ + Q_UNUSED(hsync); + Q_UNUSED(data); + + // move the position to the loopStart + DRAudioStream *l_stream = static_cast(userdata); + if (l_stream->is_repeatable()) + { + BASS_ChannelSetPosition(ch, l_stream->m_loop_start_pos, BASS_POS_BYTE); + emit l_stream->looped(); + } +} + +void DRAudioStream::fade_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata) +{ + Q_UNUSED(hsync); + Q_UNUSED(ch); + Q_UNUSED(data); + + DRAudioStream *l_stream = static_cast(userdata); + l_stream->m_fade_running = false; + emit l_stream->faded(l_stream->m_fade); +} + +bool DRAudioStream::ensure_init() +{ + if (m_init_state != InitNotDone) + return m_init_state == InitFinished; + m_init_state = InitError; + + if (m_filename.isEmpty()) + return false; + + HSTREAM l_hstream; + if (m_filename.endsWith("opus", Qt::CaseInsensitive)) + l_hstream = BASS_OPUS_StreamCreateFile(FALSE, m_filename.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); + else + l_hstream = BASS_StreamCreateFile(FALSE, m_filename.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE | BASS_STREAM_PRESCAN); + + if (!l_hstream) + { + qWarning() << "error: failed to create audio stream (file:" << m_filename << ")"; + return false; + } + m_hstream = l_hstream; + + BASS_ChannelSetSync(l_hstream, BASS_SYNC_END, 0, &end_sync, this); + BASS_ChannelSetSync(l_hstream, BASS_SYNC_SLIDE, 0, &fade_sync, this); + m_init_state = InitFinished; + init_loop(); + update_volume(); + return true; +} + +bool DRAudioStream::ensure_init() const +{ + return const_cast(this)->ensure_init(); +} + +void DRAudioStream::init_loop() +{ + if (m_loop_sync) + { + BASS_ChannelRemoveSync(m_hstream, m_loop_sync); + m_loop_sync = 0; + } + + if (m_repeatable) + { + float l_sample_rate; + BASS_ChannelGetAttribute(m_hstream, BASS_ATTRIB_FREQ, &l_sample_rate); + + const QWORD l_length = BASS_ChannelGetLength(m_hstream, BASS_POS_BYTE); + m_loop_start_pos = BASS_ChannelSeconds2Bytes(m_hstream, m_loop_start / double(l_sample_rate)); + if (m_loop_start_pos >= l_length) + { + m_loop_start_pos = 0; + } + + m_loop_end_pos = BASS_ChannelSeconds2Bytes(m_hstream, m_loop_end / double(l_sample_rate)); + if (m_loop_end_pos <= m_loop_start_pos || m_loop_end_pos > l_length) + { + m_loop_end_pos = l_length; + } + + m_loop_sync = BASS_ChannelSetSync(m_hstream, BASS_SYNC_POS | BASS_SYNC_MIXTIME, m_loop_end_pos, &loop_sync, this); + } +} + +void DRAudioStream::update_device(DRAudioDevice p_device) +{ + if (!ensure_init()) + return; + if (BASS_ChannelGetDevice(m_hstream) != p_device.get_id()) + { + if (!BASS_ChannelSetDevice(m_hstream, p_device.get_id())) + { + qDebug() << "error: failed to switch stream device;" << DRAudio::get_last_bass_error() << p_device.get_name(); + } + } +} + +void DRAudioStream::update_volume() +{ + float l_volume = m_volume * 0.01f; + if (m_fade_running) + { + BASS_ChannelGetAttribute(m_hstream, BASS_ATTRIB_VOL, &l_volume); + } + BASS_ChannelSetAttribute(m_hstream, BASS_ATTRIB_VOL, l_volume); +} diff --git a/src/draudiostream.h b/src/draudiostream.h new file mode 100644 index 000000000..0f9f7464b --- /dev/null +++ b/src/draudiostream.h @@ -0,0 +1,123 @@ +#pragma once + +#include "draudio.h" +#include "draudiodevice.h" +#include "draudioerror.h" + +#include + +#include +#include + +#include + +class DRAudioEngine; +class DRAudioEnginePrivate; +class DRAudioStreamFamily; + +class DRAudioStreamSync +{ +public: + HSYNC sync; + DWORD type; +}; + +class DRAudioStream : public QObject +{ + Q_OBJECT + + DRAudioStream(DRAudio::Family p_family); + +public: + enum Fade + { + NoFade, + FadeIn, + FadeOut, + }; + Q_ENUM(Fade) + + using ptr = QSharedPointer; + + static void registerMetatypes(); + + ~DRAudioStream(); + + DRAudio::Family get_family() const; + QString get_file_name() const; + bool is_repeatable() const; + bool is_playing() const; + DRAudioStream::Fade get_fade() const; + +public slots: + std::optional set_file_name(QString file); + void set_volume(float volume); + void set_repeatable(bool); + void set_loop(quint64 start, quint64 end); + + /** + * @brief Fades in or out the channel predicated by the duration. + * + * @param type The type of fade. + * + * @param duration The duration of the fade in milliseconds. + */ + void fade(DRAudioStream::Fade type, int duration); + void fadeIn(int duration); + void fadeOut(int duration); + + void play(); + void stop(); + +signals: + void file_name_changed(QString file); + + void faded(DRAudioStream::Fade type); + + void looped(); + + void finished(); + +public: + static void CALLBACK end_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata); + static void CALLBACK loop_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata); + static void CALLBACK fade_sync(HSYNC hsync, DWORD ch, DWORD data, void *userdata); + +private: + friend class DRAudioStreamFamily; + + enum InitState + { + InitError, + InitNotDone, + InitFinished, + }; + + DRAudioEngine *m_engine = nullptr; + DRAudio::Family m_family; + QString m_filename; + DRAudioStream::InitState m_init_state = InitNotDone; + DRAudioStream::Fade m_fade; + int m_fade_duration = 0; + bool m_fade_running = false; + HSTREAM m_hstream = 0; + float m_volume = 0.0f; + bool m_repeatable = false; + QWORD m_loop_start = 0; + QWORD m_loop_start_pos = 0; + QWORD m_loop_end = 0; + QWORD m_loop_end_pos = 0; + HSYNC m_loop_sync = 0; + + bool ensure_init(); + bool ensure_init() const; + void update_device(DRAudioDevice); + void init_loop(); + void seek_loop_start(); + +private slots: + void update_volume(); + +signals: + void device_error(QPrivateSignal); +}; diff --git a/src/draudiostreamfamily.cpp b/src/draudiostreamfamily.cpp new file mode 100644 index 000000000..395a79443 --- /dev/null +++ b/src/draudiostreamfamily.cpp @@ -0,0 +1,181 @@ +#include "draudiostream.h" +#define NOMINMAX + +#include "draudiostreamfamily.h" + +#include "draudioengine.h" + +#include + +DRAudioStreamFamily::DRAudioStreamFamily(DRAudio::Family p_family) + : m_family(p_family) +{} + +int32_t DRAudioStreamFamily::get_capacity() const +{ + return m_capacity; +} + +DRAudio::Options DRAudioStreamFamily::get_options() const +{ + return m_options; +} + +bool DRAudioStreamFamily::is_suppressed() const +{ + return m_options.testFlag(DRAudio::OSuppressed); +} + +bool DRAudioStreamFamily::is_ignore_suppression() const +{ + return m_options.testFlag(DRAudio::OIgnoreSuppression); +} + +void DRAudioStreamFamily::set_capacity(int32_t p_capacity) +{ + p_capacity = qMax(0, p_capacity); + + if (m_capacity == p_capacity) + return; + + m_capacity = p_capacity; + update_capacity(); + Q_EMIT capacity_changed(m_capacity); +} + +void DRAudioStreamFamily::set_options(DRAudio::Options p_options) +{ + if (m_options == p_options) + return; + + m_options = p_options; + update_options(); + Q_EMIT options_changed(m_options); +} + +void DRAudioStreamFamily::set_volume(int32_t p_volume) +{ + p_volume = std::clamp(p_volume, 0, 100); + + if (m_volume == p_volume) + return; + + m_volume = p_volume; + update_volume(); + Q_EMIT volume_changed(m_volume); +} + +void DRAudioStreamFamily::set_suppressed(bool p_enabled) +{ + DRAudio::Options options = m_options; + options.setFlag(DRAudio::OSuppressed, p_enabled); + set_options(options); +} + +void DRAudioStreamFamily::set_ignore_suppression(bool p_enabled) +{ + DRAudio::Options options = m_options; + options.setFlag(DRAudio::OIgnoreSuppression, p_enabled); + set_options(options); +} + +DRAudioStream::ptr DRAudioStreamFamily::create_stream(QString p_filename) +{ + DRAudioStream::ptr l_stream(new DRAudioStream(m_family)); + if (auto err = l_stream->set_file_name(p_filename); err) + { + qWarning() << err->what(); + return nullptr; + } + + m_stream_list.append(l_stream); + + connect(l_stream.get(), SIGNAL(finished()), this, SLOT(on_stream_finished())); + + update_capacity(); + + l_stream->set_volume(calculate_volume()); + + return l_stream; +} + +DRAudioStream::ptr DRAudioStreamFamily::play_stream(QString p_filename) +{ + DRAudioStream::ptr l_stream = create_stream(p_filename); + if (!l_stream.isNull()) + { + qInfo() << "Playing" << l_stream->get_file_name(); + l_stream->play(); + } + + return l_stream; +} + +DRAudioStreamFamily::stream_list DRAudioStreamFamily::get_stream_list() const +{ + return m_stream_list; +} + +int32_t DRAudioStreamFamily::get_volume() const +{ + return m_volume; +} + +float DRAudioStreamFamily::calculate_volume() +{ + float volume = float(m_volume) * 0.01f; + + if (!is_ignore_suppression() && (is_suppressed() || DRAudioEngine::is_suppressed())) + { + volume = 0; + } + else + { + // master volume adjustment + volume *= (float(DRAudioEngine::get_volume()) * 0.01f); + } + + return volume * 100.0f; +} + +void DRAudioStreamFamily::update_capacity() +{ + if (m_capacity == 0) + return; + while (m_capacity < m_stream_list.length()) + m_stream_list.removeFirst(); +} + +void DRAudioStreamFamily::update_options() +{ + // suppressed + update_volume(); +} + +void DRAudioStreamFamily::update_volume() +{ + const float volume = calculate_volume(); + for (auto &stream : m_stream_list) + stream->set_volume(volume); +} + +void DRAudioStreamFamily::on_stream_finished() +{ + DRAudioStream *invoker = dynamic_cast(sender()); + if (invoker == nullptr) + return; + + if (const QString l_file = invoker->get_file_name(); !l_file.isEmpty()) + qInfo() << "removing" << l_file; + else + qWarning() << "removing unspecified stream"; + + stream_list new_stream_list; + for (auto &i_stream : m_stream_list) + { + if (i_stream.get() == invoker) + continue; + new_stream_list.append(std::move(i_stream)); + } + m_stream_list = std::move(new_stream_list); +} diff --git a/src/draudiostreamfamily.h b/src/draudiostreamfamily.h new file mode 100644 index 000000000..cacad2e07 --- /dev/null +++ b/src/draudiostreamfamily.h @@ -0,0 +1,67 @@ +#pragma once + +#include "draudiostream.h" + +#include +#include +#include + +class DRAudioEngine; +class DRAudioEngineData; + +class DRAudioStreamFamily : public QObject +{ + Q_OBJECT + +public: + using ptr = QSharedPointer; + using stream_list = QVector; + + DRAudioStream::ptr create_stream(QString p_file); + DRAudioStream::ptr play_stream(QString p_file); + + // get + stream_list get_stream_list() const; + int32_t get_volume() const; + int32_t get_capacity() const; + DRAudio::Options get_options() const; + + // options get + bool is_suppressed() const; + bool is_ignore_suppression() const; + +public slots: + void set_volume(int32_t p_volume); + void set_capacity(int32_t p_capacity); + void set_options(DRAudio::Options p_options); + // options setter + void set_suppressed(bool p_enabled); + void set_ignore_suppression(bool p_enabled); + +signals: + void volume_changed(int32_t); + void capacity_changed(int32_t); + void options_changed(DRAudio::Options); + +private: + friend class DRAudioEngine; + friend class DRAudioEngineData; + friend class DRAudioEnginePrivate; + + DRAudio::Family m_family; + int32_t m_volume = 0; + int32_t m_capacity = 0; + DRAudio::Options m_options; + stream_list m_stream_list; + + DRAudioStreamFamily(DRAudio::Family p_family); + + float calculate_volume(); + + void update_capacity(); + void update_options(); + void update_volume(); + +private slots: + void on_stream_finished(); +}; diff --git a/src/draudiotrackmetadata.cpp b/src/draudiotrackmetadata.cpp new file mode 100644 index 000000000..b49b8bcf5 --- /dev/null +++ b/src/draudiotrackmetadata.cpp @@ -0,0 +1,127 @@ +#include "draudiotrackmetadata.h" + +#include +#include +#include +#include +#include +#include + +#include "aoapplication.h" +#include "utils.h" + +static QMap s_audiotrack_cache; + +void DRAudiotrackMetadata::update_cache() +{ + AOApplication *ao_app = dynamic_cast(qApp); + if (!ao_app) + { + qCritical() << "error: AOApplication has not been created"; + return; + } + + QList l_ini_list; + { // fetch ini files + const QDir l_dir(ao_app->get_music_folder_path()); + const QList l_file_list = l_dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::IgnoreCase); + for (const QFileInfo &i_file : l_file_list) + { + if (i_file.suffix().toLower() != "ini") + continue; + l_ini_list.append(i_file); + } + } + + QMap l_new_audiotrack_cache; + QMap l_new_audiotrack_origin; + for (const QFileInfo &i_ini_file : l_ini_list) + { + const QString l_ini_path = i_ini_file.absoluteFilePath(); + QSettings l_settings(l_ini_path, QSettings::IniFormat); + l_settings.setIniCodec("UTF-8"); + if (l_settings.status() != QSettings::NoError) + { + qWarning() << "error: failed to read audiotrack metadata" << l_settings.status() << "file:" << l_ini_path; + continue; + } + qDebug() << "reading audiotrack metadata" << l_ini_path; + utils::QSettingsKeyFetcher l_fetcher(l_settings); + + const QStringList l_group_list = l_settings.childGroups(); + for (const QString &i_group : l_group_list) + { + l_settings.beginGroup(i_group); + DRAudiotrackMetadata l_audiotrack; + l_audiotrack.m_title = l_settings.value(l_fetcher.lookup_value("title")).toString(); + l_audiotrack.m_filename = l_settings.value(l_fetcher.lookup_value("filename")).toString(); + l_audiotrack.m_play_once = l_settings.value(l_fetcher.lookup_value("play_once")).toBool(); + l_audiotrack.m_loop_start = l_settings.value(l_fetcher.lookup_value("loop_start")).toULongLong(); + l_audiotrack.m_loop_end = l_settings.value(l_fetcher.lookup_value("loop_end")).toULongLong(); + l_settings.endGroup(); + + const QString l_track_name = l_audiotrack.m_filename; + if (l_track_name.isEmpty()) + { + qWarning() << "error: empty file name in section" << i_group; + continue; + } + else if (!QFileInfo::exists(ao_app->get_music_path(l_track_name))) + { + qWarning() << "error: audiotrack not found" << l_track_name; + continue; + } + + const QString l_lower_track_name = l_track_name.toLower(); + if (l_new_audiotrack_origin.contains(l_lower_track_name)) + { + qWarning() << "warning: replacing track" << l_track_name << "; previously defined in" + << l_new_audiotrack_origin[l_lower_track_name]; + } + l_new_audiotrack_origin.insert(l_lower_track_name, l_ini_path); + l_new_audiotrack_cache.insert(l_lower_track_name, std::move(l_audiotrack)); + } + } + s_audiotrack_cache = std::move(l_new_audiotrack_cache); +} + +DRAudiotrackMetadata::DRAudiotrackMetadata() +{} + +DRAudiotrackMetadata::DRAudiotrackMetadata(QString p_file_name) + : m_filename(p_file_name) +{ + const QString l_lower_file_name = p_file_name.toLower(); + if (s_audiotrack_cache.contains(l_lower_file_name)) + { + *this = s_audiotrack_cache[l_lower_file_name]; + } +} + +DRAudiotrackMetadata::~DRAudiotrackMetadata() +{} + +QString DRAudiotrackMetadata::filename() +{ + return m_filename; +} + +QString DRAudiotrackMetadata::title() +{ + return m_title.isEmpty() ? m_filename : m_title; +} + +bool DRAudiotrackMetadata::play_once() +{ + return m_play_once; +} + +quint64 DRAudiotrackMetadata::loop_start() +{ + return m_loop_start; +} + +quint64 DRAudiotrackMetadata::loop_end() +{ + return m_loop_end; +} diff --git a/src/draudiotrackmetadata.h b/src/draudiotrackmetadata.h new file mode 100644 index 000000000..02e5de280 --- /dev/null +++ b/src/draudiotrackmetadata.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +class DRAudiotrackMetadata +{ +public: + static void update_cache(); + + DRAudiotrackMetadata(); + DRAudiotrackMetadata(QString file_name); + ~DRAudiotrackMetadata(); + + QString filename(); + QString title(); + bool play_once(); + quint64 loop_start(); + quint64 loop_end(); + +private: + QString m_filename; + QString m_title; + bool m_play_once = false; + quint64 m_loop_start = 0; + quint64 m_loop_end = 0; +}; diff --git a/src/drcharactermovie.cpp b/src/drcharactermovie.cpp new file mode 100644 index 000000000..932edd506 --- /dev/null +++ b/src/drcharactermovie.cpp @@ -0,0 +1,39 @@ +#include "drcharactermovie.h" + +#include "aoapplication.h" +#include "file_functions.h" + +#include + +DRCharacterMovie::DRCharacterMovie(AOApplication *ao_app, QGraphicsItem *parent) + : DRMovie(parent) + , ao_app(ao_app) +{ + Q_ASSERT(ao_app); + set_scaling_mode(ScalingMode::HeightScaling); +} + +DRCharacterMovie::~DRCharacterMovie() +{} + +void DRCharacterMovie::play(QString p_character, QString p_emote, QString p_prefix, bool p_use_placeholder, bool p_play_once) +{ + set_file_name(ao_app->get_character_sprite_path(p_character, p_emote, p_prefix, p_use_placeholder)); + set_play_once(p_play_once); + start(); +} + +void DRCharacterMovie::play_pre(QString p_character, QString p_emote) +{ + play(p_character, p_emote, nullptr, false, true); +} + +void DRCharacterMovie::play_talk(QString p_character, QString p_emote) +{ + play(p_character, p_emote, "(b)", true, false); +} + +void DRCharacterMovie::play_idle(QString p_character, QString p_emote) +{ + play(p_character, p_emote, "(a)", true, false); +} diff --git a/src/drcharactermovie.h b/src/drcharactermovie.h new file mode 100644 index 000000000..d62a620aa --- /dev/null +++ b/src/drcharactermovie.h @@ -0,0 +1,22 @@ +#pragma once + +#include "drmovie.h" + +class AOApplication; + +class DRCharacterMovie : public DRMovie +{ + Q_OBJECT + +public: + explicit DRCharacterMovie(AOApplication *ao_app, QGraphicsItem *parent = nullptr); + ~DRCharacterMovie(); + + void play(QString character, QString emote, QString prefix, bool p_use_placeholder, bool play_once); + void play_pre(QString character, QString emote); + void play_talk(QString character, QString emote); + void play_idle(QString character, QString emote); + +private: + AOApplication *ao_app = nullptr; +}; diff --git a/src/drchatlog.cpp b/src/drchatlog.cpp new file mode 100644 index 000000000..9106d3ffb --- /dev/null +++ b/src/drchatlog.cpp @@ -0,0 +1,155 @@ +#include "drchatlog.h" + +#include "aoconfig.h" + +#include +#include +#include +#include +#include + +DRChatLog::DRChatLog(QWidget *parent) + : QTextBrowser(parent) + , dr_config(new AOConfig(this)) +{ + connect(this, SIGNAL(message_queued()), this, SLOT(_p_write_message_queue())); +} + +void DRChatLog::append_chatmessage(QString p_name, QString p_text) +{ + queue_message(p_name.trimmed().isEmpty() ? "Anonymous" : p_name, p_text, false); +} + +void DRChatLog::append_information(QString p_text) +{ + queue_message(nullptr, p_text, false); +} + +void DRChatLog::append_html(QString p_text) +{ + queue_message(nullptr, p_text, true); +} + +void DRChatLog::reset_message_format() +{ + _p_reset_log(); +} + +void DRChatLog::queue_message(QString p_name, QString p_text, bool p_is_html) +{ + Message l_message; + l_message.timestamp = QDateTime::currentDateTime(); + l_message.name = p_name; + l_message.text = p_text; + l_message.is_html = p_is_html; + m_message_queue.append(std::move(l_message)); + Q_EMIT message_queued(); +} + +void DRChatLog::_p_write_message_queue() +{ + QScrollBar *l_scrollbar = verticalScrollBar(); + const bool l_is_end_scroll_pos = l_scrollbar->value() == l_scrollbar->maximum(); + + QTextCharFormat l_normal_format; + l_normal_format.setFont(font()); + l_normal_format.setFontWeight(QFont::Normal); + QTextCharFormat l_name_format = l_normal_format; + l_name_format.setFontWeight(QFont::Bold); + QTextCharFormat l_href_format = l_normal_format; + l_href_format.setAnchor(true); + l_href_format.setFontWeight(QFont::Bold); + l_href_format.setForeground(Qt::blue); + l_href_format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + + QTextCursor l_cursor = textCursor(); + l_cursor.movePosition(QTextCursor::End); + l_cursor.setCharFormat(l_normal_format); + + while (!m_message_queue.isEmpty()) + { + if (!m_message_list.isEmpty()) + l_cursor.insertText(QString(QChar::LineFeed)); + const Message l_message = m_message_queue.dequeue(); + m_message_list.append(l_message); + + l_cursor.insertText(QString(QString("[%1] ").arg(l_message.timestamp.toString("hh:mm"))), l_normal_format); + + if (!l_message.name.isEmpty()) + { + l_cursor.insertText(l_message.name, l_name_format); + l_cursor.insertText(": ", l_normal_format); + } + + if (l_message.is_html) + { + insertHtml(l_message.text); + } + else + { + const QString l_text = l_message.text; + + class TextPiece + { + public: + QString text; + bool is_href = false; + + TextPiece() + {} + TextPiece(QString p_text, bool p_is_href = false) + : text(p_text) + , is_href(p_is_href) + {} + }; + QVector l_piece_list; + + const QRegularExpression l_regex("(https?://[^\\s/$.?#].[^\\s]*)"); + QRegularExpressionMatchIterator l_iterator = l_regex.globalMatch(l_text); + { // capture all text pieces + int l_index = 0; + + while (l_iterator.hasNext()) + { + QRegularExpressionMatch l_match = l_iterator.next(); + + const int l_captureIndex = l_match.capturedStart(); + if (l_index < l_captureIndex) + l_piece_list.append(l_text.mid(l_index, l_captureIndex - l_index)); + l_piece_list.append(TextPiece(l_match.captured(), true)); + l_index = l_match.capturedEnd(); + } + + l_piece_list.append(l_text.mid(l_index)); + } + + for (const TextPiece &i_piece : qAsConst(l_piece_list)) + { + if (i_piece.text.isEmpty()) + continue; + + QTextCharFormat l_piece_format = l_normal_format; + if (i_piece.is_href) + { + l_piece_format = l_href_format; + l_piece_format.setAnchorHref(i_piece.text); + } + + l_cursor.insertText(i_piece.is_href ? QUrl(i_piece.text).toString() : i_piece.text, l_piece_format); + } + } + } + + if (l_is_end_scroll_pos) + l_scrollbar->setValue(l_scrollbar->maximum()); +} + +void DRChatLog::_p_reset_log() +{ + clear(); + QQueue l_new_message_queue; + dynamic_cast &>(l_new_message_queue) = std::move(m_message_list); + l_new_message_queue.append(std::move(m_message_queue)); + m_message_queue = std::move(l_new_message_queue); + _p_write_message_queue(); +} diff --git a/src/drchatlog.h b/src/drchatlog.h new file mode 100644 index 000000000..82ce535f5 --- /dev/null +++ b/src/drchatlog.h @@ -0,0 +1,44 @@ +#pragma once + +class AOConfig; + +#include +#include +#include +#include + +class DRChatLog : public QTextBrowser +{ + Q_OBJECT + +public: + DRChatLog(QWidget *parent = nullptr); + + void append_chatmessage(QString name, QString message); + void append_information(QString message); + void append_html(QString html); + void reset_message_format(); + +signals: + void message_queued(); + +private: + AOConfig *dr_config = nullptr; + + class Message + { + public: + QDateTime timestamp; + QString name; + QString text; + bool is_html = false; + }; + QList m_message_list; + QQueue m_message_queue; + + void queue_message(QString name, QString text, bool is_html); + +private slots: + void _p_write_message_queue(); + void _p_reset_log(); +}; diff --git a/src/drdiscord.cpp b/src/drdiscord.cpp new file mode 100644 index 000000000..766af5c04 --- /dev/null +++ b/src/drdiscord.cpp @@ -0,0 +1,256 @@ +#include "drdiscord.h" + +#include +#include +#include +#include + +#include + +Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, DISCORD_APPLICATION_ID, ("818850172330442793")); +Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, DISCORD_LARGE_IMAGE_KEY, ("danganronpa_online")); +Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, DISCORD_LARGE_IMAGE_TEXT, ("Sore Wa Chigau Yo!")); + +QByteArray resize_buf(const QString &f_str_message, const int f_max_size) +{ + QByteArray l_message = f_str_message.toUtf8(); + + // reduce by one to take '\0' in account + const int l_target_max_size = f_max_size - 1; + + if (l_message.size() > l_target_max_size) + { + const QByteArray l_omission_text("..."); + l_message = l_message.mid(0, l_target_max_size - l_omission_text.size()) + l_omission_text; + } + + return l_message; +} + +DRDiscord::DRDiscord(QObject *f_parent) + : QObject(f_parent) +{ + m_waiter = new QTimer(this); + m_waiter->setInterval(std::chrono::seconds(1)); + m_waiter->setSingleShot(true); + connect(m_waiter, SIGNAL(timeout()), this, SLOT(on_update_queued())); + connect(this, SIGNAL(options_changed(DRDiscord::Options)), m_waiter, SLOT(start())); + connect(this, SIGNAL(state_changed(DRDiscord::State)), m_waiter, SLOT(start())); + connect(this, SIGNAL(server_name_changed(QString)), m_waiter, SLOT(start())); + connect(this, SIGNAL(server_name_cleared()), m_waiter, SLOT(start())); + connect(this, SIGNAL(character_name_changed(QString)), m_waiter, SLOT(start())); + connect(this, SIGNAL(character_name_cleared()), m_waiter, SLOT(start())); + connect(this, SIGNAL(started()), m_waiter, SLOT(start())); + connect(this, SIGNAL(stopped()), m_waiter, SLOT(start())); + + start(); +} + +DRDiscord::~DRDiscord() +{ + this->disconnect(m_waiter); + stop(); +} + +void DRDiscord::start(const bool p_restart) +{ + if (p_restart) + stop(); + if (m_started) + return; + m_started = true; + m_timestamp = QDateTime::currentDateTime().toSecsSinceEpoch(); + qInfo().noquote() << QString("[Discord-RPC] %1").arg(p_restart ? "Restarting" : "Starting"); + // reset & set + std::memset(&m_handler, 0, sizeof(m_handler)); + m_handler.ready = [](const DiscordUser *user) { + qInfo().noquote() << QString("[Discord-RPC] Ready, as username: %1").arg(user->username); + }; + m_handler.disconnected = [](int errorCode, const char *message) { + qInfo().noquote() << QString("[Discord-RPC] Disconnected, code %1: %2").arg(errorCode).arg(message); + }; + m_handler.errored = [](int errorCode, const char *message) { + qWarning().noquote() << QString("[Discord-RPC] Error %1: %2").arg(errorCode).arg(message); + }; + Discord_Initialize(DISCORD_APPLICATION_ID->constData(), &m_handler, 1, nullptr); + Q_EMIT started(); +} + +void DRDiscord::stop() +{ + if (!m_started) + return; + m_started = false; + m_timestamp = 0; + qInfo().noquote() << "[Discord-RPC] Shutting down"; + Discord_Shutdown(); + Q_EMIT stopped(); +} + +DRDiscord::Options DRDiscord::get_options() const +{ + return m_options; +} + +bool DRDiscord::option_enabled(const DRDiscord::Option p_option) const +{ + return m_options.testFlag(p_option); +} + +bool DRDiscord::presence_enabled() const +{ + return option_enabled(OPresence); +} + +bool DRDiscord::hide_server_enabled() const +{ + return option_enabled(OHideServer); +} + +bool DRDiscord::hide_character_enabled() const +{ + return option_enabled(OHideCharacter); +} + +DRDiscord::State DRDiscord::get_state() const +{ + return m_state; +} + +bool DRDiscord::is_state(const DRDiscord::State p_state) const +{ + return m_state == p_state; +} + +bool DRDiscord::is_idle() const +{ + return is_state(State::Idle); +} + +bool DRDiscord::is_connected() const +{ + return is_state(State::Connected); +} + +void DRDiscord::set_options(const Options &f_options) +{ + if (m_options == f_options) + return; + m_options = f_options; + Q_EMIT options_changed(m_options); +} + +void DRDiscord::set_option(const DRDiscord::Option &f_option, const bool p_enabled) +{ + if (option_enabled(f_option) == p_enabled) + return; + Options l_options = m_options; + l_options.setFlag(f_option, p_enabled); + set_options(l_options); +} + +void DRDiscord::set_presence(const bool p_enabled) +{ + set_option(OPresence, p_enabled); +} + +void DRDiscord::set_hide_server(const bool p_enabled) +{ + set_option(OHideServer, p_enabled); +} + +void DRDiscord::set_hide_character(const bool p_enabled) +{ + set_option(OHideCharacter, p_enabled); +} + +void DRDiscord::set_state(const DRDiscord::State f_state) +{ + if (m_state == f_state) + return; + m_state = f_state; + Q_EMIT state_changed(m_state); +} + +void DRDiscord::set_server_name(const QString &f_server_name) +{ + Q_ASSERT_X(!f_server_name.trimmed().isEmpty(), "DRDiscord", "a server name is required"); + if (m_server_name.has_value() && m_server_name.value() == f_server_name) + return; + m_server_name = f_server_name; + Q_EMIT server_name_changed(m_server_name.value()); +} + +void DRDiscord::clear_server_name() +{ + m_server_name.reset(); + Q_EMIT server_name_cleared(); +} + +void DRDiscord::set_character_name(const QString &f_character_name) +{ + Q_ASSERT_X(!f_character_name.trimmed().isEmpty(), "DRDiscord", "a server name is required"); + if (m_character_name.has_value() && m_character_name.value() == f_character_name) + return; + m_character_name = f_character_name; + Q_EMIT character_name_changed(m_character_name.value()); +} + +void DRDiscord::clear_character_name() +{ + m_character_name.reset(); + Q_EMIT character_name_cleared(); +} + +void DRDiscord::on_update_queued() +{ + if (!presence_enabled()) + { + Discord_ClearPresence(); + return; + } + + switch (m_state) + { + default: + case State::Idle: + m_buf_details.clear(); + if (!hide_server_enabled()) + { + m_buf_details = "Lobby"; + } + m_buf_state.clear(); + break; + + case State::Connected: + m_buf_details.clear(); + if (!hide_server_enabled()) + { + m_buf_details = QString("In: %1") + .arg(m_server_name.has_value() ? m_server_name.value().toUtf8() : QString("")) + .toUtf8(); + } + + m_buf_state.clear(); + if (!hide_character_enabled()) + { + m_buf_state.clear(); + m_buf_state = m_character_name.has_value() ? QString("As: %1").arg(m_character_name.value()).toUtf8() + : QByteArray("Spectating"); + } + break; + } + + // respect max size for each fields; see discord_rpc.h struct + m_buf_details = resize_buf(QString::fromUtf8(m_buf_details), 128); + m_buf_state = resize_buf(QString::fromUtf8(m_buf_state), 128); + + // reset & set presence + std::memset(&m_presence, 0, sizeof(m_presence)); + m_presence.largeImageKey = DISCORD_LARGE_IMAGE_KEY->constData(); + m_presence.largeImageText = DISCORD_LARGE_IMAGE_TEXT->constData(); + m_presence.state = m_buf_state.constData(); + m_presence.details = m_buf_details.constData(); + m_presence.startTimestamp = m_timestamp; + Discord_UpdatePresence(&m_presence); +} diff --git a/src/drdiscord.h b/src/drdiscord.h new file mode 100644 index 000000000..14dbf1cf4 --- /dev/null +++ b/src/drdiscord.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +#include +#include + +class QTimer; + +#include + +class DRDiscord : public QObject +{ + Q_OBJECT + +public: + enum Option : uint32_t + { + OPresence = 0x1, + OHideServer = 0x2, + OHideCharacter = 0x4, + }; + Q_DECLARE_FLAGS(Options, Option) + + enum class State + { + Idle, + Connected, + }; + Q_ENUM(State) + + DRDiscord(QObject *f_parent = nullptr); + ~DRDiscord(); + + Options get_options() const; + bool option_enabled(const Option p_option) const; + bool presence_enabled() const; + bool hide_server_enabled() const; + bool hide_character_enabled() const; + State get_state() const; + bool is_state(const State p_state) const; + bool is_idle() const; + bool is_connected() const; + +public slots: + void set_options(const DRDiscord::Options &f_options); + void set_option(const DRDiscord::Option &f_option, const bool p_enabled); + void set_presence(const bool p_enabled); + void set_hide_server(const bool p_enabled); + void set_hide_character(const bool p_enabled); + void set_state(const DRDiscord::State f_state); + void set_server_name(const QString &f_server_name); + void clear_server_name(); + void set_character_name(const QString &f_character_name); + void clear_character_name(); + + void start(const bool p_restart = false); + void stop(); + +signals: + void options_changed(DRDiscord::Options); + void state_changed(DRDiscord::State); + void server_name_changed(QString); + void server_name_cleared(); + void character_name_changed(QString); + void character_name_cleared(); + void started(); + void stopped(); + +private: + Options m_options; + State m_state = State::Idle; + QPointer m_waiter; + DiscordEventHandlers m_handler; + DiscordRichPresence m_presence; + bool m_started = false; + std::optional m_server_name; + std::optional m_character_name; + QByteArray m_buf_details, m_buf_state; + int64_t m_timestamp; + +private slots: + void on_update_queued(); +}; diff --git a/src/dreffectmovie.cpp b/src/dreffectmovie.cpp new file mode 100644 index 000000000..76dd1496a --- /dev/null +++ b/src/dreffectmovie.cpp @@ -0,0 +1,24 @@ +#include "dreffectmovie.h" + +#include "aoapplication.h" + +DREffectMovie::DREffectMovie(AOApplication *ao_app, QGraphicsItem *parent) + : DRMovie(parent) + , ao_app(ao_app) +{ + set_hide_on_done(true); +} + +DREffectMovie::~DREffectMovie() +{} + +void DREffectMovie::play(QString p_file_name, QString p_character) +{ + set_file_name(ao_app->get_theme_sprite_path(p_file_name, p_character)); + start(); +} + +void DREffectMovie::play(QString p_file_name) +{ + play(p_file_name, ""); +} diff --git a/src/dreffectmovie.h b/src/dreffectmovie.h new file mode 100644 index 000000000..c5a290619 --- /dev/null +++ b/src/dreffectmovie.h @@ -0,0 +1,20 @@ +#pragma once + +#include "drmovie.h" + +class AOApplication; + +class DREffectMovie : public DRMovie +{ + Q_OBJECT + +public: + explicit DREffectMovie(AOApplication *ao_app, QGraphicsItem *parent = nullptr); + ~DREffectMovie(); + + void play(QString p_file_name, QString p_character); + void play(QString p_file_name); + +private: + AOApplication *ao_app; +}; diff --git a/src/drgraphicscene.cpp b/src/drgraphicscene.cpp new file mode 100644 index 000000000..6d1e8b3be --- /dev/null +++ b/src/drgraphicscene.cpp @@ -0,0 +1,61 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ +#include "drgraphicscene.h" + +#include +#include +#include + +#include + +DRGraphicsView::DRGraphicsView(QWidget *parent) + : QGraphicsView(parent) + , m_scene(new QGraphicsScene(this)) +{ + setInteractive(false); + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + setFrameShape(QFrame::NoFrame); + setFrameStyle(0); + + setScene(m_scene); + + setBackgroundBrush(Qt::black); +} + +DRGraphicsView::~DRGraphicsView() +{} + +void DRGraphicsView::resizeEvent(QResizeEvent *event) +{ + QGraphicsView::resizeEvent(event); + for (QGraphicsItem *i_item : scene()->items()) + { + auto l_object = dynamic_cast(i_item); + if (l_object) + { + l_object->setProperty("size", event->size()); + } + } + m_scene->setSceneRect(rect()); + setSceneRect(rect()); +} diff --git a/src/drgraphicscene.h b/src/drgraphicscene.h new file mode 100644 index 000000000..b399061d2 --- /dev/null +++ b/src/drgraphicscene.h @@ -0,0 +1,38 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ +#pragma once + +#include +#include + +class DRGraphicsView : public QGraphicsView +{ + Q_OBJECT + +public: + DRGraphicsView(QWidget *parent = nullptr); + ~DRGraphicsView(); + +protected: + void resizeEvent(QResizeEvent *event) final; + +private: + QGraphicsScene *m_scene; +}; diff --git a/src/drmasterclient.cpp b/src/drmasterclient.cpp new file mode 100644 index 000000000..a88c9600e --- /dev/null +++ b/src/drmasterclient.cpp @@ -0,0 +1,108 @@ +#include "drmasterclient.h" + +// qt +#include +#include +#include +#include +#include +#include + +DRMasterClient::DRMasterClient(QObject *parent) + : QObject(parent) + , m_network(new QNetworkAccessManager(this)) +{} + +DRMasterClient::~DRMasterClient() +{} + +QString DRMasterClient::address() const +{ + return m_address; +} + +void DRMasterClient::set_address(QString p_address) +{ + if (m_address == p_address) + return; + m_address = p_address; + emit address_changed(); +} + +QString DRMasterClient::motd() const +{ + return m_motd; +} + +void DRMasterClient::request_motd() +{ + send_get_request("/motd", &DRMasterClient::process_motd); +} + +DRServerInfoList DRMasterClient::server_list() const +{ + return m_server_list; +} + +void DRMasterClient::request_server_list() +{ + send_get_request("/servers", &DRMasterClient::process_server_list); +} + +void DRMasterClient::send_get_request(QString request, Delegate delegate) +{ + if (m_address.isEmpty()) + { + qWarning() << "error: address is undefined"; + return; + } + + QNetworkRequest l_request(m_address + request); + QNetworkReply *l_reply = m_network->get(l_request); + l_reply->setParent(this); + m_pending_delegates.insert(l_reply, delegate); + connect(l_reply, SIGNAL(finished()), this, SLOT(process_request())); +} + +void DRMasterClient::process_request() +{ + QNetworkReply *l_reply = dynamic_cast(sender()); + if (!l_reply) + { + qCritical() << "error: sender is not expected object" << sender(); + return; + } + + QVariant l_data = l_reply->readAll(); + const QJsonDocument l_doc = QJsonDocument::fromJson(l_data.toByteArray()); + if (!l_doc.isNull()) + l_data = l_doc.toVariant(); + + const Delegate l_delegate = m_pending_delegates.take(l_reply); + (this->*l_delegate)(l_data); + l_reply->deleteLater(); +} + +void DRMasterClient::process_motd(QVariant p_data) +{ + m_motd = p_data.toString(); + emit motd_changed(); +} + +void DRMasterClient::process_server_list(QVariant p_data) +{ + DRServerInfoList l_server_list; + const QJsonArray l_json_array = p_data.toJsonArray(); + for (const QJsonValue &i_value : l_json_array) + { + const QJsonObject i_object = i_value.toObject(); + DRServerInfo l_server; + l_server.name = i_object.value("name").toString(); + l_server.description = i_object.value("description").toString(); + l_server.address = i_object.value("ip").toString(); + l_server.port = i_object.value("port").toInt(); + l_server_list.append(std::move(l_server)); + } + m_server_list = std::move(l_server_list); + emit server_list_changed(); +} diff --git a/src/drmasterclient.h b/src/drmasterclient.h new file mode 100644 index 000000000..0a355d665 --- /dev/null +++ b/src/drmasterclient.h @@ -0,0 +1,51 @@ +#pragma once + +// std +#include + +// qt +#include +#include +#include + +class QNetworkAccessManager; + +// src +#include "datatypes.h" + +class DRMasterClient : public QObject +{ + Q_OBJECT + +public: + DRMasterClient(QObject *parent = nullptr); + ~DRMasterClient(); + + QString address() const; + QString motd() const; + DRServerInfoList server_list() const; + +public slots: + void set_address(QString address); + void request_motd(); + void request_server_list(); + +signals: + void address_changed(); + void motd_changed(); + void server_list_changed(); + +private: + QNetworkAccessManager *m_network = nullptr; + QString m_address; + QString m_motd; + DRServerInfoList m_server_list; + using Delegate = void (DRMasterClient::*)(QVariant); + QHash m_pending_delegates; + +private slots: + void send_get_request(QString request, Delegate delegate); + void process_request(); + void process_motd(QVariant); + void process_server_list(QVariant); +}; diff --git a/src/drmediatester.cpp b/src/drmediatester.cpp new file mode 100644 index 000000000..bb6569bb4 --- /dev/null +++ b/src/drmediatester.cpp @@ -0,0 +1,39 @@ +#include "drmediatester.h" + +#include + +#include "debug_functions.h" + +DRMediaTester::DRMediaTester(QObject *parent) + : QObject(parent) +{ + m_player.setMuted(true); + + connect(&m_player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, SLOT(p_check_status(QMediaPlayer::MediaStatus))); + + m_player.setMedia(QUrl("qrc:/data/sample.avi")); +} + +DRMediaTester::~DRMediaTester() +{} + +void DRMediaTester::p_check_status(QMediaPlayer::MediaStatus p_status) +{ + switch (p_status) + { + case QMediaPlayer::InvalidMedia: + call_warning("Your operating system appears to not currently support video playback. The video " + "playback will not work properly.

In order for the feature to function properly, you " + "will need to install additional codecs.

For more information visit " + "https://www.danganronpaonline.com"); + emit done(); + break; + + case QMediaPlayer::LoadedMedia: + emit done(); + break; + + default: + break; + } +} diff --git a/src/drmediatester.h b/src/drmediatester.h new file mode 100644 index 000000000..c8ee9bc3c --- /dev/null +++ b/src/drmediatester.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +class DRMediaTester : public QObject +{ + Q_OBJECT + +public: + DRMediaTester(QObject *parent = nullptr); + ~DRMediaTester(); + +signals: + void done(); + +private: + QMediaPlayer m_player; + +private slots: + void p_check_status(QMediaPlayer::MediaStatus p_status); +}; diff --git a/src/drmovie.cpp b/src/drmovie.cpp new file mode 100644 index 000000000..4ff55117b --- /dev/null +++ b/src/drmovie.cpp @@ -0,0 +1,41 @@ +#include "drmovie.h" + +DRMovie::DRMovie(QGraphicsItem *parent) + : mk2::GraphicsSpriteItem{parent} + , m_hide_when_done{false} + , m_mirrored{false} +{ + connect(this, SIGNAL(started()), this, SLOT(update_visibility())); + connect(this, SIGNAL(finished()), this, SIGNAL(done())); + connect(this, SIGNAL(finished()), this, SLOT(update_visibility())); +} + +DRMovie::~DRMovie() +{} + +QString DRMovie::file_name() +{ + return get_file_name(); +} + +void DRMovie::set_hide_on_done(bool p_enabled) +{ + m_hide_when_done = p_enabled; +} + +void DRMovie::set_mirrored(bool p_enabled) +{ + set_mirror(p_enabled); +} + +void DRMovie::update_visibility() +{ + if (is_running()) + { + show(); + } + else if (m_hide_when_done) + { + hide(); + } +} diff --git a/src/drmovie.h b/src/drmovie.h new file mode 100644 index 000000000..06e21a284 --- /dev/null +++ b/src/drmovie.h @@ -0,0 +1,26 @@ +#pragma once + +#include "mk2/graphicsspriteitem.h" + +class DRMovie : public mk2::GraphicsSpriteItem +{ + Q_OBJECT + +public: + DRMovie(QGraphicsItem *parent = nullptr); + ~DRMovie(); + + QString file_name(); + void set_hide_on_done(bool enabled); + void set_mirrored(bool enabled); + +signals: + void done(); + +private: + bool m_hide_when_done; + bool m_mirrored; + +private slots: + void update_visibility(); +}; diff --git a/src/drpacket.cpp b/src/drpacket.cpp new file mode 100644 index 000000000..2969a6bff --- /dev/null +++ b/src/drpacket.cpp @@ -0,0 +1,41 @@ +#include "drpacket.h" + +#include + +QString DRPacket::encode(QString p_data) +{ + return p_data.replace("#", "").replace("%", "").replace("$", "").replace("&", ""); +} + +QString DRPacket::decode(QString p_data) +{ + return p_data.replace("", "#").replace("", "%").replace("", "$").replace("", "&"); +} + +DRPacket::DRPacket(QString p_header) + : DRPacket(p_header, {}) +{} + +DRPacket::DRPacket(QString p_header, QStringList p_content) +{ + m_header = p_header; + m_content = p_content; +} + +const QString &DRPacket::get_header() const +{ + return m_header; +} + +const QStringList &DRPacket::get_content() const +{ + return m_content; +} + +QString DRPacket::to_string(const bool p_encode) const +{ + QString r_data; + for (const QString &i_value : qAsConst(m_content)) + r_data += (p_encode ? encode(i_value) : i_value) + "#"; + return m_header + "#" + r_data + "%"; +} diff --git a/src/drpacket.h b/src/drpacket.h new file mode 100644 index 000000000..81552a89c --- /dev/null +++ b/src/drpacket.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +class DRPacket +{ +public: + static QString encode(QString data); + static QString decode(QString data); + + DRPacket(QString header); + DRPacket(QString header, QStringList content); + + const QString &get_header() const; + const QStringList &get_content() const; + QString to_string(const bool encode = false) const; + +private: + QString m_header; + QStringList m_content; +}; diff --git a/src/drpather.cpp b/src/drpather.cpp new file mode 100644 index 000000000..616f016b0 --- /dev/null +++ b/src/drpather.cpp @@ -0,0 +1,25 @@ +#include "drpather.h" + +#include +#include + +/* @brief Gets the directory containing the base folder and the application. + * + * This function is a system-independent QDir::currentPath() system-independent. The only edge case really is MacOS, + * where due to the way applications are bundled, makes QDir::currentPath() return nonsensical values by default and + * QCoreApplication::applicationDirPath() returns the directory to the internal executable of the bundle rather + * than the bundle location itself. + * + * @return Directory. + */ +QString DRPather::get_application_path() +{ +#ifdef Q_OS_MACOS + QDir l_mac_path(QCoreApplication::applicationDirPath()); + for (int i = 0; i < 3; ++i) // equivalent of "/../../.." + l_mac_path.cdUp(); + return l_mac_path.canonicalPath(); +#else + return QDir::currentPath(); +#endif +} diff --git a/src/drpather.h b/src/drpather.h new file mode 100644 index 000000000..a4003716d --- /dev/null +++ b/src/drpather.h @@ -0,0 +1,9 @@ +#pragma once + +class QString; + +class DRPather +{ +public: + static QString get_application_path(); +}; diff --git a/src/drposition.cpp b/src/drposition.cpp new file mode 100644 index 000000000..8566800f0 --- /dev/null +++ b/src/drposition.cpp @@ -0,0 +1,123 @@ +#include "drposition.h" + +#include +#include + +const QMap DRPositionMap::LEGACY_POSITION_MAP{ + { + "wit", + DRPosition("witnessempty", "stand"), + }, + { + "def", + DRPosition("defenseempty", "defensedesk"), + }, + { + "pro", + DRPosition("prosecutorempty", "prosecutiondesk"), + }, + { + "jud", + DRPosition("judgestand", "judgedesk"), + }, + { + "hld", + DRPosition("helperstand", "helperdesk"), + }, + { + "hlp", + DRPosition("prohelperstand", "prohelperdesk"), + }, +}; + +DRPosition::DRPosition() +{} + +DRPosition::DRPosition(QString p_back, QString p_front) + : m_back(p_back) + , m_front(p_front) +{} + +DRPosition::DRPosition(QString p_back, QString p_front, QString p_ambient_sfx) + : m_back(p_back) + , m_front(p_front) + , m_ambient_sfx(p_ambient_sfx) +{} + +DRPosition::~DRPosition() +{} + +QString DRPosition::get_back() +{ + return m_back; +} + +QString DRPosition::get_front() +{ + return m_front; +} + +QString DRPosition::get_ambient_sfx() +{ + return m_ambient_sfx; +} + +void DRPosition::set_back(QString p_back) +{ + m_back = p_back; +} + +void DRPosition::set_front(QString p_front) +{ + m_front = p_front; +} + +void DRPosition::set_ambient_sfx(QString p_ambient_sfx) +{ + m_ambient_sfx = p_ambient_sfx; +} + +DRPositionMap::DRPositionMap() +{} + +DRPositionMap::~DRPositionMap() +{} + +DRPosition DRPositionMap::get_position(QString p_id) +{ + const QString l_lower_id = p_id.toLower(); + return m_position_map.contains(l_lower_id) ? m_position_map.value(l_lower_id) : LEGACY_POSITION_MAP.value(l_lower_id, LEGACY_POSITION_MAP.value("wit")); +} + +void DRPositionMap::set_position(QString p_id, DRPosition p_position) +{ + m_position_map.insert(p_id.toLower(), p_position); +} + +bool DRPositionMap::load_file(QString p_filename) +{ + QSettings l_settings(p_filename, QSettings::IniFormat); + l_settings.setIniCodec("UTF-8"); + if (l_settings.status() != QSettings::NoError) + { + qWarning() << "[Position Map]" + << "warning:" + << "could not load positions.ini file"; + return false; + } + + QMap l_position_map; + const QStringList l_group_list = l_settings.childGroups(); + for (const QString &i_group : l_group_list) + { + const QString l_lower_group = i_group.toLower(); + l_settings.beginGroup(i_group); + const QString l_back = l_settings.value("back").toString(); + const QString l_front = l_settings.value("front").toString(); + const QString l_ambient_sfx = l_settings.value("ambient_sfx").toString(); + l_position_map.insert(l_lower_group, DRPosition(l_back, l_front, l_ambient_sfx)); + l_settings.endGroup(); + } + m_position_map = l_position_map; + return true; +} diff --git a/src/drposition.h b/src/drposition.h new file mode 100644 index 000000000..c2f5f0995 --- /dev/null +++ b/src/drposition.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +class DRPosition +{ +public: + DRPosition(); + + DRPosition(QString back, QString front); + + DRPosition(QString back, QString front, QString ambient_sfx); + + ~DRPosition(); + + QString get_back(); + + QString get_front(); + + QString get_ambient_sfx(); + + void set_back(QString back); + + void set_front(QString front); + + void set_ambient_sfx(QString ambient_sfx); + +private: + QString m_back; + + QString m_front; + + QString m_ambient_sfx; +}; + +class DRPositionMap +{ +public: + static const QMap LEGACY_POSITION_MAP; + + DRPositionMap(); + ~DRPositionMap(); + + DRPosition get_position(QString id); + + void set_position(QString id, DRPosition position); + + bool load_file(QString filename); + +private: + QMap m_position_map; +}; diff --git a/src/drscenemovie.cpp b/src/drscenemovie.cpp new file mode 100644 index 000000000..f5888c5c8 --- /dev/null +++ b/src/drscenemovie.cpp @@ -0,0 +1,25 @@ +#include "drscenemovie.h" + +#include "aoapplication.h" +#include "file_functions.h" + +DRSceneMovie::DRSceneMovie(AOApplication *ao_app, QGraphicsItem *parent) + : DRMovie(parent) + , ao_app(ao_app) +{ + set_scaling_mode(ScalingMode::DynamicScaling); +} + +DRSceneMovie::~DRSceneMovie() +{} + +void DRSceneMovie::set_background_image(QString p_background_name, QString p_image) +{ + const QString l_filename = ao_app->get_background_sprite_path(p_background_name, p_image); + if (l_filename == file_name()) + { + return; + } + set_file_name(l_filename); + start(); +} diff --git a/src/drscenemovie.h b/src/drscenemovie.h new file mode 100644 index 000000000..5adc1cf89 --- /dev/null +++ b/src/drscenemovie.h @@ -0,0 +1,19 @@ +#pragma once + +#include "drmovie.h" + +class AOApplication; + +class DRSceneMovie : public DRMovie +{ + Q_OBJECT + +public: + explicit DRSceneMovie(AOApplication *ao_app, QGraphicsItem *parent = nullptr); + ~DRSceneMovie(); + + void set_background_image(QString p_background_name, QString p_image); + +private: + AOApplication *ao_app; +}; diff --git a/src/drserverinfoeditor.cpp b/src/drserverinfoeditor.cpp new file mode 100644 index 000000000..503919b67 --- /dev/null +++ b/src/drserverinfoeditor.cpp @@ -0,0 +1,57 @@ +#include "drserverinfoeditor.h" + +#include "aoguiloader.h" + +#include + +DRServerInfoEditor::DRServerInfoEditor(QWidget *parent) + : QDialog(parent) +{ + setModal(true); + + AOGuiLoader l_loader; + l_loader.load_from_file(":/src/drserverinfoeditor.ui", this); + + ui_name = AO_GUI_WIDGET(QLineEdit, "name"); + ui_description = AO_GUI_WIDGET(QPlainTextEdit, "description"); + ui_address = AO_GUI_WIDGET(QLineEdit, "address"); + ui_port = AO_GUI_WIDGET(QSpinBox, "port"); + ui_button_box = AO_GUI_WIDGET(QDialogButtonBox, "button_box"); + + connect(ui_button_box->button(QDialogButtonBox::Reset), SIGNAL(clicked()), this, SLOT(clear_server_info())); + connect(ui_button_box, SIGNAL(accepted()), this, SLOT(accept())); + connect(ui_button_box, SIGNAL(rejected()), this, SLOT(reject())); +} + +DRServerInfoEditor::~DRServerInfoEditor() +{} + +DRServerInfo DRServerInfoEditor::get_server_info() +{ + DRServerInfo l_server_info; + + l_server_info.name = ui_name->text(); + if (l_server_info.name.isEmpty()) + l_server_info.name = ui_name->placeholderText(); + l_server_info.description = ui_description->toPlainText(); + l_server_info.address = ui_address->text(); + l_server_info.port = ui_port->value(); + + return l_server_info; +} + +void DRServerInfoEditor::set_server_info(DRServerInfo p_server_info) +{ + ui_name->setText(p_server_info.name); + ui_description->setPlainText(p_server_info.description); + ui_address->setText(p_server_info.address); + ui_port->setValue(p_server_info.port); +} + +void DRServerInfoEditor::clear_server_info() +{ + ui_name->setText(nullptr); + ui_description->setPlainText(nullptr); + ui_address->setText(nullptr); + ui_port->setValue(ui_port->minimum()); +} diff --git a/src/drserverinfoeditor.h b/src/drserverinfoeditor.h new file mode 100644 index 000000000..e84870da1 --- /dev/null +++ b/src/drserverinfoeditor.h @@ -0,0 +1,31 @@ +#pragma once + +#include "datatypes.h" + +#include +#include +#include +#include +#include + +class DRServerInfoEditor : public QDialog +{ + Q_OBJECT + +public: + DRServerInfoEditor(QWidget *parent = nullptr); + ~DRServerInfoEditor(); + + DRServerInfo get_server_info(); + +public slots: + void set_server_info(DRServerInfo p_server_info); + void clear_server_info(); + +private: + QLineEdit *ui_name; + QPlainTextEdit *ui_description; + QLineEdit *ui_address; + QSpinBox *ui_port; + QDialogButtonBox *ui_button_box; +}; diff --git a/src/drserverinfoeditor.ui b/src/drserverinfoeditor.ui new file mode 100644 index 000000000..90e44d8f2 --- /dev/null +++ b/src/drserverinfoeditor.ui @@ -0,0 +1,108 @@ + + + Form + + + + 0 + 0 + 320 + 209 + + + + + 0 + 0 + + + + + 320 + 0 + + + + Form + + + + + + Name: + + + + + + + The name of the server. + + + Undefined + + + + + + + Description: + + + + + + + The description of the server. + + + + + + + Address: + + + + + + + The address of the server. May be IPv4 or IPv6. + + + 127.0.0.1 + + + + + + + Port: + + + + + + + The port to connect to the server. May range from 1024 to 65535. + + + 1024 + + + 65535 + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + + + + + + + + diff --git a/src/drserversocket.cpp b/src/drserversocket.cpp new file mode 100644 index 000000000..3d1190908 --- /dev/null +++ b/src/drserversocket.cpp @@ -0,0 +1,116 @@ +#include "drserversocket.h" +#include "qabstractsocket.h" + +#include +#include + +const int DRServerSocket::CONNECTING_DELAY = 5000; + +namespace +{ +QString drFormatServerInfo(const DRServerInfo &server) +{ + const QString l_server_info = server.to_info(); + return !l_server_info.isEmpty() ? QString("<%1>").arg(l_server_info) : nullptr; +} +} // namespace + +DRServerSocket::DRServerSocket(QObject *p_parent) + : QObject(p_parent) +{ + m_socket = new QTcpSocket(this); + m_connecting_timeout = new QTimer(this); + + m_connecting_timeout->setSingleShot(true); + m_connecting_timeout->setInterval(CONNECTING_DELAY); + + connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(_p_check_socket_error())); + connect(m_socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(_p_update_state(QAbstractSocket::SocketState))); + connect(m_socket, SIGNAL(readyRead()), this, SLOT(_p_read_socket())); + + connect(m_connecting_timeout, SIGNAL(timeout()), this, SLOT(disconnect_from_server())); + m_socket->close(); +} + +bool DRServerSocket::is_connected() const +{ + return m_socket->state() == QTcpSocket::ConnectedState; +} + +void DRServerSocket::connect_to_server(DRServerInfo p_server) +{ + disconnect_from_server(); + m_server = p_server; + m_socket->connectToHost(p_server.address, p_server.port); +} + +void DRServerSocket::disconnect_from_server() +{ + m_socket->close(); + m_socket->abort(); + m_buffer.clear(); +} + +void DRServerSocket::send_packet(DRPacket p_packet) +{ + if (!is_connected()) + { + const QString l_server_info = m_server.to_info(); + qWarning().noquote() << QString("Failed to send packet; not connected to server%1").arg(drFormatServerInfo(m_server)); + return; + } + m_socket->write(p_packet.to_string(true).toUtf8()); +} + +void DRServerSocket::_p_update_state(QAbstractSocket::SocketState p_state) +{ + bool l_state_changed = true; + switch (p_state) + { + case QAbstractSocket::ConnectingState: + m_connecting_timeout->start(); + m_state = Connecting; + break; + + case QAbstractSocket::ConnectedState: + m_connecting_timeout->stop(); + m_state = Connected; + break; + + case QAbstractSocket::UnconnectedState: + m_connecting_timeout->stop(); + m_state = NotConnected; + break; + + default: + l_state_changed = false; + break; + } + + if (l_state_changed) + { + emit connection_state_changed(m_state); + } +} + +void DRServerSocket::_p_check_socket_error() +{ + const QString l_error = QString("Server%1 error: %2").arg(drFormatServerInfo(m_server), m_socket->errorString()); + qWarning().noquote() << l_error; + Q_EMIT socket_error(l_error); +} + +void DRServerSocket::_p_read_socket() +{ + m_buffer += QString::fromUtf8(m_socket->readAll()); + QStringList l_raw_packet_list = m_buffer.split("#%", DR::KeepEmptyParts); + m_buffer = l_raw_packet_list.takeLast(); + for (const QString &i_raw_packet : l_raw_packet_list) + { + QStringList l_raw_data_list = i_raw_packet.split("#"); + const QString l_header = l_raw_data_list.takeFirst(); + for (QString &i_raw_data : l_raw_data_list) + i_raw_data = DRPacket::decode(i_raw_data); + Q_EMIT packet_received(DRPacket(l_header, l_raw_data_list)); + } +} diff --git a/src/drserversocket.h b/src/drserversocket.h new file mode 100644 index 000000000..cb2193b00 --- /dev/null +++ b/src/drserversocket.h @@ -0,0 +1,53 @@ +#pragma once + +#include "datatypes.h" +#include "drpacket.h" + +#include +#include + +class QTcpSocket; +class QTimer; + +class DRServerSocket : public QObject +{ + Q_OBJECT + +public: + enum ConnectionState + { + NotConnected, + Connecting, + Connected, + }; + + DRServerSocket(QObject *parent = nullptr); + + bool is_connected() const; + +public slots: + void connect_to_server(DRServerInfo server); + + void disconnect_from_server(); + + void send_packet(DRPacket packet); + +signals: + void connection_state_changed(ConnectionState); + void packet_received(DRPacket); + void socket_error(QString); + +private: + static const int CONNECTING_DELAY; + + DRServerInfo m_server; + QTcpSocket *m_socket = nullptr; + QTimer *m_connecting_timeout = nullptr; + ConnectionState m_state = NotConnected; + QString m_buffer; + +private slots: + void _p_update_state(QAbstractSocket::SocketState); + void _p_check_socket_error(); + void _p_read_socket(); +}; diff --git a/src/drshoutmovie.cpp b/src/drshoutmovie.cpp new file mode 100644 index 000000000..fe194717e --- /dev/null +++ b/src/drshoutmovie.cpp @@ -0,0 +1,24 @@ +#include "drshoutmovie.h" + +#include "aoapplication.h" + +#include + +DRShoutMovie::DRShoutMovie(AOApplication *ao_app, QGraphicsItem *parent) + : DRMovie(parent) + , ao_app(ao_app) +{ + Q_ASSERT(ao_app); + set_play_once(true); + set_hide_on_done(true); +} + +DRShoutMovie::~DRShoutMovie() +{} + +void DRShoutMovie::play_interjection(QString p_character, QString p_shout) +{ + set_file_name(ao_app->get_shout_sprite_path(p_character, p_shout)); + set_play_once(true); + start(); +} diff --git a/src/drshoutmovie.h b/src/drshoutmovie.h new file mode 100644 index 000000000..50351fd6e --- /dev/null +++ b/src/drshoutmovie.h @@ -0,0 +1,19 @@ +#pragma once + +#include "drmovie.h" + +class AOApplication; + +class DRShoutMovie : public DRMovie +{ + Q_OBJECT + +public: + explicit DRShoutMovie(AOApplication *ao_app, QGraphicsItem *parent = nullptr); + ~DRShoutMovie(); + + void play_interjection(QString p_char_name, QString p_interjection_name); + +private: + AOApplication *ao_app = nullptr; +}; diff --git a/src/drsplashmovie.cpp b/src/drsplashmovie.cpp new file mode 100644 index 000000000..608df0544 --- /dev/null +++ b/src/drsplashmovie.cpp @@ -0,0 +1,25 @@ +#include "drsplashmovie.h" + +#include "aoapplication.h" + +DRSplashMovie::DRSplashMovie(AOApplication *ao_app, QGraphicsItem *parent) + : DRMovie(parent) + , ao_app(ao_app) +{ + set_play_once(true); + set_hide_on_done(true); +} + +DRSplashMovie::~DRSplashMovie() +{} + +void DRSplashMovie::play(QString p_file_name, QString p_character) +{ + set_file_name(ao_app->get_theme_sprite_path(p_file_name, p_character)); + start(); +} + +void DRSplashMovie::play(QString p_file_name) +{ + play(p_file_name, ""); +} diff --git a/src/drsplashmovie.h b/src/drsplashmovie.h new file mode 100644 index 000000000..9b68045f0 --- /dev/null +++ b/src/drsplashmovie.h @@ -0,0 +1,20 @@ +#pragma once + +#include "drmovie.h" + +class AOApplication; + +class DRSplashMovie : public DRMovie +{ + Q_OBJECT + +public: + explicit DRSplashMovie(AOApplication *ao_app, QGraphicsItem *parent = nullptr); + ~DRSplashMovie(); + + void play(QString p_file_name, QString p_character); + void play(QString p_file_name); + +private: + AOApplication *ao_app; +}; diff --git a/src/drstickerviewer.cpp b/src/drstickerviewer.cpp new file mode 100644 index 000000000..d866712fa --- /dev/null +++ b/src/drstickerviewer.cpp @@ -0,0 +1,41 @@ +#include "drstickerviewer.h" + +#include "aoapplication.h" + +DRStickerViewer::DRStickerViewer(AOApplication *ao_app, QWidget *parent) + : mk2::SpriteViewer(parent) + , ao_app(ao_app) + , m_hide_when_done(false) +{ + connect(this, SIGNAL(started()), this, SLOT(show())); + connect(this, SIGNAL(finished()), this, SLOT(maybe_hide())); +} + +DRStickerViewer::~DRStickerViewer() +{} + +void DRStickerViewer::set_hide_when_done(bool p_enabled) +{ + if (m_hide_when_done == p_enabled) + { + return; + } + m_hide_when_done = p_enabled; + if (!is_running()) + { + maybe_hide(); + } +} + +void DRStickerViewer::set_theme_image(QString p_file_name) +{ + set_file_name(ao_app->get_theme_sprite_path(p_file_name, "")); +} + +void DRStickerViewer::maybe_hide() +{ + if (m_hide_when_done) + { + hide(); + } +} diff --git a/src/drstickerviewer.h b/src/drstickerviewer.h new file mode 100644 index 000000000..72f10c7e4 --- /dev/null +++ b/src/drstickerviewer.h @@ -0,0 +1,28 @@ +#pragma once + +#include "mk2/spriteviewer.h" + +class AOApplication; + +class DRStickerViewer : public mk2::SpriteViewer +{ + Q_OBJECT + +public: + explicit DRStickerViewer(AOApplication *ao_app, QWidget *parent = nullptr); + ~DRStickerViewer(); + + void set_hide_when_done(bool enabled); + + void set_theme_image(QString p_file_name, QString p_character); + + void set_theme_image(QString p_file_name); + +private: + AOApplication *ao_app = nullptr; + + bool m_hide_when_done; + +private slots: + void maybe_hide(); +}; diff --git a/src/drtextedit.cpp b/src/drtextedit.cpp new file mode 100644 index 000000000..aa9729e11 --- /dev/null +++ b/src/drtextedit.cpp @@ -0,0 +1,121 @@ +#include "drtextedit.h" +#include "debug_functions.h" + +#include +#include + +DRTextEdit::DRTextEdit(QWidget *parent) + : QTextEdit(parent) +{ + connect(this, SIGNAL(textChanged()), this, SLOT(on_text_changed())); + connect(this, SIGNAL(text_alignment_changed(Qt::Alignment)), this, SLOT(on_text_changed())); +} + +void DRTextEdit::set_outline(bool p_enabled) +{ + if (has_outline == p_enabled) + return; + has_outline = p_enabled; + QTextCharFormat widget_format = currentCharFormat(); + if (p_enabled) + widget_format.setTextOutline(QPen(Qt::black, 1)); + else + widget_format.setTextOutline(Qt::NoPen); + setCurrentCharFormat(widget_format); +} + +void DRTextEdit::set_auto_align(bool p_enabled) +{ + if (is_auto_align == p_enabled) + return; + is_auto_align = p_enabled; +} + +void DRTextEdit::set_text_alignment(Qt::Alignment p_align) +{ + if (m_text_align == p_align) + return; + m_text_align = p_align; + Q_EMIT text_alignment_changed(m_text_align); +} + +void DRTextEdit::realign_text() +{ + if (m_status == Status::InProgress) + return; + m_status = Status::InProgress; + setAlignment(m_text_align); + // refresh_horizontal_alignment(); + refresh_vertical_alignment(); + m_status = Status::Done; +} + +Qt::Alignment DRTextEdit::get_text_alignment() const +{ + return m_text_align; +} + +void DRTextEdit::on_text_changed() +{ + if (!is_auto_align) + return; + realign_text(); +} + +void DRTextEdit::refresh_horizontal_alignment() +{ + // This stores the number of blocks. We use blocks here as Qt makes its horizontal + // alignment based on blocks (rather than apparent line breaks/places where words wrap). + int new_document_blocks = document()->blockCount(); + if (document()->toPlainText().isEmpty()) + { + // Qt is very special and does not set this to 0 for empty documents. + m_current_document_blocks = 0; + // We also don't need to do any adjusting for empty documents, so return immediately + return; + } + // If we have not changed the number of blocks in the document with this new incoming text change, + // We do not need to update anything, so we exit early. + if (new_document_blocks == m_current_document_blocks) + return; + + // Otherwise, we have changed the number of blocks. By induction only the current block needs to be + // updated, which is why we can get away with doing this setAlignment once, and right here. + // qDebug() << this << document()->toPlainText() << new_document_blocks; + setAlignment(m_text_align); +} + +void DRTextEdit::refresh_vertical_alignment() +{ + // This stores the total height of the totality of the text saved. + int new_document_height = document()->size().height(); + if (document()->toPlainText().isEmpty()) + { + // Qt is very special and does not set this to 0 for empty documents. + m_current_document_height = 0; + // We also don't need to do any adjusting for empty documents, so return immediately + return; + } + // If we have not changed the document height with this new incoming text change, + // We do not need to update anything, so we exit early. + if (new_document_height == m_current_document_height) + return; + + m_current_document_height = new_document_height; + + // The way we will simulate vertical alignment is by adjusting the top margin to simulate + // center alignment, or bottom alignment. + int top_margin = 0; + switch (m_text_align & (Qt::AlignVCenter | Qt::AlignBottom)) + { + case Qt::AlignVCenter: + top_margin = (height() - new_document_height) / 2; + break; + case Qt::AlignBottom: + top_margin = (height() - new_document_height); + break; + default: + break; + } + setViewportMargins(0, top_margin, 0, 0); +} diff --git a/src/drtextedit.h b/src/drtextedit.h new file mode 100644 index 000000000..7f5d9f0cc --- /dev/null +++ b/src/drtextedit.h @@ -0,0 +1,46 @@ +#ifndef DRTEXTEDIT_H +#define DRTEXTEDIT_H + +#include + +class DRTextEdit : public QTextEdit +{ + Q_OBJECT + + Q_PROPERTY(Qt::Alignment text_alignment READ get_text_alignment WRITE set_text_alignment NOTIFY text_alignment_changed) + +public: + DRTextEdit(QWidget *p_parent = nullptr); + + Qt::Alignment get_text_alignment() const; + +public slots: + void set_outline(bool enabled); + void set_auto_align(bool enabled); + void set_text_alignment(Qt::Alignment alignment); + void realign_text(); + +signals: + void text_alignment_changed(Qt::Alignment); + +private: + Qt::Alignment m_text_align = Qt::AlignTop | Qt::AlignLeft; + bool has_outline = false; + bool is_auto_align = true; + enum class Status + { + Done, + InProgress, + }; + Status m_status = Status::Done; + int m_current_document_blocks = 0; + int m_current_document_height = 0; + + void refresh_horizontal_alignment(); + void refresh_vertical_alignment(); + +private slots: + void on_text_changed(); +}; + +#endif // DRTEXTEDIT_H diff --git a/src/drthememovie.cpp b/src/drthememovie.cpp new file mode 100644 index 000000000..afedef80f --- /dev/null +++ b/src/drthememovie.cpp @@ -0,0 +1,17 @@ +#include "drthememovie.h" + +#include "aoapplication.h" + +DRThemeMovie::DRThemeMovie(AOApplication *app, QGraphicsItem *parent) + : DRMovie(parent) + , ao_app(app) +{} + +DRThemeMovie::~DRThemeMovie() +{} + +void DRThemeMovie::set_theme_image(QString p_image) +{ + set_file_name(ao_app->find_theme_asset_path(p_image)); + start(); +} diff --git a/src/drthememovie.h b/src/drthememovie.h new file mode 100644 index 000000000..7218b6cc2 --- /dev/null +++ b/src/drthememovie.h @@ -0,0 +1,20 @@ +#pragma once + +#include "drmovie.h" + +class AOApplication; + +class DRThemeMovie : public DRMovie +{ + Q_OBJECT + +public: + DRThemeMovie(AOApplication *app, QGraphicsItem *parent = nullptr); + ~DRThemeMovie(); + +public slots: + void set_theme_image(QString p_image); + +private: + AOApplication *ao_app; +}; diff --git a/src/emotes.cpp b/src/emotes.cpp new file mode 100644 index 000000000..16a56e31d --- /dev/null +++ b/src/emotes.cpp @@ -0,0 +1,289 @@ +#include "courtroom.h" + +#include "aoapplication.h" +#include "aobutton.h" +#include "aoconfig.h" +#include "aoemotebutton.h" +#include "aoimagedisplay.h" +#include "commondefs.h" +#include "drcharactermovie.h" +#include "drgraphicscene.h" +#include "theme.h" + +#include +#include +#include +#include +#include + +void Courtroom::construct_emotes() +{ + ui_emotes = new QWidget(this); + + ui_emote_left = new AOButton(this, ao_app); + ui_emote_right = new AOButton(this, ao_app); + + ui_emote_preview = new DRGraphicsView(nullptr); + ui_emote_preview->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint | Qt::BypassGraphicsProxyWidget); + ui_emote_preview->setAttribute(Qt::WA_TransparentForMouseEvents); + + { + auto *l_scene = ui_emote_preview->scene(); + + ui_emote_preview_background = new DRThemeMovie(ao_app); + l_scene->addItem(ui_emote_preview_background); + + ui_emote_preview_character = new DRCharacterMovie(ao_app); + l_scene->addItem(ui_emote_preview_character); + } + + ui_emote_dropdown = new QComboBox(this); + ui_pos_dropdown = new QComboBox(this); + ui_pos_dropdown->addItem("Default"); + ui_pos_dropdown->addItem("Witness", "wit"); + ui_pos_dropdown->addItem("Defense", "def"); + ui_pos_dropdown->addItem("Prosecutor", "pro"); + ui_pos_dropdown->addItem("Judge", "jud"); + ui_pos_dropdown->addItem("Defense Assistant", "hld"); + ui_pos_dropdown->addItem("Prosecutor Assistant", "hlp"); + + construct_emote_page_layout(); +} + +void Courtroom::construct_emote_page_layout() +{ + // delete previous buttons + while (!ui_emote_list.isEmpty()) + delete ui_emote_list.takeLast(); + + // resize and move + set_size_and_pos(ui_emotes, "emotes", COURTROOM_DESIGN_INI, ao_app); + + QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", COURTROOM_DESIGN_INI); + + const int button_width = 40; + int x_spacing = f_spacing.x(); + int x_mod_count = 0; + + const int button_height = 40; + int y_spacing = f_spacing.y(); + int y_mod_count = 0; + + emote_columns = ((ui_emotes->width() - button_width) / (x_spacing + button_width)) + 1; + emote_rows = ((ui_emotes->height() - button_height) / (y_spacing + button_height)) + 1; + + m_page_max_emote_count = qMax(1, emote_columns * emote_rows); + for (int n = 0; n < m_page_max_emote_count; ++n) + { + int x_pos = (button_width + x_spacing) * x_mod_count; + int y_pos = (button_height + y_spacing) * y_mod_count; + + AOEmoteButton *f_emote = new AOEmoteButton(ui_emotes, ao_app, x_pos, y_pos); + + ui_emote_list.append(f_emote); + + f_emote->set_emote_number(n); + + connect(f_emote, SIGNAL(emote_clicked(int)), this, SLOT(on_emote_clicked(int))); + connect(f_emote, SIGNAL(tooltip_requested(int, QPoint)), this, SLOT(show_emote_tooltip(int, QPoint))); + connect(f_emote, SIGNAL(mouse_left(int)), this, SLOT(hide_emote_tooltip(int))); + + ++x_mod_count; + + if (x_mod_count == emote_columns) + { + ++y_mod_count; + x_mod_count = 0; + } + } + + refresh_emote_page(true); +} + +void Courtroom::reset_emote_page() +{ + m_emote_id = 0; + m_current_emote_page = 0; + if (ui_emote_dropdown->count()) + ui_emote_dropdown->setCurrentIndex(m_emote_id); + refresh_emote_page(true); +} + +void Courtroom::refresh_emote_page(const bool p_scroll_to_current_emote) +{ + ui_emote_left->hide(); + ui_emote_right->hide(); + + for (AOEmoteButton *i_button : qAsConst(ui_emote_list)) + i_button->hide(); + hide_emote_tooltip(m_emote_preview_id); + + if (is_spectating()) + return; + + const int l_emote_count = m_emote_list.length(); + const int l_page_count = qFloor(l_emote_count / m_page_max_emote_count) + bool(l_emote_count % m_page_max_emote_count); + + if (p_scroll_to_current_emote) + m_current_emote_page = m_emote_id / m_page_max_emote_count; + m_current_emote_page = qBound(0, m_current_emote_page, l_page_count - 1); + + const int l_current_page_emote_count = qBound(0, l_emote_count - m_current_emote_page * m_page_max_emote_count, m_page_max_emote_count); + + if (m_current_emote_page + 1 < l_page_count) + ui_emote_right->show(); + + if (m_current_emote_page > 0) + ui_emote_left->show(); + + for (int i = 0; i < l_current_page_emote_count; ++i) + { + const int l_real_i = i + m_current_emote_page * m_page_max_emote_count; + AOEmoteButton *l_button = ui_emote_list.at(i); + l_button->set_image(get_emote(l_real_i), l_real_i == m_emote_id); + l_button->show(); + } +} + +void Courtroom::fill_emote_dropdown() +{ + QSignalBlocker l_blocker(ui_emote_dropdown); + ui_emote_dropdown->clear(); + + QStringList l_emote_list; + for (const DREmote &i_emote : qAsConst(m_emote_list)) + l_emote_list.append(i_emote.comment); + ui_emote_dropdown->addItems(l_emote_list); +} + +DREmote Courtroom::get_emote(const int p_emote_id) +{ + if (p_emote_id < 0 || p_emote_id >= m_emote_list.length()) + return DREmote(); + return m_emote_list.at(p_emote_id); +} + +DREmote Courtroom::get_current_emote() +{ + return get_emote(m_emote_id); +} + +void Courtroom::select_emote(int p_id) +{ + const int l_min = m_current_emote_page * m_page_max_emote_count; + const int l_max = (m_page_max_emote_count - 1) + m_current_emote_page * m_page_max_emote_count; + + const DREmote &l_prev_emote = get_emote(m_emote_id); + if (m_emote_id >= l_min && m_emote_id <= l_max) + { + AOEmoteButton *l_prev_button = ui_emote_list.at(m_emote_id % m_page_max_emote_count); + l_prev_button->set_image(l_prev_emote, false); + l_prev_button->repaint(); + } + + const int l_prev_emote_id = m_emote_id; + m_emote_id = p_id; + const DREmote &l_emote = get_emote(m_emote_id); + + if (m_emote_id >= l_min && m_emote_id <= l_max) + { + AOEmoteButton *l_new_button = ui_emote_list.at(m_emote_id % m_page_max_emote_count); + l_new_button->set_image(l_emote, true); + l_new_button->repaint(); + } + + const int emote_mod = l_emote.modifier; + if (l_prev_emote_id == m_emote_id) // toggle + ui_pre->setChecked(!ui_pre->isChecked()); + else + ui_pre->setChecked(emote_mod == 1 || ao_config->always_pre_enabled()); + + if (!ao_config->sticky_sfx_enabled()) + select_default_sfx(); + + ui_emote_dropdown->setCurrentIndex(m_emote_id); + + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_emote_clicked(int p_id) +{ + select_emote(p_id + m_page_max_emote_count * m_current_emote_page); +} + +void Courtroom::show_emote_tooltip(int p_id, QPoint p_global_pos) +{ + if (!ao_config->emote_preview_enabled()) + return; + + if (m_emote_preview_id != -1 || m_emote_preview_id == p_id) + return; + m_emote_preview_id = p_id; + const int l_real_id = p_id + m_page_max_emote_count * m_current_emote_page; + const DREmote &l_emote = m_emote_list.at(l_real_id); + ui_emote_preview_character->set_mirrored(ui_flip->isChecked()); + ui_emote_preview_character->play_idle(l_emote.character, l_emote.dialog); + + QScreen *screen = QApplication::screenAt(p_global_pos); + if (screen == nullptr) + { + return; + } + QRect l_screen_geometry = screen->geometry(); + + // position below cursor + const int l_vertical_spacing = 8; + QPoint l_final_global_pos(p_global_pos.x(), p_global_pos.y() + l_vertical_spacing); + + if (l_screen_geometry.width() < ui_emote_preview->width() + l_final_global_pos.x()) + { + l_final_global_pos.setX(p_global_pos.x() - ui_emote_preview->width()); + } + + if (l_screen_geometry.height() < ui_emote_preview->height() + l_final_global_pos.y()) + { + l_final_global_pos.setY(p_global_pos.y() - ui_emote_preview->height() - l_vertical_spacing); + } + + ui_emote_preview->move(l_final_global_pos); + ui_emote_preview->show(); +} + +void Courtroom::hide_emote_tooltip(int p_id) +{ + if (m_emote_preview_id == -1 || m_emote_preview_id != p_id) + return; + m_emote_preview_id = -1; + ui_emote_preview->hide(); + ui_emote_preview_character->set_file_name(nullptr); + ui_emote_preview_character->stop(); +} + +void Courtroom::on_emote_preview_toggled(bool p_enabled) +{ + if (!p_enabled) + hide_emote_tooltip(m_emote_preview_id); +} + +void Courtroom::on_emote_left_clicked() +{ + --m_current_emote_page; + + refresh_emote_page(); + + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_emote_right_clicked() +{ + ++m_current_emote_page; + + refresh_emote_page(); + + ui_ic_chat_message_field->setFocus(); +} + +void Courtroom::on_emote_dropdown_changed(int p_index) +{ + select_emote(p_index); +} diff --git a/src/file_functions.cpp b/src/file_functions.cpp new file mode 100644 index 000000000..09e218508 --- /dev/null +++ b/src/file_functions.cpp @@ -0,0 +1,37 @@ +#include "file_functions.h" + +#include +#include + +QStringList animated_extensions() +{ + return QStringList{".webp", ".apng", ".gif"}; +} + +QStringList static_extensions() +{ + return QStringList{".png"}; +} + +QStringList animated_or_static_extensions() +{ + return animated_extensions() + static_extensions(); +} + +QStringList audio_extensions(bool no_suffix) +{ + static QStringList s_ext_list{"", ".wav", ".ogg", ".opus", ".mp3"}; + return no_suffix ? s_ext_list.mid(1) : s_ext_list; +} + +bool file_exists(QString file_path) +{ + QFileInfo check_file(file_path); + return check_file.exists() && check_file.isFile(); +} + +bool dir_exists(QString dir_path) +{ + QDir check_dir(dir_path); + return check_dir.exists(); +} diff --git a/src/file_functions.h b/src/file_functions.h new file mode 100644 index 000000000..c06aa4744 --- /dev/null +++ b/src/file_functions.h @@ -0,0 +1,15 @@ +#ifndef FILE_FUNCTIONS_H +#define FILE_FUNCTIONS_H + +class QString; +class QStringList; + +QStringList animated_extensions(); +QStringList static_extensions(); +QStringList animated_or_static_extensions(); +QStringList audio_extensions(bool no_suffix = false); + +bool file_exists(QString file_path); +bool dir_exists(QString file_path); + +#endif // FILE_FUNCTIONS_H diff --git a/src/hardware_functions.cpp b/src/hardware_functions.cpp new file mode 100755 index 000000000..194fe5ea1 --- /dev/null +++ b/src/hardware_functions.cpp @@ -0,0 +1,81 @@ +#include "hardware_functions.h" + +#include + +#ifdef Q_OS_WINDOWS +#include + +DWORD dwVolSerial; +BOOL bIsRetrieved; + +QString get_hdid() +{ + bIsRetrieved = GetVolumeInformation(TEXT("C:\\"), NULL, 0, &dwVolSerial, NULL, NULL, NULL, 0); + + if (bIsRetrieved) + return QString::number(dwVolSerial, 16); + else + // a totally random string + // what could possibly go wrong + return "gxsps32sa9fnwic92mfbs0"; +} +#elif defined(Q_OS_LINUX) +#include +#include + +QString get_hdid() +{ + QFile fstab_file("/etc/fstab"); + if (!fstab_file.open(QIODevice::ReadOnly)) + return "gxcps32sa9fnwic92mfbs0"; + + QTextStream in(&fstab_file); + + while (!in.atEnd()) + { + QString line = in.readLine(); + int i = line.indexOf(QRegExp("UUID|uuid"), 0); + if (i >= 0) + { + line = line.chopped(i); + QStringList line_elements = line.split("="); + + if (line_elements.size() > 1) + return line_elements.at(1).left(23).trimmed(); + } + } + + return "gxcpz32sa9fnwic92mfbs0"; +} +#elif defined(Q_OS_MACOS) +#include +#include + +QString get_hdid() +{ + // This code is from AO. + CFStringRef serial; + char buffer[64] = {0}; + QString hdid; + io_service_t platformExpert = + IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); + if (platformExpert) + { + CFTypeRef serialNumberAsCFString = + IORegistryEntryCreateCFProperty(platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0); + if (serialNumberAsCFString) + { + serial = (CFStringRef)serialNumberAsCFString; + } + if (CFStringGetCString(serial, buffer, 64, kCFStringEncodingUTF8)) + { + hdid = buffer; + } + + IOObjectRelease(platformExpert); + } + return hdid; +} +#else +#error This operating system is unsupported for hardware functions. +#endif diff --git a/hardware_functions.h b/src/hardware_functions.h similarity index 74% rename from hardware_functions.h rename to src/hardware_functions.h index 3fcb27cb1..d3cf5a17d 100644 --- a/hardware_functions.h +++ b/src/hardware_functions.h @@ -1,9 +1,7 @@ #ifndef HARDWARE_FUNCTIONS_H #define HARDWARE_FUNCTIONS_H -#include - -#include +class QString; QString get_hdid(); diff --git a/src/lobby.cpp b/src/lobby.cpp new file mode 100644 index 000000000..3cc9b9867 --- /dev/null +++ b/src/lobby.cpp @@ -0,0 +1,724 @@ +#include "lobby.h" + +#include "aoapplication.h" +#include "aobutton.h" +#include "aoconfig.h" +#include "aoimagedisplay.h" +#include "commondefs.h" +#include "datatypes.h" +#include "debug_functions.h" +#include "drchatlog.h" +#include "drmasterclient.h" +#include "drpacket.h" +#include "drserverinfoeditor.h" +#include "drtextedit.h" +#include "theme.h" +#include "version.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +Lobby::Lobby(AOApplication *p_ao_app) + : QMainWindow() +{ + ao_app = p_ao_app; + ao_config = new AOConfig(this); + m_master_client = new DRMasterClient(this); + + setWindowTitle("Danganronpa Online (" + get_version_string() + ")"); + + ui_background = new AOImageDisplay(this, ao_app); + ui_public_server_filter = new AOButton(this, ao_app); + ui_favorite_server_filter = new AOButton(this, ao_app); + ui_refresh = new AOButton(this, ao_app); + ui_toggle_favorite = new AOButton(this, ao_app); + ui_connect = new AOButton(this, ao_app); + ui_version = new DRTextEdit(this); + ui_version->setFrameStyle(QFrame::NoFrame); + ui_version->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_version->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_version->setReadOnly(true); + ui_config_panel = new AOButton(this, ao_app); + ui_server_list = new QListWidget(this); + ui_server_list->setContextMenuPolicy(Qt::CustomContextMenu); + + ui_server_menu = new QMenu(this); + ui_server_menu->addSection(tr("Server")); + ui_create_server = ui_server_menu->addAction(tr("Add")); + ui_modify_server = ui_server_menu->addAction(tr("Edit")); + ui_move_up_server = ui_server_menu->addAction(tr("Move up")); + ui_move_down_server = ui_server_menu->addAction(tr("Move down")); + ui_delete_server = ui_server_menu->addAction(tr("Remove")); + + ui_player_count = new DRTextEdit(this); + ui_player_count->setFrameStyle(QFrame::NoFrame); + ui_player_count->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_player_count->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui_player_count->setWordWrapMode(QTextOption::NoWrap); + ui_player_count->setReadOnly(true); + ui_description = new QTextBrowser(this); + ui_description->setOpenExternalLinks(true); + ui_description->setReadOnly(true); + ui_chatbox = new DRChatLog(this); + ui_chatbox->setOpenExternalLinks(true); + ui_chatbox->setReadOnly(true); + ui_loading_background = new AOImageDisplay(this, ao_app); + ui_loading_text = new DRTextEdit(ui_loading_background); + ui_progress_bar = new QProgressBar(ui_loading_background); + ui_progress_bar->setMinimum(0); + ui_progress_bar->setMaximum(100); + ui_progress_bar->setStyleSheet("QProgressBar{ color: white; }"); + ui_cancel = new AOButton(ui_loading_background, ao_app); + + connect(ao_app, SIGNAL(reload_theme()), this, SLOT(update_widgets())); + connect(ao_app, &AOApplication::server_status_changed, this, &Lobby::_p_update_description); + + connect(ao_config, SIGNAL(theme_changed(QString)), this, SLOT(update_widgets())); + connect(ao_config, SIGNAL(server_advertiser_changed(QString)), m_master_client, SLOT(set_address(QString))); + + connect(m_master_client, SIGNAL(address_changed()), this, SLOT(request_advertiser_update())); + connect(m_master_client, SIGNAL(motd_changed()), this, SLOT(update_motd())); + connect(m_master_client, SIGNAL(server_list_changed()), this, SLOT(update_server_list())); + + connect(ui_public_server_filter, SIGNAL(clicked()), this, SLOT(toggle_public_server_filter())); + + connect(ui_favorite_server_filter, SIGNAL(clicked()), this, SLOT(toggle_favorite_server_filter())); + + connect(ui_refresh, SIGNAL(pressed()), this, SLOT(on_refresh_pressed())); + connect(ui_refresh, SIGNAL(released()), this, SLOT(on_refresh_released())); + + connect(ui_toggle_favorite, SIGNAL(pressed()), this, SLOT(on_add_to_fav_pressed())); + connect(ui_toggle_favorite, SIGNAL(released()), this, SLOT(on_add_to_fav_released())); + + connect(ui_connect, SIGNAL(pressed()), this, SLOT(on_connect_pressed())); + connect(ui_connect, SIGNAL(released()), this, SLOT(on_connect_released())); + + connect(ui_config_panel, SIGNAL(pressed()), this, SLOT(on_config_pressed())); + connect(ui_config_panel, SIGNAL(released()), this, SLOT(on_config_released())); + + connect(ui_server_list, SIGNAL(currentRowChanged(int)), this, SLOT(connect_to_server(int))); + connect(ui_server_list, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(show_server_context_menu(QPoint))); + + connect(ui_create_server, SIGNAL(triggered(bool)), this, SLOT(create_server_info())); + connect(ui_modify_server, SIGNAL(triggered(bool)), this, SLOT(modify_server_info())); + connect(ui_delete_server, SIGNAL(triggered(bool)), this, SLOT(prompt_delete_server())); + connect(ui_move_up_server, SIGNAL(triggered(bool)), this, SLOT(move_up_server())); + connect(ui_move_down_server, SIGNAL(triggered(bool)), this, SLOT(move_down_server())); + + connect(ui_cancel, SIGNAL(clicked()), ao_app, SLOT(loading_cancelled())); + + load_settings(); + load_favorite_server_list(); + update_widgets(); + m_master_client->set_address(ao_config->server_advertiser()); + set_choose_a_server(); +} + +Lobby::~Lobby() +{ + save_settings(); +} + +DRServerInfoList Lobby::get_combined_server_list() +{ + return m_combined_server_list; +} + +// sets images, position and size +void Lobby::update_widgets() +{ + pos_size_type f_lobby = ao_app->get_element_dimensions("lobby", LOBBY_DESIGN_INI); + if (f_lobby.width < 0 || f_lobby.height < 0) + { + qWarning() << "W: did not find lobby width or height in " << LOBBY_DESIGN_INI; + f_lobby.width = 517; + f_lobby.height = 666; + } + setWindowState(Qt::WindowNoState); + resize(f_lobby.width, f_lobby.height); + center_widget_to_screen(this); + + set_size_and_pos(ui_background, "lobby", LOBBY_DESIGN_INI, ao_app); + ui_background->set_theme_image("lobbybackground.png"); + + set_size_and_pos(ui_public_server_filter, "public_servers", LOBBY_DESIGN_INI, ao_app); + ui_public_server_filter->set_image(m_server_filter == PublicOnly ? "publicservers_selected.png" : "publicservers.png"); + + set_size_and_pos(ui_favorite_server_filter, "favorites", LOBBY_DESIGN_INI, ao_app); + ui_favorite_server_filter->set_image(m_server_filter == FavoriteOnly ? "favorites_selected.png" : "favorites.png"); + + set_size_and_pos(ui_refresh, "refresh", LOBBY_DESIGN_INI, ao_app); + ui_refresh->set_image("refresh.png"); + + set_size_and_pos(ui_toggle_favorite, "add_to_fav", LOBBY_DESIGN_INI, ao_app); + ui_toggle_favorite->set_image("addtofav.png"); + + set_size_and_pos(ui_connect, "connect", LOBBY_DESIGN_INI, ao_app); + ui_connect->set_image("connect.png"); + + set_size_and_pos(ui_version, "version", LOBBY_DESIGN_INI, ao_app); + ui_version->setText("Version: " + get_version_string()); + + set_size_and_pos(ui_config_panel, "config_panel", LOBBY_DESIGN_INI, ao_app); + ui_config_panel->set_image_and_text("lobby_config_panel.png", "Config"); + if (ui_config_panel->isHidden() || ui_config_panel->size().isEmpty()) + { + ui_config_panel->resize(64, 64); + ui_config_panel->move(0, 0); + ui_config_panel->show(); + } + + set_size_and_pos(ui_server_list, "server_list", LOBBY_DESIGN_INI, ao_app); + ui_server_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + "font: bold;"); + + set_size_and_pos(ui_player_count, "player_count", LOBBY_DESIGN_INI, ao_app); + ui_player_count->setStyleSheet("font: bold;" + "color: white;" + "qproperty-alignment: AlignCenter;"); + + set_size_and_pos(ui_description, "description", LOBBY_DESIGN_INI, ao_app); + ui_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + "color: white;"); + + set_size_and_pos(ui_chatbox, "chatbox", LOBBY_DESIGN_INI, ao_app); + ui_chatbox->setReadOnly(true); + ui_chatbox->setStyleSheet("QTextBrowser{background-color: rgba(0, 0, 0, 0);}"); + + ui_loading_background->resize(this->width(), this->height()); + ui_loading_background->set_theme_image("loadingbackground.png"); + + set_size_and_pos(ui_loading_text, "loading_label", LOBBY_DESIGN_INI, ao_app); + ui_loading_text->setFont(QFont("Arial", 20, QFont::Bold)); + ui_loading_text->setReadOnly(true); + ui_loading_text->setAlignment(Qt::AlignCenter); + ui_loading_text->setFrameStyle(QFrame::NoFrame); + ui_loading_text->setStyleSheet("background-color: rgba(0, 0, 0, 0);" + "color: rgba(255, 128, 0, 255);"); + ui_loading_text->append("Loading"); + + set_size_and_pos(ui_progress_bar, "progress_bar", LOBBY_DESIGN_INI, ao_app); + set_size_and_pos(ui_cancel, "cancel", LOBBY_DESIGN_INI, ao_app); + ui_cancel->setText("Cancel"); + + ui_loading_background->hide(); + + set_fonts(); + set_stylesheets(); + update_server_listing(); + + update_server_filter_buttons(); +} + +void Lobby::set_fonts() +{ + set_drtextedit_font(ui_player_count, "player_count", LOBBY_FONTS_INI, ao_app); + set_font(ui_description, "description", LOBBY_FONTS_INI, ao_app); + set_font(ui_chatbox, "chatbox", LOBBY_FONTS_INI, ao_app); + set_drtextedit_font(ui_loading_text, "loading_text", LOBBY_FONTS_INI, ao_app); + set_font(ui_server_list, "server_list", LOBBY_FONTS_INI, ao_app); +} + +void Lobby::set_stylesheet(QWidget *widget, QString target_tag) +{ + QString f_file = "lobby_stylesheets.css"; + QString style_sheet_string = ao_app->get_stylesheet(target_tag, f_file); + if (style_sheet_string != "") + widget->setStyleSheet(style_sheet_string); +} + +void Lobby::set_stylesheets() +{ + set_stylesheet(ui_player_count, "[PLAYER COUNT]"); + set_stylesheet(ui_description, "[DESCRIPTION]"); + set_stylesheet(ui_chatbox, "[CHAT BOX]"); + set_stylesheet(ui_loading_text, "[LOADING TEXT]"); + set_stylesheet(ui_server_list, "[SERVER LIST]"); +} + +void Lobby::show_loading_overlay() +{ + ui_loading_background->show(); +} + +void Lobby::hide_loading_overlay() +{ + ui_loading_background->hide(); +} + +void Lobby::set_loading_text(QString p_text) +{ + ui_loading_text->clear(); + ui_loading_text->setAlignment(Qt::AlignCenter); + ui_loading_text->append(p_text); +} + +DRServerInfo Lobby::get_selected_server() +{ + return m_current_server; +} + +void Lobby::set_loading_value(int p_value) +{ + ui_progress_bar->setValue(p_value); +} + +void Lobby::load_settings() +{ + QSettings l_ini(ao_app->get_base_file_path(BASE_SERVER_BROWSER_INI), QSettings::IniFormat); + l_ini.setIniCodec("UTF-8"); + + l_ini.beginGroup("filters"); + m_server_filter = ServerFilter(l_ini.value("server_filter", NoFilter).toInt()); + l_ini.endGroup(); +} + +void Lobby::save_settings() +{ + QSettings l_ini(ao_app->get_base_file_path(BASE_SERVER_BROWSER_INI), QSettings::IniFormat); + l_ini.setIniCodec("UTF-8"); + + l_ini.beginGroup("filters"); + l_ini.setValue("server_filter", int(m_server_filter)); + l_ini.endGroup(); + l_ini.sync(); +} + +void Lobby::load_favorite_server_list() +{ + const QString l_file_path = ao_app->find_asset_path(ao_app->get_base_file_path(BASE_FAVORITE_SERVERS_INI)); + if (l_file_path.isEmpty()) + { + load_legacy_favorite_server_list(); + return; + } + + DRServerInfoList l_server_list; + QSettings l_ini(l_file_path, QSettings::IniFormat); + l_ini.setIniCodec("UTF-8"); + l_server_list.clear(); + QStringList l_group_list = l_ini.childGroups(); + + { + QCollator l_sorter; + l_sorter.setNumericMode(true); + std::sort(l_group_list.begin(), l_group_list.end(), l_sorter); + } + + for (const QString &i_group : qAsConst(l_group_list)) + { + l_ini.beginGroup(i_group); + DRServerInfo l_server; + l_server.name = l_ini.value("name").toString(); + l_server.description = l_ini.value("description").toString(); + l_server.address = l_ini.value("address").toString(); + l_server.port = l_ini.value("port").toInt(); + l_server_list.append(std::move(l_server)); + l_ini.endGroup(); + } + set_favorite_server_list(l_server_list); +} + +void Lobby::load_legacy_favorite_server_list() +{ + DRServerInfoList l_server_list; + QFile l_file(ao_app->get_base_file_path(BASE_SERVERLIST_TXT)); + if (l_file.open(QIODevice::ReadOnly)) + { + QTextStream in(&l_file); + while (!in.atEnd()) + { + const QStringList l_contents = in.readLine().split(":"); + if (l_contents.length() < 3) + continue; + DRServerInfo f_server; + f_server.address = l_contents.at(0); + f_server.port = l_contents.at(1).toInt(); + f_server.name = l_contents.at(2); + l_server_list.append(std::move(f_server)); + } + l_file.remove(); + } + set_favorite_server_list(l_server_list); +} + +void Lobby::save_favorite_server_list() +{ + QSettings l_ini(ao_app->get_base_file_path(BASE_FAVORITE_SERVERS_INI), QSettings::IniFormat); + l_ini.setIniCodec("UTF-8"); + + l_ini.clear(); + for (int i = 0; i < m_favorite_server_list.length(); ++i) + { + const DRServerInfo &i_server = m_favorite_server_list.at(i); + l_ini.beginGroup(QString::number(i)); + l_ini.setValue("name", i_server.name); + l_ini.setValue("description", i_server.description); + l_ini.setValue("address", i_server.address); + l_ini.setValue("port", i_server.port); + l_ini.endGroup(); + } + l_ini.sync(); +} + +void Lobby::request_advertiser_update() +{ + m_master_client->request_motd(); + m_master_client->request_server_list(); +} + +void Lobby::update_motd() +{ + ui_chatbox->append_html(m_master_client->motd()); +} + +void Lobby::update_server_list() +{ + m_server_list = m_master_client->server_list(); + update_combined_server_list(); + emit server_list_changed(); +} + +void Lobby::set_favorite_server_list(DRServerInfoList p_server_list) +{ + m_favorite_server_list = p_server_list; + save_favorite_server_list(); + update_combined_server_list(); + emit favorite_server_list_changed(); +} + +void Lobby::update_combined_server_list() +{ + m_combined_server_list = m_favorite_server_list + m_server_list; + update_server_listing(); +} + +void Lobby::update_server_listing() +{ + ui_server_list->clear(); + const QIcon l_favorite_icon = QPixmap(ao_app->find_theme_asset_path("favorite_server.png")); + const QBrush l_favorite_color = ao_app->get_color("favorite_server_color", LOBBY_DESIGN_INI); + for (int i = 0; i < m_combined_server_list.length(); ++i) + { + const DRServerInfo &l_server = m_combined_server_list.at(i); + QListWidgetItem *l_server_item = new QListWidgetItem; + ui_server_list->addItem(l_server_item); + l_server_item->setText(l_server.name); + l_server_item->setData(Qt::UserRole, false); + if (i < m_favorite_server_list.length()) + { + l_server_item->setIcon(l_favorite_icon); + l_server_item->setBackground(l_favorite_color); + l_server_item->setData(Qt::UserRole, true); + } + } + filter_server_listing(); +} + +void Lobby::filter_server_listing() +{ + for (int i = 0; i < ui_server_list->count(); ++i) + { + QListWidgetItem *l_server_item = ui_server_list->item(i); + l_server_item->setHidden(m_server_filter == (l_server_item->data(Qt::UserRole).toBool() ? PublicOnly : FavoriteOnly)); + } + select_current_server(); +} + +void Lobby::select_current_server() +{ + if (m_current_server.name.isEmpty()) + { + return; + } + + for (int i = 0; i < ui_server_list->count(); ++i) + { + QListWidgetItem *l_item = ui_server_list->item(i); + if (l_item->text() == m_current_server.name) + { + ui_server_list->scrollToItem(l_item); + ui_server_list->setCurrentItem(l_item); + ui_server_list->setFocus(); + break; + } + } +} + +void Lobby::toggle_public_server_filter() +{ + m_server_filter = m_server_filter == PublicOnly ? NoFilter : PublicOnly; + update_server_filter_buttons(); +} + +void Lobby::toggle_favorite_server_filter() +{ + m_server_filter = m_server_filter == FavoriteOnly ? NoFilter : FavoriteOnly; + update_server_filter_buttons(); +} + +void Lobby::update_server_filter_buttons() +{ + ui_public_server_filter->set_image(m_server_filter == PublicOnly ? "publicservers_selected.png" : "publicservers.png"); + ui_favorite_server_filter->set_image(m_server_filter == FavoriteOnly ? "favorites_selected.png" : "favorites.png"); + filter_server_listing(); +} + +void Lobby::on_refresh_pressed() +{ + ui_refresh->set_image("refresh_pressed.png"); +} + +void Lobby::on_refresh_released() +{ + ui_refresh->set_image("refresh.png"); + m_master_client->request_server_list(); + load_favorite_server_list(); +} + +void Lobby::on_add_to_fav_pressed() +{ + ui_toggle_favorite->set_image("addtofav_pressed.png"); +} + +void Lobby::on_add_to_fav_released() +{ + ui_toggle_favorite->set_image("addtofav.png"); + const auto l_index = ui_server_list->currentIndex(); + if (!l_index.isValid() || l_index.row() < m_favorite_server_list.length()) + { + return; + } + const auto l_selected_server = m_combined_server_list.at(l_index.row()); + DRServerInfoList l_server_list = m_favorite_server_list; + if (m_favorite_server_list.contains(l_selected_server)) + { + l_server_list.removeAll(m_current_server); + } + else + { + l_server_list.append(m_current_server); + } + set_favorite_server_list(l_server_list); +} + +void Lobby::on_connect_pressed() +{ + ui_connect->set_image("connect_pressed.png"); +} + +void Lobby::on_connect_released() +{ + const VersionStatus l_status = ao_app->get_server_client_version_status(); + if (l_status != VersionStatus::Ok) + { + QString l_reason; + switch (l_status) + { + case VersionStatus::NotCompatible: + l_reason = "The server did not report any client version."; + break; + case VersionStatus::ServerOutdated: + l_reason = QString("The server is outdated.
(Server version: %1, expected version: %2)").arg(ao_app->get_server_client_version().to_string(), get_version_number().to_string()); + break; + case VersionStatus::ClientOutdated: + l_reason = QString("Your client is outdated.
(Client version: %1, expected version: %2)").arg(get_version_number().to_string(), ao_app->get_server_client_version().to_string()); + break; + default: + break; + } + + call_warning("You are connecting to an incompatible DRO server.

Reason: " + l_reason + + "

" + "The client may not work properly, if at all."); + } + + ui_connect->set_image("connect.png"); + ao_app->send_server_packet(DRPacket("askchaa")); +} + +void Lobby::on_config_pressed() +{ + ui_config_panel->set_image("lobby_config_panel_pressed.png"); +} + +void Lobby::on_config_released() +{ + ui_config_panel->set_image("lobby_config_panel.png"); + ao_app->toggle_config_panel(); +} + +void Lobby::connect_to_server(int p_row) +{ + if (p_row == -1) + return; + + const DRServerInfo l_prev_server = std::move(m_current_server); + m_current_server = m_combined_server_list.at(p_row); + if (l_prev_server != m_current_server) + { + ui_player_count->setText(nullptr); + ao_app->connect_to_server(m_current_server); + } +} + +void Lobby::show_server_context_menu(QPoint p_point) +{ + const QPoint l_global_point = ui_server_list->viewport()->mapToGlobal(p_point); + + m_server_index.reset(); + m_server_index_type = NoServerType; + const auto l_item = ui_server_list->indexAt(p_point); + ui_create_server->setEnabled(true); + ui_modify_server->setDisabled(true); + ui_delete_server->setDisabled(true); + ui_move_up_server->setDisabled(true); + ui_move_down_server->setDisabled(true); + if (l_item.isValid()) + { + const int l_item_row = l_item.row(); + m_server_index = l_item_row; + if (l_item_row < m_favorite_server_list.length()) + { + m_server_index_type = FavoriteServer; + ui_modify_server->setEnabled(true); + ui_delete_server->setEnabled(true); + ui_move_up_server->setEnabled(l_item_row - 1 >= 0); + ui_move_down_server->setEnabled(l_item_row + 1 < m_favorite_server_list.length()); + } + } + ui_server_menu->popup(l_global_point); +} + +void Lobby::prompt_server_info_editor() +{ + DRServerInfoEditor l_editor(this); + l_editor.setWindowTitle(tr("Server Info Editor")); + if (m_server_index.has_value()) + { + l_editor.set_server_info(m_combined_server_list.at(m_server_index.value())); + } + if (l_editor.exec()) + { + auto l_server_info = l_editor.get_server_info(); + auto l_server_info_list = m_favorite_server_list; + if (m_server_index.has_value() && m_server_index.value() < l_server_info_list.length()) + { + l_server_info_list.replace(m_server_index.value(), l_server_info); + } + else + { + l_server_info_list.append(l_server_info); + } + set_favorite_server_list(l_server_info_list); + } +} + +void Lobby::create_server_info() +{ + if (m_server_index_type == FavoriteServer) + { + m_server_index.reset(); + } + prompt_server_info_editor(); +} + +void Lobby::modify_server_info() +{ + prompt_server_info_editor(); +} + +void Lobby::prompt_delete_server() +{ + const auto l_server_index = m_server_index.value(); + const auto l_server = m_combined_server_list.at(l_server_index); + if (prompt_warning(tr("Are you sure you wish to remove the server %1?").arg(l_server.name))) + { + auto l_server_list = m_favorite_server_list; + l_server_list.remove(l_server_index); + set_favorite_server_list(l_server_list); + } +} + +void Lobby::move_up_server() +{ + auto l_server_list = m_favorite_server_list; + const int l_server_index = m_server_index.value(); + std::swap(l_server_list.begin()[l_server_index], l_server_list.begin()[l_server_index - 1]); + set_favorite_server_list(l_server_list); +} + +void Lobby::move_down_server() +{ + auto l_server_list = m_favorite_server_list; + const int l_server_index = m_server_index.value(); + std::swap(l_server_list.begin()[l_server_index], l_server_list.begin()[l_server_index + 1]); + set_favorite_server_list(l_server_list); +} + +void Lobby::_p_update_description() +{ + QMap l_report_map{ + {AOApplication::NotConnected, tr("Choose a server.")}, + {AOApplication::Connecting, tr("Connecting to server.")}, + {AOApplication::Connected, tr("Connected to server.")}, + {AOApplication::Joined, tr("Joined server.")}, + {AOApplication::TimedOut, tr("Failed to connect to server.")}, + {AOApplication::Disconnected, tr("Choose a server.")}, + }; + + QString l_message = l_report_map[ao_app->last_server_status()]; + + if (!m_current_server.name.isEmpty()) + { + l_message = QString("%1\n\n" + "==== STATUS ====\n" + "%2") + .arg(m_current_server.name.toHtmlEscaped()) + .arg(l_message); + } + + if (!m_current_server.description.isEmpty()) + { + QString l_description = m_current_server.description.toHtmlEscaped(); + const QRegExp l_regex("(https?://[^\\s/$.?#].[^\\s]*)"); + if (l_description.contains(l_regex)) + { + l_description.replace(l_regex, "\\1"); + } + l_message = QString("%1\n\n" + "==== DESCRIPTION ====\n" + "%2") + .arg(l_description); + } + + ui_description->setHtml(l_message.replace("\n", "
")); +} + +void Lobby::set_choose_a_server() +{ + ui_player_count->setText(nullptr); + _p_update_description(); +} + +void Lobby::set_player_count(int players_online, int max_players) +{ + const QString f_string = "Connected: " + QString::number(players_online) + "/" + QString::number(max_players); + ui_player_count->setText(f_string); + ui_player_count->setAlignment(Qt::AlignHCenter); +} diff --git a/src/lobby.h b/src/lobby.h new file mode 100644 index 000000000..903c4860f --- /dev/null +++ b/src/lobby.h @@ -0,0 +1,142 @@ +#ifndef LOBBY_H +#define LOBBY_H + +#include "datatypes.h" + +#include +#include + +#include + +class AOApplication; +class AOButton; +class AOConfig; +class AOImageDisplay; +class DRChatLog; +class DRMasterClient; +class DRTextEdit; + +class QListWidget; +class QLineEdit; +class QProgressBar; +class QTextBrowser; +class Lobby : public QMainWindow +{ + Q_OBJECT + +public: + Lobby(AOApplication *p_ao_app); + ~Lobby(); + + DRServerInfoList get_combined_server_list(); + + void set_choose_a_server(); + void set_player_count(int players_online, int max_players); + void set_loading_text(QString p_text); + void set_stylesheet(QWidget *widget, QString target_tag); + void set_stylesheets(); + void set_fonts(); + void show_loading_overlay(); + void hide_loading_overlay(); + DRServerInfo get_selected_server(); + void set_loading_value(int p_value); + +signals: + void server_list_changed(); + void favorite_server_list_changed(); + void combined_server_list_changed(); + +private: + AOApplication *ao_app = nullptr; + AOConfig *ao_config = nullptr; + + DRMasterClient *m_master_client = nullptr; + DRServerInfoList m_server_list; + DRServerInfoList m_favorite_server_list; + DRServerInfoList m_combined_server_list; + DRServerInfo m_current_server; + + // ui + AOImageDisplay *ui_background = nullptr; + AOButton *ui_public_server_filter = nullptr; + AOButton *ui_favorite_server_filter = nullptr; + enum ServerFilter + { + NoFilter, + PublicOnly, + FavoriteOnly, + }; + ServerFilter m_server_filter = NoFilter; + AOButton *ui_refresh = nullptr; + AOButton *ui_toggle_favorite = nullptr; + AOButton *ui_connect = nullptr; + DRTextEdit *ui_version = nullptr; + AOButton *ui_config_panel = nullptr; + QListWidget *ui_server_list = nullptr; + DRTextEdit *ui_player_count = nullptr; + QTextBrowser *ui_description = nullptr; + DRChatLog *ui_chatbox = nullptr; + AOImageDisplay *ui_loading_background = nullptr; + DRTextEdit *ui_loading_text = nullptr; + QProgressBar *ui_progress_bar = nullptr; + AOButton *ui_cancel = nullptr; + + QMenu *ui_server_menu; + std::optional m_server_index; + enum ServerType + { + NoServerType, + FavoriteServer, + }; + ServerType m_server_index_type = NoServerType; + QAction *ui_create_server; + QAction *ui_modify_server; + QAction *ui_delete_server; + QMenu *ui_swap_with; + QAction *ui_move_up_server; + QAction *ui_move_down_server; + + void load_settings(); + void save_settings(); + + void load_favorite_server_list(); + void load_legacy_favorite_server_list(); + void save_favorite_server_list(); + +private slots: + void update_widgets(); + + void request_advertiser_update(); + void update_motd(); + void update_server_list(); + void set_favorite_server_list(DRServerInfoList server_list); + void update_combined_server_list(); + void toggle_public_server_filter(); + void toggle_favorite_server_filter(); + void update_server_filter_buttons(); + void update_server_listing(); + void filter_server_listing(); + void select_current_server(); + + void on_refresh_pressed(); + void on_refresh_released(); + void on_add_to_fav_pressed(); + void on_add_to_fav_released(); + void on_connect_pressed(); + void on_connect_released(); + void on_config_pressed(); + void on_config_released(); + void connect_to_server(int row); + + void show_server_context_menu(QPoint); + void prompt_server_info_editor(); + void create_server_info(); + void modify_server_info(); + void prompt_delete_server(); + void move_up_server(); + void move_down_server(); + + void _p_update_description(); +}; + +#endif // LOBBY_H diff --git a/src/logger.cpp b/src/logger.cpp new file mode 100644 index 000000000..91089cc52 --- /dev/null +++ b/src/logger.cpp @@ -0,0 +1,175 @@ +#include "logger.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// Get the default Qt message handler. +static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(0); +static const int C_MAX_FILE_SIZE = 1e7; // 10 MB +static const QString C_FILE_NAME = "base/logs/debug.log"; +static const QString C_FILE_NAME_BACKUP = "base/logs/debug_b.log"; +static bool s_verbose_logging = false; + +static bool s_initialized = false; +static QMutex s_msg_list_lock; +static QStringList s_msg_list; +static std::thread s_logger; +static std::atomic_bool s_continue = false; + +void check_file_size_and_rename() +{ + QFile l_log(C_FILE_NAME); + if (l_log.exists() && l_log.size() >= C_MAX_FILE_SIZE) + { + if (l_log.exists(C_FILE_NAME_BACKUP)) + { + if (!l_log.remove(C_FILE_NAME_BACKUP)) + { + s_continue = false; + qInstallMessageHandler(0); + qWarning() << "error: failed to remove log file" << l_log.errorString(); + return; + } + } + + if (!l_log.rename(C_FILE_NAME_BACKUP)) + { + s_continue = false; + qInstallMessageHandler(0); + qWarning() << "error: failed to rename log file" << l_log.errorString(); + } + } +} + +void task() +{ + s_continue = true; + QSystemSemaphore l_system_lock("dro-logger-lock", 1); + QStringList l_msg_list; + while (s_continue) + { + if (s_msg_list_lock.tryLock()) + { + QStringList l_msg_list(std::move(s_msg_list)); + s_msg_list_lock.unlock(); + + if (!l_msg_list.isEmpty()) + { + l_system_lock.acquire(); + check_file_size_and_rename(); + QFile l_log(C_FILE_NAME); + if (l_log.open(QFile::WriteOnly | QFile::Append)) + { + QTextStream in(&l_log); + for (const QString &i_msg : l_msg_list) + { + in << i_msg +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + << Qt::endl; +#else + << "\n"; +#endif + } + } + l_log.close(); + l_system_lock.release(); + } + } + + QThread::msleep(10); + } +} + +QString generate_message(QtMsgType p_type, const QMessageLogContext &p_context, const QString &p_message) +{ + QString l_output; + switch (p_type) + { + case QtDebugMsg: + l_output = QString("D"); + break; + case QtInfoMsg: + l_output = QString("I"); + break; + case QtWarningMsg: + l_output = QString("W"); + break; + case QtCriticalMsg: + l_output = QString("C"); + break; + case QtFatalMsg: + l_output = QString("F"); + break; + } + + const QString l_timestamp = QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss.zzz"); + const QString l_pid = QString::number(QCoreApplication::applicationPid()); + + QString l_final_message = p_message; + // Censor HDID + if (p_message.startsWith("S/S: HI#")) + l_final_message = "S/S: HI#HDID#%"; + else if (p_message.startsWith("M/S: HI#")) + l_final_message = "M/S: HI#HDID#%"; + else + l_final_message = p_message; + + l_output = QString("[%1][%2][%3] %4").arg(l_timestamp, l_pid, l_output, l_final_message); + if (s_verbose_logging) + l_output.append(QString(" (%1:%2, %3)").arg(p_context.file).arg(p_context.line).arg(p_context.function)); + + return l_output; +} + +void save_log_line(QString p_log_line) +{ + s_msg_list_lock.lock(); + s_msg_list.append(p_log_line); + s_msg_list_lock.unlock(); +} + +void logger::log(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + if (!s_initialized) + { + s_initialized = true; + s_logger = std::thread(task); + } + + save_log_line(generate_message(type, context, msg)); + + // call the default handler. + (*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg); +} + +void logger::shutdown() +{ + s_continue = false; + try + { + if (s_logger.joinable()) + s_logger.join(); + } + catch (const std::exception &e) + { + qWarning() << "error:" << e.what(); + } + catch (...) + { + qWarning() << "error: unknown exception"; + } +} + +void logger::set_verbose_logging(bool enabled) +{ + s_verbose_logging = enabled; +} diff --git a/src/logger.h b/src/logger.h new file mode 100644 index 000000000..915ccf2dc --- /dev/null +++ b/src/logger.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace logger +{ +void log(QtMsgType type, const QMessageLogContext &context, const QString &msg); +void shutdown(); +void set_verbose_logging(bool enabled); +} // namespace logger diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 000000000..d14ee620f --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,69 @@ +#include "aoapplication.h" +#include "aoconfig.h" +#include "drmediatester.h" +#include "lobby.h" +#include "logger.h" + +#include + +int main(int argc, char *argv[]) +{ +#if defined(Q_OS_WINDOWS) + qputenv("QT_MULTIMEDIA_PREFERRED_PLUGINS", "windowsmediafoundation"); +#elif defined(Q_OS_MACOS) + qputenv("QT_MAC_WANTS_LAYER", "1"); +#endif + + qInstallMessageHandler(logger::log); + qInfo() << "Starting Danganronpa Online..."; + + bool l_dpi_scaling = false; + for (int i = 0; i < argc; ++i) + { + const QString l_arg(argv[i]); + + if (l_arg == "-dpiscaling") + { + l_dpi_scaling = true; + } + } + + if (l_dpi_scaling) + { + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + } + else + { + qputenv("QT_FONT_DPI", "96"); + qputenv("QT_SCALE_FACTOR", "1"); + qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0"); + + QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, false); + } + + AOApplication app(argc, argv); + + int l_exit_code = 0; + { + AOConfig l_config; + + DRMediaTester *l_media_tester = new DRMediaTester(&app); + QObject::connect(l_media_tester, SIGNAL(done()), l_media_tester, SLOT(deleteLater())); + + app.load_fonts(); + app.construct_lobby(); + app.get_lobby()->show(); + + l_exit_code = app.exec(); + + logger::shutdown(); + + if (l_config.autosave()) + { + l_config.save_file(); + } + } + + return l_exit_code; +} diff --git a/misc_functions.cpp b/src/misc_functions.cpp similarity index 85% rename from misc_functions.cpp rename to src/misc_functions.cpp index e767b2ef1..288af4ecf 100644 --- a/misc_functions.cpp +++ b/src/misc_functions.cpp @@ -1,12 +1,12 @@ #include "misc_functions.h" -#include #include +#include void delay(int p_milliseconds) { QTime dieTime = QTime::currentTime().addMSecs(p_milliseconds); - while(QTime::currentTime() < dieTime) + while (QTime::currentTime() < dieTime) QCoreApplication::processEvents(QEventLoop::AllEvents, 100); } diff --git a/misc_functions.h b/src/misc_functions.h similarity index 100% rename from misc_functions.h rename to src/misc_functions.h diff --git a/src/mk2/graphicsspriteitem.cpp b/src/mk2/graphicsspriteitem.cpp new file mode 100644 index 000000000..fa00fe4b6 --- /dev/null +++ b/src/mk2/graphicsspriteitem.cpp @@ -0,0 +1,172 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ +#include "graphicsspriteitem.h" + +#include +#include + +#include + +using namespace mk2; + +GraphicsSpriteItem::GraphicsSpriteItem(QGraphicsItem *parent) + : QGraphicsObject(parent) + , m_player(new SpritePlayer) +{ + connect(m_player.get(), SIGNAL(size_changed(QSize)), this, SLOT(notify_size())); + connect(m_player.get(), SIGNAL(current_frame_changed()), this, SLOT(notify_update())); + connect(m_player.get(), SIGNAL(file_name_changed(QString)), this, SIGNAL(file_name_changed(QString))); + connect(m_player.get(), SIGNAL(reader_changed()), this, SIGNAL(reader_changed())); + connect(m_player.get(), SIGNAL(started()), this, SIGNAL(started())); + connect(m_player.get(), SIGNAL(finished()), this, SIGNAL(finished())); +} + +GraphicsSpriteItem::~GraphicsSpriteItem() +{} + +SpritePlayer::ScalingMode GraphicsSpriteItem::get_scaling_mode() const +{ + return m_player->get_scaling_mode(); +} + +void GraphicsSpriteItem::set_scaling_mode(SpritePlayer::ScalingMode p_scaling_mode) +{ + m_player->set_scaling_mode(p_scaling_mode); +} + +QSizeF GraphicsSpriteItem::get_size() const +{ + return m_player->get_size(); +} + +void GraphicsSpriteItem::set_size(QSizeF p_size) +{ + m_player->set_size(p_size.toSize()); +} + +void GraphicsSpriteItem::set_play_once(bool p_enabled) +{ + m_player->set_play_once(p_enabled); +} + +void GraphicsSpriteItem::set_mirror(bool p_enabled) +{ + m_player->set_mirror(p_enabled); +} + +QString GraphicsSpriteItem::get_file_name() const +{ + return m_player->get_file_name(); +} + +void GraphicsSpriteItem::set_file_name(QString p_file_name) +{ + m_player->set_file_name(p_file_name); +} + +QIODevice *GraphicsSpriteItem::get_device() const +{ + return m_player->get_device(); +} + +void GraphicsSpriteItem::set_device(QIODevice *p_device) +{ + m_player->set_device(p_device); +} + +SpriteReader::ptr GraphicsSpriteItem::get_reader() const +{ + return m_player->get_reader(); +} + +void GraphicsSpriteItem::set_reader(SpriteReader::ptr p_reader) +{ + m_player->set_reader(p_reader); +} + +SpritePlayer *GraphicsSpriteItem::get_player() const +{ + return m_player.get(); +} + +void GraphicsSpriteItem::start() +{ + m_player->start(); +} + +void GraphicsSpriteItem::restart() +{ + stop(); + start(); +} + +void GraphicsSpriteItem::stop() +{ + m_player->stop(); +} + +bool GraphicsSpriteItem::is_valid() const +{ + return m_player->is_valid(); +} + +bool GraphicsSpriteItem::is_running() const +{ + return m_player->is_running(); +} + +QRectF GraphicsSpriteItem::boundingRect() const +{ + return QRectF(0, 0, m_player->get_size().width(), m_player->get_size().height()); +} + +void GraphicsSpriteItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + const QImage l_image = m_player->get_current_frame(); + if (!l_image.isNull()) + { + painter->save(); + painter->setCompositionMode(QPainter::CompositionMode_SourceOver); + + // calculate center position + QPointF l_horizontal_center; + if (auto *l_scene = scene()) + { + const QPointF l_center = l_scene->sceneRect().center() - m_player->get_scaled_bounding_rect().center(); + l_horizontal_center.setX(l_center.x()); + } + + painter->drawImage(l_horizontal_center, m_player->get_current_frame()); + painter->restore(); + } +} + +void GraphicsSpriteItem::notify_size() +{ + emit size_changed(QSizeF(m_player->get_size())); +} + +void GraphicsSpriteItem::notify_update() +{ + update(); +} diff --git a/src/mk2/graphicsspriteitem.h b/src/mk2/graphicsspriteitem.h new file mode 100644 index 000000000..1ecafaefe --- /dev/null +++ b/src/mk2/graphicsspriteitem.h @@ -0,0 +1,99 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ +#pragma once + +#include "spriteplayer.h" +#include "spritereader.h" + +#include +#include + +namespace mk2 +{ +class GraphicsSpriteItem : public QGraphicsObject +{ + Q_OBJECT + + Q_PROPERTY(QSizeF size READ get_size WRITE set_size NOTIFY size_changed) + +public: + using ScalingMode = SpritePlayer::ScalingMode; + + GraphicsSpriteItem(QGraphicsItem *parent = nullptr); + ~GraphicsSpriteItem(); + + SpritePlayer::ScalingMode get_scaling_mode() const; + + QSizeF get_size() const; + + QString get_file_name() const; + + QIODevice *get_device() const; + + SpriteReader::ptr get_reader() const; + + SpritePlayer *get_player() const; + + bool is_valid() const; + + bool is_running() const; + + QRectF boundingRect() const final; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) final; + +public slots: + void set_scaling_mode(SpritePlayer::ScalingMode scaling_mode); + + void set_size(QSizeF size); + + void set_play_once(bool enabled); + + void set_mirror(bool enabled); + + void set_file_name(QString file_name); + + void set_device(QIODevice *device); + + void set_reader(SpriteReader::ptr reader); + + void start(); + void restart(); + void stop(); + +signals: + void size_changed(QSizeF); + + void file_name_changed(QString); + + void reader_changed(); + + void started(); + void finished(); + +private: + QScopedPointer m_player; + +private slots: + void notify_size(); + + void notify_update(); +}; +} // namespace mk2 diff --git a/src/mk2/graphicsvideoscreen.cpp b/src/mk2/graphicsvideoscreen.cpp new file mode 100644 index 000000000..4a4b6d781 --- /dev/null +++ b/src/mk2/graphicsvideoscreen.cpp @@ -0,0 +1,258 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ +#include "graphicsvideoscreen.h" + +#include +#include +#include +#include +#include + +DRVideoScreen::DRVideoScreen(AOApplication *ao_app, QGraphicsItem *parent) + : QGraphicsVideoItem(parent) + , ao_app(ao_app) + , m_config(new AOConfig(this)) + , m_engine(new DRAudioEngine(this)) + , m_family(m_engine->get_family(DRAudio::Family::FVideo)) + , m_scanned(false) + , m_video_available(false) + , m_running(false) + , m_player(new QMediaPlayer(this, QMediaPlayer::LowLatency)) +{ + setAspectRatioMode(Qt::KeepAspectRatioByExpanding); + + m_player->setVideoOutput(this); + + connect(m_player, SIGNAL(videoAvailableChanged(bool)), this, SLOT(update_video_availability(bool))); + connect(m_player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, SLOT(check_status(QMediaPlayer::MediaStatus))); + connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(check_state(QMediaPlayer::State))); + + connect(m_engine, SIGNAL(current_device_changed(DRAudioDevice)), this, SLOT(update_audio_output())); + connect(m_config, SIGNAL(video_volume_changed(int)), this, SLOT(update_volume())); + connect(m_engine, SIGNAL(volume_changed(int32_t)), this, SLOT(update_volume())); + connect(m_engine, SIGNAL(options_changed(DRAudio::Options)), this, SLOT(update_volume())); + connect(m_family.get(), SIGNAL(volume_changed(int32_t)), this, SLOT(update_volume())); + connect(m_family.get(), SIGNAL(options_changed(DRAudio::Options)), this, SLOT(update_volume())); + + update_audio_output(); +} + +DRVideoScreen::~DRVideoScreen() +{} + +QString DRVideoScreen::get_file_name() const +{ + return m_file_name; +} + +void DRVideoScreen::set_file_name(QString p_file_name) +{ + if (m_file_name == p_file_name) + { + return; + } + stop(); + qInfo() << "loading media file" << p_file_name; + m_scanned = false; + m_video_available = false; + m_file_name = p_file_name; + if (m_file_name.isEmpty()) + { + m_scanned = true; + } + m_player->setMedia(QUrl::fromLocalFile(m_file_name)); +} + +void DRVideoScreen::play_character_video(QString p_character, QString p_video) +{ + QStringList l_filepath_list; + const QString l_video_path = QString("videos/%1").arg(p_video); + for (const QString &i_character_name : ao_app->get_char_include_tree(p_character)) + { + l_filepath_list.append(ao_app->get_character_path(i_character_name, l_video_path)); + } + + const QString l_filepath = ao_app->find_asset_path(l_filepath_list); + if (l_filepath.isEmpty()) + { + qWarning() << "error: no character media file" << p_character << p_video; + finish_playback(); + return; + } + set_file_name(l_filepath); + play(); +} + +void DRVideoScreen::play() +{ + stop(); + m_running = true; + if (!m_scanned) + { + return; + } + if (!m_video_available) + { + finish_playback(); + return; + } + start_playback(); +} + +void DRVideoScreen::stop() +{ + m_running = false; + if (m_player->state() != QMediaPlayer::StoppedState) + { + m_player->stop(); + } +} + +void DRVideoScreen::update_video_availability(bool p_video_available) +{ + m_video_available = p_video_available; +} + +void DRVideoScreen::check_status(QMediaPlayer::MediaStatus p_status) +{ + if (m_running) + { + switch (p_status) + { + case QMediaPlayer::InvalidMedia: + m_scanned = true; + qWarning() << "error: media file is invalid:" << m_file_name; + finish_playback(); + break; + + case QMediaPlayer::NoMedia: + m_scanned = true; + finish_playback(); + + case QMediaPlayer::LoadedMedia: + m_scanned = true; + if (m_video_available) + { + start_playback(); + } + else + { + finish_playback(); + } + break; + + default: + break; + } + } +} + +void DRVideoScreen::check_state(QMediaPlayer::State p_state) +{ + switch (p_state) + { + case QMediaPlayer::PlayingState: + emit started(); + break; + + case QMediaPlayer::StoppedState: + if (m_running) + { + finish_playback(); + } + break; + + default: + break; + } +} + +void DRVideoScreen::start_playback() +{ + if (m_player->state() == QMediaPlayer::StoppedState) + { + update_audio_output(); + + m_player->play(); + } +} + +void DRVideoScreen::finish_playback() +{ + stop(); + emit finished(); +} + +void DRVideoScreen::update_audio_output() +{ + const auto l_target_device = m_engine->get_current_device(); + if (!l_target_device.has_value()) + { + qWarning() << "error: no device to switch to"; + return; + } + + QMediaService *l_service = m_player->service(); + if (!l_service) + { + qWarning() << "error: missing media service, device unchanged"; + return; + } + + QAudioOutputSelectorControl *l_control = l_service->requestControl(); + if (!l_control) + { + qWarning() << "error: missing audio output control, device unchanged"; + } + else + { + const QStringList l_device_name_list = l_control->availableOutputs(); + for (const QString &i_device_name : l_device_name_list) + { + const QString l_device_description = l_control->outputDescription(i_device_name); + if (i_device_name == l_target_device->get_name() || i_device_name == l_target_device->get_driver() || l_device_description == l_target_device->get_name() || l_device_description == l_target_device->get_driver()) + { + qDebug() << "Media player changed audio device to" << l_target_device->get_name(); + l_control->setActiveOutput(i_device_name); + break; + } + } + return; + } + l_service->releaseControl(l_control); + + update_volume(); +} + +void DRVideoScreen::update_volume() +{ + int l_volume = (m_family->get_volume() * m_engine->get_volume()) / 100; + + if (!m_family->is_ignore_suppression() && (m_family->is_suppressed() || m_engine->is_suppressed())) + { + l_volume = 0; + } + + if (m_player->volume() == l_volume) + { + return; + } + m_player->setVolume(l_volume); +} diff --git a/src/mk2/graphicsvideoscreen.h b/src/mk2/graphicsvideoscreen.h new file mode 100644 index 000000000..af9f7ff13 --- /dev/null +++ b/src/mk2/graphicsvideoscreen.h @@ -0,0 +1,87 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ +#pragma once + +#include "aoapplication.h" +#include "aoconfig.h" +#include "draudioengine.h" +#include "draudiostreamfamily.h" + +#include +#include + +class DRVideoScreen : public QGraphicsVideoItem +{ + Q_OBJECT + +public: + DRVideoScreen(AOApplication *ao_app, QGraphicsItem *parent = nullptr); + ~DRVideoScreen(); + + QString get_file_name() const; + +public slots: + void set_file_name(QString file_name); + + void play_character_video(QString character, QString video); + + void play(); + + void stop(); + +signals: + void started(); + + void finished(); + +private: + AOApplication *ao_app; + + AOConfig *m_config; + + DRAudioEngine *m_engine; + + DRAudioStreamFamily::ptr m_family; + + QString m_file_name; + + bool m_scanned; + + bool m_video_available; + + bool m_running; + + QMediaPlayer *m_player; + + void start_playback(); + + void finish_playback(); + +private slots: + void update_video_availability(bool); + + void check_status(QMediaPlayer::MediaStatus); + + void check_state(QMediaPlayer::State); + + void update_audio_output(); + + void update_volume(); +}; diff --git a/src/mk2/spritecachingreader.cpp b/src/mk2/spritecachingreader.cpp new file mode 100644 index 000000000..aa8bd3ec7 --- /dev/null +++ b/src/mk2/spritecachingreader.cpp @@ -0,0 +1,169 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#include "mk2/spritecachingreader.h" + +#include +#include +#include +#include +#include + +#include + +using namespace mk2; + +SpriteCachingReader::SpriteCachingReader(QObject *parent) + : SpriteReader{parent} + , m_sprite_size{} + , m_frame_count{0} + , m_exit_task{false} +{} + +SpriteCachingReader::~SpriteCachingReader() +{ + _p_stop_preload(); +} + +QSize SpriteCachingReader::get_sprite_size() const +{ + return m_sprite_size; +} + +int SpriteCachingReader::get_frame_count() const +{ + return m_frame_count; +} + +SpriteFrame SpriteCachingReader::get_frame(int p_number) +{ + if (!is_valid()) + { + return SpriteFrame{}; + } + p_number = qBound(0, p_number, qMax(m_frame_count - 1, 0)); + m_available_frames.acquire(p_number + 1); + QSemaphoreReleaser l_releaser(m_available_frames, p_number + 1); + QMutexLocker l_locker(&m_lock); + return m_frame_list.at(p_number); +} + +QVector SpriteCachingReader::get_frame_list() +{ + m_available_frames.acquire(get_frame_count()); + QSemaphoreReleaser l_releaser(m_available_frames, get_frame_count()); + QMutexLocker l_locker(&m_lock); + return m_frame_list; +} + +void SpriteCachingReader::load() +{ + _p_stop_preload(); + m_available_frames.acquire(m_available_frames.available()); + m_sprite_size = QSize{}; + m_frame_count = 0; + m_frame_list.clear(); + + QByteArray l_raw_data; + QIODevice *l_device = get_device(); + const int l_prev_pos = l_device->pos(); + if (!l_device->isOpen() && !l_device->open(QIODevice::ReadOnly)) + { + set_error(Error::DeviceError); + return; + } + l_device->seek(0); + l_raw_data = l_device->readAll(); + l_device->seek(l_prev_pos); + + { + QBuffer l_buffer(&l_raw_data); + QImageReader l_reader(&l_buffer); + if (!l_reader.canRead()) + { + set_error(Error::InvalidDataError); + return; + } + m_sprite_size = l_reader.size(); + m_frame_count = l_reader.imageCount(); + } + + m_exit_task = false; + m_task = QtConcurrent::run(this, &SpriteCachingReader::_p_preload, l_raw_data); +} + +void SpriteCachingReader::_p_stop_preload() +{ + m_exit_task = true; + m_task.waitForFinished(); +} + +void SpriteCachingReader::_p_preload(QByteArray p_raw_data) +{ + set_state(State::NotLoaded); + set_loading_progress(0); + + QBuffer l_buffer(&p_raw_data); + QImageReader l_reader(&l_buffer); + const QSize l_size = l_reader.size(); + const int l_frame_count = l_reader.imageCount(); + if (l_frame_count > 0) + { + QElapsedTimer l_elapsed_timer; + l_elapsed_timer.start(); + + // create a buffer for images + QVector l_image_buffer_list; + for (int i = 0; i < l_frame_count; ++i) + { + l_image_buffer_list.append(QImage(l_size, QImage::Format_ARGB32)); + } + + int l_frame_number = 0; + int l_percent_progress = 0; + while (!m_exit_task && l_frame_number < l_frame_count && l_reader.canRead()) + { + SpriteFrame l_frame; + QImage l_image_buffer = l_image_buffer_list.takeFirst(); + l_reader.read(&l_image_buffer); + l_frame.image = l_image_buffer; + l_frame.delay = l_reader.nextImageDelay(); + { + QMutexLocker locker(&m_lock); + m_frame_list.append(std::move(l_frame)); + l_frame_number = m_frame_list.length(); + m_available_frames.release(); + } + + l_percent_progress = ((double)l_frame_number / (l_frame_count + 1)) * 100; + set_loading_progress(l_percent_progress); + } + + if (!m_exit_task) + { + set_loading_progress(100); + set_state(State::FullyLoaded); + } + } + else + { + set_error(Error::InvalidDataError); + } +} diff --git a/src/mk2/spritecachingreader.h b/src/mk2/spritecachingreader.h new file mode 100644 index 000000000..2125a99a9 --- /dev/null +++ b/src/mk2/spritecachingreader.h @@ -0,0 +1,67 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#pragma once + +#include "mk2/spritereader.h" + +#include +#include +#include + +#include + +namespace mk2 +{ +class SpriteCachingReader : public SpriteReader +{ + Q_OBJECT + +public: + explicit SpriteCachingReader(QObject *parent = nullptr); + virtual ~SpriteCachingReader(); + +public: + QSize get_sprite_size() const final; + + int get_frame_count() const final; + + SpriteFrame get_frame(int number) final; + + QVector get_frame_list() final; + +protected: + void load() final; + +private: + mutable QMutex m_lock; + + QSize m_sprite_size; + int m_frame_count; + mutable QSemaphore m_available_frames; + QVector m_frame_list; + + QFuture m_task; + std::atomic_bool m_exit_task; + + void _p_preload(QByteArray raw_data); + void _p_stop_preload(); +}; +} // namespace mk2 diff --git a/src/mk2/spritedynamicreader.cpp b/src/mk2/spritedynamicreader.cpp new file mode 100644 index 000000000..91bb0fd63 --- /dev/null +++ b/src/mk2/spritedynamicreader.cpp @@ -0,0 +1,214 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#include "spritedynamicreader.h" + +#include "spritecachingreader.h" +#include "spriteseekingreader.h" + +#include +#include +#include +#include + +#if defined(Q_OS_WINDOWS) +#include +#include + +double get_mem_usage_percent(quint64 p_requested_mem_size) +{ + MEMORYSTATUSEX l_mem_status; + l_mem_status.dwLength = sizeof(MEMORYSTATUSEX); + if (GlobalMemoryStatusEx(&l_mem_status)) + { + const quint64 l_used_ram = (l_mem_status.ullTotalPhys - l_mem_status.ullAvailPhys) + p_requested_mem_size; + if (l_used_ram < l_mem_status.ullTotalPhys) + { + const double l_percent = std::abs(((double)l_used_ram / l_mem_status.ullTotalPhys) * 100); + return l_percent; + } + else + { + return 100.0; + } + } + else + { + return 100.0; + } +} +#elif defined(Q_OS_LINUX) +#include + +double get_mem_usage_percent(quint64 p_needed_ram) +{ + struct sysinfo l_sys_info; + if (sysinfo(&l_sys_info) != -1) + { + const quint64 l_used_ram = (l_sys_info.totalram - l_sys_info.freeram) + p_needed_ram; + if (l_used_ram < l_sys_info.totalram) + { + const double l_percent = std::abs(((double)l_used_ram / l_sys_info.totalram) * 100); + return l_percent; + } + else + { + return 100.0; + } + } + else + { + return 100.0; + } +} +#elif defined(Q_OS_MAC) +#include +#include + +double get_mem_usage_percent(quint64 p_requested_mem_size) +{ + mach_port_t l_mach_port = mach_host_self(); + + vm_size_t l_page_size; + mach_msg_type_number_t count = sizeof(vm_statistics64_data_t) / sizeof(natural_t); + if (host_page_size(l_mach_port, &l_page_size) != KERN_SUCCESS) + { + return 100; + } + + vm_statistics64_data_t vm_stats; + if (host_statistics64(l_mach_port, HOST_VM_INFO, + (host_info64_t)&vm_stats, &count) != KERN_SUCCESS) + { + return 100; + } + + const quint64 l_free_ram = (quint64)vm_stats.free_count * l_page_size; + const quint64 l_total_ram = ((quint64)vm_stats.free_count + vm_stats.active_count + vm_stats.inactive_count + vm_stats.wire_count + vm_stats.zero_fill_count) * l_page_size; + + const quint64 l_used_ram = (l_total_ram - l_free_ram) + p_requested_mem_size; + if (l_used_ram < l_total_ram) + { + const double l_percent = std::abs(((double)l_used_ram / l_total_ram) * 100); + return l_percent; + } + else + { + return 100.0; + } +} +#else +#error Unsupported platform. +#endif + +using namespace mk2; + +std::atomic_int s_system_memory_threshold = 50; +std::atomic_uint64_t s_total_memory_used = 0; + +int SpriteDynamicReader::get_system_memory_threshold() +{ + return s_system_memory_threshold; +} + +void SpriteDynamicReader::set_system_memory_threshold(int p_percent) +{ + s_system_memory_threshold = qBound(10, p_percent, 80); +} + +SpriteDynamicReader::SpriteDynamicReader(QObject *parent) + : SpriteReader{parent} + , m_used_memory{0} +{ + _p_create_reader(true); +} + +SpriteDynamicReader::~SpriteDynamicReader() +{ + _p_free_memory(); +} + +QSize SpriteDynamicReader::get_sprite_size() const +{ + return m_reader->get_sprite_size(); +} + +int SpriteDynamicReader::get_frame_count() const +{ + return m_reader->get_frame_count(); +} + +SpriteFrame SpriteDynamicReader::get_frame(int number) +{ + return m_reader->get_frame(number); +} + +QVector SpriteDynamicReader::get_frame_list() +{ + return m_reader->get_frame_list(); +} + +void SpriteDynamicReader::load() +{ + _p_free_memory(); + QImageReader l_image_reader(get_device()); + const QSize l_size = l_image_reader.size(); + + bool l_caching = true; + qint64 l_projected_memory = 0; + if (l_size.isValid() && l_image_reader.imageCount() > 0) + { + l_projected_memory = qint64(((qint64)l_image_reader.size().width() * l_image_reader.size().height()) * l_image_reader.imageCount() * 4); + + if (get_mem_usage_percent(s_total_memory_used + l_projected_memory) > s_system_memory_threshold) + { + l_caching = false; + l_projected_memory = 0; + } + } + m_used_memory = l_projected_memory; + s_total_memory_used += m_used_memory; + + _p_create_reader(l_caching); + m_reader->set_device(get_device()); +} + +void SpriteDynamicReader::_p_create_reader(bool p_caching) +{ + mk2::SpriteReader *l_reader = nullptr; + if (p_caching) + { + l_reader = new SpriteCachingReader; + } + else + { + l_reader = new SpriteSeekingReader; + } + connect(l_reader, SIGNAL(state_changed(mk2::SpriteReader::State)), this, SLOT(set_state(mk2::SpriteReader::State))); + connect(l_reader, SIGNAL(loading_progress_changed(int)), this, SLOT(set_loading_progress(int))); + connect(l_reader, SIGNAL(error(mk2::SpriteReader::Error)), this, SLOT(set_error(mk2::SpriteReader::Error))); + m_reader = mk2::SpriteReader::ptr(l_reader); +} + +void SpriteDynamicReader::_p_free_memory() +{ + s_total_memory_used -= m_used_memory; + m_used_memory = 0; +} diff --git a/src/mk2/spritedynamicreader.h b/src/mk2/spritedynamicreader.h new file mode 100644 index 000000000..34910d59d --- /dev/null +++ b/src/mk2/spritedynamicreader.h @@ -0,0 +1,59 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#pragma once + +#include "mk2/spritereader.h" + +#include +#include + +namespace mk2 +{ +class SpriteDynamicReader : public SpriteReader +{ + Q_OBJECT + +public: + static int get_system_memory_threshold(); + static void set_system_memory_threshold(int percent); + + explicit SpriteDynamicReader(QObject *parent = nullptr); + virtual ~SpriteDynamicReader(); + + QSize get_sprite_size() const final; + + int get_frame_count() const final; + + SpriteFrame get_frame(int number) final; + + QVector get_frame_list() final; + +protected: + void load() final; + +private: + QSharedPointer m_reader; + std::atomic_uint64_t m_used_memory; + + void _p_create_reader(bool caching); + void _p_free_memory(); +}; +} // namespace mk2 diff --git a/src/mk2/spriteplayer.cpp b/src/mk2/spriteplayer.cpp new file mode 100644 index 000000000..38cf99f46 --- /dev/null +++ b/src/mk2/spriteplayer.cpp @@ -0,0 +1,337 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#include "mk2/spritedynamicreader.h" +#include "mk2/spriteviewer.h" + +#include +#include + +using namespace mk2; + +SpritePlayer::SpritePlayer(QObject *parent) + : QObject{parent} + , m_reader{new SpriteDynamicReader} + , m_scaling_mode{StretchScaling} + , m_resolved_scaling_mode{StretchScaling} + , m_transform{Qt::SmoothTransformation} + , m_running{false} + , m_mirror{false} + , m_play_once{false} + , m_frame_count{0} + , m_frame_number{0} +{ + m_frame_timer.setTimerType(Qt::PreciseTimer); + m_frame_timer.setSingleShot(true); + + m_repaint_timer.setInterval(200); + m_repaint_timer.setSingleShot(true); + + connect(&m_frame_timer, SIGNAL(timeout()), this, SLOT(fetch_next_frame())); + connect(&m_repaint_timer, SIGNAL(timeout()), this, SLOT(scale_current_frame())); +} + +SpritePlayer::~SpritePlayer() +{} + +QImage SpritePlayer::get_current_frame() const +{ + return m_scaled_current_frame; +} + +QRectF SpritePlayer::get_scaled_bounding_rect() const +{ + return m_scaled_current_frame.rect(); +} + +QString SpritePlayer::get_file_name() const +{ + return m_reader->get_file_name(); +} + +QIODevice *SpritePlayer::get_device() const +{ + return nullptr; +} + +SpritePlayer::ScalingMode SpritePlayer::get_scaling_mode() const +{ + return m_scaling_mode; +} + +QSize SpritePlayer::get_size() const +{ + return m_size; +} + +void SpritePlayer::set_file_name(QString p_file_name) +{ + if (!p_file_name.isEmpty() && p_file_name == get_file_name()) + { + return; + } + stop(); + m_reader->set_file_name(p_file_name); + m_frame_count = m_reader->get_frame_count(); + emit file_name_changed(p_file_name); +} + +void SpritePlayer::set_device(QIODevice *p_device) +{ + const QString l_prev_file_name = get_file_name(); + stop(); + m_reader->set_device(p_device); + m_frame_count = m_reader->get_frame_count(); + const QString l_file_name = get_file_name(); + if (l_file_name != l_prev_file_name) + { + emit file_name_changed(l_file_name); + } +} + +void SpritePlayer::set_play_once(bool p_enabled) +{ + m_play_once = p_enabled; +} + +void SpritePlayer::set_mirror(bool p_enabled) +{ + m_mirror = p_enabled; +} + +void SpritePlayer::set_scaling_mode(ScalingMode scaling_mode) +{ + if (m_scaling_mode == scaling_mode) + { + return; + } + m_scaling_mode = scaling_mode; + resolve_scaling_mode(); + scale_current_frame(); +} + +void SpritePlayer::set_size(QSize p_size) +{ + if (m_size == p_size) + { + return; + } + m_size = p_size; + resolve_scaling_mode(); + m_repaint_timer.start(); + emit size_changed(m_size); +} + +SpriteReader::ptr SpritePlayer::get_reader() const +{ + return m_reader; +} + +void SpritePlayer::set_reader(SpriteReader::ptr p_reader) +{ + const QString l_prev_file_name = get_file_name(); + stop(); + if (p_reader == nullptr) + { + p_reader = SpriteReader::ptr(new SpriteDynamicReader); + } + m_reader = p_reader; + m_frame_count = m_reader->get_frame_count(); + const QString l_file_name = get_file_name(); + if (l_file_name != l_prev_file_name) + { + emit file_name_changed(l_file_name); + } +} + +bool SpritePlayer::is_valid() const +{ + return m_reader->is_valid(); +} + +bool SpritePlayer::is_running() const +{ + return m_running; +} + +void SpritePlayer::start() +{ + m_running = true; + m_elapsed_timer.start(); + emit started(); + resolve_scaling_mode(); + fetch_next_frame(); +} + +void SpritePlayer::restart() +{ + stop(); + start(); +} + +void SpritePlayer::stop() +{ + m_running = false; + m_frame_timer.stop(); + m_frame_number = 0; +} + +void SpritePlayer::resolve_scaling_mode() +{ + m_resolved_scaling_mode = m_scaling_mode; + + const QSize l_image_size = m_reader->get_sprite_size(); + if (m_size == l_image_size || !l_image_size.isValid()) + { + m_resolved_scaling_mode = NoScaling; + } + else if (m_resolved_scaling_mode == DynamicScaling) + { + const qreal l_width_factor = (qreal)qMax(l_image_size.width(), 1) / qMax(m_size.width(), 1); + const qreal l_height_factor = (qreal)qMax(l_image_size.height(), 1) / qMax(m_size.height(), 1); + + const QSize l_by_width_size{ + int((qreal)l_image_size.width() / l_width_factor), + int((qreal)l_image_size.height() / l_width_factor), + }; + const QSize l_by_height_size{ + int((qreal)l_image_size.width() / l_height_factor), + int((qreal)l_image_size.height() / l_height_factor), + }; + + if (l_by_width_size.height() >= m_size.height()) + { + m_resolved_scaling_mode = WidthScaling; + } + else if (l_by_height_size.width() >= m_size.width()) + { + m_resolved_scaling_mode = HeightScaling; + } + else + { + m_resolved_scaling_mode = StretchScaling; + } + } + + m_transform = Qt::SmoothTransformation; + if (l_image_size.width() < m_size.width() || l_image_size.height() < m_size.height()) + { + m_transform = Qt::FastTransformation; + } +} + +void SpritePlayer::fetch_next_frame() +{ + QElapsedTimer l_timer; + l_timer.start(); + + if (!is_valid()) + { + if (m_running && m_play_once) + { + m_running = false; + emit finished(); + } + + return; + } + + if (!m_running) + { + return; + } + + if (m_frame_number >= m_frame_count) + { + if (m_play_once) + { + m_running = false; + emit finished(); + return; + } + + if (m_frame_count > 1) + { + m_frame_number = 0; + } + else + { + return; + } + } + + const int l_current_frame_number = m_frame_number; + m_current_frame = m_reader->get_frame(l_current_frame_number); + m_frame_number++; + + scale_current_frame(); + + const int l_next_delay = qMax(0, int(m_current_frame.delay - l_timer.elapsed())); + if (l_next_delay == 0 && m_frame_count == 1) + { + m_running = false; + + if (m_play_once) + { + emit finished(); + } + } + else + { + m_frame_timer.start(l_next_delay); + } +} + +void SpritePlayer::scale_current_frame() +{ + QImage l_image = m_current_frame.image; + + if (!l_image.isNull()) + { + switch (m_resolved_scaling_mode) + { + case NoScaling: + [[fallthrough]]; + default: + break; + + case StretchScaling: + l_image = l_image.scaled(m_size, Qt::IgnoreAspectRatio, m_transform); + break; + + case WidthScaling: + l_image = l_image.scaledToWidth(m_size.width(), m_transform); + break; + + case HeightScaling: + l_image = l_image.scaledToHeight(m_size.height(), m_transform); + break; + } + } + + // slow operation... + if (m_mirror) + { + l_image = l_image.mirrored(true, false); + } + + m_scaled_current_frame = l_image; + emit current_frame_changed(); +} diff --git a/src/mk2/spriteplayer.h b/src/mk2/spriteplayer.h new file mode 100644 index 000000000..9d9dfa6c6 --- /dev/null +++ b/src/mk2/spriteplayer.h @@ -0,0 +1,123 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#pragma once + +#include "spritereader.h" + +#include +#include +#include + +#include + +namespace mk2 +{ +class SpritePlayer : public QObject +{ + Q_OBJECT + +public: + enum ScalingMode + { + NoScaling, + WidthScaling, + HeightScaling, + StretchScaling, + DynamicScaling, + }; + Q_ENUM(ScalingMode) + + SpritePlayer(QObject *parent = nullptr); + ~SpritePlayer(); + + QImage get_current_frame() const; + + QRectF get_scaled_bounding_rect() const; + + SpritePlayer::ScalingMode get_scaling_mode() const; + + QSize get_size() const; + + QString get_file_name() const; + + QIODevice *get_device() const; + + SpriteReader::ptr get_reader() const; + + bool is_valid() const; + + bool is_running() const; + +public slots: + void set_play_once(bool enabled); + + void set_mirror(bool enabled); + + void set_scaling_mode(SpritePlayer::ScalingMode scaling_mode); + + void set_size(QSize size); + + void set_file_name(QString file_name); + + void set_device(QIODevice *device); + + void set_reader(SpriteReader::ptr reader); + + void start(); + void restart(); + void stop(); + +signals: + void current_frame_changed(); + + void size_changed(QSize); + + void file_name_changed(QString); + + void reader_changed(); + + void started(); + void finished(); + +private: + SpriteReader::ptr m_reader; + SpriteFrame m_current_frame; + QImage m_scaled_current_frame; + SpritePlayer::ScalingMode m_scaling_mode; + SpritePlayer::ScalingMode m_resolved_scaling_mode; + Qt::TransformationMode m_transform; + QSize m_size; + bool m_running; + bool m_mirror; + bool m_play_once; + int m_frame_count; + int m_frame_number; + QElapsedTimer m_elapsed_timer; + QTimer m_frame_timer; + QTimer m_repaint_timer; + + void resolve_scaling_mode(); + +private slots: + void fetch_next_frame(); + void scale_current_frame(); +}; +} // namespace mk2 diff --git a/src/mk2/spritereader.cpp b/src/mk2/spritereader.cpp new file mode 100644 index 000000000..39c865e6e --- /dev/null +++ b/src/mk2/spritereader.cpp @@ -0,0 +1,179 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#include "mk2/spritereader.h" + +#include +#include +#include +#include + +using namespace mk2; + +SpriteFrame::SpriteFrame() + : delay{0} +{} + +SpriteFrame::~SpriteFrame() +{} + +void SpriteReader::registerMetatypes() +{ + static bool s_registerRequired = true; + if (s_registerRequired) + { + s_registerRequired = false; + qRegisterMetaType(); + qRegisterMetaType(); + } +} + +SpriteReader::SpriteReader(QObject *parent) + : QObject{parent} + , m_device{new QFile} + , m_own_device{true} + , m_state{State::NotLoaded} + , m_last_error{Error::NoError} + , m_loading_progress{0} +{ + registerMetatypes(); +} + +SpriteReader::~SpriteReader() +{ + _p_delete_device(); +} + +QString SpriteReader::get_file_name() const +{ + QFile *l_file = dynamic_cast(m_device); + return l_file ? l_file->fileName() : QString{}; +} + +QIODevice *SpriteReader::get_device() const +{ + return m_device; +} + +bool SpriteReader::is_valid() const +{ + return get_frame_count() > 0; +} + +QSize SpriteReader::get_sprite_size() const +{ + return QSize{}; +} + +int SpriteReader::get_frame_count() const +{ + return 0; +} + +SpriteFrame SpriteReader::get_frame(int p_number) +{ + Q_UNUSED(p_number); + return SpriteFrame{}; +} + +QVector SpriteReader::get_frame_list() +{ + return QVector{}; +} + +SpriteReader::State SpriteReader::get_state() const +{ + return m_state; +} + +bool SpriteReader::is_loaded() const +{ + return m_state == State::FullyLoaded; +} + +int SpriteReader::get_loading_progress() const +{ + return m_loading_progress; +} + +SpriteReader::Error SpriteReader::get_last_error() const +{ + return m_last_error; +} + +void SpriteReader::set_file_name(QString p_file_name) +{ + set_device(new QFile(p_file_name)); + m_own_device = true; + emit file_name_changed(get_file_name()); +} + +void SpriteReader::set_device(QIODevice *p_device) +{ + _p_delete_device(); + m_device = p_device; + m_own_device = false; + set_state(State::NotLoaded); + set_error(Error::NoError); + set_loading_progress(0); + load(); +} + +void SpriteReader::load() +{} + +void SpriteReader::set_state(State p_state) +{ + if (m_state == p_state) + { + return; + } + m_state = p_state; + emit state_changed(m_state); +} + +void SpriteReader::set_loading_progress(int p_progress) +{ + p_progress = qBound(0, p_progress, 100); + if (m_loading_progress == p_progress) + { + return; + } + m_loading_progress = p_progress; + emit loading_progress_changed(m_loading_progress); +} + +void SpriteReader::set_error(Error p_error) +{ + m_last_error = p_error; + if (m_last_error != Error::NoError) + { + emit error(m_last_error); + } +} + +void SpriteReader::_p_delete_device() +{ + if (m_own_device) + { + delete m_device; + m_device = nullptr; + } +} diff --git a/src/mk2/spritereader.h b/src/mk2/spritereader.h new file mode 100644 index 000000000..0a8a14809 --- /dev/null +++ b/src/mk2/spritereader.h @@ -0,0 +1,121 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace mk2 +{ +class SpriteFrame +{ +public: + QImage image; + int delay = 0; + + SpriteFrame(); + ~SpriteFrame(); +}; + +class SpriteReader : public QObject +{ + Q_OBJECT + +public: + using ptr = QSharedPointer; + + enum class State + { + NotLoaded, + FullyLoaded, + }; + Q_ENUM(State) + + enum class Error + { + NoError, + DeviceError, + InvalidDataError, + }; + Q_ENUM(Error) + + static void registerMetatypes(); + + explicit SpriteReader(QObject *parent = nullptr); + virtual ~SpriteReader(); + + QString get_file_name() const; + + QIODevice *get_device() const; + + virtual bool is_valid() const; + + virtual QSize get_sprite_size() const; + + virtual int get_frame_count() const; + + virtual SpriteFrame get_frame(int number); + + virtual QVector get_frame_list(); + + mk2::SpriteReader::State get_state() const; + + bool is_loaded() const; + + int get_loading_progress() const; + + Error get_last_error() const; + +public slots: + void set_file_name(QString file_name); + + void set_device(QIODevice *device); + +signals: + void file_name_changed(QString file_name); + + void state_changed(mk2::SpriteReader::State state); + + void loading_progress_changed(int); + + void error(mk2::SpriteReader::Error error); + +protected: + virtual void load(); + +protected slots: + void set_state(mk2::SpriteReader::State state); + + void set_loading_progress(int percent); + + void set_error(mk2::SpriteReader::Error error); + +private: + QIODevice *m_device; + bool m_own_device; + std::atomic m_state; + std::atomic m_last_error; + std::atomic_int m_loading_progress; + + void _p_delete_device(); +}; +} // namespace mk2 diff --git a/src/mk2/spritereadersynchronizer.cpp b/src/mk2/spritereadersynchronizer.cpp new file mode 100644 index 000000000..0e05a766c --- /dev/null +++ b/src/mk2/spritereadersynchronizer.cpp @@ -0,0 +1,127 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#include "mk2/spritereadersynchronizer.h" + +using namespace mk2; + +SpriteReaderSynchronizer::SpriteReaderSynchronizer(QObject *parent) + : QObject{parent} + , m_waiting{false} + , m_finished{false} + , m_threshold{50} +{} + +SpriteReaderSynchronizer::~SpriteReaderSynchronizer() +{} + +int SpriteReaderSynchronizer::get_threshold() const +{ + return m_threshold; +} + +QVector SpriteReaderSynchronizer::get_reader_list() const +{ + return m_reader_list; +} + +bool SpriteReaderSynchronizer::is_waiting() const +{ + return m_waiting; +} + +bool SpriteReaderSynchronizer::is_finished() const +{ + return m_finished; +} + +void SpriteReaderSynchronizer::set_threshold(int p_threshold) +{ + p_threshold = qBound(0, p_threshold, 100); + if (m_threshold == p_threshold) + { + return; + } + const int l_prev_threshold = m_threshold; + m_threshold = p_threshold; + if (m_threshold < l_prev_threshold) + { + _p_check_progress(); + } +} + +void SpriteReaderSynchronizer::add(mk2::SpriteReader::ptr p_reader) +{ + if (m_reader_list.contains(p_reader)) + { + return; + } + m_finished = false; + m_reader_list.append(p_reader); + connect(p_reader.data(), SIGNAL(loading_progress_changed(int)), this, SLOT(_p_check_progress())); + _p_check_progress(); +} + +void SpriteReaderSynchronizer::clear() +{ + m_waiting = false; + m_finished = false; + for (const mk2::SpriteReader::ptr &i_reader : qAsConst(m_reader_list)) + { + i_reader->disconnect(this); + } + m_reader_list.clear(); +} + +void SpriteReaderSynchronizer::start() +{ + m_waiting = true; + if (m_finished) + { + emit finished(); + } + else + { + _p_check_progress(); + } +} + +void SpriteReaderSynchronizer::_p_check_progress() +{ + if (m_finished || m_reader_list.isEmpty()) + { + return; + } + + for (const mk2::SpriteReader::ptr &i_reader : qAsConst(m_reader_list)) + { + // if the reader is invalid, it's the same as being fully loaded + if (i_reader->is_valid() && i_reader->get_loading_progress() < m_threshold) + { + return; + } + } + + m_finished = true; + if (m_waiting) + { + emit finished(); + } +} diff --git a/src/mk2/spritereadersynchronizer.h b/src/mk2/spritereadersynchronizer.h new file mode 100644 index 000000000..b35042365 --- /dev/null +++ b/src/mk2/spritereadersynchronizer.h @@ -0,0 +1,66 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#pragma once + +#include "mk2/spritereader.h" + +#include + +namespace mk2 +{ +class SpriteReaderSynchronizer : public QObject +{ + Q_OBJECT + +public: + SpriteReaderSynchronizer(QObject *parent = nullptr); + ~SpriteReaderSynchronizer(); + + int get_threshold() const; + + QVector get_reader_list() const; + + bool is_waiting() const; + + bool is_finished() const; + +public slots: + void set_threshold(int percent); + + void add(mk2::SpriteReader::ptr reader); + + void clear(); + + void start(); + +signals: + void finished(); + +private: + QVector m_reader_list; + bool m_waiting; + bool m_finished; + int m_threshold; + +private slots: + void _p_check_progress(); +}; +} // namespace mk2 diff --git a/src/mk2/spriteseekingreader.cpp b/src/mk2/spriteseekingreader.cpp new file mode 100644 index 000000000..2444ee7a4 --- /dev/null +++ b/src/mk2/spriteseekingreader.cpp @@ -0,0 +1,140 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#include "mk2/spriteseekingreader.h" + +using namespace mk2; + +SpriteSeekingReader::SpriteSeekingReader(QObject *parent) + : SpriteReader{parent} + , m_sprite_size(QSize{}) + , m_frame_count{0} + , m_frame_number{-1} +{} + +SpriteSeekingReader::~SpriteSeekingReader() +{} + +QSize SpriteSeekingReader::get_sprite_size() const +{ + return m_sprite_size; +} + +int SpriteSeekingReader::get_frame_count() const +{ + return m_frame_count; +} + +mk2::SpriteFrame SpriteSeekingReader::get_frame(int p_number) +{ + if (!is_valid() || p_number < 0 || p_number >= m_frame_count) + { + return SpriteFrame{}; + } + + if (p_number == m_frame_number) + { + return m_current_frame; + } + + { // check whatever we need to rewind the device or not + const int l_next_frame_number = m_frame_number + 1; + if (p_number < l_next_frame_number) + { + _p_reset_buffer_device(); + } + } + + // seek frame + QImage l_image(m_reader.size(), QImage::Format_ARGB32); + for (; m_frame_number < p_number; ++m_frame_number) + { + m_reader.read(&l_image); + m_current_frame.delay = m_reader.nextImageDelay(); + } + m_current_frame.image = l_image; + + return m_current_frame; +} + +QVector SpriteSeekingReader::get_frame_list() +{ + QVector l_frame_list; + + if (is_valid()) + { + l_frame_list.resize(m_reader.imageCount()); + + // always seek the frame after the current one + int l_next_frame_number = m_frame_number + 1; + for (int i = 0; i < m_frame_count; ++i) + { + l_next_frame_number %= m_frame_count; + l_frame_list.replace(l_next_frame_number, get_frame(l_next_frame_number)); + ++l_next_frame_number; + } + } + + return l_frame_list; +} + +void SpriteSeekingReader::load() +{ + m_raw_data.clear(); + m_sprite_size = QSize{}; + m_frame_count = 0; + + QIODevice *l_device = get_device(); + const int l_prev_pos = l_device->pos(); + if (!l_device->isOpen() && !l_device->open(QIODevice::ReadOnly)) + { + set_error(Error::DeviceError); + return; + } + l_device->seek(0); + m_raw_data = l_device->readAll(); + l_device->seek(l_prev_pos); + _p_reset_buffer_device(); + if (!m_reader.canRead()) + { + set_error(Error::InvalidDataError); + return; + } + m_sprite_size = m_reader.size(); + m_frame_count = m_reader.imageCount(); + m_current_frame = SpriteFrame{}; + set_loading_progress(100); + set_state(State::FullyLoaded); +} + +void SpriteSeekingReader::_p_reset_buffer_device() +{ + m_data_buffer.reset(new QBuffer(&m_raw_data)); + + if (m_data_buffer->open(QIODevice::ReadOnly)) + { + m_reader.setDevice(m_data_buffer.data()); + m_frame_number = -1; + } + else + { + set_error(Error::DeviceError); + } +} diff --git a/src/mk2/spriteseekingreader.h b/src/mk2/spriteseekingreader.h new file mode 100644 index 000000000..2372ecf99 --- /dev/null +++ b/src/mk2/spriteseekingreader.h @@ -0,0 +1,62 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#pragma once + +#include "mk2/spritereader.h" + +#include +#include +#include +#include + +namespace mk2 +{ +class SpriteSeekingReader : public SpriteReader +{ + Q_OBJECT + +public: + explicit SpriteSeekingReader(QObject *parent = nullptr); + virtual ~SpriteSeekingReader(); + + QSize get_sprite_size() const final; + + int get_frame_count() const final; + + SpriteFrame get_frame(int number) final; + + QVector get_frame_list() final; + +protected: + void load() final; + +private: + QByteArray m_raw_data; + QImageReader m_reader; + QScopedPointer m_data_buffer; + QSize m_sprite_size; + int m_frame_count; + int m_frame_number; + SpriteFrame m_current_frame; + + void _p_reset_buffer_device(); +}; +} // namespace mk2 diff --git a/src/mk2/spriteviewer.cpp b/src/mk2/spriteviewer.cpp new file mode 100644 index 000000000..27deab8f4 --- /dev/null +++ b/src/mk2/spriteviewer.cpp @@ -0,0 +1,136 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#include "mk2/spriteviewer.h" +#include "mk2/spritedynamicreader.h" + +#include +#include + +using namespace mk2; + +SpriteViewer::SpriteViewer(QWidget *parent) + : QLabel{parent} + , m_player(new SpritePlayer) +{ + setAlignment(Qt::AlignCenter); + + connect(m_player.get(), SIGNAL(current_frame_changed()), this, SLOT(paint_frame())); + connect(m_player.get(), SIGNAL(file_name_changed(QString)), this, SIGNAL(file_name_changed(QString))); + connect(m_player.get(), SIGNAL(reader_changed()), this, SIGNAL(reader_changed())); + connect(m_player.get(), SIGNAL(started()), this, SIGNAL(started())); + connect(m_player.get(), SIGNAL(finished()), this, SIGNAL(finished())); +} + +SpriteViewer::~SpriteViewer() +{} + +SpritePlayer::ScalingMode SpriteViewer::get_scaling_mode() const +{ + return m_player->get_scaling_mode(); +} + +void SpriteViewer::set_scaling_mode(SpritePlayer::ScalingMode p_scaling_mode) +{ + m_player->set_scaling_mode(p_scaling_mode); +} + +void SpriteViewer::set_play_once(bool p_enabled) +{ + m_player->set_play_once(p_enabled); +} + +void SpriteViewer::set_mirror(bool p_enabled) +{ + m_player->set_mirror(p_enabled); +} + +QString SpriteViewer::get_file_name() const +{ + return m_player->get_file_name(); +} + +void SpriteViewer::set_file_name(QString p_file_name) +{ + m_player->set_file_name(p_file_name); +} + +QIODevice *SpriteViewer::get_device() const +{ + return m_player->get_device(); +} + +void SpriteViewer::set_device(QIODevice *p_device) +{ + m_player->set_device(p_device); +} + +SpriteReader::ptr SpriteViewer::get_reader() const +{ + return m_player->get_reader(); +} + +void SpriteViewer::set_reader(SpriteReader::ptr p_reader) +{ + m_player->set_reader(p_reader); +} + +SpritePlayer *SpriteViewer::get_player() const +{ + return m_player.get(); +} + +bool SpriteViewer::is_valid() const +{ + return m_player->is_valid(); +} + +bool SpriteViewer::is_running() const +{ + return m_player->is_running(); +} + +void SpriteViewer::start() +{ + m_player->start(); +} + +void SpriteViewer::restart() +{ + stop(); + start(); +} + +void SpriteViewer::stop() +{ + m_player->stop(); +} + +void SpriteViewer::resizeEvent(QResizeEvent *p_event) +{ + QLabel::resizeEvent(p_event); + m_player->set_size(p_event->size()); + paint_frame(); +} + +void SpriteViewer::paint_frame() +{ + setPixmap(QPixmap::fromImage(m_player->get_current_frame())); +} diff --git a/src/mk2/spriteviewer.h b/src/mk2/spriteviewer.h new file mode 100644 index 000000000..e176160f5 --- /dev/null +++ b/src/mk2/spriteviewer.h @@ -0,0 +1,89 @@ +/************************************************************************** +** +** mk2 +** Copyright (C) 2022 Tricky Leifa +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU Affero General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Affero General Public License for more details. +** +** You should have received a copy of the GNU Affero General Public License +** along with this program. If not, see . +** +**************************************************************************/ + +#pragma once + +#include "spriteplayer.h" +#include "spritereader.h" + +#include +#include + +namespace mk2 +{ +class SpriteViewer : public QLabel +{ + Q_OBJECT + +public: + using ScalingMode = SpritePlayer::ScalingMode; + + SpriteViewer(QWidget *parent = nullptr); + ~SpriteViewer(); + + SpritePlayer::ScalingMode get_scaling_mode() const; + + QString get_file_name() const; + + QIODevice *get_device() const; + + SpriteReader::ptr get_reader() const; + + SpritePlayer *get_player() const; + + bool is_valid() const; + + bool is_running() const; + +public slots: + void set_scaling_mode(SpritePlayer::ScalingMode scaling_mode); + + void set_play_once(bool enabled); + + void set_mirror(bool enabled); + + void set_file_name(QString file_name); + + void set_device(QIODevice *device); + + void set_reader(SpriteReader::ptr reader); + + void start(); + void restart(); + void stop(); + +signals: + void file_name_changed(QString); + + void reader_changed(); + + void started(); + void finished(); + +protected: + void resizeEvent(QResizeEvent *event) final; + +private: + QScopedPointer m_player; + +private slots: + void paint_frame(); +}; +} // namespace mk2 diff --git a/src/path_functions.cpp b/src/path_functions.cpp new file mode 100644 index 000000000..3aa10ff94 --- /dev/null +++ b/src/path_functions.cpp @@ -0,0 +1,223 @@ +#include "aoapplication.h" + +#include "aoconfig.h" +#include "commondefs.h" +#include "courtroom.h" +#include "drpather.h" +#include "file_functions.h" + +#include +#include +#include +#include +#include + +// Copied over from Vanilla. +// As said in the comments there, this is a *super broad* definition. +// Linux is guaranteed to be case-sensitive, however, in case of Mac, it... +// depends. On Mac, it can be toggled. So, should the time ever come to that, +// manually define CASE_SENSITIVE_FILESYSTEM if you're working on a Mac that +// has, well, a case-sensitive filesystem. +#ifdef Q_OS_LINUX +#define CASE_SENSITIVE_FILESYSTEM +#endif + +QString AOApplication::get_base_path() +{ + return DRPather::get_application_path() + "/base/"; +} + +QString AOApplication::get_base_file_path(QString p_file) +{ + return get_base_path() + p_file; +} + +QString AOApplication::get_character_folder_path(QString p_chr) +{ + return get_base_path() + "characters/" + p_chr; +} + +QString AOApplication::get_character_path(QString p_chr, QString p_file) +{ + return get_character_folder_path(p_chr) + "/" + p_file; +} + +QString AOApplication::get_music_folder_path() +{ + const QString l_path = get_base_path() + "sounds/music/"; + return get_case_sensitive_path(l_path); +} + +QString AOApplication::get_music_path(QString p_song) +{ + const QString l_path = get_music_folder_path() + p_song; + return get_case_sensitive_path(l_path); +} + +QString AOApplication::get_background_path(QString p_identifier) +{ + return get_base_path() + "background/" + p_identifier; +} + +QString AOApplication::get_background_dir_path(QString p_identifier) +{ + return get_case_sensitive_path(get_background_path(p_identifier)); +} + +/** + * @brief Returns the 'correct' path for the file given as the parameter by + * trying to match the case of the actual path. + * + * @details This function is mostly used on case-sensitive file systems, like + * ext4, generally used on Linux. On FAT, there is no difference between + * "file" and "FILE". On ext4, those are two different files. This results in + * assets that are detected correctly on Windows not being detected on Linux. + * + * For this reason, the implementation of this function is system-dependent: + * on case-insensitive systems, it just returns the parameter itself. + * + * @param p_file The path whose casing must be checked against the actual + * directory structure. + * + * @return The parameter path with fixed casing. + */ +QString AOApplication::get_case_sensitive_path(QString p_file) +#ifndef CASE_SENSITIVE_FILESYSTEM +{ + return p_file.replace("//", "/"); +} +#else +{ + if (QFile::exists(p_file) || p_file.isEmpty()) + { + return p_file; + } + + const auto l_dir_path = get_case_sensitive_path(QFileInfo(p_file).absolutePath()); + if (l_dir_path.isEmpty()) + { + return p_file; + } + + const QDir l_dir(l_dir_path); + const auto l_file_list = l_dir.entryList(QDir::Files); + const auto l_regex = QRegExp(p_file, Qt::CaseInsensitive, QRegExp::FixedString); + for (auto &i_file : l_file_list) + { + const QString l_file_path = l_dir.absoluteFilePath(i_file); + if (l_regex.exactMatch(l_file_path)) + { + p_file = l_file_path; + break; + } + } + return p_file; +} +#endif + +/** + * @brief Returns the first case-sensitive file that is the combination of one + * of the given root and extensions, or empty string if no combination exists. + * + * @details A root is matched to all given extensions in order before + * continuing to the next root. + * + * @param possible_roots The potential roots the filepath could have. + * Case-insensitive. + * @param possible_exts The potential extensions the filepath could have. + * Case-insensitive. + * + * @return The first case-sensitive root+extension path for which a file + * exists, or an empty string, if not one does. + */ +QString AOApplication::find_asset_path(QStringList p_file_list, QStringList p_extension_list) +{ + for (const QString &i_file : qAsConst(p_file_list)) + { + const QDir l_dir(get_case_sensitive_path(QFileInfo(i_file).absolutePath())); + + for (const QString &i_extension : qAsConst(p_extension_list)) + { + const QString l_file_path = get_case_sensitive_path(l_dir.filePath(i_file + i_extension)); + if (file_exists(l_file_path)) + { + return l_file_path; + } + } + } + + return nullptr; +} + +QString AOApplication::find_asset_path(QStringList p_file_list) +{ + return find_asset_path(p_file_list, QStringList{""}); +} + +QString AOApplication::find_asset_path(QString p_file, QStringList p_extension_list) +{ + return find_asset_path(QStringList{p_file}, p_extension_list); +} + +QString AOApplication::find_asset_path(QString p_file) +{ + return find_asset_path(QStringList{p_file}); +} + +/** + * @brief Returns the first case-sensitive file in the theme folder that is + * of the form name+extension, or empty string if it fails. + * + * @details The p_exts list is browsed in order. A name+extension file is + * searched in order in the following directories before checking the next + * extension: + * 1. The current time of day folder in the current gamemode folder + * 2. The current gamemode folder + * 3. The current time of day folder + * 4. The current theme folder. + * The first path that is matched is the one that is returned. If no file + * is found at all, it returns an empty string. + * + * @param p_name Name of the file to look for. Case-insensitive. + * @param p_exts The potential extensions the filepath could have. + * Case-insensitive. + * + * @return The first case-sensitive root+extension path that corresponds to an + * actual file, or an empty string, if not one does. + */ +QString AOApplication::find_theme_asset_path(QString p_file, QStringList p_extension_list) +{ + QStringList l_path_list; + + // Only add gamemode and/or time of day if non empty. + const QString l_gamemode = + ao_config->is_manual_gamemode_selection_enabled() ? ao_config->manual_gamemode() : ao_config->gamemode(); + const QString l_timeofday = + ao_config->is_manual_timeofday_selection_enabled() ? ao_config->manual_timeofday() : ao_config->timeofday(); + const QString l_theme_root = get_base_path() + "themes/" + ao_config->theme(); + + if (!l_gamemode.isEmpty()) + { + if (!l_timeofday.isEmpty()) + l_path_list.append(l_theme_root + "/gamemodes/" + l_gamemode + "/times/" + l_timeofday + "/" + p_file); + l_path_list.append(l_theme_root + "/gamemodes/" + l_gamemode + "/" + p_file); + } + + if (!l_timeofday.isEmpty()) + l_path_list.append(l_theme_root + "/times/" + l_timeofday + "/" + p_file); + + l_path_list.append(l_theme_root + "/" + p_file); + + // Check if default folder exists. We do this here as it is cheaper than doing it in find_asset_path + // (as we know there should not be capitalization or folder jumping shenanigans here. + const QString l_default_theme_path = get_base_path() + "themes/default/"; + if (dir_exists(l_default_theme_path)) + l_path_list.append(l_default_theme_path + p_file); + + return find_asset_path(l_path_list, p_extension_list); +} + +QString AOApplication::find_theme_asset_path(QString p_file) +{ + return find_theme_asset_path(p_file, QStringList{""}); +} diff --git a/src/server_socket.cpp b/src/server_socket.cpp new file mode 100644 index 000000000..50ed0be1c --- /dev/null +++ b/src/server_socket.cpp @@ -0,0 +1,506 @@ +#include "aoapplication.h" + +#include + +#include "aoconfig.h" +#include "courtroom.h" +#include "debug_functions.h" +#include "drdiscord.h" +#include "drpacket.h" +#include "drserversocket.h" +#include "file_functions.h" +#include "hardware_functions.h" +#include "lobby.h" +#include "version.h" + +void AOApplication::connect_to_server(DRServerInfo p_server) +{ + m_server_socket->connect_to_server(p_server); +} + +void AOApplication::send_server_packet(DRPacket p_packet) +{ + if (!m_server_socket->is_connected()) + { + qDebug() << "Failed to send packet: not connected to server"; + return; + } + qDebug().noquote() << "S/S:" << p_packet.to_string(); + m_server_socket->send_packet(p_packet); +} + +AOApplication::ServerStatus AOApplication::last_server_status() +{ + return m_server_status; +} + +bool AOApplication::joined_server() +{ + return m_server_status == Joined; +} + +void AOApplication::_p_handle_server_state_update(DRServerSocket::ConnectionState p_state) +{ + const ServerStatus l_previous_status = m_server_status; + switch (p_state) + { + case DRServerSocket::NotConnected: + switch (l_previous_status) + { + case Connecting: + m_server_status = TimedOut; + break; + + case Joined: + m_server_status = Disconnected; + if (is_courtroom_constructed) + { + m_courtroom->stop_all_audio(); + call_notice("Disconnected from server."); + construct_lobby(); + destruct_courtroom(); + } + break; + + default: + m_server_status = NotConnected; + break; + } + break; + + case DRServerSocket::Connecting: + m_server_status = Connecting; + break; + + case DRServerSocket::Connected: + m_server_status = Connected; + break; + } + + if (m_server_status != l_previous_status) + { + emit server_status_changed(m_server_status); + } +} + +void AOApplication::_p_handle_server_packet(DRPacket p_packet) +{ + const QString l_header = p_packet.get_header(); + const QStringList l_content = p_packet.get_content(); + + if (l_header != "checkconnection") + qDebug().noquote() << "S/R:" << p_packet.to_string(); + + if (l_header == "decryptor") + { + // This packet is maintained as is for legacy purposes, + // even though its sole argument is no longer used for anything + // productive + if (l_content.size() == 0) + return; + + m_server_client_version = VersionNumber(); + m_server_client_version_status = VersionStatus::NotCompatible; + send_server_packet(DRPacket("HI", {get_hdid()})); + } + else if (l_header == "ID") + { + if (l_content.size() < 2) + return; + + m_client_id = l_content.at(0).toInt(); + m_server_software = l_content.at(1); + + send_server_packet(DRPacket("ID", {"DRO", get_version_string()})); + } + else if (l_header == "CT") + { + if (l_content.size() < 2) + return; + + if (is_courtroom_constructed) + m_courtroom->append_server_chatmessage(l_content.at(0), l_content.at(1)); + } + else if (l_header == "client_version") + { + if (l_content.size() < 3) + return; + + m_server_client_version = VersionNumber(l_content.at(0).toInt(), l_content.at(1).toInt(), l_content.at(2).toInt()); + const VersionNumber l_client_version = get_version_number(); + if (l_client_version == m_server_client_version) + { + m_server_client_version_status = VersionStatus::Ok; + } + else if (l_client_version < m_server_client_version) + { + m_server_client_version_status = VersionStatus::ClientOutdated; + } + else if (l_client_version > m_server_client_version) + { + m_server_client_version_status = VersionStatus::ServerOutdated; + } + } + else if (l_header == "PN") + { + if (l_content.size() < 2) + return; + + m_lobby->set_player_count(l_content.at(0).toInt(), l_content.at(1).toInt()); + } + else if (l_header == "SI") + { + if (l_content.size() != 3) + return; + + m_character_count = l_content.at(0).toInt(); + m_evidence_count = l_content.at(1).toInt(); + m_music_count = l_content.at(2).toInt(); + + if (m_character_count < 1 || m_evidence_count < 0 || m_music_count < 0) + return; + + m_loaded_characters = 0; + m_loaded_evidence = 0; + m_loaded_music = 0; + m_loaded_music_list = false; + m_loaded_area_list = false; + + construct_courtroom(); + + DRServerInfo l_current_server = m_lobby->get_selected_server(); + QString l_window_title = "Danganronpa Online (" + get_version_string() + ")"; + if (!l_current_server.name.isEmpty()) + l_window_title = l_window_title + ": " + l_current_server.to_info(); + m_courtroom->set_window_title(l_window_title); + + m_lobby->show_loading_overlay(); + m_lobby->set_loading_text("Loading"); + m_lobby->set_loading_value(0); + + send_server_packet(DRPacket("RC")); + + dr_discord->set_state(DRDiscord::State::Connected); + dr_discord->set_server_name(l_current_server.to_info()); + } + else if (l_header == "CharsCheck") + { + if (!is_courtroom_constructed) + return; + + QVector l_chr_list = m_courtroom->get_character_list(); + if (l_content.length() != l_chr_list.length()) + { + qWarning() << "Server sent a character list of length " << l_content.length() << "which is different from the expected length " << l_chr_list.length() << "so ignoring it."; + return; + } + + for (int i = 0; i < l_chr_list.length(); ++i) + l_chr_list[i].taken = l_content.at(i) == "-1"; + m_courtroom->set_character_list(l_chr_list); + } + else if (l_header == "SC") + { + if (!is_courtroom_constructed) + return; + + QVector l_chr_list; + for (const QString &i_chr_name : qAsConst(l_content)) + { + char_type l_chr; + l_chr.name = i_chr_name; + l_chr_list.append(std::move(l_chr)); + } + m_courtroom->set_character_list(l_chr_list); + m_loaded_characters = m_character_count; + + if (is_lobby_constructed) + { + m_lobby->set_loading_text("Loading chars:\n" + QString::number(m_loaded_characters) + "/" + QString::number(m_character_count)); + int total_loading_size = m_character_count + m_evidence_count + m_music_count; + int loading_value = (m_loaded_characters / static_cast(total_loading_size)) * 100; + m_lobby->set_loading_value(loading_value); + + send_server_packet(DRPacket("RM")); + } + } + else if (l_header == "SM") // TODO remove block for 1.2.0+ + { + if (!is_courtroom_constructed) + return; + + QStringList l_area_list; + QStringList l_music_list; + + for (int i = 0; i < l_content.length(); ++i) + { + bool l_found_music = false; + + { // look for first song + const QString &i_value = l_content.at(i); + for (const QString &i_ext : audio_extensions(true)) + { + if (!i_value.endsWith(i_ext, Qt::CaseInsensitive)) + continue; + l_found_music = true; + break; + } + } + + if (!l_found_music) + continue; + l_area_list = l_content.mid(0, i - 1); + l_music_list = l_content.mid(i - 1); + break; + } + m_courtroom->set_area_list(l_area_list); + m_courtroom->set_music_list(l_music_list); + + m_loaded_music = m_music_count; + m_lobby->set_loading_text("Loading music:\n" + QString::number(m_loaded_music) + "/" + QString::number(m_music_count)); + int total_loading_size = m_character_count + m_evidence_count + m_music_count; + int loading_value = ((m_loaded_characters + m_loaded_evidence + m_loaded_music) / static_cast(total_loading_size)) * 100; + m_lobby->set_loading_value(loading_value); + send_server_packet(DRPacket("RD")); + } + else if (l_header == "FA") + { + if (!is_courtroom_constructed) + return; + m_courtroom->set_area_list(l_content); + + if (!m_loaded_area_list && is_lobby_constructed) + { + m_lobby->set_loading_text("Loading areas..."); + } + m_loaded_area_list = true; + } + else if (l_header == "FM") + { + if (!is_courtroom_constructed) + return; + m_courtroom->set_music_list(l_content); + + if (!m_loaded_area_list && is_lobby_constructed) + { + m_lobby->set_loading_text("Loading music..."); + send_server_packet(DRPacket("RD")); + } + m_loaded_music_list = true; + } + else if (l_header == "DONE") + { + if (!is_courtroom_constructed) + return; + + m_courtroom->done_received(); + m_server_status = Joined; + emit server_status_changed(m_server_status); + + destruct_lobby(); + } + else if (l_header == "joined_area") + { + if (!is_courtroom_constructed) + return; + + m_courtroom->reset_viewport(); + } + else if (l_header == "BN") + { + if (l_content.size() < 1) + return; + + if (!is_courtroom_constructed) + return; + + DRAreaBackground l_area_bg; + l_area_bg.background = l_content.at(0); + + for (int i = 1; i < l_content.size(); ++i) + { + const QStringList l_tod_data = l_content.at(i).split("|", DR::SkipEmptyParts); + if (l_tod_data.size() < 2) + continue; + l_area_bg.background_tod_map.insert(l_tod_data.at(0), l_tod_data.at(1)); + } + + qDebug() << l_area_bg.background << l_area_bg.background_tod_map; + + m_courtroom->set_background(l_area_bg); + } + else if (l_header == "area_ambient") + { + if (l_content.size() < 1) + return; + + if (!is_courtroom_constructed) + return; + + m_courtroom->set_ambient(l_content.at(0)); + } + else if (l_header == "chat_tick_rate") + { + if (l_content.size() < 1) + return; + if (!is_courtroom_constructed) + return; + m_courtroom->set_tick_rate(l_content.at(0).toInt()); + } + // server accepting char request(CC) packet + else if (l_header == "PV") + { + if (l_content.size() < 3) + return; + + if (is_courtroom_constructed) + m_courtroom->set_character_id(l_content.at(2).toInt()); + } + else if (l_header == "MS") + { + if (is_courtroom_constructed && joined_server()) + m_courtroom->next_chatmessage(l_content); + } + else if (l_header == "ackMS") + { + if (is_courtroom_constructed && joined_server()) + m_courtroom->handle_acknowledged_ms(); + } + else if (l_header == "MC") + { + if (is_courtroom_constructed && joined_server()) + m_courtroom->handle_song(l_content); + } + else if (l_header == "RT") + { + if (l_content.size() < 1) + return; + if (is_courtroom_constructed) + m_courtroom->handle_wtce(l_content.at(0)); + } + else if (l_header == "HP") + { + if (is_courtroom_constructed && l_content.size() > 1) + m_courtroom->set_hp_bar(l_content.at(0).toInt(), l_content.at(1).toInt()); + } + else if (l_header == "KK") + { + if (is_courtroom_constructed && l_content.size() > 0) + { + int f_cid = m_courtroom->get_character_id(); + int remote_cid = l_content.at(0).toInt(); + + if (f_cid != remote_cid && remote_cid != -1) + return; + + call_notice("You have been kicked."); + leave_server(); + construct_lobby(); + destruct_courtroom(); + } + } + else if (l_header == "KB") + { + if (is_courtroom_constructed && l_content.size() > 0) + m_courtroom->set_ban(l_content.at(0).toInt()); + } + else if (l_header == "BD") + { + call_notice("You are banned on this server."); + } + else if (l_header == "ZZ") + { + if (is_courtroom_constructed && l_content.size() > 0) + m_courtroom->mod_called(l_content.at(0)); + } + else if (l_header == "CL") + { + if (is_courtroom_constructed && l_content.size() > 0) + m_courtroom->handle_clock(l_content.at(1)); + } + else if (l_header == "GM") + { + if (l_content.length() < 1) + return; + ao_config->set_gamemode(l_content.at(0)); + } + else if (l_header == "TOD") + { + if (l_content.length() < 1) + return; + ao_config->set_timeofday(l_content.at(0)); + } + else if (l_header == "TR") + { + // Timer resume + if (l_content.size() != 1) + return; + if (!is_courtroom_constructed) + return; + int timer_id = l_content.at(0).toInt(); + m_courtroom->resume_timer(timer_id); + } + else if (l_header == "TST") + { + // Timer set time + if (l_content.size() != 2) + return; + if (!is_courtroom_constructed) + return; + int timer_id = l_content.at(0).toInt(); + int new_time = l_content.at(1).toInt(); + m_courtroom->set_timer_time(timer_id, new_time); + } + else if (l_header == "TSS") + { + // Timer set timeStep length + if (l_content.size() != 2) + return; + if (!is_courtroom_constructed) + return; + int timer_id = l_content.at(0).toInt(); + int timestep_length = l_content.at(1).toInt(); + m_courtroom->set_timer_timestep(timer_id, timestep_length); + } + else if (l_header == "TSF") + { + // Timer set Firing interval + if (l_content.size() != 2) + return; + if (!is_courtroom_constructed) + return; + int timer_id = l_content.at(0).toInt(); + int firing_interval = l_content.at(1).toInt(); + m_courtroom->set_timer_firing(timer_id, firing_interval); + } + else if (l_header == "TP") + { + // Timer pause + if (l_content.size() != 1) + return; + if (!is_courtroom_constructed) + return; + int timer_id = l_content.at(0).toInt(); + m_courtroom->pause_timer(timer_id); + } + else if (l_header == "SP") + { + // Set position + if (l_content.size() != 1) + return; + if (!is_courtroom_constructed) + return; + m_courtroom->set_character_position(l_content.at(0)); + } + else if (l_header == "SN") + { + if (l_content.size() != 1) + return; + if (!is_courtroom_constructed) + return; + const QString &l_showname = l_content.at(0); + if (ao_config->showname() != l_showname) + m_courtroom->ignore_next_showname(); + ao_config->set_showname(l_showname); + } +} diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp new file mode 100644 index 000000000..798d0b795 --- /dev/null +++ b/src/text_file_functions.cpp @@ -0,0 +1,706 @@ +#include "aoapplication.h" + +#include +#include +#include +#include +#include + +#include "aoconfig.h" +#include "commondefs.h" +#include "file_functions.h" +#include "utils.h" + +QStringList AOApplication::get_callwords() +{ + return ao_config->callwords().split(" ", DR::SkipEmptyParts); +} + +QString AOApplication::read_note(QString filename) +{ + QFile note_txt(filename); + + if (!note_txt.open(QIODevice::ReadOnly | QFile::Text)) + { + qDebug() << "Couldn't open" << filename; + return ""; + } + + QTextStream in(¬e_txt); + QString text = in.readAll(); + note_txt.close(); + return text; +} + +void AOApplication::write_note(QString p_text, QString p_file) +{ + QFile f_log(p_file); + if (f_log.open(QIODevice::WriteOnly | QFile::Text)) + { + QTextStream out(&f_log); + + out << p_text; + + f_log.flush(); + f_log.close(); + } +} + +void AOApplication::append_note(QString p_line, QString p_file) +{ + QFile f_log(p_file); + if (f_log.open(QIODevice::WriteOnly | QIODevice::Append)) + { + QTextStream out(&f_log); + + out << p_line << "\r\n"; + + f_log.flush(); + f_log.close(); + } +} + +/** + * @brief Reads p_path and returns the value associated with key + * p_identifier. If the file or key do not exist, return empty. + * + * @param p_identifier Key to look for. + * @param p_path Full path to ini file + * @return Value associated with key, or empty if not found. + */ +QString AOApplication::read_ini(QString p_identifier, QString p_path) +{ + QFile ini; + ini.setFileName(p_path); + if (!ini.open(QIODevice::ReadOnly)) + return ""; + + QTextStream in(&ini); + QString result = ""; + while (!in.atEnd()) + { + QString f_line = in.readLine().trimmed(); + + if (!f_line.startsWith(p_identifier)) + continue; + + QStringList line_elements = f_line.split("="); + + if (line_elements.at(0).trimmed() != p_identifier) + continue; + if (line_elements.size() < 2) + continue; + result = line_elements.at(1).trimmed(); + break; + } + + ini.close(); + return result; +} + +QPoint AOApplication::get_button_spacing(QString p_identifier, QString p_file) +{ + QPoint return_value; + return_value.setX(0); + return_value.setY(0); + + QString f_result = read_theme_ini(p_identifier, p_file); + if (f_result.isEmpty()) + return return_value; + + QStringList sub_line_elements = f_result.split(","); + if (sub_line_elements.size() < 2) + return return_value; + + return_value.setX(sub_line_elements.at(0).toInt()); + return_value.setY(sub_line_elements.at(1).toInt()); + + return return_value; +} + +pos_size_type AOApplication::get_element_dimensions(QString p_identifier, QString p_file) +{ + pos_size_type return_value; + return_value.x = 0; + return_value.y = 0; + return_value.width = -1; + return_value.height = -1; + + QString f_result = read_theme_ini(p_identifier, p_file); + if (f_result.isEmpty()) + return return_value; + + QStringList sub_line_elements = f_result.split(","); + + if (sub_line_elements.size() < 4) + return return_value; + + return_value.x = sub_line_elements.at(0).toInt(); + return_value.y = sub_line_elements.at(1).toInt(); + return_value.width = sub_line_elements.at(2).toInt(); + return_value.height = sub_line_elements.at(3).toInt(); + + return return_value; +} + +int AOApplication::get_font_property(QString p_identifier, QString p_file) +{ + QString f_result = read_theme_ini(p_identifier, p_file); + + if (f_result.isEmpty()) + return 0; + return f_result.toInt(); +} + +std::optional AOApplication::maybe_color(QString p_identifier, QString p_file) +{ + const QString l_raw_color = read_theme_ini(p_identifier, p_file); + if (l_raw_color.isEmpty()) + return std::nullopt; + + const QStringList l_raw_light_list = l_raw_color.split(","); + if (l_raw_light_list.length() < 3) + return std::nullopt; + + QColor r_color; + r_color.setRed(l_raw_light_list.at(0).toInt()); + r_color.setGreen(l_raw_light_list.at(1).toInt()); + r_color.setBlue(l_raw_light_list.at(2).toInt()); + if (l_raw_light_list.length() == 4) + r_color.setAlpha(l_raw_light_list.at(3).toInt()); + if (!r_color.isValid()) + return std::nullopt; + return r_color; +} + +QColor AOApplication::get_color(QString p_identifier, QString p_file) +{ + return maybe_color(p_identifier, p_file).value_or(QColor(127, 127, 127, 127)); +} + +QString AOApplication::get_font_name(QString p_identifier, QString p_file) +{ + QString f_result = read_theme_ini(p_identifier, p_file); + + if (f_result.isEmpty()) + qDebug() << "Failure retrieving font name"; + + return f_result; +} + +QString AOApplication::get_sfx(QString p_identifier) +{ + return read_theme_ini(p_identifier, COURTROOM_SOUNDS_INI); +} + +QString AOApplication::get_stylesheet(QString target_tag, QString p_file) +{ + // File lookup order + // 1. In the theme folder (gamemode-timeofday/main/default), look for + // `p_file`. + + QString path = find_theme_asset_path(p_file); + if (path.isEmpty()) + return ""; + + QFile design_ini(path); + if (!design_ini.open(QIODevice::ReadOnly)) + return ""; + + QTextStream in(&design_ini); + QString f_text; + bool tag_found = false; + + while (!in.atEnd()) + { + QString line = in.readLine(); + if (line.startsWith(target_tag, Qt::CaseInsensitive)) + { + tag_found = true; + continue; + } + + if (tag_found) + { + if ((line.startsWith("[") && line.endsWith("]"))) + break; + f_text.append(line); + } + } + + design_ini.close(); + return f_text; // This is the empty string if no appends took place +} + +QMap AOApplication::get_chatmessage_colors() +{ + QMap color_map = DR::get_default_color_map(); + + // File lookup order + // 1. In the theme folder (gamemode-timeofday/main/default) + + QString path = find_theme_asset_path(COURTROOM_TEXT_COLOR_INI); + if (path.isEmpty()) + { + qInfo().noquote() << QString("[color] theme %1 is missing file: %2, using default colors instead") + .arg(ao_config->theme()) + .arg(COURTROOM_TEXT_COLOR_INI); + return color_map; + } + qInfo().noquote() << QString("[color] loading colors for theme %1").arg(ao_config->theme()); + + QSettings color_settings(path, QSettings::IniFormat); + + QMap color_replacement_map; + for (QString &i_group : color_settings.childGroups()) + { + const QString lower_name = i_group.toLower(); + + color_settings.beginGroup(i_group); + if (!color_settings.contains("code")) + continue; + const QString code = color_settings.value("code").toString().toLower(); + + if (!QColor::isValidColor(code)) + { + qWarning().noquote() << QString("[color] for color %1: color code is invalid: %2").arg(lower_name).arg(code); + continue; + } + + if (color_replacement_map.contains(lower_name)) + qWarning().noquote() << QString("[color] color %1 is already defined, replacing anyway").arg(lower_name); + color_replacement_map[lower_name].code = code.toLower(); + color_settings.endGroup(); + } + + // replace the data in the map we will return + for (DR::Color &i_color : color_map.keys()) + { + DR::ColorInfo &color_info = color_map[i_color]; + const QString lower_name = color_info.name.toLower(); + if (!color_replacement_map.contains(lower_name)) + continue; + color_info.code = color_replacement_map[lower_name].code; + } + + return color_map; +} + +QVector AOApplication::get_highlight_colors() +{ + // File lookup order + // 1. In the theme folder (gamemode-timeofday/main/default), look for + // COURTROOM_INI_CONFIG. + + QString path = find_theme_asset_path(COURTROOM_CONFIG_INI); + if (path.isEmpty()) + return QVector(); + + QFile design_ini(path); + if (!design_ini.open(QIODevice::ReadOnly)) + return QVector(); + + QTextStream in(&design_ini); + bool tag_found = false; + QVector f_vec; + + while (!in.atEnd()) + { + QString line = in.readLine(); + + if (line.startsWith("[HIGHLIGHTS]", Qt::CaseInsensitive)) + { + tag_found = true; + continue; + } + + if (tag_found) + { + if ((line.startsWith("[") && line.endsWith("]"))) + break; + // Syntax + // OpenercharCloserchar = Color, Shown + // Shown is 1 if the character should be displayed in IC, 0 otherwise. + // If not present, assume 1. + QString chars = line.split("=")[0].trimmed(); + QString chars_parameters = line.mid(line.indexOf("=") + 1); + QStringList parameters = chars_parameters.split(","); + for (int i = 0; i < parameters.size(); i++) + parameters[i] = parameters[i].trimmed(); + if (parameters.size() == 1) + parameters.append("1"); + f_vec.append({chars, parameters[0], parameters[1]}); + } + } + + design_ini.close(); + return f_vec; // Could be an empty vector if no appends were made +} + +QString AOApplication::get_spbutton(QString p_tag, int index) +{ + // File lookup order + // 1. In the theme folder (gamemode-timeofday/main/default), look for + // COURTROOM_INI_CONFIG. + + QString path = find_theme_asset_path(COURTROOM_CONFIG_INI); + if (path.isEmpty()) + return ""; + + QFile design_ini(path); + if (!design_ini.open(QIODevice::ReadOnly)) + return ""; + + QTextStream in(&design_ini); + bool tag_found = false; + QString res; + + while (!in.atEnd()) + { + QString line = in.readLine(); + + if (line.startsWith(p_tag, Qt::CaseInsensitive)) + { + tag_found = true; + continue; + } + + if (tag_found) + { + if ((line.startsWith("[") && line.endsWith("]"))) + break; + + QStringList line_contents = line.split("="); + if (line_contents.at(0).trimmed() == QString::number(index)) + res = line_contents.at(1); + } + } + + design_ini.close(); + return res; // Could be the empty string if no matches were found. +} + +QStringList AOApplication::get_effect(int index) +{ + // File lookup order + // 1. In the theme folder (gamemode-timeofday/main/default), look for + // COURTROOM_INI_CONFIG. + + QString path = find_theme_asset_path(COURTROOM_CONFIG_INI); + if (path.isEmpty()) + return QStringList(); + + QFile design_ini(path); + if (!design_ini.open(QIODevice::ReadOnly)) + return QStringList(); + + QTextStream in(&design_ini); + bool tag_found = false; + QStringList res; + + while (!in.atEnd()) + { + QString line = in.readLine(); + + if (line.startsWith("[EFFECTS]", Qt::CaseInsensitive)) + { + tag_found = true; + continue; + } + + if (tag_found) + { + if ((line.startsWith("[") && line.endsWith("]"))) + break; + + QStringList line_contents = line.split("="); + if (line_contents.at(0).trimmed() == QString::number(index)) + res = line_contents.at(1).split(","); + + if (res.size() == 1) + res.append("1"); + } + } + + design_ini.close(); + return res; +} + +QStringList AOApplication::get_sfx_list() +{ + QStringList r_sfx_list; + + QStringList l_file_list; + l_file_list.append(get_base_path() + CONFIG_SOUNDS_INI); + for (const QString &i_chr : get_char_include_tree(get_current_char())) + l_file_list.append(get_character_path(i_chr, CHARACTER_SOUNDS_INI)); + + for (const QString &i_file_path : qAsConst(l_file_list)) + { + QFile l_file(i_file_path); + if (l_file.open(QIODevice::ReadOnly)) + { + QTextStream l_in(&l_file); + while (!l_in.atEnd()) + r_sfx_list.append(l_in.readLine()); + } + } + + // remove blanks + for (int i = 0; i < r_sfx_list.length(); ++i) + { + const QString &l_line = r_sfx_list.at(i); + if (!l_line.trimmed().isEmpty()) + continue; + r_sfx_list.removeAt(i--); + } + + return r_sfx_list; +} + +// returns whatever is to the right of "search_line =" within target_tag and +// terminator_tag, trimmed returns the empty string if the search line couldnt +// be found +QVariant AOApplication::read_char_ini(QString p_chr, QString p_group, QString p_key, QVariant p_def) +{ + QSettings s(get_character_path(p_chr, CHARACTER_CHAR_INI), QSettings::IniFormat); + s.setIniCodec("UTF-8"); + utils::QSettingsKeyFetcher l_fetcher(s); + s.beginGroup(l_fetcher.lookup_group(p_group)); + return s.value(l_fetcher.lookup_value(p_key), p_def); +} + +QVariant AOApplication::read_char_ini(QString p_chr, QString p_group, QString p_key) +{ + return read_char_ini(p_chr, p_group, p_key, QVariant()); +} + +QString AOApplication::get_char_name(QString p_chr) +{ + return read_char_ini(p_chr, "options", "name", p_chr).toString(); +} + +#include + +QStringList AOApplication::get_char_include(QString p_chr) +{ + QStringList r_list; + + QStringList l_queue{p_chr}; + while (!l_queue.isEmpty()) + { + const QString l_target_chr = l_queue.takeFirst().trimmed(); + if (r_list.contains(l_target_chr) || l_target_chr.isEmpty()) + continue; + r_list.append(l_target_chr); + l_queue.append(read_char_ini(l_target_chr, "options", "include").toStringList()); + } + r_list.removeAll(p_chr); + + return r_list; +} + +QStringList AOApplication::get_char_include_tree(QString p_chr) +{ + QStringList r_list = get_char_include(p_chr); + r_list.prepend(p_chr); + return r_list; +} + +QString AOApplication::get_showname(QString p_chr) +{ + return read_char_ini(p_chr, "options", "showname", p_chr).toString(); +} + +QString AOApplication::get_char_side(QString p_chr) +{ + return read_char_ini(p_chr, "options", "side", "wit").toString(); +} + +QString AOApplication::get_gender(QString p_chr) +{ + return read_char_ini(p_chr, "options", "gender", "male").toString(); +} + +QString AOApplication::get_chat(QString p_chr) +{ + return read_char_ini(p_chr, "options", "chat").toString().toLower(); +} + +QString drLookupKey(const QStringList &keyList, const QString &targetKey) +{ + const QString finalTargetKey = targetKey.toLower(); + for (const QString &i_key : qAsConst(keyList)) + if (i_key.toLower() == finalTargetKey) + return i_key; + return targetKey; +} + +QVector AOApplication::get_emote_list(QString p_chr) +{ + QVector r_emote_list; + + QStringList l_chr_list = get_char_include(p_chr); + l_chr_list.append(p_chr); + +#ifdef QT_DEBUG + qDebug().noquote() << QString("Compiling char.ini for character <%1>").arg(p_chr); +#endif + for (const QString &i_chr : l_chr_list) + { + if (!dir_exists(get_character_folder_path(i_chr))) + { + qWarning().noquote() + << QString("Parent character <%1> not found, character <%2> cannot use it.").arg(i_chr, p_chr); + continue; + } +#ifdef QT_DEBUG + qDebug().noquote() << QString("Adding <%1>").arg(i_chr); +#endif + + QSettings l_chrini(get_character_path(i_chr, CHARACTER_CHAR_INI), QSettings::IniFormat); + l_chrini.setIniCodec("UTF-8"); + utils::QSettingsKeyFetcher l_fetcher(l_chrini); + + QStringList l_keys; + { // recover all numbered keys, ignore words + l_chrini.beginGroup(l_fetcher.lookup_group("emotions")); + l_keys = l_chrini.childKeys(); + l_chrini.endGroup(); + + // remove keywords + l_keys.removeAll(drLookupKey(l_keys, "firstmode")); + l_keys.removeAll(drLookupKey(l_keys, "number")); + + // remove all negative and non-numbers + for (int i = 0; i < l_keys.length(); ++i) + { + const QString &i_key = l_keys.at(i); + bool ok = false; + const int l_num = i_key.toInt(&ok); + if (ok && l_num >= 0) + continue; + l_keys.removeAt(i--); + } + + std::stable_sort(l_keys.begin(), l_keys.end(), [](const QString &a, const QString &b) -> bool { + // if 0s are added at the beginning of the key, consider a whole number + if (a.length() < b.length()) + return true; + return a.toInt() < b.toInt(); + }); + } + + for (const QString &i_key : qAsConst(l_keys)) + { + l_chrini.beginGroup(l_fetcher.lookup_group("emotions")); + const QStringList l_emotions = l_chrini.value(i_key).toString().split("#", DR::KeepEmptyParts); + l_chrini.endGroup(); + + if (l_emotions.length() < 4) + { + qWarning().noquote() << QString("Emote <%2> of <%1>; emote is malformed.").arg(i_chr, i_key); + continue; + } + enum EmoteField + { + Comment, + Animation, + Dialog, + Modifier, + DeskModifier, + }; + + DREmote l_emote; + l_emote.key = i_key; + l_emote.character = i_chr; + l_emote.comment = l_emotions.at(Comment); + l_emote.anim = l_emotions.at(Animation); + l_emote.dialog = l_emotions.at(Dialog); + l_emote.modifier = qMax(l_emotions.at(Modifier).toInt(), 0); + if (DeskModifier < l_emotions.length()) + l_emote.desk_modifier = l_emotions.at(DeskModifier).toInt(); + + l_chrini.beginGroup(l_fetcher.lookup_group("soundn")); + l_emote.sound_file = l_chrini.value(i_key).toString(); + l_chrini.endGroup(); + + l_chrini.beginGroup(l_fetcher.lookup_group("soundd")); + if (l_chrini.contains(i_key)) + { + l_emote.sound_delay = l_chrini.value(i_key).toInt(); + } + else + { + l_chrini.endGroup(); + l_chrini.beginGroup(l_fetcher.lookup_group("soundt")); + l_emote.sound_delay = l_chrini.value(i_key).toInt() * 60; + } + l_chrini.endGroup(); + l_emote.sound_delay = qMax(0, l_emote.sound_delay); + + l_chrini.beginGroup(l_fetcher.lookup_group("videos")); + l_emote.video_file = l_chrini.value(i_key).toString(); + l_chrini.endGroup(); + + // add the emote + r_emote_list.append(l_emote); + } + } + + return r_emote_list; +} + +QStringList AOApplication::get_effect_offset(QString p_chr, int p_effect) +{ + QStringList r_offset = read_char_ini(p_chr, "offsets", QString::number(p_effect)).toString().split(","); + while (r_offset.length() < 2) + r_offset.append(nullptr); + return r_offset; +} + +QStringList AOApplication::get_overlay(QString p_chr, int p_overlay) +{ + QStringList r_overlay = read_char_ini(p_chr, "overlay", QString::number(p_overlay)).toString().split("#"); + while (r_overlay.length() < 2) + r_overlay.append(nullptr); + return r_overlay; +} + +/** + * @brief Searches p_file in theme folder and returns the value associated + * with key p_identifier. If the file or key do not exist, return empty. + * + * @details p_file is looked for in the following directories. The earliest + * directory where it is found is the one that is considered. + * 1. The current time of day folder in the current gamemode folder + * 2. The current gamemode folder + * 3. The current time of day folder + * 4. The current theme folder. + * 5. The default theme folder. + * + * @param p_identifier Key to look for. + * @param p_file Name of file+ini to look for. + * @return Value associated with key, or empty if not found. + */ +QString AOApplication::read_theme_ini(QString p_identifier, QString p_file) +{ + // File lookup order + // 1. In the theme folder (gamemode-timeofday/main/default), look for + // `p_identifier`. + QString path = find_theme_asset_path(p_file); + if (path.isEmpty()) + return ""; + + return read_ini(p_identifier, path); // may be an empty string +} + +bool AOApplication::read_theme_ini_bool(QString p_identifier, QString p_file) +{ + return read_theme_ini(p_identifier, p_file) == "true"; +} + +int AOApplication::read_theme_ini_int(QString p_identifier, QString p_file) +{ + return read_theme_ini(p_identifier, p_file).toInt(); +} diff --git a/src/theme.cpp b/src/theme.cpp new file mode 100644 index 000000000..ea5061c73 --- /dev/null +++ b/src/theme.cpp @@ -0,0 +1,147 @@ +#include "theme.h" + +// std +#include + +// qt +#include +#include +#include + +// src +#include "aoapplication.h" +#include "datatypes.h" +#include "drstickerviewer.h" +#include "drtextedit.h" + +void set_size_and_pos(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) +{ + pos_size_type design_ini_result = ao_app->get_element_dimensions(p_identifier, p_ini_file); + + if (design_ini_result.width < 0 || design_ini_result.height < 0) + { + qDebug() << "W: could not find" << p_identifier << "in " << p_ini_file; + // Don't hide, as some widgets don't have a built-in way of reappearing again. + p_widget->move(0, 0); + p_widget->resize(0, 0); + } + else + { + p_widget->move(design_ini_result.x, design_ini_result.y); + p_widget->resize(design_ini_result.width, design_ini_result.height); + } +} + +void set_text_alignment_or_default(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app, + std::string p_property, Qt::Alignment p_default_horizontal, + Qt::Alignment p_default_vertical) +{ + const QStringList l_values = + ao_app->read_theme_ini(p_identifier + "_align", p_ini_file).split(",", DR::SkipEmptyParts); + if (!p_widget->property(p_property.c_str()).isValid()) + return; + p_widget->setProperty(p_property.c_str(), + QVariant(QHash{ + {"left", Qt::AlignLeft}, {"center", Qt::AlignHCenter}, {"right", Qt::AlignRight}} + .value(l_values.value(0).trimmed().toLower(), p_default_horizontal) | + QHash{ + {"top", Qt::AlignTop}, {"center", Qt::AlignVCenter}, {"bottom", Qt::AlignBottom}} + .value(l_values.value(1).trimmed().toLower(), p_default_vertical))); +} + +void set_text_alignment(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) +{ + set_text_alignment_or_default(p_widget, p_identifier, p_ini_file, ao_app, "alignment", Qt::AlignLeft, + Qt::AlignVCenter); +} + +void set_font(QWidget *p_widget, QString p_identifier, QString ini_file, AOApplication *ao_app) +{ + QString class_name = p_widget->metaObject()->className(); + + QFont l_font; + // Font priority + // 1. "font_" + p_identifier + // 2. "font_default" + // 3. System font + QFontDatabase font_database; + QString font_name = ao_app->get_font_name("font_" + p_identifier, ini_file); + if (!font_database.families().contains(font_name)) + { + font_name = ao_app->get_font_name("font_default", ini_file); + } + if (!font_name.isEmpty()) + { + l_font.setFamily(font_name); + } + + int f_weight = ao_app->get_font_property(p_identifier, ini_file); + l_font.setPointSize(f_weight); + + bool is_bold = ao_app->get_font_property(p_identifier + "_bold", ini_file) == 1; + l_font.setBold(is_bold); + + p_widget->setFont(l_font); + + const QColor l_font_color = ao_app->get_color(p_identifier + "_color", ini_file); + QString style_sheet_string = class_name + " { " + "background-color: rgba(0, 0, 0, 0);\n" + + "color: " + l_font_color.name(QColor::HexArgb) + ";\n" + (is_bold ? "font: bold;" : "") + + "}"; + p_widget->setStyleSheet(style_sheet_string); +} + +void set_drtextedit_font(DRTextEdit *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) +{ + set_font(p_widget, p_identifier, p_ini_file, ao_app); + + // Do outlines + bool outline = (ao_app->get_font_property(p_identifier + "_outline", p_ini_file) == 1); + p_widget->set_outline(outline); + + // alignment + set_text_alignment_or_default(p_widget, p_identifier, p_ini_file, ao_app, "text_alignment", Qt::AlignLeft, + Qt::AlignTop); +} + +/** + * @brief set_stylesheet + * @param p_widget The widget to apply the stylesheet to + * @param p_identifier The identifier within the stylesheet + * @param p_ini_file The stylesheet file (glorified ini) + * @param ao_app AOApplication shenanigan + * @return Return true if any data (even if invalid) was applied to the widget + */ +bool set_stylesheet(QWidget *p_widget, QString p_identifier, QString p_ini_file, AOApplication *ao_app) +{ + const QString p_style = ao_app->get_stylesheet(p_identifier, p_ini_file); + p_widget->setStyleSheet(p_style); + return !p_style.isEmpty(); +} + +/** + * @brief Center the widget on the screen it's resting in. + * @param widget The widget to center. + */ +void center_widget_to_screen(QWidget *p_widget) +{ + if (!p_widget || p_widget->parentWidget()) + return; + + QScreen *screen = QApplication::screenAt(p_widget->pos()); + if (screen == nullptr) + return; + QRect screen_geometry = screen->geometry(); + int x = (screen_geometry.width() - p_widget->width()) / 2; + int y = (screen_geometry.height() - p_widget->height()) / 2; + p_widget->move(x, y); +} + +void set_sticker_play_once(DRStickerViewer *p_sticker, QString p_identifier, QString p_ini_file, AOApplication *ao_app) +{ + const bool l_play_once = ao_app->read_theme_ini_bool(p_identifier + "_play_once", p_ini_file); + p_sticker->set_play_once(l_play_once); + if (!p_sticker->is_running()) + { + p_sticker->start(); + } +} diff --git a/src/theme.h b/src/theme.h new file mode 100644 index 000000000..fa892134e --- /dev/null +++ b/src/theme.h @@ -0,0 +1,19 @@ +#pragma once + +// src +class AOApplication; +class DRStickerViewer; +class DRTextEdit; + +// qt +class QLineEdit; +class QString; +class QWidget; + +void set_size_and_pos(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); +void set_text_alignment(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); +void set_font(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); +void set_drtextedit_font(DRTextEdit *widget, QString identifier, QString ini_file, AOApplication *ao_app); +bool set_stylesheet(QWidget *widget, QString identifier, QString ini_file, AOApplication *ao_app); +void center_widget_to_screen(QWidget *widget); +void set_sticker_play_once(DRStickerViewer *sticker, QString identifier, QString ini_file, AOApplication *ao_app); diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 000000000..096e8063c --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,28 @@ +#include "utils.h" + +#include + +using namespace utils; + +static QString lookupKey(const QStringList &p_key_list, const QString &p_target_key) +{ + const QString l_lower_target_key = p_target_key.toLower(); + for (const QString &i_key : qAsConst(p_key_list)) + if (i_key.toLower() == l_lower_target_key) + return i_key; + return p_target_key; +} + +QSettingsKeyFetcher::QSettingsKeyFetcher(QSettings &settings) + : m_settings(settings) +{} + +QString QSettingsKeyFetcher::lookup_group(QString p_name) +{ + return lookupKey(m_settings.childGroups(), p_name); +} + +QString QSettingsKeyFetcher::lookup_value(QString p_name) +{ + return lookupKey(m_settings.childKeys(), p_name); +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 000000000..5031a82a4 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,21 @@ +#pragma once + +class QSettings; +class QString; + +namespace utils +{ + +class QSettingsKeyFetcher +{ +public: + explicit QSettingsKeyFetcher(QSettings &settings); + + QString lookup_group(QString key_name); + QString lookup_value(QString key_name); + +private: + QSettings &m_settings; +}; + +} // namespace utils diff --git a/src/version.cpp b/src/version.cpp new file mode 100644 index 000000000..ccebff63a --- /dev/null +++ b/src/version.cpp @@ -0,0 +1,100 @@ +#include "version.h" + +#include +#include +#include + +#include + +#include "datatypes.h" + +int get_release_version() +{ + return 1; +} + +int get_major_version() +{ + return 2; +} + +int get_minor_version() +{ + return 3; +} + +VersionNumber get_version_number() +{ + return VersionNumber(get_release_version(), get_major_version(), get_minor_version()); +} + +QString get_post_version() +{ + return ""; +} + +QString get_version_string() +{ + QString l_version = get_version_number().to_string(); + + const QString l_post = get_post_version(); + if (!l_post.isEmpty()) + { + l_version += QString("+" + l_post); + } + + return l_version; +} + +QString get_resource_file_text(QString filename) +{ + QString data; + + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) + { + return ""; + } + data = file.readAll(); + file.close(); + return data; +} + +QString get_about_message() +{ + const bool hasApng = QImageReader::supportedImageFormats().contains("apng"); + const QString git_branch = get_resource_file_text(":/res/git/git_branch.txt"); + const QString git_hash = get_resource_file_text(":/res/git/git_hash.txt"); + + QString msg = QString("

Danganronpa Online

" + "version: %1" + "

Source code: " + "" + "https://github.com/Chrezm/DRO-Client" + "

Development:
" + "Cerapter, Elf, Iuvee, Tricky Leifa, Ziella" + "

Based on Attorney Online 2:
" + "" + "https://github.com/AttorneyOnline/AO2-Client" + "

Running on Qt version %2 with the BASS %3 audio engine.
" + "APNG plugin loaded: %4" + "

Built on %5 %6
") + .arg(get_version_string()) + .arg(QLatin1String(QT_VERSION_STR)) + .arg(QLatin1String(BASSVERSIONTEXT)) + .arg(hasApng ? "Yes" : "No") + .arg(QLatin1String(__DATE__)) + .arg(QLatin1String(__TIME__)); + + if (git_branch.isEmpty()) + msg += QString("No git branch information available.
"); + else + msg += QString("Built from git branch %1
").arg(git_branch); + + if (git_hash.isEmpty()) + msg += QString("No git hash information available."); + else + msg += QString("Hash of the latest commit: %1").arg(git_hash); + + return msg; +} diff --git a/src/version.h b/src/version.h new file mode 100644 index 000000000..9e80e59b3 --- /dev/null +++ b/src/version.h @@ -0,0 +1,13 @@ +#pragma once + +class QString; + +class VersionNumber; + +int get_release_version(); +int get_major_version(); +int get_minor_version(); +VersionNumber get_version_number(); +QString get_post_version(); +QString get_version_string(); +QString get_about_message(); diff --git a/text_file_functions.cpp b/text_file_functions.cpp deleted file mode 100644 index 52676fc12..000000000 --- a/text_file_functions.cpp +++ /dev/null @@ -1,945 +0,0 @@ -#include "aoapplication.h" - -#include "file_functions.h" - -#include -#include -#include -#include -#include - -QString AOApplication::read_config(QString searchline) -{ - QString return_value = ""; - - QFile config_file(get_base_path() + "config.ini"); - if (!config_file.open(QIODevice::ReadOnly)) - return return_value; - - QTextStream in(&config_file); - - while(!in.atEnd()) - { - QString f_line = in.readLine().trimmed(); - - if (!f_line.startsWith(searchline)) - continue; - - QStringList line_elements = f_line.split("="); - - if (line_elements.at(0).trimmed() != searchline) - continue; - - if (line_elements.size() < 2) - continue; - - return_value = line_elements.at(1).trimmed(); - break; - } - - config_file.close(); - - return return_value; -} - -QString AOApplication::read_theme() -{ - QString result = read_config("theme"); - - if (result == "") - return "default"; - else - return result; -} - -int AOApplication::read_blip_rate() -{ - QString result = read_config("blip_rate"); - - //note: the empty string converted to int will return 0 - if (result.toInt() <= 0) - return 1; - else - return result.toInt(); -} - -int AOApplication::get_default_music() -{ - QString f_result = read_config("default_music"); - - if (f_result == "") - return 50; - else return f_result.toInt(); -} - -int AOApplication::get_default_sfx() -{ - QString f_result = read_config("default_sfx"); - - if (f_result == "") - return 50; - else return f_result.toInt(); -} - -int AOApplication::get_default_blip() -{ - QString f_result = read_config("default_blip"); - - if (f_result == "") - return 50; - else return f_result.toInt(); -} - -QStringList AOApplication::get_call_words() -{ - QStringList return_value; - - QFile callwords_ini; - - callwords_ini.setFileName(get_base_path() + "callwords.ini"); - - if (!callwords_ini.open(QIODevice::ReadOnly)) - return return_value; - - QTextStream in(&callwords_ini); - - while (!in.atEnd()) - { - QString line = in.readLine(); - return_value.append(line); - } - - return return_value; -} - -void AOApplication::write_theme(QString theme) -{ - QString filename = get_base_path() + "config.ini"; - QFile config_file(filename); - - if(!config_file.open(QIODevice::ReadOnly | QIODevice::Text)) - { - qDebug() << "Couldn't open config.ini"; - return; - } - - QTextStream in(&config_file); - - QByteArray t = ""; - - while(!in.atEnd()) - { - QString f_line = in.readLine(); - if(!f_line.startsWith("theme")) - { - t += f_line + "\n"; - continue; - } - - QStringList line_elements = f_line.split("="); - - if(line_elements.at(0).trimmed() != "theme") - { - t += f_line + "\n"; - continue; - } - - if(line_elements.size() < 2) - { - t += f_line +"\n"; - continue; - } - t += "theme = " + theme + "\n"; - } - config_file.close(); - - QFile ex(filename); - if(!ex.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) - { - qDebug() << "Couldn't open config.ini"; - return; - } - - ex.write(t); - ex.close(); -} - -QString AOApplication::read_note(QString filename) -{ - QFile note_txt(filename); - - if(!note_txt.open(QIODevice::ReadOnly | QFile::Text)) - { - qDebug() << "Couldn't open" << filename; - return ""; - } - - QTextStream in(¬e_txt); - QString text = in.readAll(); - note_txt.close(); - return text; -} - -void AOApplication::write_note(QString p_text, QString p_file) -{ - QFile f_log(p_file); - if(f_log.open(QIODevice::WriteOnly | QFile::Text)) - { - QTextStream out(&f_log); - - out << p_text; - - f_log.flush(); - f_log.close(); - } -} - -void AOApplication::append_note(QString p_line, QString p_file) -{ - QFile f_log(p_file); - if(f_log.open(QIODevice::WriteOnly | QIODevice::Append)) - { - QTextStream out(&f_log); - - out << p_line << "\r\n"; - - f_log.flush(); - f_log.close(); - } -} - -void AOApplication::write_to_serverlist_txt(QString p_line) -{ - QFile serverlist_txt; - QString serverlist_txt_path = get_base_path() + "serverlist.txt"; - - serverlist_txt.setFileName(serverlist_txt_path); - - if (!serverlist_txt.open(QIODevice::WriteOnly | QIODevice::Append)) - { - return; - } - - QTextStream out(&serverlist_txt); - - out << "\r\n" << p_line; - - serverlist_txt.close(); -} - -QVector AOApplication::read_serverlist_txt() -{ - QVector f_server_list; - - QFile serverlist_txt; - QString serverlist_txt_path = get_base_path() + "serverlist.txt"; - - serverlist_txt.setFileName(serverlist_txt_path); - - if (!serverlist_txt.open(QIODevice::ReadOnly)) - { - return f_server_list; - } - - QTextStream in(&serverlist_txt); - - while(!in.atEnd()) - { - QString line = in.readLine(); - server_type f_server; - QStringList line_contents = line.split(":"); - - if (line_contents.size() < 3) - continue; - - f_server.ip = line_contents.at(0); - f_server.port = line_contents.at(1).toInt(); - f_server.name = line_contents.at(2); - f_server.desc = ""; - - f_server_list.append(f_server); - } - - return f_server_list; -} - -QString AOApplication::read_design_ini(QString p_identifier, QString p_design_path) -{ - QFile design_ini; - - design_ini.setFileName(p_design_path); - - if (!design_ini.open(QIODevice::ReadOnly)) - { - return ""; - } - QTextStream in(&design_ini); - - QString result = ""; - - while (!in.atEnd()) - { - QString f_line = in.readLine().trimmed(); - - if (!f_line.startsWith(p_identifier)) - continue; - - QStringList line_elements = f_line.split("="); - - if (line_elements.at(0).trimmed() != p_identifier) - continue; - - if (line_elements.size() < 2) - continue; - - result = line_elements.at(1).trimmed(); - break; - } - - design_ini.close(); - - return result; -} - -int AOApplication::get_design_ini_value(QString p_identifier, QString p_file) -{ - QString design_ini_path = get_theme_path() + p_file; - QString result = read_design_ini(p_identifier, design_ini_path); - int r = result.toInt(); - if (result == "") r = 0; - return r; -} - -QPoint AOApplication::get_button_spacing(QString p_identifier, QString p_file) -{ - QString design_ini_path = get_theme_path() + p_file; - QString default_path = get_default_theme_path() + p_file; - QString f_result = read_design_ini(p_identifier, design_ini_path); - - QPoint return_value; - - return_value.setX(0); - return_value.setY(0); - - if (f_result == "") - { - f_result = read_design_ini(p_identifier, default_path); - - if (f_result == "") - return return_value; - } - - QStringList sub_line_elements = f_result.split(","); - - if (sub_line_elements.size() < 2) - return return_value; - - return_value.setX(sub_line_elements.at(0).toInt()); - return_value.setY(sub_line_elements.at(1).toInt()); - - return return_value; -} - -pos_size_type AOApplication::get_element_dimensions(QString p_identifier, QString p_file) -{ - QString design_ini_path = get_theme_path() + p_file; - QString default_path = get_default_theme_path() + p_file; - QString f_result = read_design_ini(p_identifier, design_ini_path); - - pos_size_type return_value; - - return_value.x = 0; - return_value.y = 0; - return_value.width = -1; - return_value.height = -1; - - if (f_result == "") - { - f_result = read_design_ini(p_identifier, default_path); - - if (f_result == "") - return return_value; - } - - QStringList sub_line_elements = f_result.split(","); - - if (sub_line_elements.size() < 4) - return return_value; - - return_value.x = sub_line_elements.at(0).toInt(); - return_value.y = sub_line_elements.at(1).toInt(); - return_value.width = sub_line_elements.at(2).toInt(); - return_value.height = sub_line_elements.at(3).toInt(); - - return return_value; -} - -int AOApplication::get_font_size(QString p_identifier, QString p_file) -{ - QString design_ini_path = get_theme_path() + p_file; - QString default_path = get_default_theme_path() + p_file; - QString f_result = read_design_ini(p_identifier, design_ini_path); - - if (f_result == "") - { - f_result = read_design_ini(p_identifier, default_path); - - if (f_result == "") - return 10; - } - - return f_result.toInt(); -} - -QColor AOApplication::get_color(QString p_identifier, QString p_file) -{ - QString design_ini_path = get_theme_path() + p_file; - QString default_path = get_default_theme_path() + p_file; - QString f_result = read_design_ini(p_identifier, design_ini_path); - - QColor return_color(255, 255, 255); - - if (f_result == "") - { - f_result = read_design_ini(p_identifier, default_path); - - if (f_result == "") - return return_color; - } - - QStringList color_list = f_result.split(","); - - if (color_list.size() < 3) - return return_color; - - return_color.setRed(color_list.at(0).toInt()); - return_color.setGreen(color_list.at(1).toInt()); - return_color.setBlue(color_list.at(2).toInt()); - - return return_color; -} - -QString AOApplication::get_font_name(QString p_identifier, QString p_file) -{ - QString design_ini_path = get_theme_path() + p_file; - QString default_path = get_default_theme_path() + p_file; - QString f_result = read_design_ini(p_identifier, design_ini_path); - - if(f_result == "") - { - f_result = read_design_ini(p_identifier, default_path); - - if(f_result == "") - { - qDebug() << "Failure retreiving font name"; - return f_result; - } - } - - return f_result; -} - -QString AOApplication::get_stylesheet(QString target_tag, QString p_file) -{ - QString design_ini_path = get_theme_path() + p_file; - - QFile design_ini; - - design_ini.setFileName(design_ini_path); - - if(!design_ini.open(QIODevice::ReadOnly)) - return ""; - - QTextStream in(&design_ini); - - QString f_text; - - bool tag_found = false; - - while(!in.atEnd()) - { - QString line = in.readLine(); - - if (line.startsWith(target_tag, Qt::CaseInsensitive)) - { - tag_found = true; - continue; - } - - if(tag_found) - { - if((line.startsWith("[") && line.endsWith("]"))) - break; - f_text.append(line); - } - } - - design_ini.close(); - return f_text; -} - -QVector AOApplication::get_highlight_color() -{ - QString design_ini_path = get_theme_path() + "courtroom_config.ini"; - - QFile design_ini; - - design_ini.setFileName(design_ini_path); - - QVector f_vec; - - if(!design_ini.open(QIODevice::ReadOnly)) - return f_vec; - - QTextStream in(&design_ini); - - bool tag_found = false; - - while(!in.atEnd()) - { - QString line = in.readLine(); - - if (line.startsWith("[HIGHLIGHTS]", Qt::CaseInsensitive)) - { - tag_found = true; - continue; - } - - if(tag_found) - { - if((line.startsWith("[") && line.endsWith("]"))) - break; - f_vec.append(line.split("=")); - } - } - - design_ini.close(); - return f_vec; -} - -QString AOApplication::get_spbutton(QString p_tag, int index) -{ - QString design_ini_path = get_theme_path() + "courtroom_config.ini"; - - QFile design_ini; - - design_ini.setFileName(design_ini_path); - - QString res = ""; - - if(!design_ini.open(QIODevice::ReadOnly)) - return res; - - QTextStream in(&design_ini); - - bool tag_found = false; - - while(!in.atEnd()) - { - QString line = in.readLine(); - - if(line.startsWith(p_tag, Qt::CaseInsensitive)) - { - tag_found = true; - continue; - } - - if(tag_found) - { - if((line.startsWith("[") && line.endsWith("]"))) - break; - - QStringList line_contents = line.split("="); - if(line_contents.at(0).trimmed() == QString::number(index)) - res = line_contents.at(1); - } - } - - design_ini.close(); - return res; -} - -QStringList AOApplication::get_effect(int index) -{ - QString design_ini_path = get_theme_path() + "courtroom_config.ini"; - - QFile design_ini; - - design_ini.setFileName(design_ini_path); - - QStringList res; - - if(!design_ini.open(QIODevice::ReadOnly)) - return res; - - QTextStream in(&design_ini); - - bool tag_found = false; - - while(!in.atEnd()) - { - QString line = in.readLine(); - - if(line.startsWith("[EFFECTS]", Qt::CaseInsensitive)) - { - tag_found = true; - continue; - } - - if(tag_found) - { - if((line.startsWith("[") && line.endsWith("]"))) - break; - - QStringList line_contents = line.split("="); - if(line_contents.at(0).trimmed() == QString::number(index)) - res = line_contents.at(1).split(","); - - if(res.size() == 1) - res.append("1"); - } - } - - design_ini.close(); - return res; -} - -QString AOApplication::get_sfx(QString p_identifier) -{ - QString design_ini_path = get_theme_path() + "courtroom_sounds.ini"; - QString default_path = get_default_theme_path() + "courtroom_sounds.ini"; - QString f_result = read_design_ini(p_identifier, design_ini_path); - - QString return_sfx = ""; - - if (f_result == "") - { - f_result = read_design_ini(p_identifier, default_path); - - if (f_result == "") - return return_sfx; - } - - return_sfx = f_result; - - return return_sfx; -} - -QStringList AOApplication::get_sfx_list() -{ - QStringList return_value; - - QFile base_sfx_list_ini; - - QFile char_sfx_list_ini; - - base_sfx_list_ini.setFileName(get_base_path() + "configs/sounds.ini"); - char_sfx_list_ini.setFileName(get_character_path(get_current_char()) + "sounds.ini"); - - if (!char_sfx_list_ini.open(QIODevice::ReadOnly) && !base_sfx_list_ini.open(QIODevice::ReadOnly)) - { - return return_value; - } - - QTextStream in_a(&base_sfx_list_ini); - QTextStream in_b(&char_sfx_list_ini); - - while (!in_a.atEnd()) - { - QString line = in_a.readLine(); - return_value.append(line); - } - - while (!in_b.atEnd()) - { - QString line = in_b.readLine(); - return_value.append(line); - } - - return return_value; -} - -//returns whatever is to the right of "search_line =" within target_tag and terminator_tag, trimmed -//returns the empty string if the search line couldnt be found -QString AOApplication::read_char_ini(QString p_char, QString p_search_line, QString target_tag, QString terminator_tag) -{ - QString char_ini_path = get_character_path(p_char) + "char.ini"; - - QFile char_ini; - - char_ini.setFileName(char_ini_path); - - if (!char_ini.open(QIODevice::ReadOnly)) - return ""; - - QTextStream in(&char_ini); - - bool tag_found = false; - - while(!in.atEnd()) - { - QString line = in.readLine(); - - if (QString::compare(line, terminator_tag, Qt::CaseInsensitive) == 0) - break; - - if (line.startsWith(target_tag, Qt::CaseInsensitive)) - { - tag_found = true; - continue; - } - - if (!line.startsWith(p_search_line, Qt::CaseInsensitive)) - continue; - - QStringList line_elements = line.split("="); - - if (QString::compare(line_elements.at(0).trimmed(), p_search_line, Qt::CaseInsensitive) != 0) - continue; - - if (line_elements.size() < 2) - continue; - - if (tag_found) - { - char_ini.close(); - return line_elements.at(1).trimmed(); - } - } - - char_ini.close(); - return ""; -} - -QString AOApplication::get_char_name(QString p_char) -{ - QString f_result = read_char_ini(p_char, "name", "[Options]", "[Time]"); - - if (f_result == "") - return p_char; - else return f_result; -} - -QString AOApplication::get_showname(QString p_char) -{ - QString f_result = read_showname(p_char); - if(f_result == "") - f_result = read_char_ini(p_char, "showname", "[Options]", "[Time]"); - - if (f_result == "") - return p_char; - else return f_result; -} - -QString AOApplication::read_showname(QString p_char) -{ - QString f_filename = get_base_path() + "configs/shownames.ini"; - QFile f_file(f_filename); - if(!f_file.open(QIODevice::ReadOnly)) - { qDebug() << "Error reading" << f_filename; return ""; } - - QTextStream in(&f_file); - while(!in.atEnd()) - { - QString f_line = in.readLine(); - if(!f_line.startsWith(p_char)) - continue; - - QStringList line_elements = f_line.split("="); - if(line_elements.at(0).trimmed() == p_char) - return line_elements.at(1).trimmed(); - } - return ""; -} - -QString AOApplication::get_char_side(QString p_char) -{ - QString f_result = read_char_ini(p_char, "side", "[Options]", "[Time]"); - - if (f_result == "") - return "wit"; - else return f_result; -} - -QString AOApplication::get_gender(QString p_char) -{ - QString f_result = read_char_ini(p_char, "gender", "[Options]", "[Time]"); - - if (f_result == "") - return "male"; - else return f_result; -} - -QString AOApplication::get_chat(QString p_char) -{ - QString f_result = read_char_ini(p_char, "chat", "[Options]", "[Time]"); - - //handling the correct order of chat is a bit complicated, we let the caller do it - return f_result.toLower(); -} - -QString AOApplication::get_char_shouts(QString p_char) -{ - QString f_result = read_char_ini(p_char, "shouts", "[Options]", "[Time]"); - - return f_result.toLower(); -} - -int AOApplication::get_preanim_duration(QString p_char, QString p_emote) -{ - QString f_result = read_char_ini(p_char, p_emote, "[Time]", "[Emotions]"); - - if (f_result == "") - return -1; - else return f_result.toInt(); -} - -int AOApplication::get_ao2_preanim_duration(QString p_char, QString p_emote) -{ - QString f_result = read_char_ini(p_char, "%" + p_emote, "[Time]", "[Emotions]"); - - if (f_result == "") - return -1; - else return f_result.toInt(); -} - -int AOApplication::get_emote_number(QString p_char) -{ - QString f_result = read_char_ini(p_char, "number", "[Emotions]", "[Offsets]"); - - if (f_result == "") - return 0; - else return f_result.toInt(); -} - -QString AOApplication::get_emote_comment(QString p_char, int p_emote) -{ - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); - - QStringList result_contents = f_result.split("#"); - - if (result_contents.size() < 4) - { - qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; - return "normal"; - } - else return result_contents.at(0); -} - -QString AOApplication::get_pre_emote(QString p_char, int p_emote) -{ - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); - - QStringList result_contents = f_result.split("#"); - - if (result_contents.size() < 4) - { - qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; - return ""; - } - else return result_contents.at(1); -} - -QString AOApplication::get_emote(QString p_char, int p_emote) -{ - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); - - QStringList result_contents = f_result.split("#"); - - if (result_contents.size() < 4) - { - qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; - return "normal"; - } - else return result_contents.at(2); -} - -int AOApplication::get_emote_mod(QString p_char, int p_emote) -{ - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); - - QStringList result_contents = f_result.split("#"); - - if (result_contents.size() < 4) - { - qDebug() << "W: misformatted char.ini: " << p_char << ", " << QString::number(p_emote); - return 0; - } - else return result_contents.at(3).toInt(); -} - -int AOApplication::get_desk_mod(QString p_char, int p_emote) -{ - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[Emotions]", "[Offsets]"); - - QStringList result_contents = f_result.split("#"); - - if (result_contents.size() < 5) - return -1; - - QString string_result = result_contents.at(4); - if (string_result == "") - return -1; - - else return string_result.toInt(); -} - -QStringList AOApplication::get_effect_offset(QString p_char, int p_effect) -{ - QStringList f_result = read_char_ini(p_char, QString::number(p_effect), "[Offsets]", "[Overlay]").split(","); - - if (f_result.size() < 2) - return decltype(f_result) {0, 0}; - - return f_result; -} - -QStringList AOApplication::get_overlay(QString p_char, int p_effect) -{ - QStringList f_result = read_char_ini(p_char, QString::number(p_effect), "[Overlay]", "[SoundN]").split("#"); - - if (f_result.size() < 2) - f_result.push_back(""); - - return f_result; -} - -QString AOApplication::get_sfx_name(QString p_char, int p_emote) -{ - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[SoundN]", "[SoundT]"); - - if (f_result == "") - return "1"; - else return f_result; -} - -int AOApplication::get_sfx_delay(QString p_char, int p_emote) -{ - QString f_result = read_char_ini(p_char, QString::number(p_emote + 1), "[SoundT]", "[TextDelay]"); - - if (f_result == "") - return 1; - else return f_result.toInt(); -} - -int AOApplication::get_text_delay(QString p_char, QString p_emote) -{ - QString f_result = read_char_ini(p_char, p_emote, "[TextDelay]", "END_OF_FILE"); - - if (f_result == "") - return -1; - else return f_result.toInt(); -} - -bool AOApplication::get_blank_blip() -{ - QString f_result = read_config("blank_blip"); - - return f_result.startsWith("true"); -} - - - - - -