Merge branch 'master' into feature/timerclock

# Conflicts:
#	include/courtroom.h
This commit is contained in:
Crystalwarrior 2021-01-19 16:04:58 +03:00
commit 1b016ddf91
41 changed files with 5265 additions and 3253 deletions

View File

@ -1,8 +1,11 @@
#ifndef AOAPPLICATION_H
#define AOAPPLICATION_H
#define UNUSED(x) (void)(x)
#include "aopacket.h"
#include "datatypes.h"
#include "demoserver.h"
#include "discord_rich_presence.h"
#include "bass.h"
@ -28,6 +31,8 @@
#include <QTextStream>
#include <QTime>
#include <QElapsedTimer>
class NetworkManager;
class Lobby;
class Courtroom;
@ -129,6 +134,7 @@ public:
QString get_default_theme_path(QString p_file);
QString get_custom_theme_path(QString p_theme, QString p_file);
QString get_character_path(QString p_char, QString p_file);
QString get_misc_path(QString p_misc, QString p_file);
QString get_sounds_path(QString p_file);
QString get_music_path(QString p_song);
QString get_background_path(QString p_file);
@ -223,6 +229,9 @@ public:
// Current wait time between messages for the queue system
int stay_time();
// Returns Minimum amount of time (in miliseconds) that must pass before the next Enter key press will send your IC message. (new behaviour)
int get_chat_ratelimit();
// Returns whether the log should go upwards (new behaviour)
// or downwards (vanilla behaviour).
bool get_log_goes_downwards();
@ -265,6 +274,9 @@ public:
// directory if it doesn't exist.
bool append_to_file(QString p_text, QString p_file, bool make_dir = false);
// Append to the currently open demo file if there is one
void append_to_demofile(QString packet_string);
// Appends the argument string to serverlist.txt
void write_to_serverlist_txt(QString p_line);
@ -294,14 +306,14 @@ public:
// Returns the color with p_identifier from p_file
QColor get_color(QString p_identifier, QString p_file);
// Returns the markdown symbol used for specified p_identifier such as colors
QString get_chat_markdown(QString p_identifier, QString p_file);
// Returns the markup symbol used for specified p_identifier such as colors
QString get_chat_markup(QString p_identifier, QString p_file);
// Returns the color from the misc folder.
QColor get_chat_color(QString p_identifier, QString p_chat);
// Returns the sfx with p_identifier from sounds.ini in the current theme path
QString get_sfx(QString p_identifier);
QString get_sfx(QString p_identifier, QString p_misc="default");
// Figure out if we can opus this or if we should fall back to wav
QString get_sfx_suffix(QString sound_to_check);
@ -337,6 +349,9 @@ public:
// Returns the showname from the ini of p_char
QString get_showname(QString p_char);
// Returns the category of this character
QString get_category(QString p_char);
// Returns the value of chat image from the specific p_char's ini file
QString get_chat(QString p_char);
@ -370,9 +385,9 @@ public:
// t
QString get_effect(QString effect, QString p_char, QString p_folder);
// Return the effect sound associated with the fx_name in the
// misc/effects/<char-defined>/sounds.ini, or theme/effects/sounds.ini.
QString get_effect_sound(QString fx_name, QString p_char);
// Return p_property of fx_name. If p_property is "sound", return
// the value associated with fx_name, otherwise use fx_name + '_' + p_property.
QString get_effect_property(QString fx_name, QString p_char, QString p_property);
// Returns the custom realisation used by the character.
QString get_custom_realization(QString p_char);
@ -422,6 +437,15 @@ public:
// Returns p_char's blips (previously called their "gender")
QString get_blips(QString p_char);
// Get a property of a given emote, or get it from "options" if emote doesn't have it
QString get_emote_property(QString p_char, QString p_emote, QString p_property);
// Return a transformation mode from a string ("smooth" for smooth, anything else for fast)
Qt::TransformationMode get_scaling(QString p_scaling);
// Returns the scaling type for p_miscname
Qt::TransformationMode get_misc_scaling(QString p_miscname);
// ======
// These are all casing-related settings.
// ======
@ -462,6 +486,9 @@ public:
void *user);
static void doBASSreset();
QElapsedTimer demo_timer;
DemoServer* demo_server = nullptr;
private:
const int RELEASE = 2;
const int MAJOR_VERSION = 8;

View File

@ -1,125 +0,0 @@
#ifndef AOCHARMOVIE_H
#define AOCHARMOVIE_H
#include <QDebug>
#include <QElapsedTimer>
#include <QImageReader>
#include <QLabel>
#include <QTimer>
class AOApplication;
class AOCharMovie : public QLabel {
Q_OBJECT
public:
AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app);
// Play a hat.gif - style preanimation
void play_pre(QString p_char, QString p_emote, int duration);
// Play a (b)normal.gif - style animation (talking)
void play_talking(QString p_char, QString p_emote);
// Play an (a)normal.gif - style animation (not talking)
void play_idle(QString p_char, QString p_emote);
// Stop the movie, clearing the image
void stop();
// Set the m_flipped variable to true/false
void set_flipped(bool p_flipped) { m_flipped = p_flipped; }
// Set the movie's playback speed (between 10% and 1000%)
void set_speed(int modifier) { speed = qMax(10, qMin(modifier, 1000)); }
// Move the label itself around
void move(int ax, int ay);
// This is somewhat pointless now as there's no "QMovie" object to resize, aka
// no "combo" to speak of
void combo_resize(int w, int h);
// Return the frame delay adjusted for speed
int get_frame_delay(int delay);
QStringList network_strings;
QString m_char;
QString m_emote;
private:
AOApplication *ao_app;
QVector<QPixmap> movie_frames;
QVector<int> movie_delays;
// Effects such as sfx, screenshakes and realization flashes are stored in
// here. QString entry format: "sfx^[sfx_name]", "shake", "flash". The program
// uses the QVector index as reference.
QVector<QVector<QString>> movie_effects;
QTimer *preanim_timer;
QTimer *ticker;
QString last_path;
QImageReader *m_reader = new QImageReader();
QElapsedTimer actual_time;
// Usually used to turn seconds into milliseconds such as for [Time] tag in
// char.ini
const int time_mod = 60;
// These are the X and Y values before they are fixed based on the sprite's
// width.
int x = 0;
int y = 0;
// These are the width and height values before they are fixed based on the
// sprite's width.
int f_w = 0;
int f_h = 0;
int frame = 0;
int max_frames = 0;
int speed = 100;
bool m_flipped = false;
bool play_once = true;
// Set the movie's image to provided paths, preparing for playback.
void load_image(QString p_char, QString p_emote, QString emote_prefix);
// Start playback of the movie (if animated).
void play();
// Play a frame-specific effect, if there's any defined for that specific
// frame.
void play_frame_effect(int frame);
// Retreive a pixmap adjused for mirroring/aspect ratio shenanigans from a
// provided QImage
QPixmap get_pixmap(QImage image);
// Set the movie's frame to provided pixmap
void set_frame(QPixmap f_pixmap);
// Initialize the frame-specific effects from the char.ini
void load_effects();
// Initialize the frame-specific effects from the provided network_strings,
// this is only initialized if network_strings has size more than 0.
void load_network_effects();
signals:
void done();
void shake();
void flash();
void play_sfx(QString sfx);
private slots:
void preanim_done();
void movie_ticker();
};
#endif // AOCHARMOVIE_H

View File

@ -2,7 +2,7 @@
#define AOEVIDENCEDISPLAY_H
#include "aoapplication.h"
#include "aomovie.h"
#include "aolayer.h"
#include "aosfxplayer.h"
#include <QDebug>
@ -21,7 +21,7 @@ public:
private:
AOApplication *ao_app;
AOMovie *evidence_movie;
InterfaceLayer *evidence_movie;
QLabel *evidence_icon;
AOSfxPlayer *sfx_player;

221
include/aolayer.h Normal file
View File

@ -0,0 +1,221 @@
#ifndef AOLAYER_H
#define AOLAYER_H
#include <QDebug>
#include <QElapsedTimer>
#include <QImageReader>
#include <QLabel>
#include <QTimer>
#include <QBitmap>
class AOApplication;
class AOLayer : public QLabel {
Q_OBJECT
public:
AOLayer(QWidget *p_parent, AOApplication *p_ao_app);
QString filename; // file name without extension, i.e. "witnesstestimony"
int static_duration; // time in ms for static images to be displayed, if
// applicable. set to 0 for infinite
int max_duration; // maximum duration in ms, image will be culled if it is
// exceeded. set this to 0 for infinite duration
bool play_once = false; // Whether to loop this animation or not
bool cull_image = true; // if we're done playing this animation, should we
// hide it? also controls durational culling
Qt::TransformationMode transform_mode = Qt::FastTransformation; // transformation mode to use for this image
bool stretch = false; // Should we stretch/squash this image to fill the screen?
// Set the movie's image to provided paths, preparing for playback.
void start_playback(QString p_image);
void set_play_once(bool p_play_once);
void set_cull_image(bool p_cull_image);
void set_static_duration(int p_static_duration);
void set_max_duration(int p_max_duration);
// Stop the movie, clearing the image
void stop();
// Set the m_flipped variable to true/false
void set_flipped(bool p_flipped) { m_flipped = p_flipped; }
// Set the movie's playback speed (between 10% and 1000%)
void set_speed(int modifier) { speed = qMax(10, qMin(modifier, 1000)); }
// Move the label itself around
void move(int ax, int ay);
// This is somewhat pointless now as there's no "QMovie" object to resize, aka
// no "combo" to speak of
void combo_resize(int w, int h);
// Return the frame delay adjusted for speed
int get_frame_delay(int delay);
// iterate through a list of paths and return the first entry that exists. if
// none exist, return NULL (safe because we check again for existence later)
QString find_image(QList<QString> p_list);
protected:
AOApplication *ao_app;
QVector<QPixmap> movie_frames;
QVector<int> movie_delays;
QTimer *preanim_timer;
QTimer *shfx_timer;
QTimer *ticker;
QString last_path;
QImageReader m_reader;
QElapsedTimer actual_time;
// Usually used to turn seconds into milliseconds such as for [Time] tag in
// char.ini
const int tick_ms = 60;
// These are the X and Y values before they are fixed based on the sprite's
// width.
int x = 0;
int y = 0;
// These are the width and height values before they are fixed based on the
// sprite's width.
int f_w = 0;
int f_h = 0;
int frame = 0;
int max_frames = 0;
int last_max_frames = 0;
int speed = 100;
bool m_flipped = false;
// Are we loading this from the same frame we left off on? TODO: actually make
// this work
bool continuous = false;
// Whether or not to forcibly bypass the simple check done by start_playback
// and use the existent value of continuous instead
bool force_continuous = false;
int duration = 0;
// Start playback of the movie (if animated).
void play();
// Freeze the movie at the current frame.
void freeze();
// Retreive a pixmap adjused for mirroring/aspect ratio shenanigans from a
// provided QImage
QPixmap get_pixmap(QImage image);
// Set the movie's frame to provided pixmap
void set_frame(QPixmap f_pixmap);
signals:
void done();
protected slots:
virtual void preanim_done();
void shfx_timer_done();
virtual void movie_ticker();
};
class BackgroundLayer : public AOLayer {
Q_OBJECT
public:
BackgroundLayer(QWidget *p_parent, AOApplication *p_ao_app);
void load_image(QString p_filename);
};
class ForegroundLayer : public AOLayer {
Q_OBJECT
public:
ForegroundLayer(QWidget *p_parent, AOApplication *p_ao_app);
QString miscname; //'misc' folder to search. we fetch this based on p_charname below
void load_image(QString p_filename, QString p_charname);
};
class CharLayer : public AOLayer {
Q_OBJECT
public:
CharLayer(QWidget *p_parent, AOApplication *p_ao_app);
QString current_emote = ""; // name of the emote we're using
bool is_preanim; // equivalent to the old play_once, if true we don't want
// to loop this
QString prefix = ""; // prefix, left blank if it's a preanim
void load_image(QString p_filename, QString p_charname, int p_duration, bool p_is_preanim);
void play(); // overloaded so we can play effects
// networked frame fx string
QStringList network_strings;
private:
QString last_char; // name of the last character we used
QString last_emote; // name of the last animation we used
QString last_prefix; // prefix of the last animation we played
bool was_preanim = false; // whether is_preanim was true last time
// Effects such as sfx, screenshakes and realization flashes are stored in
// here. QString entry format: "sfx^[sfx_name]", "shake", "flash". The program
// uses the QVector index as reference.
QVector<QVector<QString>> movie_effects;
// used for effect loading
QString m_char = "";
QString m_emote = "";
// overloaded for effects reasons
void start_playback(QString p_image);
// Initialize the frame-specific effects from the char.ini
void load_effects();
// Initialize the frame-specific effects from the provided network_strings,
// this is only initialized if network_strings has size more than 0.
void load_network_effects();
// Play a frame-specific effect, if there's any defined for that specific
// frame.
void play_frame_effect(int p_frame);
private slots:
void preanim_done() override; // overridden so we don't accidentally cull characters
void movie_ticker() override; // overridden so we can play effects
signals:
void shake();
void flash();
void play_sfx(QString sfx);
};
class InterjectionLayer : public AOLayer {
Q_OBJECT
public:
InterjectionLayer(QWidget *p_parent, AOApplication *p_ao_app);
void load_image(QString p_filename, QString p_charname, QString p_miscname);
};
class EffectLayer : public AOLayer {
Q_OBJECT
public:
EffectLayer(QWidget *p_parent, AOApplication *p_ao_app);
void load_image(QString p_filename, bool p_looping);
};
class InterfaceLayer : public AOLayer {
Q_OBJECT
public:
InterfaceLayer(QWidget *p_parent, AOApplication *p_ao_app);
void load_image(QString p_filename, QString p_miscname);
};
class StickerLayer : public AOLayer {
Q_OBJECT
public:
StickerLayer(QWidget *p_parent, AOApplication *p_ao_app);
void load_image(QString p_charname);
};
#endif // AOLAYER_H

View File

@ -1,36 +0,0 @@
#ifndef AOMOVIE_H
#define AOMOVIE_H
#include <QLabel>
#include <QMovie>
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_image, QString p_char = "", QString p_custom_theme = "",
int default_duration = 0);
void combo_resize(int w, int h);
void stop();
private:
QMovie *m_movie;
AOApplication *ao_app;
QTimer *timer;
bool play_once = true;
signals:
void done();
private slots:
void frame_change(int n_frame);
void timer_done();
};
#endif // AOMOVIE_H

View File

@ -58,6 +58,8 @@ private:
QCheckBox *ui_desync_logs_cb;
QLabel *ui_instant_objection_lbl;
QCheckBox *ui_instant_objection_cb;
QLabel *ui_chat_ratelimit_lbl;
QSpinBox *ui_chat_ratelimit_spinbox;
QLabel *ui_log_ic_actions_lbl;
QCheckBox *ui_log_ic_actions_cb;
QFrame *ui_log_names_divider;

View File

@ -12,7 +12,7 @@ public:
QString get_header() { return m_header; }
QStringList &get_contents() { return m_contents; }
QString to_string();
QString to_string(bool encoded = false);
void net_encode();
void net_decode();

View File

@ -1,42 +0,0 @@
#ifndef AOSCENE_H
#define AOSCENE_H
#include <QDebug>
#include <QLabel>
#include <QMovie>
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);
// Move the label itself around
void move(int ax, int ay);
// This is somewhat pointless now as there's no "QMovie" object to resize, aka
// no "combo" to speak of
void combo_resize(int w, int h);
private:
QWidget *m_parent;
QMovie *m_movie;
AOApplication *ao_app;
QString last_image;
// These are the X and Y values before they are fixed based on the sprite's
// width.
int x = 0;
int y = 0;
// These are the width and height values before they are fixed based on the
// sprite's width.
int f_w = 0;
int f_h = 0;
};
#endif // AOSCENE_H

View File

@ -5,18 +5,16 @@
#include "aoblipplayer.h"
#include "aobutton.h"
#include "aocharbutton.h"
#include "aocharmovie.h"
#include "aoclocklabel.h"
#include "aoemotebutton.h"
#include "aoevidencebutton.h"
#include "aoevidencedisplay.h"
#include "aoimage.h"
#include "aolayer.h"
#include "aolineedit.h"
#include "aomovie.h"
#include "aomusicplayer.h"
#include "aooptionsdialog.h"
#include "aopacket.h"
#include "aoscene.h"
#include "aosfxplayer.h"
#include "aotextarea.h"
#include "aotextedit.h"
@ -152,6 +150,9 @@ public:
// reads theme inis and sets size and pos based on the identifier
void set_size_and_pos(QWidget *p_widget, QString p_identifier);
// reads theme and char inis and sets size and pos based on the identifier
void set_size_and_pos(QWidget *p_widget, QString p_identifier, QString p_char);
// reads theme inis and returns the size and pos as defined by it
QPoint get_theme_pos(QString p_identifier);
@ -225,11 +226,13 @@ public:
// Parse the chat message packet and unpack it into the m_chatmessage[ITEM] format
void unpack_chatmessage(QStringList p_contents);
// Log the message contents and information such as evidence presenting etc. into the log file
void log_chatmessage(QString f_message, int f_char_id, QString f_showname = "", int f_color = 0);
// Display the message contents and information such as evidence presenting etc. in the IC logs
void display_log_chatmessage(QString f_message, int f_char_id, QString f_showname = "", int f_color = 0);
enum LogMode {
IO_ONLY,
DISPLAY_ONLY,
DISPLAY_AND_IO
};
// Log the message contents and information such as evidence presenting etc. into the log file, the IC log, or both.
void log_chatmessage(QString f_message, int f_char_id, QString f_showname = "", int f_color = 0, LogMode f_log_mode=IO_ONLY);
// Log the message contents and information such as evidence presenting etc. into the IC logs
void handle_callwords();
@ -265,7 +268,8 @@ public:
QString filter_ic_text(QString p_text, bool colorize = false, int pos = -1,
int default_color = 0);
void log_ic_text(QString p_name, QString p_showname, QString p_message, QString p_action="", int p_color=0);
void log_ic_text(QString p_name, QString p_showname, QString p_message,
QString p_action = "", int p_color = 0);
// 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
@ -357,6 +361,7 @@ private:
QVector<QString> arup_locks;
QVector<chatlogpiece> ic_chatlog_history;
QString last_ic_message = "";
QQueue<QStringList> chatmessage_queue;
@ -392,7 +397,8 @@ private:
// True, if log should display colors.
bool log_colors = true;
// True, if the log should display the message like name<br>text instead of name: text
// True, if the log should display the message like name<br>text instead of
// name: text
bool log_newline = false;
// True, if the log should include RP actions like interjections, showing evidence, etc.
@ -420,16 +426,21 @@ private:
const int time_mod = 40;
// the amount of time non-animated objection/hold it/takethat images stay
// onscreen for in ms
const int shout_stay_time = 724;
// onscreen for in ms, and the maximum amount of time any interjections are
// allowed to play
const int shout_static_time = 724;
const int shout_max_time = 1500;
// the amount of time non-animated guilty/not guilty images stay onscreen for
// in ms
const int verdict_stay_time = 3000;
// in ms, and the maximum amount of time g/ng images are allowed to play
const int verdict_static_time = 3000;
const int verdict_max_time = 4000;
// the amount of time non-animated witness testimony/cross-examination images
// stay onscreen for in ms
const int wtce_stay_time = 1500;
// stay onscreen for in ms, and the maximum time any wt/ce image is allowed to
// play
const int wtce_static_time = 1500;
const int wtce_max_time = 4000;
// characters we consider punctuation
const QString punctuation_chars = ".,?!:;";
@ -453,7 +464,7 @@ private:
bool is_muted = false;
// state of animation, 0 = objecting, 1 = preanim, 2 = talking, 3 = idle, 4 =
// noniterrupting preanim
// noniterrupting preanim, 5 = (c) animation
int anim_state = 3;
// whether or not current color is a talking one
@ -516,8 +527,15 @@ private:
// List of all currently available pos
QStringList pos_dropdown_list;
// Current list file sorted line by line
QStringList sound_list;
// Current SFX the user put in for the sfx dropdown list
QString custom_sfx = "";
// is the message we're about to send supposed to present evidence?
bool is_presenting_evidence = false;
bool c_played = false; // whether we've played a (c)-style postanimation yet
// have we already presented evidence for this message?
bool evidence_presented = false;
@ -585,21 +603,20 @@ private:
AOImage *ui_background;
QWidget *ui_viewport;
AOScene *ui_vp_background;
AOMovie *ui_vp_speedlines;
AOCharMovie *ui_vp_player_char;
AOCharMovie *ui_vp_sideplayer_char;
AOScene *ui_vp_desk;
AOScene *ui_vp_legacy_desk;
BackgroundLayer *ui_vp_background;
ForegroundLayer *ui_vp_speedlines;
CharLayer *ui_vp_player_char;
CharLayer *ui_vp_sideplayer_char;
BackgroundLayer *ui_vp_desk;
AOEvidenceDisplay *ui_vp_evidence_display;
AOImage *ui_vp_chatbox;
QLabel *ui_vp_showname;
AOMovie *ui_vp_chat_arrow;
InterfaceLayer *ui_vp_chat_arrow;
QTextEdit *ui_vp_message;
AOMovie *ui_vp_effect;
AOMovie *ui_vp_testimony;
AOMovie *ui_vp_wtce;
AOMovie *ui_vp_objection;
EffectLayer *ui_vp_effect;
InterfaceLayer *ui_vp_testimony;
InterjectionLayer *ui_vp_wtce;
InterjectionLayer *ui_vp_objection;
QTextEdit *ui_ic_chatlog;
@ -611,7 +628,9 @@ private:
QTreeWidget *ui_music_list;
ScrollText *ui_music_name;
AOMovie *ui_music_display;
InterfaceLayer *ui_music_display;
StickerLayer *ui_vp_sticker;
static const int max_clocks = 5;
AOClockLabel *ui_clock[max_clocks];
@ -724,6 +743,9 @@ private:
AOImage *ui_char_select_background;
// pretty list of characters
QTreeWidget *ui_char_list;
// abstract widget to hold char buttons
QWidget *ui_char_buttons;
@ -765,6 +787,7 @@ private:
void regenerate_ic_chatlog();
public slots:
void objection_done();
void effect_done();
void preanim_done();
void do_screenshake();
void do_flash();
@ -820,6 +843,7 @@ private slots:
void on_iniswap_remove_clicked();
void on_sfx_dropdown_changed(int p_index);
void on_sfx_dropdown_custom(QString p_sfx);
void set_sfx_dropdown();
void on_sfx_context_menu_requested(const QPoint &pos);
void on_sfx_edit_requested();
@ -916,6 +940,7 @@ private slots:
void on_back_to_lobby_clicked();
void on_char_list_double_clicked(QTreeWidgetItem *p_item, int column);
void on_char_select_left_clicked();
void on_char_select_right_clicked();
void on_char_search_changed();

55
include/demoserver.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef DEMOSERVER_H
#define DEMOSERVER_H
#include "aopacket.h"
#include <QDebug>
#include <QObject>
#include <QQueue>
#include <QTcpServer>
#include <QTcpSocket>
#include <QTimer>
#include <QFileDialog>
class DemoServer : public QObject
{
Q_OBJECT
public:
explicit DemoServer(QObject *parent = nullptr);
bool server_started = false;
int port = 27088;
int max_wait = -1;
int min_wait = -1;
private:
void handle_packet(AOPacket packet);
void load_demo(QString filename);
QTcpServer* tcp_server;
QTcpSocket* client_sock = nullptr;
bool client_connected = false;
bool partial_packet = false;
QString temp_packet = "";
QQueue<QString> demo_data;
QString sc_packet;
int num_chars = 0;
QString p_path;
QTimer *timer;
int elapsed_time = 0;
private slots:
void accept_connection();
void destroy_connection();
void recv_data();
void client_disconnect();
void playback();
public slots:
void start_server();
signals:
};
#endif // DEMOSERVER_H

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -45,6 +45,10 @@ void AOApplication::construct_lobby()
if (is_discord_enabled())
discord->state_lobby();
if (demo_server)
demo_server->deleteLater();
demo_server = new DemoServer();
w_lobby->show();
}
@ -182,6 +186,10 @@ void AOApplication::call_announce_menu(Courtroom *court)
void CALLBACK AOApplication::BASSreset(HSTREAM handle, DWORD channel,
DWORD data, void *user)
{
UNUSED(handle);
UNUSED(channel);
UNUSED(data);
UNUSED(user);
doBASSreset();
}

View File

@ -1,332 +0,0 @@
#include "aocharmovie.h"
#include "aoapplication.h"
#include "file_functions.h"
#include "misc_functions.h"
AOCharMovie::AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app)
: QLabel(p_parent)
{
ao_app = p_ao_app;
preanim_timer = new QTimer(this);
preanim_timer->setSingleShot(true);
ticker = new QTimer(this);
ticker->setTimerType(Qt::PreciseTimer);
ticker->setSingleShot(false);
connect(ticker, SIGNAL(timeout()), this, SLOT(movie_ticker()));
// connect(m_movie, SIGNAL(frameChanged(int)), this,
// SLOT(frame_change(int)));
connect(preanim_timer, SIGNAL(timeout()), this, SLOT(preanim_done()));
}
void AOCharMovie::load_image(QString p_char, QString p_emote,
QString emote_prefix)
{
#ifdef DEBUG_CHARMOVIE
actual_time.restart();
#endif
QString emote_path;
QList<QString> pathlist;
pathlist = {
ao_app->get_image_suffix(ao_app->get_character_path(
p_char, emote_prefix + p_emote)), // Default path
ao_app->get_image_suffix(ao_app->get_character_path(
p_char, emote_prefix + "/" +
p_emote)), // Path check if it's categorized into a folder
ao_app->get_image_suffix(ao_app->get_character_path(
p_char, p_emote)), // Just use the non-prefixed image, animated or not
ao_app->get_image_suffix(
ao_app->get_theme_path("placeholder")), // Theme placeholder path
ao_app->get_image_suffix(ao_app->get_default_theme_path(
"placeholder")), // Default theme placeholder path
};
for (QString path : pathlist) {
if (file_exists(path)) {
emote_path = path;
break;
}
}
this->clear();
ticker->stop();
preanim_timer->stop();
movie_frames.clear();
movie_delays.clear();
movie_effects.clear();
if (!file_exists(emote_path))
return;
m_reader->setFileName(emote_path);
// set format to apng if png supports animation
if (emote_path.endsWith("png")) {
m_reader->setFormat("apng");
if (!m_reader->supportsAnimation()) {
m_reader->setFormat("png");
}
}
QPixmap f_pixmap = this->get_pixmap(m_reader->read());
int f_delay = m_reader->nextImageDelay();
frame = 0;
max_frames = m_reader->imageCount();
this->set_frame(f_pixmap);
this->show();
if (max_frames > 1) {
movie_frames.append(f_pixmap);
movie_delays.append(f_delay);
}
m_char = p_char;
m_emote = emote_prefix + p_emote;
if (network_strings.size() > 0) // our FX overwritten by networked ones
this->load_network_effects();
else // Use default ini FX
this->load_effects();
#ifdef DEBUG_CHARMOVIE
qDebug() << max_frames << "Setting image to " << emote_path
<< "Time taken to process image:" << actual_time.elapsed();
actual_time.restart();
#endif
}
void AOCharMovie::load_effects()
{
movie_effects.clear();
movie_effects.resize(max_frames);
for (int e_frame = 0; e_frame < max_frames; ++e_frame) {
QString effect = ao_app->get_screenshake_frame(m_char, m_emote, e_frame);
if (effect != "") {
movie_effects[e_frame].append("shake");
}
effect = ao_app->get_flash_frame(m_char, m_emote, e_frame);
if (effect != "") {
movie_effects[e_frame].append("flash");
}
effect = ao_app->get_sfx_frame(m_char, m_emote, e_frame);
if (effect != "") {
movie_effects[e_frame].append("sfx^" + effect);
}
}
}
void AOCharMovie::load_network_effects()
{
movie_effects.clear();
movie_effects.resize(max_frames);
// Order is important!!!
QStringList effects_list = {"shake", "flash", "sfx^"};
// Determines which list is smaller - effects_list or network_strings - and
// uses it as basis for the loop. This way, incomplete network_strings would
// still be parsed, and excess/unaccounted for networked information is
// omitted.
int effects_size = qMin(effects_list.size(), network_strings.size());
for (int i = 0; i < effects_size; ++i) {
QString netstring = network_strings.at(i);
QStringList emote_splits = netstring.split("^");
foreach (QString emote, emote_splits) {
QStringList parsed = emote.split("|");
if (parsed.size() <= 0 || parsed.at(0) != m_emote)
continue;
foreach (QString frame_data, parsed) {
QStringList frame_split = frame_data.split("=");
if (frame_split.size() <=
1) // We might still be hanging at the emote itself (entry 0).
continue;
int f_frame = frame_split.at(0).toInt();
if (f_frame >= max_frames) {
qDebug() << "Warning: out of bounds" << effects_list[i] << "frame"
<< f_frame << "out of" << max_frames << "for" << m_char
<< m_emote;
continue;
}
QString f_data = frame_split.at(1);
if (f_data != "") {
QString effect = effects_list[i];
if (effect == "sfx^") // Currently the only frame result that feeds us
// data, let's yank it in.
effect += f_data;
qDebug() << effect << f_data << "frame" << f_frame << "for" << m_char
<< m_emote;
movie_effects[f_frame].append(effect);
}
}
}
}
}
void AOCharMovie::play()
{
play_frame_effect(frame);
if (max_frames <= 1) {
if (play_once)
ticker->start(60);
}
else
ticker->start(this->get_frame_delay(movie_delays[frame]));
}
void AOCharMovie::play_pre(QString p_char, QString p_emote, int duration)
{
load_image(p_char, p_emote, "");
// As much as I'd like to screw around with [Time] durations modifying the
// animation speed, I don't think I can reliably do that, not without looping
// through all frames in the image at least - which causes lag. So for now it
// simply ends the preanimation early instead.
play_once = true;
if (duration >
0) // It's -1 if there's no definition in [Time] for it. In which case, it
// will let the animation run out in full. Duration 0 does the same.
preanim_timer->start(duration *
time_mod); // This timer will not fire if the animation
// finishes earlier than that
play();
}
void AOCharMovie::play_talking(QString p_char, QString p_emote)
{
play_once = false;
load_image(p_char, p_emote, "(b)");
play();
}
void AOCharMovie::play_idle(QString p_char, QString p_emote)
{
play_once = false;
load_image(p_char, p_emote, "(a)");
play();
}
void AOCharMovie::play_frame_effect(int frame)
{
if (frame < max_frames) {
foreach (QString effect, movie_effects[frame]) {
if (effect == "shake") {
shake();
#ifdef DEBUG_CHARMOVIE
qDebug() << "Attempting to play shake on frame" << frame;
#endif
}
if (effect == "flash") {
flash();
#ifdef DEBUG_CHARMOVIE
qDebug() << "Attempting to play flash on frame" << frame;
#endif
}
if (effect.startsWith("sfx^")) {
QString sfx = effect.section("^", 1);
play_sfx(sfx);
#ifdef DEBUG_CHARMOVIE
qDebug() << "Attempting to play sfx" << sfx << "on frame" << frame;
#endif
}
}
}
}
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
ticker->stop();
preanim_timer->stop();
this->hide();
}
QPixmap AOCharMovie::get_pixmap(QImage image)
{
QPixmap f_pixmap;
if (m_flipped)
f_pixmap = QPixmap::fromImage(image.mirrored(true, false));
else
f_pixmap = QPixmap::fromImage(image);
// auto aspect_ratio = Qt::KeepAspectRatio;
auto transform_mode = Qt::FastTransformation;
if (f_pixmap.height() > f_h) // We are downscaling, use anti-aliasing.
transform_mode = Qt::SmoothTransformation;
f_pixmap = f_pixmap.scaledToHeight(f_h, transform_mode);
this->resize(f_pixmap.size());
return f_pixmap;
}
void AOCharMovie::set_frame(QPixmap f_pixmap)
{
this->setPixmap(f_pixmap);
QLabel::move(
x + (f_w - f_pixmap.width()) / 2,
y + (f_h - f_pixmap.height())); // Always center horizontally, always put
// at the bottom vertically
}
void AOCharMovie::combo_resize(int w, int h)
{
QSize f_size(w, h);
f_w = w;
f_h = h;
this->resize(f_size);
}
int AOCharMovie::get_frame_delay(int delay)
{
return static_cast<int>(double(delay) * double(speed / 100));
}
void AOCharMovie::move(int ax, int ay)
{
x = ax;
y = ay;
QLabel::move(x, y);
}
void AOCharMovie::movie_ticker()
{
++frame;
if (frame >= max_frames) {
if (play_once) {
preanim_done();
return;
}
else
frame = 0;
}
// qint64 difference = elapsed - movie_delays[frame];
if (frame >= movie_frames.size()) {
m_reader->jumpToImage(frame);
movie_frames.resize(frame + 1);
movie_frames[frame] = this->get_pixmap(m_reader->read());
movie_delays.resize(frame + 1);
movie_delays[frame] = m_reader->nextImageDelay();
}
#ifdef DEBUG_CHARMOVIE
qDebug() << frame << movie_delays[frame]
<< "actual time taken from last frame:" << actual_time.restart();
#endif
this->set_frame(movie_frames[frame]);
play_frame_effect(frame);
ticker->setInterval(this->get_frame_delay(movie_delays[frame]));
}
void AOCharMovie::preanim_done()
{
ticker->stop();
preanim_timer->stop();
done();
}

View File

@ -11,7 +11,7 @@ AOEvidenceDisplay::AOEvidenceDisplay(QWidget *p_parent, AOApplication *p_ao_app)
evidence_icon = new QLabel(this);
sfx_player = new AOSfxPlayer(this, ao_app);
evidence_movie = new AOMovie(this, ao_app);
evidence_movie = new InterfaceLayer(this, ao_app);
connect(evidence_movie, SIGNAL(done()), this, SLOT(show_done()));
}
@ -46,9 +46,11 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image,
evidence_icon->setPixmap(f_pixmap);
evidence_icon->resize(f_pixmap.size());
evidence_icon->move(icon_dimensions.x, icon_dimensions.y);
evidence_movie->play(gif_name);
sfx_player->play(ao_app->get_sfx("evidence_present"));
evidence_movie->static_duration = 320;
evidence_movie->max_duration = 1000;
evidence_movie->set_play_once(true);
evidence_movie->load_image(gif_name, "");
sfx_player->play(ao_app->get_sfx("evidence_present", "default"));
}
void AOEvidenceDisplay::reset()

601
src/aolayer.cpp Normal file
View File

@ -0,0 +1,601 @@
#include "aolayer.h"
#include "aoapplication.h"
#include "file_functions.h"
#include "misc_functions.h"
AOLayer::AOLayer(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent)
{
ao_app = p_ao_app;
// used for culling images when their max_duration is exceeded
shfx_timer = new QTimer(this);
shfx_timer->setTimerType(Qt::PreciseTimer);
shfx_timer->setSingleShot(true);
connect(shfx_timer, SIGNAL(timeout()), this, SLOT(shfx_timer_done()));
ticker = new QTimer(this);
ticker->setTimerType(Qt::PreciseTimer);
ticker->setSingleShot(false);
connect(ticker, SIGNAL(timeout()), this, SLOT(movie_ticker()));
preanim_timer = new QTimer(this);
preanim_timer->setSingleShot(true);
connect(preanim_timer, SIGNAL(timeout()), this, SLOT(preanim_done()));
}
BackgroundLayer::BackgroundLayer(QWidget *p_parent, AOApplication *p_ao_app)
: AOLayer(p_parent, p_ao_app)
{
}
ForegroundLayer::ForegroundLayer(QWidget *p_parent, AOApplication *p_ao_app)
: AOLayer(p_parent, p_ao_app)
{
}
CharLayer::CharLayer(QWidget *p_parent, AOApplication *p_ao_app)
: AOLayer(p_parent, p_ao_app)
{
}
EffectLayer::EffectLayer(QWidget *p_parent, AOApplication *p_ao_app)
: AOLayer(p_parent, p_ao_app)
{
}
InterjectionLayer::InterjectionLayer(QWidget *p_parent, AOApplication *p_ao_app)
: AOLayer(p_parent, p_ao_app)
{
}
InterfaceLayer::InterfaceLayer(QWidget *p_parent, AOApplication *p_ao_app)
: AOLayer(p_parent, p_ao_app)
{
}
StickerLayer::StickerLayer(QWidget *p_parent, AOApplication *p_ao_app)
: AOLayer(p_parent, p_ao_app)
{
}
QString AOLayer::find_image(QList<QString> p_list)
{
QString image_path;
for (QString path : p_list) {
#ifdef DEBUG_MOVIE
qDebug() << "checking path " << path;
#endif
if (file_exists(path)) {
image_path = path;
#ifdef DEBUG_MOVIE
qDebug() << "found path " << path;
#endif
break;
}
}
return image_path;
}
QPixmap AOLayer::get_pixmap(QImage image)
{
QPixmap f_pixmap;
if (m_flipped)
f_pixmap = QPixmap::fromImage(image.mirrored(true, false));
else
f_pixmap = QPixmap::fromImage(image);
// auto aspect_ratio = Qt::KeepAspectRatio;
if (f_pixmap.height() > f_h) // We are downscaling, use anti-aliasing.
transform_mode = Qt::SmoothTransformation;
if (stretch)
f_pixmap = f_pixmap.scaled(f_w, f_h);
else
f_pixmap = f_pixmap.scaledToHeight(f_h, transform_mode);
this->resize(f_pixmap.size());
return f_pixmap;
}
void AOLayer::set_frame(QPixmap f_pixmap)
{
this->setPixmap(f_pixmap);
QLabel::move(
x + (f_w - f_pixmap.width()) / 2,
y + (f_h - f_pixmap.height())); // Always center horizontally, always put
// at the bottom vertically
this->setMask(
QRegion((f_pixmap.width() - f_w) / 2, (f_pixmap.height() - f_h) / 2, f_w,
f_h)); // make sure we don't escape the area we've been given
}
void AOLayer::combo_resize(int w, int h)
{
QSize f_size(w, h);
f_w = w;
f_h = h;
this->resize(f_size);
}
int AOLayer::get_frame_delay(int delay)
{
return static_cast<int>(double(delay) * double(speed / 100));
}
void AOLayer::move(int ax, int ay)
{
x = ax;
y = ay;
QLabel::move(x, y);
}
void BackgroundLayer::load_image(QString p_filename)
{
play_once = false;
cull_image = false;
QString design_path = ao_app->get_background_path("design.ini");
transform_mode =
ao_app->get_scaling(ao_app->read_design_ini("scaling", design_path));
stretch = ao_app->read_design_ini("stretch", design_path).startsWith("true");
qDebug() << "[BackgroundLayer] BG loaded: " << p_filename;
start_playback(ao_app->get_image_suffix(ao_app->get_background_path(p_filename)));
}
void ForegroundLayer::load_image(QString p_filename, QString p_charname)
{
play_once = false;
cull_image = false;
miscname = ao_app->get_char_shouts(p_charname);
qDebug() << "[ForegroundLayer] FG loaded: " << p_filename;
QList<QString> pathlist = {
ao_app->get_image_suffix(ao_app->get_character_path(
p_charname, p_filename)), // first check the character folder
ao_app->get_image_suffix(ao_app->get_theme_path(
"misc/" + miscname + "/" +
p_filename)), // then check our theme's misc directory
ao_app->get_image_suffix(ao_app->get_misc_path(
miscname, p_filename)), // then check our global misc folder
ao_app->get_image_suffix(
ao_app->get_theme_path(p_filename)), // then check the user's theme
ao_app->get_image_suffix(ao_app->get_default_theme_path(
p_filename))}; // and finally check the default theme
start_playback(find_image(pathlist));
}
void CharLayer::load_image(QString p_filename, QString p_charname,
int p_duration, bool p_is_preanim)
{
duration = p_duration;
cull_image = false;
force_continuous = false;
transform_mode = ao_app->get_scaling(
ao_app->get_emote_property(p_charname, p_filename, "scaling"));
stretch = ao_app->get_emote_property(p_charname, p_filename, "stretch")
.startsWith(true);
if ((p_charname == last_char) &&
((p_filename == last_emote) ||
(p_filename.mid(3, -1) == last_emote.mid(3, -1))) &&
(!is_preanim) && (!was_preanim)) {
continuous = true;
force_continuous = true;
}
else {
continuous = false;
force_continuous = true;
}
prefix = "";
current_emote = p_filename;
was_preanim = is_preanim;
m_char = p_charname;
m_emote = current_emote;
last_char = p_charname;
last_emote = current_emote;
last_prefix = prefix;
is_preanim = p_is_preanim;
if ((p_filename.left(3) == "(a)") || (p_filename.left(3) == "(b)")) {
prefix = p_filename.left(3);
current_emote = p_filename.mid(3, -1);
}
else if ((duration > 0) || (p_filename.left(3) == "(c)")) {
if (p_filename.left(3) == "(c)") {
prefix = "(c)";
current_emote = p_filename.mid(3, -1);
}
is_preanim = true;
play_once = true;
preanim_timer->start(duration * tick_ms);
}
qDebug() << "[CharLayer] anim loaded: prefix " << prefix << " filename "
<< current_emote << " from character: " << p_charname
<< " continuous: " << continuous;
QList<QString> pathlist = {
ao_app->get_image_suffix(ao_app->get_character_path(
p_charname, prefix + current_emote)), // Default path
ao_app->get_image_suffix(ao_app->get_character_path(
p_charname,
prefix + "/" + current_emote)), // Path check if it's categorized
// into a folder
ao_app->get_image_suffix(ao_app->get_character_path(
p_charname,
current_emote)), // Just use the non-prefixed image, animated or not
ao_app->get_image_suffix(
ao_app->get_theme_path("placeholder")), // Theme placeholder path
ao_app->get_image_suffix(ao_app->get_default_theme_path(
"placeholder"))}; // Default theme placeholder path
this->start_playback(find_image(pathlist));
}
void InterjectionLayer::load_image(QString p_filename, QString p_charname,
QString p_miscname)
{
continuous = false;
force_continuous = true;
play_once = true;
transform_mode = ao_app->get_misc_scaling(p_miscname);
QList<QString> pathlist = {
ao_app->get_image_suffix(ao_app->get_character_path(
p_charname, p_filename)), // Character folder
ao_app->get_image_suffix(ao_app->get_theme_path(
"misc/" + p_miscname + "/" + p_filename)), // Theme misc path
ao_app->get_image_suffix(
ao_app->get_misc_path(p_miscname, p_filename)), // Misc path
ao_app->get_image_suffix(
ao_app->get_theme_path(p_filename)), // Theme path
ao_app->get_image_suffix(
ao_app->get_default_theme_path(p_filename)), // Default theme path
ao_app->get_image_suffix(
ao_app->get_theme_path("placeholder")), // Placeholder path
ao_app->get_image_suffix(ao_app->get_default_theme_path(
"placeholder")), // Default placeholder path
};
QString final_image = find_image(pathlist);
if (final_image == ao_app->get_theme_path("custom.png") ||
final_image == ao_app->get_default_theme_path("custom.png") ||
final_image == ao_app->get_theme_path("witnesstestimony.png") ||
final_image == ao_app->get_default_theme_path("witnesstestimony.png") ||
final_image == ao_app->get_theme_path("crossexamination.png") ||
final_image == ao_app->get_default_theme_path("crossexamination.png"))
// stupid exceptions because themes are stupid
final_image = find_image(
{ao_app->get_image_suffix(ao_app->get_theme_path("placeholder")),
ao_app->get_image_suffix(ao_app->get_default_theme_path("placeholder"))});
start_playback(final_image);
}
void EffectLayer::load_image(QString p_filename, bool p_looping)
{
if (p_looping)
play_once = false;
else
play_once = true;
continuous = false;
force_continuous = true;
start_playback(p_filename); // handled in its own file before we see it
}
void InterfaceLayer::load_image(QString p_filename, QString p_miscname)
{
transform_mode = ao_app->get_misc_scaling(p_miscname);
QList<QString> pathlist = {
ao_app->get_image_suffix(ao_app->get_theme_path(
"misc/" + p_miscname + "/" +
p_filename)), // first check our theme's misc directory
ao_app->get_image_suffix(ao_app->get_misc_path(
p_miscname, p_filename)), // then check our global misc folder
ao_app->get_image_suffix(ao_app->get_theme_path(
p_filename)), // then check the user's theme for a default image
ao_app->get_image_suffix(ao_app->get_default_theme_path(
p_filename))}; // and finally check the default theme
start_playback(find_image(pathlist));
}
void StickerLayer::load_image(QString p_charname)
{
QString miscname = ao_app->get_char_shouts(p_charname);
transform_mode = ao_app->get_misc_scaling(miscname);
QList<QString> pathlist = {
ao_app->get_image_suffix(ao_app->get_base_path() + "misc/" +
miscname + "/sticker/" + p_charname), // Misc path
ao_app->get_image_suffix(ao_app->get_custom_theme_path(miscname, "sticker/" + p_charname)), // Custom theme path
ao_app->get_image_suffix(ao_app->get_theme_path("sticker/" + p_charname)), // Theme path
ao_app->get_image_suffix(
ao_app->get_default_theme_path("sticker/" + p_charname)), // Default theme path
ao_app->get_image_suffix(
ao_app->get_character_path(p_charname, "sticker")), // Character folder
ao_app->get_image_suffix(
ao_app->get_character_path(p_charname, "showname")), // Scuffed DRO way
};
start_playback(find_image(pathlist));
}
void CharLayer::start_playback(QString p_image)
{
movie_effects.clear();
AOLayer::start_playback(p_image);
if (network_strings.size() > 0) // our FX overwritten by networked ones
load_network_effects();
else // Use default ini FX
load_effects();
}
void AOLayer::start_playback(QString p_image)
{
#ifdef DEBUG_MOVIE
actual_time.restart();
#endif
this->clear();
freeze();
movie_frames.clear();
movie_delays.clear();
if (!file_exists(p_image))
return;
QString scaling_override =
ao_app->read_design_ini("scaling", p_image + ".ini");
if (scaling_override != "")
transform_mode = ao_app->get_scaling(scaling_override);
QString stretch_override =
ao_app->read_design_ini("stretch", p_image + ".ini");
if (stretch_override != "")
stretch = stretch_override.startsWith("true");
qDebug() << "stretch:" << stretch << "filename:" << p_image;
m_reader.setFileName(p_image);
if (m_reader.loopCount() == 0)
play_once = true;
if ((last_path == p_image) && (!force_continuous))
continuous = true;
else if ((last_path != p_image) && !force_continuous)
continuous = false;
if (!continuous)
frame = 0;
force_continuous = false;
last_max_frames = max_frames;
max_frames = m_reader.imageCount();
if (((continuous) && (max_frames != last_max_frames)) || max_frames == 0) {
frame = 0;
continuous = false;
}
// CANTFIX: this causes a slight hitch
// The correct way of doing this would be to use QImageReader::jumpToImage()
// and populate missing data in the movie ticker when it's needed. This is
// unforunately completely impossible, because QImageReader::jumpToImage() is
// not implemented in any image format AO2 is equipped to use. Instead, the
// default behavior is used - that is, absolutely nothing.
if (continuous) {
for (int i = frame; i--;) {
if (i <= -1)
break;
QPixmap l_pixmap = this->get_pixmap(m_reader.read());
int l_delay = m_reader.nextImageDelay();
movie_frames.append(l_pixmap);
movie_delays.append(l_delay);
// qDebug() << "appending delay of " << l_delay;
}
}
// qDebug() << "CONT: " << continuous << " MAX: " << max_frames
// << " LAST MAX: " << last_max_frames << " FRAME: " << frame;
QPixmap f_pixmap = this->get_pixmap(m_reader.read());
int f_delay = m_reader.nextImageDelay();
this->set_frame(f_pixmap);
this->show();
if (max_frames > 1) {
movie_frames.append(f_pixmap);
movie_delays.append(f_delay);
}
else if (max_frames <= 1) {
duration = static_duration;
play_once = false;
#ifdef DEBUG_MOVIE
qDebug() << "max_frames is <= 1, using static duration";
#endif
}
if (duration > 0 && cull_image == true)
shfx_timer->start(duration);
play();
#ifdef DEBUG_MOVIE
qDebug() << max_frames << "Setting image to " << image_path
<< "Time taken to process image:" << actual_time.elapsed();
actual_time.restart();
#endif
}
void CharLayer::play()
{
play_frame_effect(frame);
AOLayer::play();
}
void AOLayer::play()
{
if (max_frames <= 1) {
if (play_once)
ticker->start(tick_ms);
else
this->freeze();
}
else
ticker->start(this->get_frame_delay(movie_delays[frame]));
}
void AOLayer::set_play_once(bool p_play_once) { play_once = p_play_once; }
void AOLayer::set_cull_image(bool p_cull_image) { cull_image = p_cull_image; }
void AOLayer::set_static_duration(int p_static_duration)
{
static_duration = p_static_duration;
}
void AOLayer::set_max_duration(int p_max_duration)
{
max_duration = p_max_duration;
}
void CharLayer::load_effects()
{
movie_effects.clear();
movie_effects.resize(max_frames);
for (int e_frame = 0; e_frame < max_frames; ++e_frame) {
QString effect = ao_app->get_screenshake_frame(m_char, m_emote, e_frame);
if (effect != "") {
movie_effects[e_frame].append("shake");
}
effect = ao_app->get_flash_frame(m_char, m_emote, e_frame);
if (effect != "") {
movie_effects[e_frame].append("flash");
}
effect = ao_app->get_sfx_frame(m_char, m_emote, e_frame);
if (effect != "") {
movie_effects[e_frame].append("sfx^" + effect);
}
}
}
void CharLayer::load_network_effects()
{
movie_effects.clear();
movie_effects.resize(max_frames);
// Order is important!!!
QStringList effects_list = {"shake", "flash", "sfx^"};
// Determines which list is smaller - effects_list or network_strings - and
// uses it as basis for the loop. This way, incomplete network_strings would
// still be parsed, and excess/unaccounted for networked information is
// omitted.
int effects_size = qMin(effects_list.size(), network_strings.size());
for (int i = 0; i < effects_size; ++i) {
QString netstring = network_strings.at(i);
QStringList emote_splits = netstring.split("^");
for (const QString &emote : emote_splits) {
QStringList parsed = emote.split("|");
if (parsed.size() <= 0 || parsed.at(0) != m_emote)
continue;
foreach (QString frame_data, parsed) {
QStringList frame_split = frame_data.split("=");
if (frame_split.size() <=
1) // We might still be hanging at the emote itself (entry 0).
continue;
int f_frame = frame_split.at(0).toInt();
if (f_frame >= max_frames || f_frame < 0) {
qDebug() << "Warning: out of bounds" << effects_list[i] << "frame"
<< f_frame << "out of" << max_frames << "for" << m_emote;
continue;
}
QString f_data = frame_split.at(1);
if (f_data != "") {
QString effect = effects_list[i];
if (effect == "sfx^") // Currently the only frame result that feeds us
// data, let's yank it in.
effect += f_data;
qDebug() << effect << f_data << "frame" << f_frame << "for"
<< m_emote;
movie_effects[f_frame].append(effect);
}
}
}
}
}
void CharLayer::play_frame_effect(int p_frame)
{
if (p_frame < max_frames) {
foreach (QString effect, movie_effects[p_frame]) {
if (effect == "shake") {
shake();
#ifdef DEBUG_MOVIE
qDebug() << "Attempting to play shake on frame" << frame;
#endif
}
if (effect == "flash") {
flash();
#ifdef DEBUG_MOVIE
qDebug() << "Attempting to play flash on frame" << frame;
#endif
}
if (effect.startsWith("sfx^")) {
QString sfx = effect.section("^", 1);
play_sfx(sfx);
#ifdef DEBUG_MOVIE
qDebug() << "Attempting to play sfx" << sfx << "on frame" << frame;
#endif
}
}
}
}
void AOLayer::stop()
{
// for all intents and purposes, stopping is the same as hiding. at no point
// do we want a frozen gif to display
this->freeze();
this->hide();
}
void AOLayer::freeze()
{
// aT nO pOiNt Do We WaNt A fRoZeN gIf To DiSpLaY
ticker->stop();
preanim_timer->stop();
shfx_timer->stop();
}
void CharLayer::movie_ticker()
{
AOLayer::movie_ticker();
play_frame_effect(frame);
}
void AOLayer::movie_ticker()
{
++frame;
if ((frame >= max_frames) && (max_frames > 1)) {
if (play_once) {
if (cull_image)
this->stop();
else
this->freeze();
preanim_done();
return;
}
else
frame = 0;
}
// qint64 difference = elapsed - movie_delays[frame];
if (frame >= movie_frames.size()) {
movie_frames.append(this->get_pixmap(m_reader.read()));
movie_delays.append(m_reader.nextImageDelay());
}
#ifdef DEBUG_MOVIE
qDebug() << frame << movie_delays[frame]
<< "actual time taken from last frame:" << actual_time.restart();
#endif
this->set_frame(movie_frames[frame]);
ticker->setInterval(this->get_frame_delay(movie_delays[frame]));
}
void CharLayer::preanim_done()
{
if (is_preanim)
AOLayer::preanim_done();
else
return;
}
void AOLayer::preanim_done()
{
ticker->stop();
preanim_timer->stop();
done();
}
void AOLayer::shfx_timer_done()
{
this->stop();
#ifdef DEBUG_MOVIE
qDebug() << "shfx timer signaled done";
#endif
// signal connected to courtroom object, let it figure out what to do
done();
}

View File

@ -1,100 +0,0 @@
#include "aomovie.h"
#include "courtroom.h"
#include "file_functions.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();
m_movie->setCacheMode(QMovie::CacheAll);
this->setMovie(m_movie);
timer = new QTimer(this);
timer->setTimerType(Qt::PreciseTimer);
timer->setSingleShot(true);
connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(frame_change(int)));
connect(timer, SIGNAL(timeout()), this, SLOT(timer_done()));
}
void AOMovie::set_play_once(bool p_play_once) { play_once = p_play_once; }
void AOMovie::play(QString p_image, QString p_char, QString p_custom_theme,
int duration)
{
m_movie->stop();
QString shout_path = p_image;
if (!file_exists(p_image)) {
QList<QString> pathlist;
pathlist = {
ao_app->get_image_suffix(
ao_app->get_character_path(p_char, p_image)), // Character folder
ao_app->get_image_suffix(ao_app->get_base_path() + "misc/" +
p_custom_theme + "/" + p_image), // Misc path
ao_app->get_image_suffix(ao_app->get_custom_theme_path(
p_custom_theme, p_image)), // Custom theme path
ao_app->get_image_suffix(ao_app->get_theme_path(p_image)), // Theme path
ao_app->get_image_suffix(
ao_app->get_default_theme_path(p_image)), // Default theme path
ao_app->get_image_suffix(
ao_app->get_theme_path("placeholder")), // Placeholder path
ao_app->get_image_suffix(ao_app->get_default_theme_path(
"placeholder")), // Default placeholder path
};
for (QString path : pathlist) {
if (file_exists(path)) {
shout_path = path;
break;
}
}
}
m_movie->setFileName(shout_path);
if (m_movie->loopCount() == 0)
play_once = true;
this->show();
m_movie->start();
if (m_movie->frameCount() == 0 && duration > 0)
timer->start(duration);
}
void AOMovie::stop()
{
m_movie->stop();
this->hide();
}
void AOMovie::frame_change(int n_frame)
{
// If it's a "static movie" (only one frame - png image), we can't change
// frames - ignore this function (use timer instead). If the frame didn't reach
// the last frame or the movie is continuous, don't stop the movie.
if (m_movie->frameCount() == 0 || n_frame < (m_movie->frameCount() - 1) ||
!play_once)
return;
// we need this or else the last frame wont show
timer->start(m_movie->nextFrameDelay());
}
void AOMovie::timer_done()
{
this->stop();
// signal connected to courtroom object, let it figure out what to do
done();
}
void AOMovie::combo_resize(int w, int h)
{
QSize f_size(w, h);
this->resize(f_size);
m_movie->setScaledSize(f_size);
}

View File

@ -96,7 +96,7 @@ void AOMusicPlayer::play(QString p_song, int channel, bool loop,
BASS_ChannelLock(oldstream, false);
}
if (effect_flags & FADE_OUT) {
if (effect_flags & FADE_OUT & (m_volume[channel] != 0)) {
// Fade out the other sample and stop it (due to -1)
BASS_ChannelSlideAttribute(oldstream, BASS_ATTRIB_VOL | BASS_SLIDE_LOG,
-1, 4000);
@ -116,6 +116,7 @@ void AOMusicPlayer::play(QString p_song, int channel, bool loop,
BASS_ChannelSlideAttribute(newstream, BASS_ATTRIB_VOL,
static_cast<float>(m_volume[channel] / 100.0f),
1000);
}
else
this->set_volume(m_volume[channel], channel);
@ -149,12 +150,15 @@ void AOMusicPlayer::set_volume(int p_value, int channel)
void CALLBACK loopProc(HSYNC handle, DWORD channel, DWORD data, void *user)
{
UNUSED(handle);
UNUSED(data);
QWORD loop_start = *(static_cast<unsigned *>(user));
BASS_ChannelLock(channel, true);
BASS_ChannelSetPosition(channel, loop_start, BASS_POS_BYTE);
BASS_ChannelLock(channel, false);
}
void AOMusicPlayer::set_looping(bool toggle, int channel)
{
m_looping = toggle;

View File

@ -182,7 +182,7 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app)
ui_stay_time_lbl->setText(tr("Text Stay Time:"));
ui_stay_time_lbl->setToolTip(tr(
"Minimum amount of time (in miliseconds) an IC message must stay on screen before "
"the next IC message is shown, acting as a 'queue'. Set to 0 to disable this behaivor."));
"the next IC message is shown, acting as a 'queue'. Set to 0 to disable this behavior."));
ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_stay_time_lbl);
@ -218,6 +218,19 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app)
ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_instant_objection_cb);
row += 1;
ui_chat_ratelimit_lbl = new QLabel(ui_form_layout_widget);
ui_chat_ratelimit_lbl->setText(tr("Chat Rate Limit:"));
ui_chat_ratelimit_lbl->setToolTip(tr(
"Minimum amount of time (in miliseconds) that must pass before the next Enter key press will send your IC message."));
ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_chat_ratelimit_lbl);
ui_chat_ratelimit_spinbox = new QSpinBox(ui_form_layout_widget);
ui_chat_ratelimit_spinbox->setMaximum(5000);
ui_chat_ratelimit_spinbox->setValue(p_ao_app->get_chat_ratelimit());
ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_chat_ratelimit_spinbox);
row += 1;
ui_log_names_divider = new QFrame(ui_form_layout_widget);
ui_log_names_divider->setFrameShape(QFrame::HLine);
@ -824,6 +837,7 @@ void AOOptionsDialog::save_pressed()
configini->setValue("desync_logs", ui_desync_logs_cb->isChecked());
configini->setValue("stay_time", ui_stay_time_spinbox->value());
configini->setValue("instant_objection", ui_instant_objection_cb->isChecked());
configini->setValue("chat_ratelimit", ui_chat_ratelimit_spinbox->value());
configini->setValue("default_username", ui_username_textbox->text());
configini->setValue("show_custom_shownames", ui_showname_cb->isChecked());
configini->setValue("master", ui_ms_textbox->text());

View File

@ -8,9 +8,15 @@ AOPacket::AOPacket(QString p_packet_string)
m_contents = packet_contents.mid(1, packet_contents.size()-2); // trims %
}
QString AOPacket::to_string()
QString AOPacket::to_string(bool encoded)
{
return m_header + "#" + m_contents.join("#") + "#%";
QStringList contents = m_contents;
if (encoded)
contents.replaceInStrings("#", "<num>")
.replaceInStrings("%", "<percent>")
.replaceInStrings("$", "<dollar>")
.replaceInStrings("&", "<and>");
return m_header + "#" + contents.join("#") + "#%";
}
void AOPacket::net_encode()

View File

@ -1,132 +0,0 @@
#include "aoscene.h"
#include "courtroom.h"
#include "file_functions.h"
AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent)
{
m_parent = parent;
ao_app = p_ao_app;
m_movie = new QMovie(this);
m_movie->setCacheMode(QMovie::CacheAll);
last_image = "";
}
void AOScene::set_image(QString p_image)
{
QString background_path =
ao_app->get_image_suffix(ao_app->get_background_path(p_image));
if (!file_exists(background_path)) // If image is missing, clear current image
{
this->clear();
this->setMovie(nullptr);
m_movie->stop();
last_image = "";
return;
}
if (!file_exists(background_path) || background_path != last_image)
{
this->clear();
this->setMovie(nullptr);
m_movie->stop();
m_movie->setFileName(background_path);
}
if (m_movie->isValid() && m_movie->frameCount() > 1) {
m_movie->jumpToNextFrame();
float scale_factor = static_cast<float>(f_h) /
static_cast<float>(m_movie->frameRect().height());
// preserve aspect ratio
int n_w = static_cast<int>(m_movie->frameRect().width() * scale_factor);
int n_h = static_cast<int>(m_movie->frameRect().height() * scale_factor);
m_movie->setScaledSize(QSize(n_w, n_h));
this->resize(m_movie->scaledSize());
if (!file_exists(background_path) || background_path != last_image)
{
this->setMovie(m_movie);
m_movie->start();
}
QLabel::move(x + (f_w - n_w) / 2, y + (f_h - n_h) / 2); // Center
}
else {
QPixmap background(background_path);
auto transform_mode = Qt::FastTransformation;
if (background.height() > f_h) // We are downscaling, use anti-aliasing.
transform_mode = Qt::SmoothTransformation;
background = background.scaledToHeight(f_h, transform_mode);
this->resize(background.size());
this->setPixmap(background);
QLabel::move(
x + (f_w - background.width()) / 2,
y + (f_h - background.height()) /
2); // Always center horizontally, always center vertically
}
last_image = background_path;
}
void AOScene::set_legacy_desk(QString p_image)
{
QString desk_path =
ao_app->get_image_suffix(ao_app->get_background_path(p_image));
if (!file_exists(desk_path)) // If image is missing, clear current image
{
this->clear();
this->setMovie(nullptr);
m_movie->stop();
last_image = "";
return;
}
if (file_exists(desk_path) && desk_path == last_image)
return;
QPixmap f_desk(desk_path);
// vanilla desks vary in both width and height. in order to make that work
// with viewport rescaling, some INTENSE math is needed.
int vp_width = m_parent->width();
int vp_height = m_parent->height();
double h_modifier = vp_height / 192;
int final_h = static_cast<int>(h_modifier * f_desk.height());
this->clear();
this->setMovie(nullptr);
m_movie->stop();
m_movie->setFileName(desk_path);
m_movie->setScaledSize(QSize(vp_width, final_h));
if (m_movie->isValid() && m_movie->frameCount() > 1) {
this->setMovie(m_movie);
m_movie->start();
}
else {
this->resize(vp_width, final_h);
this->setPixmap(f_desk.scaled(vp_width, final_h));
}
last_image = desk_path;
}
void AOScene::combo_resize(int w, int h)
{
QSize f_size(w, h);
f_w = w;
f_h = h;
this->resize(f_size);
}
void AOScene::move(int ax, int ay)
{
x = ax;
y = ay;
QLabel::move(x, y);
}

View File

@ -11,6 +11,16 @@ void Courtroom::construct_char_select()
ui_char_select_background = new AOImage(this, ao_app);
ui_char_list = new QTreeWidget(ui_char_select_background);
ui_char_list->setColumnCount(2);
ui_char_list->setHeaderLabels({"Name", "ID"});
ui_char_list->setHeaderHidden(true);
ui_char_list->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
ui_char_list->hideColumn(1);
ui_char_list->setDropIndicatorShown(true);
set_size_and_pos(ui_char_list, "char_list");
ui_char_buttons = new QWidget(ui_char_select_background);
ui_selector = new AOImage(ui_char_select_background, ao_app);
@ -46,6 +56,9 @@ void Courtroom::construct_char_select()
set_size_and_pos(ui_char_buttons, "char_buttons");
connect(ui_char_list, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)),
this, SLOT(on_char_list_double_clicked(QTreeWidgetItem *, int)));
connect(ui_back_to_lobby, SIGNAL(clicked()), this,
SLOT(on_back_to_lobby_clicked()));
@ -126,6 +139,21 @@ void Courtroom::set_char_select_page()
put_button_in_place(current_char_page * max_chars_on_page, chars_on_page);
}
void Courtroom::on_char_list_double_clicked(QTreeWidgetItem *p_item, int column)
{
UNUSED(column);
int cid = p_item->text(1).toInt();
if (cid == -1 && !p_item->isExpanded()) {
p_item->setExpanded(true);
return;
}
else if (cid == -1) {
p_item->setExpanded(false);
return;
}
char_clicked(cid);
}
void Courtroom::char_clicked(int n_char)
{
if (n_char != -1)
@ -218,7 +246,32 @@ void Courtroom::character_loading_finished()
char_button->set_image(char_list.at(n).name);
char_button->setToolTip(char_list.at(n).name);
ui_char_button_list.append(char_button);
QString char_category = ao_app->get_category(char_list.at(n).name);
QList<QTreeWidgetItem*> matching_list = ui_char_list->findItems(char_category, Qt::MatchFixedString, 0);
// create the character tree item
QTreeWidgetItem *treeItem = new QTreeWidgetItem();
treeItem->setText(0, char_list.at(n).name);
treeItem->setIcon(0, QIcon(ao_app->get_static_image_suffix(
ao_app->get_character_path(char_list.at(n).name, "char_icon"))));
treeItem->setText(1, QString::number(n));
// category logic
QTreeWidgetItem *category;
if (char_category == "") // no category
ui_char_list->addTopLevelItem(treeItem);
else if (!matching_list.isEmpty()) { // our category already exists
category = matching_list[0];
category->addChild(treeItem);
}
else { // we need to make a new category
category = new QTreeWidgetItem();
category->setText(0, char_category);
category->setText(1, "-1");
category->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicatorWhenChildless);
ui_char_list->insertTopLevelItem(0, category);
category->addChild(treeItem);
}
connect(char_button, &AOCharButton::clicked,
[this, n]() { this->char_clicked(n); });
@ -241,7 +294,7 @@ void Courtroom::character_loading_finished()
.arg(QString::number(ao_app->char_list_size)));
}
}
ui_char_list->expandAll();
filter_character_list();
}
@ -250,24 +303,37 @@ void Courtroom::filter_character_list()
ui_char_button_list_filtered.clear();
for (int i = 0; i < char_list.size(); i++) {
AOCharButton *current_char = ui_char_button_list.at(i);
QTreeWidgetItem* current_char_list_item = ui_char_list->findItems(QString::number(i), Qt::MatchExactly | Qt::MatchRecursive, 1)[0];
// It seems passwording characters is unimplemented yet?
// Until then, this will stay here, I suppose.
// if (ui_char_passworded->isChecked() && character_is_passworded??)
// continue;
if (!ui_char_taken->isChecked() && char_list.at(i).taken)
if (!ui_char_taken->isChecked() && char_list.at(i).taken) {
current_char_list_item->setHidden(true);
continue;
}
if (!char_list.at(i).name.contains(ui_char_search->text(),
Qt::CaseInsensitive))
Qt::CaseInsensitive)) {
current_char_list_item->setHidden(true);
continue;
}
// We only really need to update the fact that a character is taken
// for the buttons that actually appear.
// You'd also update the passwordedness and etc. here later.
current_char->reset();
current_char_list_item->setHidden(false);
current_char->set_taken(char_list.at(i).taken);
current_char_list_item->setText(0, char_list.at(i).name);
// reset disabled
current_char_list_item->setDisabled(false);
if (char_list.at(i).taken) // woops, we are taken
current_char_list_item->setDisabled(true);
ui_char_button_list_filtered.append(current_char);
}

File diff suppressed because it is too large Load Diff

304
src/demoserver.cpp Normal file
View File

@ -0,0 +1,304 @@
#include "demoserver.h"
#include "lobby.h"
DemoServer::DemoServer(QObject *parent) : QObject(parent)
{
timer = new QTimer(this);
timer->setTimerType(Qt::PreciseTimer);
timer->setSingleShot(true);
tcp_server = new QTcpServer(this);
connect(tcp_server, &QTcpServer::newConnection, this, &DemoServer::accept_connection);
connect(timer, &QTimer::timeout, this, &DemoServer::playback);
}
void DemoServer::start_server()
{
if (server_started) return;
if (!tcp_server->listen(QHostAddress::LocalHost, 0)) {
qCritical() << "Could not start demo playback server...";
qDebug() << tcp_server->errorString();
return;
}
this->port = tcp_server->serverPort();
qDebug() << "Server started";
server_started = true;
}
void DemoServer::destroy_connection()
{
QTcpSocket* temp_socket = tcp_server->nextPendingConnection();
connect(temp_socket, &QAbstractSocket::disconnected, temp_socket, &QObject::deleteLater);
temp_socket->disconnectFromHost();
return;
}
void DemoServer::accept_connection()
{
QString path = QFileDialog::getOpenFileName(nullptr, tr("Load Demo"), "logs/", tr("Demo Files (*.demo)"));
if (path.isEmpty())
{
destroy_connection();
return;
}
load_demo(path);
if (demo_data.isEmpty())
{
destroy_connection();
return;
}
if (demo_data.head().startsWith("SC#"))
{
sc_packet = demo_data.dequeue();
AOPacket sc(sc_packet);
num_chars = sc.get_contents().length();
}
else
{
sc_packet = "SC#%";
num_chars = 0;
}
if (client_sock) {
// Client is already connected...
qDebug() << "Multiple connections to demo server disallowed.";
QTcpSocket* temp_socket = tcp_server->nextPendingConnection();
connect(temp_socket, &QAbstractSocket::disconnected, temp_socket, &QObject::deleteLater);
temp_socket->disconnectFromHost();
return;
}
client_sock = tcp_server->nextPendingConnection();
connect(client_sock, &QAbstractSocket::disconnected, this, &DemoServer::client_disconnect);
connect(client_sock, &QAbstractSocket::readyRead, this, &DemoServer::recv_data);
client_sock->write("decryptor#NOENCRYPT#%");
}
void DemoServer::recv_data()
{
QString in_data = QString::fromUtf8(client_sock->readAll());
// Copypasted from NetworkManager
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 ao_packet(packet);
handle_packet(ao_packet);
}
}
void DemoServer::handle_packet(AOPacket packet)
{
packet.net_decode();
// This code is literally a barebones AO server
// It is wise to do it this way, because I can
// avoid touching any of this disgusting shit
// related to hardcoding this stuff in.
// Also, at some point, I will make akashit
// into a shared library.
QString header = packet.get_header();
QStringList contents = packet.get_contents();
if (header == "HI") {
client_sock->write("ID#0#DEMOINTERNAL#0#%");
}
else if (header == "ID") {
QStringList feature_list = {
"noencryption", "yellowtext", "prezoom",
"flipping", "customobjections", "fastloading",
"deskmod", "evidence", "cccc_ic_support",
"arup", "casing_alerts", "modcall_reason",
"looping_sfx", "additive", "effects",
"y_offset", "expanded_desk_mods"};
client_sock->write("PN#0#1#%");
client_sock->write("FL#");
client_sock->write(feature_list.join('#').toUtf8());
client_sock->write("#%");
}
else if (header == "askchaa") {
client_sock->write("SI#");
client_sock->write(QString::number(num_chars).toUtf8());
client_sock->write("#0#1#%");
}
else if (header == "RC") {
client_sock->write(sc_packet.toUtf8());
}
else if (header == "RM") {
client_sock->write("SM#%");
}
else if (header == "RD") {
client_sock->write("DONE#%");
}
else if (header == "CC") {
client_sock->write("PV#0#CID#-1#%");
client_sock->write("CT#DEMO#Demo file loaded. Send /play or > in OOC to begin playback.#1#%");
}
else if (header == "CT") {
if (contents[1].startsWith("/load"))
{
QString path = QFileDialog::getOpenFileName(nullptr, tr("Load Demo"), "logs/", tr("Demo Files (*.demo)"));
if (path.isEmpty())
return;
load_demo(path);
client_sock->write("CT#DEMO#Demo file loaded. Send /play or > in OOC to begin playback.#1#%");
}
else if (contents[1].startsWith("/play") || contents[1] == ">")
{
if (timer->interval() != 0 && !timer->isActive())
{
timer->start();
client_sock->write("CT#DEMO#Resuming playback.#1#%");
}
else
{
if (demo_data.isEmpty() && p_path != "")
load_demo(p_path);
playback();
}
}
else if (contents[1].startsWith("/pause") || contents[1] == "|")
{
int timeleft = timer->remainingTime();
timer->stop();
timer->setInterval(timeleft);
client_sock->write("CT#DEMO#Pausing playback.#1#%");
}
else if (contents[1].startsWith("/max_wait"))
{
QStringList args = contents[1].split(" ");
if (args.size() > 1)
{
bool ok;
int p_max_wait = args.at(1).toInt(&ok);
if (ok)
{
if (p_max_wait < 0)
p_max_wait = -1;
max_wait = p_max_wait;
client_sock->write("CT#DEMO#Setting max_wait to ");
client_sock->write(QString::number(max_wait).toUtf8());
client_sock->write(" milliseconds.#1#%");
}
else
{
client_sock->write("CT#DEMO#Not a valid integer!#1#%");
}
}
else
{
client_sock->write("CT#DEMO#Current max_wait is ");
client_sock->write(QString::number(max_wait).toUtf8());
client_sock->write(" milliseconds.#1#%");
}
}
else if (contents[1].startsWith("/min_wait"))
{
QStringList args = contents[1].split(" ");
if (args.size() > 1)
{
bool ok;
int p_min_wait = args.at(1).toInt(&ok);
if (ok)
{
if (p_min_wait < 0)
p_min_wait = -1;
min_wait = p_min_wait;
client_sock->write("CT#DEMO#Setting min_wait to ");
client_sock->write(QString::number(min_wait).toUtf8());
client_sock->write(" milliseconds.#1#%");
}
else
{
client_sock->write("CT#DEMO#Not a valid integer!#1#%");
}
}
else
{
client_sock->write("CT#DEMO#Current min_wait is ");
client_sock->write(QString::number(min_wait).toUtf8());
client_sock->write(" milliseconds.#1#%");
}
}
else if (contents[1].startsWith("/help"))
{
client_sock->write("CT#DEMO#Available commands:\nload, play, pause, max_wait, min_wait, help#1#%");
}
}
}
void DemoServer::load_demo(QString filename)
{
QFile demo_file(filename);
demo_file.open(QIODevice::ReadOnly);
if (!demo_file.isOpen())
return;
demo_data.clear();
p_path = filename;
QTextStream demo_stream(&demo_file);
QString line = demo_stream.readLine();
while (!line.isNull()) {
if (!line.endsWith("%")) {
line += "\n";
}
demo_data.enqueue(line);
line = demo_stream.readLine();
}
}
void DemoServer::playback()
{
if (demo_data.isEmpty())
return;
QString current_packet = demo_data.dequeue();
// We reset the elapsed time with this packet
if (current_packet.startsWith("MS#"))
elapsed_time = 0;
while (!current_packet.startsWith("wait") && !demo_data.isEmpty()) {
client_sock->write(current_packet.toUtf8());
current_packet = demo_data.dequeue();
}
if (!demo_data.isEmpty()) {
AOPacket wait_packet = AOPacket(current_packet);
int duration = wait_packet.get_contents().at(0).toInt();
if (max_wait != -1 && duration + elapsed_time > max_wait)
duration = qMax(0, max_wait - elapsed_time);
// We use elapsed_time to make sure that the packet we're using min_wait on is "priority" (e.g. IC)
if (elapsed_time == 0 && min_wait != -1 && duration < min_wait)
duration = min_wait;
elapsed_time += duration;
timer->start(duration);
}
else
{
client_sock->write("CT#DEMO#Reached the end of the demo file. Send /play or > in OOC to restart, or /load to open a new file.#1#%");
timer->setInterval(0);
}
}
void DemoServer::client_disconnect()
{
client_sock->deleteLater();
client_sock = nullptr;
}

View File

@ -3,6 +3,7 @@
#include "aoapplication.h"
#include "aosfxplayer.h"
#include "debug_functions.h"
#include "demoserver.h"
#include "networkmanager.h"
#include <QImageReader>
@ -28,6 +29,7 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow()
ui_server_list = new QTreeWidget(this);
ui_server_list->setHeaderLabels({"#", "Name"}); //, "Players"});
ui_server_list->hideColumn(0);
ui_server_list->setHeaderHidden(true);
ui_server_search = new QLineEdit(this);
ui_server_search->setFrame(false);
@ -438,7 +440,15 @@ void Lobby::on_server_list_clicked(QTreeWidgetItem *p_item, int column)
ui_connect->setEnabled(false);
ao_app->net_manager->connect_to_server(f_server);
if (f_server.port == 99999 && f_server.ip == "127.0.0.1") {
// Demo playback server selected
ao_app->demo_server->start_server();
server_type demo_server;
demo_server.ip = "127.0.0.1";
demo_server.port = ao_app->demo_server->port;
ao_app->net_manager->connect_to_server(demo_server);
}
else ao_app->net_manager->connect_to_server(f_server);
}
}

View File

@ -131,7 +131,7 @@ void NetworkManager::on_srv_lookup()
qDebug() << "Connecting to " << record.target() << ":" << record.port();
#endif
ms_socket->connectToHost(record.target(), record.port());
QTime timer;
QElapsedTimer timer;
timer.start();
do {
ao_app->processEvents();

View File

@ -102,6 +102,19 @@ end:
delete p_packet;
}
void AOApplication::append_to_demofile(QString packet_string)
{
if (get_auto_logging_enabled() && !log_filename.isEmpty())
{
QString path = log_filename.left(log_filename.size()).replace(".log", ".demo");
append_to_file(packet_string, path, true);
if (!demo_timer.isValid())
demo_timer.start();
else
append_to_file("wait#"+ QString::number(demo_timer.restart()) + "#%", path, true);
}
}
void AOApplication::server_packet_received(AOPacket *p_packet)
{
p_packet->net_decode();
@ -164,6 +177,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
else
w_courtroom->append_server_chatmessage(f_contents.at(0),
f_contents.at(1), "0");
append_to_demofile(p_packet->to_string(true));
}
}
else if (header == "FL") {
@ -232,7 +247,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
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)
if (char_list_size < 0 || evidence_list_size < 0 || music_list_size < 0)
goto end;
loaded_chars = 0;
@ -255,7 +270,6 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
server_name = info.name;
server_address =
QString("%1:%2").arg(info.ip, QString::number(info.port));
qDebug() << server_address;
window_title += ": " + server_name;
}
}
@ -265,7 +279,6 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
server_name = info.name;
server_address =
QString("%1:%2").arg(info.ip, QString::number(info.port));
qDebug() << server_address;
window_title += ": " + server_name;
}
}
@ -283,7 +296,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
// Remove any characters not accepted in folder names for the server_name
// here
if (AOApplication::get_auto_logging_enabled()) {
if (AOApplication::get_auto_logging_enabled() && server_name != "Demo playback") {
this->log_filename = QDateTime::currentDateTime().toUTC().toString(
"'logs/" + server_name.remove(QRegExp("[\\\\/:*?\"<>|\']")) +
"/'yyyy-MM-dd hh-mm-ss t'.log'");
@ -292,6 +305,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
QDateTime::currentDateTime().toUTC().toString(),
log_filename, true);
}
else
this->log_filename = "";
QCryptographicHash hash(QCryptographicHash::Algorithm::Sha256);
hash.addData(server_address.toUtf8());
@ -312,7 +327,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
}
else if (header == "SC") {
if (!courtroom_constructed)
if (!courtroom_constructed || courtroom_loaded)
goto end;
for (int n_element = 0; n_element < f_contents.size(); ++n_element) {
@ -344,9 +359,10 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
}
send_server_packet(new AOPacket("RM#%"));
append_to_demofile(p_packet->to_string(true));
}
else if (header == "SM") {
if (!courtroom_constructed)
if (!courtroom_constructed || courtroom_loaded)
goto end;
bool musics_time = false;
@ -445,6 +461,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
2) // We have a pos included in the background packet!
w_courtroom->set_side(f_contents.at(1));
w_courtroom->set_background(f_contents.at(0), f_contents.size() >= 2);
append_to_demofile(p_packet->to_string(true));
}
}
else if (header == "SP") {
@ -454,6 +471,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
if (courtroom_constructed) // We were sent a "set position" packet
{
w_courtroom->set_side(f_contents.at(0));
append_to_demofile(p_packet->to_string(true));
}
}
else if (header == "SD") // Send pos dropdown
@ -475,27 +493,37 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
}
else if (header == "MS") {
if (courtroom_constructed && courtroom_loaded)
{
w_courtroom->chatmessage_enqueue(p_packet->get_contents());
append_to_demofile(p_packet->to_string(true));
}
}
else if (header == "MC") {
if (courtroom_constructed && courtroom_loaded)
{
w_courtroom->handle_song(&p_packet->get_contents());
append_to_demofile(p_packet->to_string(true));
}
}
else if (header == "RT") {
if (f_contents.size() < 1)
goto end;
if (courtroom_constructed) {
if (f_contents.size() == 1)
w_courtroom->handle_wtce(f_contents.at(0), 0);
else if (f_contents.size() == 2) {
w_courtroom->handle_wtce(f_contents.at(0), f_contents.at(1).toInt());
if (f_contents.size() == 1)
w_courtroom->handle_wtce(f_contents.at(0), 0);
else if (f_contents.size() == 2) {
w_courtroom->handle_wtce(f_contents.at(0), f_contents.at(1).toInt());
append_to_demofile(p_packet->to_string(true));
}
}
}
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());
append_to_demofile(p_packet->to_string(true));
}
}
else if (header == "LE") {
if (courtroom_constructed) {

View File

@ -65,6 +65,16 @@ QString AOApplication::get_character_path(QString p_char, QString p_file)
return get_case_sensitive_path(path);
}
QString AOApplication::get_misc_path(QString p_misc, QString p_file)
{
QString path = get_base_path() + "misc/" + p_misc + "/" + p_file;
#ifndef CASE_SENSITIVE_FILESYSTEM
return path;
#else
return get_case_sensitive_path(path);
#endif
}
QString AOApplication::get_sounds_path(QString p_file)
{
QString path = get_base_path() + "sounds/general/" + p_file;

View File

@ -52,6 +52,12 @@ int AOApplication::stay_time()
return result;
}
int AOApplication::get_chat_ratelimit()
{
int result = configini->value("chat_ratelimit", 300).toInt();
return result;
}
bool AOApplication::get_log_goes_downwards()
{
QString result =
@ -61,8 +67,7 @@ bool AOApplication::get_log_goes_downwards()
bool AOApplication::get_log_newline()
{
QString result =
configini->value("log_newline", "false").value<QString>();
QString result = configini->value("log_newline", "false").value<QString>();
return result.startsWith("true");
}
@ -74,8 +79,7 @@ int AOApplication::get_log_margin()
bool AOApplication::get_log_timestamp()
{
QString result =
configini->value("log_timestamp", "false").value<QString>();
QString result = configini->value("log_timestamp", "false").value<QString>();
return result.startsWith("true");
}
@ -178,6 +182,10 @@ bool AOApplication::write_to_file(QString p_text, QString p_file, bool make_dir)
bool AOApplication::append_to_file(QString p_text, QString p_file,
bool make_dir)
{
if(!file_exists(p_file)) //Don't create a newline if file didn't exist before now
{
return write_to_file(p_text, p_file, make_dir);
}
QString path = QFileInfo(p_file).path();
// Create the dir if it doesn't exist yet
if (make_dir) {
@ -249,6 +257,13 @@ QVector<server_type> AOApplication::read_serverlist_txt()
f_server_list.append(f_server);
}
server_type demo_server;
demo_server.ip = "127.0.0.1";
demo_server.port = 99999;
demo_server.name = "Demo playback";
demo_server.desc = "Play back demos you have previously recorded";
f_server_list.append(demo_server);
return f_server_list;
}
@ -265,6 +280,13 @@ QString AOApplication::read_design_ini(QString p_identifier,
}
}
Qt::TransformationMode AOApplication::get_scaling(QString p_scaling)
{
if (p_scaling == "smooth")
return Qt::SmoothTransformation;
return Qt::FastTransformation;
}
QPoint AOApplication::get_button_spacing(QString p_identifier, QString p_file)
{
QString design_ini_path = get_theme_path(p_file);
@ -298,28 +320,12 @@ pos_size_type AOApplication::get_element_dimensions(QString p_identifier,
QString p_file,
QString p_char)
{
QString char_ini_path =
get_base_path() + "misc/" + get_chat(p_char) + "/" + 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, char_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, design_ini_path);
if (f_result == "") {
f_result = read_design_ini(p_identifier, default_path);
if (f_result == "")
return return_value;
}
}
QString f_result = get_design_element(p_identifier, p_file, p_char);
QStringList sub_line_elements = f_result.split(",");
@ -336,17 +342,16 @@ pos_size_type AOApplication::get_element_dimensions(QString p_identifier,
QString AOApplication::get_design_element(QString p_identifier, QString p_file,
QString p_char)
{
QString char_ini_path =
get_base_path() + "misc/" + get_chat(p_char) + "/" + 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, char_ini_path);
if (f_result == "") {
f_result = read_design_ini(p_identifier, design_ini_path);
if (f_result == "")
f_result = read_design_ini(p_identifier, default_path);
QStringList paths{get_theme_path("misc/" + get_chat(p_char) + "/" +
p_file), // user theme overrides base/misc
get_base_path() + "misc/" + get_chat(p_char) + "/" + p_file,
get_theme_path(p_file), get_default_theme_path(p_file)};
for (const QString &path : paths) {
QString value = read_design_ini(p_identifier, path);
if (!value.isEmpty())
return value;
}
return f_result;
return "";
}
QString AOApplication::get_font_name(QString p_identifier, QString p_file)
{
@ -466,34 +471,30 @@ QString AOApplication::get_tagged_stylesheet(QString target_tag, QString p_file)
return f_text;
}
QString AOApplication::get_chat_markdown(QString p_identifier, QString p_chat)
QString AOApplication::get_chat_markup(QString p_identifier, QString p_chat)
{
QString design_ini_path =
get_base_path() + "misc/" + get_chat(p_chat) + "/config.ini";
QString default_path = get_base_path() + "misc/default/config.ini";
QString f_result = read_design_ini(p_identifier, design_ini_path);
QStringList paths{get_theme_path("misc/" + get_chat(p_chat) + "/config.ini"),
get_base_path() + "misc/" + get_chat(p_chat) +
"/config.ini",
get_base_path() + "misc/default/config.ini",
get_theme_path("misc/default/config.ini")};
if (f_result == "")
f_result = read_design_ini(p_identifier, default_path);
for (const QString &path : paths) {
QString value = read_design_ini(p_identifier, path);
if (!value.isEmpty()) {
return value.toLatin1();
}
}
return f_result.toLatin1();
return "";
}
QColor AOApplication::get_chat_color(QString p_identifier, QString p_chat)
{
QColor return_color(255, 255, 255);
QString design_ini_path =
get_base_path() + "misc/" + get_chat(p_chat) + "/config.ini";
QString default_path = get_base_path() + "misc/default/config.ini";
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 return_color;
}
QString f_result = get_chat_markup(p_identifier, p_chat);
if (f_result == "")
return return_color;
QStringList color_list = f_result.split(",");
@ -507,23 +508,21 @@ QColor AOApplication::get_chat_color(QString p_identifier, QString p_chat)
return return_color;
}
QString AOApplication::get_sfx(QString p_identifier)
QString AOApplication::get_sfx(QString p_identifier, QString p_misc)
{
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);
QStringList paths{get_theme_path("misc/" + p_misc + "/courtroom_sounds.ini"),
get_misc_path(p_misc, "courtroom_sounds.ini"),
get_theme_path("courtroom_sounds.ini"),
get_default_theme_path("courtroom_sounds.ini")};
QString return_sfx = "";
if (f_result == "") {
f_result = read_design_ini(p_identifier, default_path);
if (f_result == "")
return return_sfx;
for (const QString &path : paths) {
QString value = read_design_ini(p_identifier, path);
if (!value.isEmpty()) {
return value.toLatin1();
}
}
return_sfx = f_result;
return return_sfx;
}
@ -650,6 +649,37 @@ QString AOApplication::get_blips(QString p_char)
return f_result;
}
QString AOApplication::get_emote_property(QString p_char, QString p_emote,
QString p_property)
{
QString f_result =
read_char_ini(p_char, p_emote, p_property); // per-emote override
if (f_result == "")
f_result = read_char_ini(p_char, p_property,
"Options"); // global for this character
return f_result;
}
Qt::TransformationMode AOApplication::get_misc_scaling(QString p_miscname)
{
if (p_miscname != "") {
QString misc_transform_mode = read_design_ini(
"scaling", get_theme_path("misc/" + p_miscname + "/config.ini"));
if (misc_transform_mode == "")
misc_transform_mode =
read_design_ini("scaling", get_misc_path(p_miscname, "config.ini"));
if (misc_transform_mode == "smooth")
return Qt::SmoothTransformation;
}
return Qt::FastTransformation;
}
QString AOApplication::get_category(QString p_char)
{
QString f_result = read_char_ini(p_char, "category", "Options");
return f_result;
}
QString AOApplication::get_chat(QString p_char)
{
if (p_char == "default")
@ -881,7 +911,7 @@ QStringList AOApplication::get_theme_effects()
QStringList lines = read_file(p_path).split("\n");
foreach (QString effect, lines) {
effect = effect.split("=")[0].trimmed();
effect = effect.split("=")[0].trimmed().split("_")[0];
if (!effect.isEmpty() && !effects.contains(effect))
effects.append(effect);
}
@ -899,7 +929,7 @@ QStringList AOApplication::get_effects(QString p_char)
QStringList lines = read_file(p_path).split("\n");
foreach (QString effect, lines) {
effect = effect.split("=")[0].trimmed();
effect = effect.split("=")[0].trimmed().split("_")[0];
if (!effect.isEmpty() && !effects.contains(effect))
effects.append(effect);
}
@ -934,25 +964,33 @@ QString AOApplication::get_effect(QString effect, QString p_char,
return p_path;
}
QString AOApplication::get_effect_sound(QString fx_name, QString p_char)
QString AOApplication::get_effect_property(QString fx_name, QString p_char,
QString p_property)
{
QString f_property;
if (p_property == "sound")
f_property = fx_name;
else
f_property = fx_name + "_" + p_property;
QString p_effect = read_char_ini(p_char, "effects", "Options");
QString p_path = get_base_path() + "misc/" + p_effect + "/effects.ini";
QString design_ini_path = get_theme_path("effects/effects.ini");
QString default_path = get_default_theme_path("effects/effects.ini");
QString f_result = read_design_ini(fx_name, p_path);
QString f_result = read_design_ini(f_property, p_path);
if (f_result == "") {
f_result = read_design_ini(fx_name, design_ini_path);
f_result = read_design_ini(f_property, design_ini_path);
if (f_result == "") {
f_result = read_design_ini(fx_name, default_path);
f_result = read_design_ini(f_property, default_path);
}
}
if (fx_name == "realization") {
if (fx_name == "realization" && p_property == "sound") {
f_result = get_custom_realization(p_char);
}
qDebug() << "got" << f_property << "of" << fx_name << "==" << f_result;
return f_result;
}