diff --git a/Attorney_Online.pro b/Attorney_Online.pro index 20a61d3..7eda548 100644 --- a/Attorney_Online.pro +++ b/Attorney_Online.pro @@ -3,7 +3,7 @@ QT += core gui widgets network TARGET = Attorney_Online TEMPLATE = app -VERSION = 2.6.2.0 +VERSION = 2.8.3.1 INCLUDEPATH += $$PWD/include DESTDIR = $$PWD/bin @@ -13,7 +13,6 @@ MOC_DIR = $$PWD/build SOURCES += $$files($$PWD/src/*.cpp) HEADERS += $$files($$PWD/include/*.h) - LIBS += -L$$PWD/lib DEFINES += DISCORD @@ -26,6 +25,7 @@ DEFINES += BASSAUDIO contains(DEFINES, BASSAUDIO) { LIBS += -lbass +LIBS += -lbassopus } #DEFINES += QTAUDIO diff --git a/README.md b/README.md index 0416e4a..4dc6956 100644 --- a/README.md +++ b/README.md @@ -2,177 +2,7 @@ [Attorney Online](https://aceattorneyonline.com) is an online version of the world-renowned courtroom drama simulator that allows you to create and play out cases in an off-the-cuff format. -## Introduction for beginners - -You may already be familiar with roleplaying in forums, Roll20, and/or [AAO](http://aaonline.fr/) (the online casemaker). In this sense, Attorney Online is nothing more than a medium - an animated chatroom client - that allows cases to be played out as if it were an Ace Attorney game. - -Not unlike other roleplaying games, cases can last an absurd amount of time (between 4 to 6 hours) and generally follow a roleplaying format directed by a case sheet. - -An implied expectation for fast typing and real-time communication may seem daunting at first, but due to the number of people in the courtroom, things get hectic very quickly even with only a few people talking. Therefore, you should not feel pressured to talk constantly: only when you have the attention of the court (or when you have an objection to make) should you feel the need to speak. - -It is recommended, but not strictly necessary, to have played an Ace Attorney game before creating your own case. You should also try to spectate or take part in a community case in order to get a grasp of how cases are done in practice. - ---- - -## Basic features - -### In-character chat - -Type in a message in the gray box under the viewport, select an emote, and press Enter. - -### Emotes - -An emote represents a set of animations played while the character is speaking and idle. Some emotes also contain a preanimation, which is played before the text is said by the character. - -### Interjections (shouts) - -Select an interjection to toggle it. When you send a message, it will interrupt all other dialogue and interject with your message. - -### Out-of-character chat - -This is a general-purpose chat isolated within areas to discuss matters without interrupting cases. You must enter a name before chatting. - -### Music list - -Double-click a track to play it. Some servers automatically loop the track. Green tracks are available locally; red tracks are not. - -### Areas - -Servers have multiple areas to hold multiple cases simultaneously. Double-click an area in the music list to switch to it. (The reason that -areas are in the music list is a historical one.) - -### Judge controls - -The judge can set health bars and play the Witness Testimony, Cross Examination, Guilty, and Not Guilty animations. - -### Mod calls - -Calling a mod notifies moderators currently in the server of an incident. (Mod call reasons require 2.6+ server-side support.) Logged-in moderators can toggle the Guard option to be notified of mod calls. - -### Muting - -Click on a character in the mute list to ignore any in-character chat from the specified character. - -### Positions - -All characters have a default position within the courtroom, but they can nonetheless be changed within the interface. - -Available positions: - -- `def` - Defense -- `pro` - Prosecution -- `hld` - Helper defense -- `hlp` - Helper prosecution -- `jud` - Judge -- `wit` - Witness -- `jur` - Juror (2.6+) -- `sea` - Seance (2.6+) - -## Advanced features - -### Markup language - -2.6.0 introduces a markup language for in-character chat. It does not require server-side support. - -#### Color - -Wrapping text with these characters will set the text inside of them to the associated color. - -- `(` and `)` (parentheses) - blue -- \` (backtick) - green -- `|` (vertical bar) - orange -- `[` and `]` (square brackets) - grey - -#### Speed - -Type `{` to slow down the text a bit, and `}` to speed it up. This takes effect after the character has been typed, so the text may take up different speeds at different points. Both of these can be stacked up to three times, and even against each other. - -Example: -``` -Hello there! This text goes at normal speed.} Now, it's a bit faster!{ Now, it's back to normal.}}} Now it goes at maximum speed! {{Now it's only a little bit faster than normal. -``` - -#### Position - -If you begin a message with `~~` (two tildes), the two tildes are removed and the message is centered. - -### Pairing (2.6+) - -If two players are in the same position and select each other's characters using the in-game pair list (or with `/pair [id]`), they will appear alongside each other. You can set the offset of your character using the provided spinbox (or with `/offset [percentage]`). - -### Non-interrupting preanimations (2.6+) - -When checked, this will force text to immediately begin displaying without waiting for the preanimation to finish. - -### Custom IC names (shownames) (2.6+) - -You can set a custom in-character name using the provided text box. An option in the interface (or `/force_nonint_pres`) is also present to disable custom IC names for other players to prevent impersonation. - -### Extended area support (2.6+) - -Areas can be listed by clicking the A/M button (or `/switch_am`). The statuses of such areas are displayed (and updated automatically) if the server has 2.6+ support. - ---- - -## Upgrade guide for 2.6 - -2.6 inherits features from the Case Café custom client and server. Old themes and servers will still work, but they will not expose the new additions to players. - -### Server - -2.6 support has only been developed for tsuserver3. serverD is currently not equipped at all for such support. - -- Apply the new code changes. -- In `areas.yaml`: - - You may add `shouts_allowed` to any of the areas to enable / disable shouts (and judge buttons, and realisation). By default, it's `shouts_allowed: true`. - - You may add `jukebox` to any of the areas to enable the jukebox in there, but you can also use `/jukebox_toggle` in game as a mod to do the same thing. By default, it's `jukebox: false`. - - You may add `showname_changes_allowed` to any of the areas to allow custom shownames used in there. If it's forbidden, players can't send messages or change music as long as they have a custom name set. By default, it's `showname_changes_allowed: false`. - - You may add `abbreviation` to override the server-generated abbreviation of the area. Instead of area numbers, this server-pack uses area abbreviations in server messages for easier understanding (but still uses area IDs in commands, of course). No default here, but here is an example: `abbreviation: SIN` gives the area the abbreviation of 'SIN'. - - You may add `noninterrupting_pres` to force users to use non-interrupting pres only. 2.6 users will see the pres play as the text goes; pre-2.6 users will not see pres at all. The default is `noninterrupting_pres: false`. - -### Client themes - -- You'll need the following, additional images: - - `notguilty.gif` - Not Guilty verdict animation - - `guilty.gif` - Guilty verdict animation - - `notguilty.png` - Not Guilty button - - `guilty.png` - Guilty button - - `pair_button.png` - Pair button - - `pair_button_pressed.png` - Pair button (selected) -- In your `courtroom_sounds.ini`: - - Add a sound effect for `not_guilty`, for example: `not_guilty = sfx-notguilty.wav`. - - Add a sound effect for `guilty`, for example: `guilty = sfx-guilty.wav`. - - Add a sound effect for the case alerts. They work similarly to modcall alerts, or callword alerts. For example: `case_call = sfx-triplegavel-soj.wav`. -- In your `courtroom_design.ini`, place the following new UI elements as desired: - - `log_limit_label`, which is a simple text that explains what the spinbox with the numbers is. Needs an X, Y, width, height number. - - `log_limit_spinbox`, which is the spinbox for the log limit, allowing you to set the size of the log limit in-game. Needs the same stuff as above. - - `ic_chat_name`, which is an input field for your custom showname. Needs the same stuff. - - `ao2_ic_chat_name`, which is the same as above, but comes into play when the background has a desk. - - Further comments on this: all `ao2_` UI elements come into play when the background has a desk. However, in AO2 nowadays, it's customary for every background to have a desk, even if it's just an empty gif. So you most likely have never seen the `ao2_`-less UI elements ever come into play, unless someone mis-named a desk or something. - - `showname_enable` is a tickbox that toggles whether you should see shownames or not. This does not influence whether you can USE custom shownames or not, so you can have it off, while still showing a custom showname to everyone else. Needs X, Y, width, height as usual. - - `settings` is a plain button that takes up the OS's looks, like the 'Call mod' button. Takes the same arguments as above. - - You can also just type `/settings` in OOC. - - `char_search` is a text input box on the character selection screen, which allows you to filter characters down to name. Needs the same arguments. - - `char_passworded` is a tickbox, that when ticked, shows all passworded characters on the character selection screen. Needs the same as above. - - `char_taken` is another tickbox, that does the same, but for characters that are taken. - - `not_guilty` is a button similar to the CE / WT buttons, that if pressed, plays the Not Guilty verdict animation. Needs the same arguments. - - `guilty` is similar to `not_guilty`, but for the Guilty verdict. - - `pair_button` is a toggleable button, that shows and hides the pairing list and the offset spinbox. Works similarly to the mute button. - - `pair_list` is a list of all characters in alphabetical order, shown when the user presses the Pair button. If a character is clicked on it, it is selected as the character the user wants to pair up with. - - `pair_offset_spinbox` is a spinbox that allows the user to choose between offsets of -100% to 100%. - - `switch_area_music` is a button with the text 'A/M', that toggles between the music list and the areas list. Though the two are different, they are programmed to take the same space. - - `pre_no_interrupt` is a tickbox with the text 'No Intrpt', that toggles whether preanimations should delay the text or not. - - `area_free_color` is a combination of red, green, and blue values ranging from 0 to 255. This determines the colour of the area in the Area list if it's free, and has a status of `IDLE`. - - `area_lfp_color` determines the colour of the area if its status is `LOOKING-FOR-PLAYERS`. - - `area_casing_color` determines the colour of the area if its status is `CASING`. - - `area_recess_color` determines the colour of the area if its status is `RECESS`. - - `area_rp_color` determines the colour of the area if its status is `RP`. - - `area_gaming_color` determines the colour of the area if its status is `GAMING`. - - `area_locked_color` determines the colour of the area if it is locked, regardless of status. - - `ooc_default_color` determines the colour of the username in the OOC chat if the message doesn't come from the server. - - `ooc_server_color` determines the colour of the username if the message arrived from the server. - - `casing_button` is a button with the text 'Casing' that when clicked, brings up the Case Announcements dialog. You can give the case a name, and tick whom do you want to alert. You need to be a CM for it to go through. Only people who have at least one of the roles ticked will get the alert. - - `casing` is a tickbox with the text 'Casing'. If ticked, you will get the case announcements alerts you should get, in accordance to the above. In the settings, you can change your defaults on the 'Casing' tab. (That's a buncha things titled 'Casing'!) +## [Refer to the Wiki](https://github.com/Crystalwarrior/KFO-Client/wiki) for more information. --- @@ -182,7 +12,6 @@ The traditional route is by undergoing the [AO2 Rite of Passage](https://gist.gi ### Dependencies -- [QtApng](https://github.com/Skycoder42/QtApng) - [BASS](http://un4seen.com) (proprietary, but will become optional in the future; see #35) - [Discord Rich Presence](https://github.com/discordapp/discord-rpc) @@ -236,6 +65,8 @@ Modifications copyright (c) 2017-2018 oldmud0 Case Café additions copyright (c) 2018 Cerapter +Killing Fever Online additions copyright (c) 2019 Crystalwarrior + ### Qt This project uses Qt 5, 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. diff --git a/base/themes/1.8/arrow_left.png b/base/themes/1.8/arrow_left.png deleted file mode 100644 index f1098c4..0000000 Binary files a/base/themes/1.8/arrow_left.png and /dev/null differ diff --git a/base/themes/1.8/arrow_right.png b/base/themes/1.8/arrow_right.png deleted file mode 100644 index 2b5ad70..0000000 Binary files a/base/themes/1.8/arrow_right.png and /dev/null differ diff --git a/base/themes/1.8/chatmed.png b/base/themes/1.8/chatmed.png deleted file mode 100644 index 0c3bae1..0000000 Binary files a/base/themes/1.8/chatmed.png and /dev/null differ diff --git a/base/themes/1.8/favorites.png b/base/themes/1.8/favorites.png deleted file mode 100644 index 0eec611..0000000 Binary files a/base/themes/1.8/favorites.png and /dev/null differ diff --git a/base/themes/1.8/favorites_selected.png b/base/themes/1.8/favorites_selected.png deleted file mode 100644 index f6a2abf..0000000 Binary files a/base/themes/1.8/favorites_selected.png and /dev/null differ diff --git a/base/themes/1.8/holdit.png b/base/themes/1.8/holdit.png deleted file mode 100644 index 09d1d37..0000000 Binary files a/base/themes/1.8/holdit.png and /dev/null differ diff --git a/base/themes/1.8/holdit_selected.png b/base/themes/1.8/holdit_selected.png deleted file mode 100644 index 51586e7..0000000 Binary files a/base/themes/1.8/holdit_selected.png and /dev/null differ diff --git a/base/themes/1.8/mute.png b/base/themes/1.8/mute.png deleted file mode 100644 index e64f702..0000000 Binary files a/base/themes/1.8/mute.png and /dev/null differ diff --git a/base/themes/1.8/mute_pressed.png b/base/themes/1.8/mute_pressed.png deleted file mode 100644 index 334b4ba..0000000 Binary files a/base/themes/1.8/mute_pressed.png and /dev/null differ diff --git a/base/themes/1.8/objection.png b/base/themes/1.8/objection.png deleted file mode 100644 index 6fbcbc2..0000000 Binary files a/base/themes/1.8/objection.png and /dev/null differ diff --git a/base/themes/1.8/objection_selected.png b/base/themes/1.8/objection_selected.png deleted file mode 100644 index 384e4e9..0000000 Binary files a/base/themes/1.8/objection_selected.png and /dev/null differ diff --git a/base/themes/1.8/publicservers.png b/base/themes/1.8/publicservers.png deleted file mode 100644 index 650c20c..0000000 Binary files a/base/themes/1.8/publicservers.png and /dev/null differ diff --git a/base/themes/1.8/publicservers_selected.png b/base/themes/1.8/publicservers_selected.png deleted file mode 100644 index 308c79f..0000000 Binary files a/base/themes/1.8/publicservers_selected.png and /dev/null differ diff --git a/base/themes/1.8/takethat.png b/base/themes/1.8/takethat.png deleted file mode 100644 index 8ff2bef..0000000 Binary files a/base/themes/1.8/takethat.png and /dev/null differ diff --git a/base/themes/1.8/takethat_selected.png b/base/themes/1.8/takethat_selected.png deleted file mode 100644 index 3fe0a8c..0000000 Binary files a/base/themes/1.8/takethat_selected.png and /dev/null differ diff --git a/base/themes/default/chat.png b/base/themes/default/chat.png index e11f8c8..47281a9 100644 Binary files a/base/themes/default/chat.png and b/base/themes/default/chat.png differ diff --git a/base/themes/default/chatbig.png b/base/themes/default/chatbig.png index 177dc02..ee8d9ce 100644 Binary files a/base/themes/default/chatbig.png and b/base/themes/default/chatbig.png differ diff --git a/base/themes/default/chatmed.png b/base/themes/default/chatmed.png index d2fee4a..9c6f87f 100644 Binary files a/base/themes/default/chatmed.png and b/base/themes/default/chatmed.png differ diff --git a/base/themes/default/courtroom_design.ini b/base/themes/default/courtroom_design.ini index 960d6e4..3779475 100644 --- a/base/themes/default/courtroom_design.ini +++ b/base/themes/default/courtroom_design.ini @@ -2,35 +2,14 @@ ; compress courtroombackground.png accordingly. courtroom = 0, 0, 714, 668 +; **COORDINATE SYSTEM RELATIVE TO "courtroom"** +; x/y coordinates 0,0 will start at top-left of the "courtroom" for everything below until specified otherwise. +; **** + ; IC Area. Changing 256, 192 will stretch or compress character gifs and the ; /bgs being used accordingly viewport = 0, 0, 256, 192 -; IC chatbox -chatbox = 0, 174, 256, 108 - -; IC chatbox if the current background's folder contains stand.png, -; defensedesk.png and prosecutiondesk.png -ao2_chatbox = 0, 174, 256, 108 - -; Textbox for custom IC name (the "showname") -showname = 6, 1, 256, 15 - -; IC message, positioned within the chatbox. Changing 250 affects how -; long text goes on before going onto the next line. Changing 89 affects -; how many lines you can see before the message starts scrolling, based on -; the formula of n = 25+(n-1)*16, where n is the number of lines to be -; displayed. (ie, set it to 25 for 1 line, 41 for 2, 57 for 3, 73 for 4, -; 89 for 5, 105 for 6... Less than 25 displays nothing) -message = 3, 18, 250, 89 - -; Where you type to make an IC chat message -ic_chat_message = 2, 283, 250, 23 - -; IC chat message if the current background's folder contains stand.png, -; defensedesk.png and prosecutiondesk.png -ao2_ic_chat_message = 2, 283, 250, 23 - ; IC chatlog ic_chatlog = 260, 0, 231, 319 @@ -50,14 +29,20 @@ ooc_chat_name = 492, 300, 85, 19 ; Toggle between Server and Master OOC chats ooc_toggle = 580, 300, 133, 19 +; The scrolling music name display +music_display = 490, 0, 224, 26 +; WARNING: music_name x/y coordinates relative to music_display! +music_name = 0, 0, 224, 26 + ; Where the jukebox is music_list = 490, 342, 224, 326 ; Jukebox search bar music_search = 490, 319, 100, 23 -found_song_color = 100, 255, 100 -missing_song_color = 255, 100, 100 +; Music list state colors +found_song_color = 144, 238, 144 +missing_song_color = 255, 125, 125 ; Labels and sliders for music/sfx/blips music_label = 282, 607, 41, 16 @@ -74,6 +59,7 @@ blip_slider = 326, 648, 140, 16 ; 49X, 49Y (ie. 490, 147 if you want 10 columns and 3 rows) emotes = 5, 342, 490, 98 emote_button_spacing = 9, 9 +emote_button_size = 40, 40 ; Page togglers for emotes emote_left = 5, 434, 60, 32 @@ -83,6 +69,22 @@ emote_right = 428, 434, 60, 32 ; display the full emote name or truncate it based on length emote_dropdown = 5, 470, 105, 20 +; Display the accessible iniswaps on this character (grabbed from iniswaps.ini) +iniswap_dropdown = 100, 442, 89, 20 +; The button to remove the current iniswap +iniswap_remove = 78, 442, 20, 20 + +; Display the accessible sfx on this character (grabbed from soundlist.ini). If none found, courtroom_soundlist.ini will be used. +sfx_dropdown = 220, 442, 89, 20 +; The button to remove the current iniswap +sfx_remove = 198, 442, 20, 20 + +; Display the list of overlay effects accessible +effects_dropdown = 330, 441, 89, 22 + +; The size of the icons for dropdown entries +effects_icon_size = 16, 16 + ; Hold it/Take That/Objection and the "BLING!" buttons hold_it = 10, 310, 76, 28 objection = 90, 310, 76, 28 @@ -93,7 +95,7 @@ realization = 5, 515, 42, 42 ; custom.gif and custom.wav in their folder, this button acts as another ; Objection/Take That/Hold It for that character that uses the custom animation ; and sfx. (Think Satorah! Such Insolence!, Gotcha!) -custom_objection = 340, 565, 76, 28 +custom_objection = 250, 310, 76, 28 ; Text color dropdown menu text_color = 115, 470, 80, 20 @@ -104,7 +106,10 @@ pos_dropdown = 200, 470, 80, 20 pre = 5, 490, 80, 21 ; Flip button -flip = 104, 490, 51, 21 +flip = 64, 490, 51, 21 + +; Additive button +Additive = 114, 490, 80, 21 ; Guard button guard = 200, 560, 61, 21 @@ -132,33 +137,12 @@ call_mod = 104, 637, 64, 23 ; Mute button mute_button = 150, 515, 42, 42 +; Screenshake +screenshake = 51, 515, 42, 42 + ; Where the Mute list pops up when you click Mute mute_list = 280, 469, 210, 198 -; ???? - if there was an Area button, where the area list would show up? -; area_list = 270, 494, 224, 174 - -; ???? - where a password box appears if an area is password locked? -; area_password = 266, 471, 224, 23 - -; >Evidence meme -evidence_button = 627, 322, 85, 18 -evidence_background = 0, 385, 490, 284 -evidence_name = 112, 4, 264, 19 -evidence_buttons = 28, 27, 430, 216 -evidence_button_spacing = 2, 3 -evidence_overlay = 24, 24, 439, 222 -evidence_delete = 78, 8, 70, 20 -evidence_image_name = 150, 8, 130, 20 -evidence_image_button = 280, 8, 60, 20 -evidence_x = 341, 8, 20, 20 -evidence_description = 78, 28, 281, 166 -evidence_left = 28, 0, 60, 24 -evidence_right = 400, 0, 60, 24 -evidence_present = 165, 247, 158, 41 -left_evidence_icon = 13, 13, 70, 70 -right_evidence_icon = 173, 13, 70, 70 - ; Character select widgets char_select = 0, 0, 714, 668 back_to_lobby = 5, 5, 91, 23 @@ -173,15 +157,6 @@ spectator = 317, 640, 80, 23 ; New in 2.6.0 ; ------------------------- -; The log limiter explaining label. This is simply a piece of text that -; explains what the spin box is for. -; log_limit_label = 190, 612, 50, 30 - -; The spinbox allows you to set the log limit ingame inbetween 1 and 10000, -; with the option to set it to 0 as well (which is considered 'infinite' by -; the log limiter). -; log_limit_spinbox = 168, 636, 70, 25 - ; This is an input field that allows you to change your in-character showname. ic_chat_name = 200, 534, 78, 23 @@ -228,12 +203,12 @@ pair_offset_spinbox = 280, 470, 210, 20 switch_area_music = 590, 319, 35, 23 ; These are colours for the various statuses an area can be in. -area_free_color = 54, 198, 68 -area_lfp_color = 255, 255, 0 -area_casing_color = 255, 166, 0 -area_recess_color = 255, 190, 30 -area_rp_color = 200, 52, 252 -area_gaming_color = 55, 255, 255 +area_free_color = 144, 238, 144 +area_lfp_color = 127, 255, 0 +area_casing_color = 255, 215, 0 +area_recess_color = 255, 246, 143 +area_rp_color = 135, 206, 255 +area_gaming_color = 171, 130, 255 area_locked_color = 165, 43, 43 ; These two are casing-related inputs. @@ -242,4 +217,86 @@ area_locked_color = 165, 43, 43 ; "casing_button" is an interface to help you announce a case (you have to be ; a CM first to be able to announce cases). casing = 200, 560, 80, 21 -casing_button = 173, 637, 60, 23 \ No newline at end of file +casing_button = 173, 637, 60, 23 + +; +; Chat system +; + +; IC chatbox +chatbox = 0, 178, 256, 104 + +; IC chatbox if the current background's folder contains stand.png, +; defensedesk.png and prosecutiondesk.png +ao2_chatbox = 0, 178, 256, 104 + +; **COORDINATE SYSTEM RELATIVE TO "chatbox"/"ao2_chatbox"** +; x/y coordinates 0,0 will start at top-left of the "chatbox"/"ao2_chatbox" for everything below until specified otherwise. +; **** + +; Textbox for custom IC name (the "showname"), width is the smallest size +showname = 1, 0, 46, 15 + +; The chatbox image used for smallest possible size is "chat". "chatblank" is used if the showname is whitespace-only. +; "chatmed" will be used if the showname size exceeds the width, at which point the showname will receive showname_extra_width. +; "chatbig" will be used if the showname size exceeds the "chatmed" width, at which point the showname will receive showname_extra_width again. +; Text any bigger than that will be cut off. If "chatmed" or "chatbig" are missing, showname will not be resized. +showname_extra_width = 24 + +; IC message, positioned within the chatbox. Changing 250 affects how +; long text goes on before going onto the next line. Changing 89 affects +; how many lines you can see before the message starts scrolling, based on +; Every line height is 25 if the font size is 10 and the Sans font is used. +; There's 4 pixels from X position until the symbol is displayed. +; the formula of n = 25+(n-1)*16, where n is the number of lines to be +; displayed. (ie, set it to 25 for 1 line, 41 for 2, 57 for 3, 73 for 4, +; 89 for 5, 105 for 6... Less than 25 displays nothing) +message = 10, 12, 242, 89 + +; A chat indicator alerting us the chat ticker isn't active (text is no longer being processed). +chat_arrow = 245, 84, 11, 9 + +; Where you type to make an IC chat message +ic_chat_message = 2, 283, 250, 23 + +; IC chat message if the current background's folder contains stand.png, +; defensedesk.png and prosecutiondesk.png +ao2_ic_chat_message = 2, 283, 250, 23 + +; +; Evidence system +; + +; **COORDINATE SYSTEM RELATIVE TO "viewport"** +; x/y coordinates 0,0 will start at top-left of the "viewport" for everything below until specified otherwise. +; **** +left_evidence_icon = 13, 13, 70, 70 +right_evidence_icon = 173, 13, 70, 70 + +; **COORDINATE SYSTEM RELATIVE TO "courtroom"** +; x/y coordinates 0,0 will start at top-left of the "courtroom" for everything below until specified otherwise. +; **** + +evidence_background = 0, 385, 490, 284 +; **COORDINATE SYSTEM RELATIVE TO "evidence_background"** +; x/y coordinates 0,0 will start at top-left of the "evidence_background" for everything below until specified otherwise. +; **** +evidence_buttons = 28, 27, 430, 216 +evidence_button_spacing = 2, 3 +evidence_button_size = 70, 70 +evidence_left = 28, 0, 60, 24 +evidence_right = 400, 0, 60, 24 +evidence_present = 165, 247, 158, 41 + +evidence_overlay = 24, 24, 439, 222 +; **COORDINATE SYSTEM RELATIVE TO "evidence_overlay"** +; x/y coordinates 0,0 will start at top-left of the "evidence_overlay" (which is parented to "evidence_background") for everything below until specified otherwise. +; **** +evidence_delete = 78, 8, 70, 20 +evidence_image_name = 150, 8, 109, 20 +evidence_image_button = 259, 8, 60, 20 +evidence_x = 341, 8, 20, 20 +evidence_ok = 320, 8, 20, 20 +evidence_button = 627, 322, 85, 18 +evidence_name = 112, 4, 264, 19 +evidence_description = 78, 28, 281, 166 \ No newline at end of file diff --git a/base/themes/default/courtroom_fonts.ini b/base/themes/default/courtroom_fonts.ini index 16e2f41..1fcba76 100644 --- a/base/themes/default/courtroom_fonts.ini +++ b/base/themes/default/courtroom_fonts.ini @@ -1,11 +1,56 @@ showname = 8 +showname_font = Sans +showname_color = 255, 255, 255 +showname_bold = 0 + message = 10 +message_font = Sans +message_color = 0, 0, 0 +message_bold = 0 + ic_chatlog = 10 -ms_chatlog = 10 -server_chatlog = 9 -music_list = 8 - +ic_chatlog_font = Sans ic_chatlog_color = 255, 255, 255 +ic_chatlog_bold = 0 -; Color for all labels and checkboxes -label_color = 255, 255, 255 +ms_chatlog = 8 +ms_chatlog_font = Sans +ms_chatlog_color = 0, 0, 0 +ms_chatlog_sender_color = 0, 0, 95 +ms_chatlog_bold = 0 + +server_chatlog = 8 +server_chatlog_font = Sans +server_chatlog_color = 0, 0, 0 +server_chatlog_sender_color = 95, 95, 0 +server_chatlog_bold = 0 + +music_list = 8 +music_list_font = Sans +music_list_color = 0, 0, 0 +music_list_bold = 0 + +music_name = 8 +music_name_font = Sans +music_name_color = 255, 255, 255 +music_name_bold = 0 + +area_list = 8 +area_list_font = Sans +area_list_color = 0, 0, 0 +area_list_bold = 0 + +evidence_name = 14 +evidence_name_font = Arial +evidence_name_color = 255, 128, 0 +evidence_name_bold = 1 + +evidence_image_name = 8 +evidence_image_name_font = Arial +evidence_image_name_color = 0, 0, 0 +evidence_image_name_bold = 0 + +evidence_description = 10 +evidence_description_font = Times New Roman +evidence_description_color = 255, 255, 255 +evidence_description_bold = 0 \ No newline at end of file diff --git a/base/themes/default/courtroombackground.png b/base/themes/default/courtroombackground.png index 5ad8d51..f42797c 100644 Binary files a/base/themes/default/courtroombackground.png and b/base/themes/default/courtroombackground.png differ diff --git a/base/themes/default/holdit.gif b/base/themes/default/holdit.gif deleted file mode 100644 index 5f71ac6..0000000 Binary files a/base/themes/default/holdit.gif and /dev/null differ diff --git a/base/themes/default/muted_old.png b/base/themes/default/muted_old.png deleted file mode 100644 index f9878f1..0000000 Binary files a/base/themes/default/muted_old.png and /dev/null differ diff --git a/base/themes/default/objection.gif b/base/themes/default/objection.gif deleted file mode 100644 index 6aae2e5..0000000 Binary files a/base/themes/default/objection.gif and /dev/null differ diff --git a/base/themes/default/takethat.gif b/base/themes/default/takethat.gif deleted file mode 100644 index dd03310..0000000 Binary files a/base/themes/default/takethat.gif and /dev/null differ diff --git a/include/aoapplication.h b/include/aoapplication.h index ef1a168..fa1757b 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -23,6 +23,9 @@ #include #include #include +#ifdef QTAUDIO +#include +#endif class NetworkManager; class Lobby; @@ -74,6 +77,9 @@ public: bool arup_enabled = false; bool casing_alerts_enabled = false; bool modcall_reason_enabled = false; + bool looping_sfx_support_enabled = false; + bool additive_enabled = false; + bool effects_enabled = false; ///////////////loading info/////////////////// @@ -141,12 +147,18 @@ public: // Returns the value of ooc_name in config.ini QString get_ooc_name(); - // Returns the blip rate from config.ini + // Returns the blip rate from config.ini (once per X symbols) int read_blip_rate(); // Returns true if blank blips is enabled in config.ini and false otherwise bool get_blank_blip(); + // Returns true if looping sound effects are enabled in the config.ini + bool get_looping_sfx(); + + // Returns true if stop music on objection is enabled in the config.ini + bool objection_stop_music(); + // Returns the value of default_music in config.ini int get_default_music(); @@ -160,6 +172,34 @@ public: // from the config.ini. bool is_discord_enabled(); + // Returns the value of whether shaking should be enabled. + // from the config.ini. + bool is_shake_enabled(); + + // Returns the value of whether effects should be enabled. + // from the config.ini. + bool is_effects_enabled(); + + // Returns the value of whether frame-specific effects defined in char.ini + // should be sent/received over the network. from the config.ini. + bool is_frame_network_enabled(); + + // Returns the value of whether colored ic log should be a thing. + // from the config.ini. + bool is_colorlog_enabled(); + + // Returns the value of whether sticky sounds should be a thing. + // from the config.ini. + bool is_stickysounds_enabled(); + + // Returns the value of whether sticky effects should be a thing. + // from the config.ini. + bool is_stickyeffects_enabled(); + + // Returns the value of whether sticky preanims should be a thing. + // from the config.ini. + bool is_stickypres_enabled(); + // Returns the value of the maximum amount of lines the IC chatlog // may contain, from config.ini. int get_max_log_size(); @@ -173,6 +213,9 @@ public: // Returns the audio device used for the client. QString get_audio_output_device(); +#ifdef QTAUDIO + QAudioDeviceInfo QtAudioDevice; +#endif // Returns whether the user would like to have custom shownames on by default. bool get_showname_enabled_by_default(); @@ -180,6 +223,20 @@ public: // Returns the list of words in callwords.ini QStringList get_call_words(); + // returns all of the file's lines in a QStringList + QStringList get_list_file(QString p_file); + + // Process a file and return its text as a QString + QString read_file(QString filename); + + // Write text to file. make_dir would auto-create the directory if it doesn't + // exist. + bool write_to_file(QString p_text, QString p_file, bool make_dir = false); + + // Append text to the end of the file. make_dir would auto-create the + // directory if it doesn't exist. + bool append_to_file(QString p_text, QString p_file, bool make_dir = false); + // Appends the argument string to serverlist.txt void write_to_serverlist_txt(QString p_line); @@ -193,7 +250,12 @@ public: 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); + pos_size_type get_element_dimensions(QString p_identifier, QString p_file, + QString p_char = ""); + + // Returns the value to you + QString get_design_element(QString p_identifier, QString p_file, + QString p_char = ""); // Returns the name of the font with p_identifier from p_file QString get_font_name(QString p_identifier, QString p_file); @@ -204,7 +266,10 @@ public: // Returns the color with p_identifier from p_file QColor get_color(QString p_identifier, QString p_file); - // Returns the colour from the misc folder. + // Returns the markdown symbol used for specified p_identifier such as colors + QString get_chat_markdown(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 @@ -213,22 +278,46 @@ public: // Figure out if we can opus this or if we should fall back to wav QString get_sfx_suffix(QString sound_to_check); - // Can we use APNG for this? If not, fall back to a gif. + // Can we use APNG for this? If not, WEBP? If not, GIF? If not, fall back to + // PNG. QString get_image_suffix(QString path_to_check); + // If this image is static and non-animated, return the supported static image + // formats. Currently only PNG. + QString get_static_image_suffix(QString path_to_check); + // 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); + // Returns a QStringList of all key=value definitions on a given tag. + QStringList read_ini_tags(QString p_file, QString target_tag = ""); + + // Sets the char.ini p_search_line key under tag target_tag to value. + void set_char_ini(QString p_char, QString value, QString p_search_line, + QString target_tag); + + // Returns the text between target_tag and terminator_tag in p_file + QString get_stylesheet(QString p_file); + + // Returns the text between target_tag and terminator_tag in p_file + QString get_tagged_stylesheet(QString target_tag, QString p_file); + // 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 + // Returns the value of chat image from the specific p_char's ini file QString get_chat(QString p_char); + // Returns the value of chat font from the specific p_char's ini file + QString get_chat_font(QString p_char); + + // Returns the value of chat font size from the specific p_char's ini file + int get_chat_size(QString p_char); + // Returns the value of shouts from the specified p_char's ini file QString get_char_shouts(QString p_char); @@ -242,6 +331,21 @@ public: // Not in use int get_text_delay(QString p_char, QString p_emote); + // Get the effects folder referenced by the char.ini, read it and return the + // list of filenames in a string + QStringList get_theme_effects(); + + // Get the theme's effects folder, read it and return the list of filenames in + // a string + QStringList get_effects(QString p_char); + + // 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//sounds.ini, or theme/effects/sounds.ini. + QString get_effect_sound(QString fx_name, QString p_char); + // Returns the custom realisation used by the character. QString get_custom_realization(QString p_char); @@ -263,6 +367,21 @@ public: // Returns the sfx of p_char's p_emote QString get_sfx_name(QString p_char, int p_emote); + // Returns the blipsound of p_char's p_emote + QString get_emote_blip(QString p_char, int p_emote); + + // Returns if the sfx is defined as looping in char.ini + QString get_sfx_looping(QString p_char, int p_emote); + + // Returns if an emote has a frame specific SFX for it + QString get_sfx_frame(QString p_char, QString p_emote, int n_frame); + + // Returns if an emote has a frame specific SFX for it + QString get_flash_frame(QString p_char, QString p_emote, int n_frame); + + // Returns if an emote has a frame specific SFX for it + QString get_screenshake_frame(QString p_char, QString p_emote, int n_frame); + // Not in use int get_sfx_delay(QString p_char, int p_emote); @@ -303,10 +422,13 @@ public: // Get the message for the CM for casing alerts. QString get_casing_can_host_cases(); + // The file name of the log file in base/logs. + QString log_filename; + private: const int RELEASE = 2; - const int MAJOR_VERSION = 6; - const int MINOR_VERSION = 2; + const int MAJOR_VERSION = 8; + const int MINOR_VERSION = 4; QString current_theme = "default"; diff --git a/include/aoblipplayer.h b/include/aoblipplayer.h index 02bd3c6..5a10471 100644 --- a/include/aoblipplayer.h +++ b/include/aoblipplayer.h @@ -3,6 +3,7 @@ #if defined(BASSAUDIO) #include "bass.h" +#include "bassopus.h" #elif defined(QTAUDIO) #include #endif @@ -10,6 +11,7 @@ #include "aoapplication.h" #include +#include #include #include @@ -24,10 +26,15 @@ public: int m_cycle = 0; private: + const int max_blip_ms = 60; + QWidget *m_parent; AOApplication *ao_app; + qreal m_volume; + QElapsedTimer delay; + + void set_volume_internal(qreal p_volume); - int m_volume; #if defined(BASSAUDIO) HSTREAM m_stream_list[5]; #elif defined(QTAUDIO) diff --git a/include/aocharmovie.h b/include/aocharmovie.h index b4a8be2..2dda0ec 100644 --- a/include/aocharmovie.h +++ b/include/aocharmovie.h @@ -2,9 +2,9 @@ #define AOCHARMOVIE_H #include +#include #include #include -#include #include class AOApplication; @@ -15,43 +15,111 @@ class AOCharMovie : public QLabel { public: AOCharMovie(QWidget *p_parent, AOApplication *p_ao_app); - void play(QString p_char, QString p_emote, QString emote_prefix); + // 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); - void set_flipped(bool p_flipped) { m_flipped = p_flipped; } - + // 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; - QMovie *m_movie; - QVector movie_frames; - QTimer *preanim_timer; + QVector movie_frames; + QVector movie_delays; - const int time_mod = 62; + // 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> 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 frame_change(int n_frame); - void timer_done(); + void preanim_done(); + void movie_ticker(); }; #endif // AOCHARMOVIE_H diff --git a/include/aoemotebutton.h b/include/aoemotebutton.h index acf0b48..cb46167 100644 --- a/include/aoemotebutton.h +++ b/include/aoemotebutton.h @@ -10,11 +10,11 @@ class AOEmoteButton : public QPushButton { Q_OBJECT public: - AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y); + AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y, + int p_w, int p_h); - // 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_image(QString p_image, QString p_emote_comment); + void set_char_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; } diff --git a/include/aoevidencebutton.h b/include/aoevidencebutton.h index b56bfb1..d7812fe 100644 --- a/include/aoevidencebutton.h +++ b/include/aoevidencebutton.h @@ -12,10 +12,9 @@ class AOEvidenceButton : public QPushButton { Q_OBJECT public: - AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, - int p_y); + AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, int p_x, int p_y, + int p_w, int p_h); - 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; } @@ -36,9 +35,9 @@ protected: void leaveEvent(QEvent *e); void mouseDoubleClickEvent(QMouseEvent *e); /* - void dragLeaveEvent(QMouseEvent *e); - void dragEnterEvent(QMouseEvent *e); - */ +void dragLeaveEvent(QMouseEvent *e); +void dragEnterEvent(QMouseEvent *e); +*/ signals: void evidence_clicked(int p_id); diff --git a/include/aoevidencedisplay.h b/include/aoevidencedisplay.h index 1d6280d..979a754 100644 --- a/include/aoevidencedisplay.h +++ b/include/aoevidencedisplay.h @@ -2,11 +2,11 @@ #define AOEVIDENCEDISPLAY_H #include "aoapplication.h" +#include "aomovie.h" #include "aosfxplayer.h" #include #include -#include class AOEvidenceDisplay : public QLabel { Q_OBJECT @@ -17,15 +17,16 @@ public: void show_evidence(QString p_evidence_image, bool is_left_side, int p_volume); QLabel *get_evidence_icon(); void reset(); + void combo_resize(int w, int h); private: AOApplication *ao_app; - QMovie *evidence_movie; + AOMovie *evidence_movie; QLabel *evidence_icon; AOSfxPlayer *sfx_player; private slots: - void frame_change(int p_frame); + void show_done(); }; #endif // AOEVIDENCEDISPLAY_H diff --git a/include/aoimage.h b/include/aoimage.h index c4fdf2e..01ef854 100644 --- a/include/aoimage.h +++ b/include/aoimage.h @@ -16,8 +16,8 @@ public: QWidget *m_parent; AOApplication *ao_app; - void set_image(QString p_image); - void set_image_from_path(QString p_path); + bool set_image(QString p_image); + bool set_chatbox(QString p_path); void set_size_and_pos(QString identifier); }; diff --git a/include/aolineedit.h b/include/aolineedit.h index 70f1f97..5dce3aa 100644 --- a/include/aolineedit.h +++ b/include/aolineedit.h @@ -10,14 +10,17 @@ class AOLineEdit : public QLineEdit { public: AOLineEdit(QWidget *parent); + void preserve_selection(bool toggle) { p_selection = toggle; } + +private: + bool p_selection = false; + protected: void mouseDoubleClickEvent(QMouseEvent *e); + void focusOutEvent(QFocusEvent *ev); signals: void double_clicked(); - -private slots: - void on_enter_pressed(); }; #endif // AOLINEEDIT_H diff --git a/include/aomovie.h b/include/aomovie.h index d22f725..eb7f7a5 100644 --- a/include/aomovie.h +++ b/include/aomovie.h @@ -14,13 +14,15 @@ public: AOMovie(QWidget *p_parent, AOApplication *p_ao_app); void set_play_once(bool p_play_once); - void play(QString p_gif, QString p_char = "", QString p_custom_theme = ""); + 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: @@ -28,6 +30,7 @@ signals: private slots: void frame_change(int n_frame); + void timer_done(); }; #endif // AOMOVIE_H diff --git a/include/aomusicplayer.h b/include/aomusicplayer.h index fd6b254..5c72649 100644 --- a/include/aomusicplayer.h +++ b/include/aomusicplayer.h @@ -1,8 +1,10 @@ #ifndef AOMUSICPLAYER_H #define AOMUSICPLAYER_H +#include "file_functions.h" #if defined(BASSAUDIO) #include "bass.h" +#include "bassopus.h" #elif defined(QTAUDIO) #include #endif @@ -16,17 +18,34 @@ class AOMusicPlayer { public: AOMusicPlayer(QWidget *parent, AOApplication *p_ao_app); - ~AOMusicPlayer(); + virtual ~AOMusicPlayer(); + void set_volume(int p_value, int channel = -1); + void set_looping(bool toggle, int channel = 0); - void play(QString p_song); - void set_volume(int p_value); + const int m_channelmax = 4; + + // These have to be public for the stupid sync thing + QWORD loop_start = 0; + QWORD loop_end = 0; + +public slots: + void play(QString p_song, int channel = 0, bool loop = false, + int effect_flags = 0); + void stop(int channel = 0); private: QWidget *m_parent; AOApplication *ao_app; - int m_volume = 0; - HSTREAM m_stream; + bool m_looping = false; + int m_volume[4] = {0, 0, 0, 0}; + + // Channel 0 = music + // Channel 1 = ambience + // Channel 2 = extra + // Channel 3 = extra + HSTREAM m_stream_list[4]; + HSYNC loop_sync[4]; }; #elif defined(QTAUDIO) class AOMusicPlayer { diff --git a/include/aooptionsdialog.h b/include/aooptionsdialog.h index ec0889b..7e983a3 100644 --- a/include/aooptionsdialog.h +++ b/include/aooptionsdialog.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,27 @@ private: QLineEdit *ui_ms_textbox; QLabel *ui_discord_lbl; QCheckBox *ui_discord_cb; + QLabel *ui_language_label; + QComboBox *ui_language_combobox; + + QLabel *ui_shake_lbl; + QCheckBox *ui_shake_cb; + QLabel *ui_effects_lbl; + QCheckBox *ui_effects_cb; + QLabel *ui_framenetwork_lbl; + QCheckBox *ui_framenetwork_cb; + + QLabel *ui_colorlog_lbl; + QCheckBox *ui_colorlog_cb; + + QLabel *ui_stickysounds_lbl; + QCheckBox *ui_stickysounds_cb; + + QLabel *ui_stickyeffects_lbl; + QCheckBox *ui_stickyeffects_cb; + + QLabel *ui_stickypres_lbl; + QCheckBox *ui_stickypres_cb; QWidget *ui_callwords_tab; QWidget *ui_callwords_widget; @@ -80,6 +102,10 @@ private: QLabel *ui_bliprate_lbl; QCheckBox *ui_blank_blips_cb; QLabel *ui_blank_blips_lbl; + QLabel *ui_loopsfx_lbl; + QCheckBox *ui_loopsfx_cb; + QLabel *ui_objectmusic_lbl; + QCheckBox *ui_objectmusic_cb; QDialogButtonBox *ui_settings_buttons; QWidget *ui_casing_tab; diff --git a/include/aoscene.h b/include/aoscene.h index 1c258f2..726e264 100644 --- a/include/aoscene.h +++ b/include/aoscene.h @@ -16,10 +16,27 @@ public: 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 diff --git a/include/aosfxplayer.h b/include/aosfxplayer.h index b6dce85..9c9824a 100644 --- a/include/aosfxplayer.h +++ b/include/aosfxplayer.h @@ -3,6 +3,7 @@ #if defined(BASSAUDIO) #include "bass.h" +#include "bassopus.h" #elif defined(QTAUDIO) #include #endif @@ -17,19 +18,29 @@ class AOSfxPlayer { public: AOSfxPlayer(QWidget *parent, AOApplication *p_ao_app); - void play(QString p_sfx, QString p_char = "", QString shout = ""); - void stop(); - void set_volume(int p_volume); + void clear(); + void loop_clear(); + void play(QString p_sfx, QString p_char = "", QString shout = "", + int channel = -1); + void stop(int channel = -1); + void set_volume(qreal p_volume); + void set_looping(bool toggle, int channel = -1); + int m_channel = 0; private: QWidget *m_parent; AOApplication *ao_app; + qreal m_volume = 0; + + bool m_looping = true; + void set_volume_internal(qreal p_volume); + #if defined(BASSAUDIO) - HSTREAM m_stream; + const int m_channelmax = 5; + HSTREAM m_stream_list[5]; #elif defined(QTAUDIO) QSoundEffect m_sfx; #endif - int m_volume = 0; }; #endif // AOSFXPLAYER_H diff --git a/include/aotextarea.h b/include/aotextarea.h index 13532d2..453e965 100644 --- a/include/aotextarea.h +++ b/include/aotextarea.h @@ -11,11 +11,12 @@ class AOTextArea : public QTextBrowser { public: AOTextArea(QWidget *p_parent = nullptr); - void append_chatmessage(QString p_name, QString p_message, QString p_colour); + void append_linked(QString p_message); + void append_chatmessage(QString p_name, QString p_message, QString p_colur); void append_error(QString p_message); private: - const QRegExp omnis_dank_url_regex = QRegExp("\\b(https?://\\S+\\.\\S+)\\b"); + const QRegExp url_parser_regex = QRegExp("\\b(https?://\\S+\\.\\S+)\\b"); void auto_scroll(QTextCursor old_cursor, int scrollbar_value, bool is_scrolled_down); diff --git a/include/bass.h b/include/bass.h index 068b963..ec2c7a2 100644 --- a/include/bass.h +++ b/include/bass.h @@ -1,6 +1,6 @@ /* BASS 2.4 C/C++ header file - Copyright (c) 1999-2018 Un4seen Developments Ltd. + Copyright (c) 1999-2019 Un4seen Developments Ltd. See the BASS.CHM file for more detailed documentation */ @@ -117,6 +117,7 @@ typedef DWORD HPLUGIN; // Plugin handle #define BASS_CONFIG_VERIFY 23 #define BASS_CONFIG_UPDATETHREADS 24 #define BASS_CONFIG_DEV_BUFFER 27 +#define BASS_CONFIG_REC_LOOPBACK 28 #define BASS_CONFIG_VISTA_TRUEPOS 30 #define BASS_CONFIG_IOS_MIXAUDIO 34 #define BASS_CONFIG_DEV_DEFAULT 36 @@ -141,11 +142,14 @@ typedef DWORD HPLUGIN; // Plugin handle #define BASS_CONFIG_AM_DISABLE 58 #define BASS_CONFIG_NET_PLAYLIST_DEPTH 59 #define BASS_CONFIG_NET_PREBUF_WAIT 60 +#define BASS_CONFIG_WASAPI_PERSIST 65 +#define BASS_CONFIG_REC_WASAPI 66 // BASS_SetConfigPtr options #define BASS_CONFIG_NET_AGENT 16 #define BASS_CONFIG_NET_PROXY 17 #define BASS_CONFIG_IOS_NOTIFY 46 +#define BASS_CONFIG_LIBSSL 64 // BASS_Init flags #define BASS_DEVICE_8BITS 1 // 8 bit @@ -526,9 +530,10 @@ 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 -#define STREAMPROC_DEVICE (STREAMPROC *)-2 // device mix stream +#define STREAMPROC_DUMMY (STREAMPROC *)0 // "dummy" stream +#define STREAMPROC_PUSH (STREAMPROC *)-1 // push stream +#define STREAMPROC_DEVICE (STREAMPROC *)-2 // device mix stream +#define STREAMPROC_DEVICE_3D (STREAMPROC *)-3 // device 3D mix stream // BASS_StreamCreateFileUser file systems #define STREAMFILE_NOBUFFER 0 @@ -584,6 +589,8 @@ user : The 'user' parameter value given when calling BASS_StreamCreateURL */ #define BASS_SYNC_MUSICINST 1 #define BASS_SYNC_MUSICFX 3 #define BASS_SYNC_OGG_CHANGE 12 +#define BASS_SYNC_DEV_FAIL 14 +#define BASS_SYNC_DEV_FORMAT 15 #define BASS_SYNC_MIXTIME 0x40000000 // flag: sync at mixtime, else at playtime #define BASS_SYNC_ONETIME 0x80000000 // flag: sync only once, else continuously @@ -623,6 +630,7 @@ RETURN : TRUE = continue recording, FALSE = stop */ #define BASS_ACTIVE_PLAYING 1 #define BASS_ACTIVE_STALLED 2 #define BASS_ACTIVE_PAUSED 3 +#define BASS_ACTIVE_PAUSED_DEVICE 4 // Channel attributes #define BASS_ATTRIB_FREQ 1 @@ -668,6 +676,7 @@ RETURN : TRUE = continue recording, FALSE = stop */ #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 +#define BASS_DATA_FFT_NYQUIST 0x100 // FFT flag: return extra Nyquist value // BASS_ChannelGetLevelEx flags #define BASS_LEVEL_MONO 1 @@ -1040,6 +1049,7 @@ float BASSDEF(BASS_GetCPU)(); BOOL BASSDEF(BASS_Start)(); BOOL BASSDEF(BASS_Stop)(); BOOL BASSDEF(BASS_Pause)(); +BOOL BASSDEF(BASS_IsStarted)(); BOOL BASSDEF(BASS_SetVolume)(float volume); float BASSDEF(BASS_GetVolume)(); diff --git a/include/bassopus.h b/include/bassopus.h new file mode 100644 index 0000000..3d8b131 --- /dev/null +++ b/include/bassopus.h @@ -0,0 +1,64 @@ +/* + BASSOPUS 2.4 C/C++ header file + Copyright (c) 2012-2015 Un4seen Developments Ltd. + + See the BASSOPUS.CHM file for more detailed documentation +*/ + +#ifndef BASSOPUS_H +#define BASSOPUS_H + +#include "bass.h" + +#if BASSVERSION != 0x204 +#error conflicting BASS and BASSOPUS versions +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef BASSOPUSDEF +#define BASSOPUSDEF(f) WINAPI f +#endif + +// BASS_CHANNELINFO type +#define BASS_CTYPE_STREAM_OPUS 0x11200 + +// Additional attributes +#define BASS_ATTRIB_OPUS_ORIGFREQ 0x13000 +#define BASS_ATTRIB_OPUS_GAIN 0x13001 + +HSTREAM BASSOPUSDEF(BASS_OPUS_StreamCreateFile)(BOOL mem, const void *file, + QWORD offset, QWORD length, + DWORD flags); +HSTREAM BASSOPUSDEF(BASS_OPUS_StreamCreateURL)(const char *url, DWORD offset, + DWORD flags, DOWNLOADPROC *proc, + void *user); +HSTREAM BASSOPUSDEF(BASS_OPUS_StreamCreateFileUser)(DWORD system, DWORD flags, + const BASS_FILEPROCS *procs, + void *user); + +#ifdef __cplusplus +} + +#if defined(_WIN32) && !defined(NOBASSOVERLOADS) +static inline HSTREAM BASS_OPUS_StreamCreateFile(BOOL mem, const WCHAR *file, + QWORD offset, QWORD length, + DWORD flags) +{ + return BASS_OPUS_StreamCreateFile(mem, (const void *)file, offset, length, + flags | BASS_UNICODE); +} + +static inline HSTREAM BASS_OPUS_StreamCreateURL(const WCHAR *url, DWORD offset, + DWORD flags, DOWNLOADPROC *proc, + void *user) +{ + return BASS_OPUS_StreamCreateURL((const char *)url, offset, + flags | BASS_UNICODE, proc, user); +} +#endif +#endif + +#endif diff --git a/include/chatlogpiece.h b/include/chatlogpiece.h index 1fbca25..e6f77c7 100644 --- a/include/chatlogpiece.h +++ b/include/chatlogpiece.h @@ -15,7 +15,7 @@ public: QString get_name(); QString get_showname(); QString get_message(); - bool get_is_song(); + bool is_song(); QDateTime get_datetime(); QString get_datetime_as_string(); @@ -26,7 +26,7 @@ private: QString showname; QString message; QDateTime datetime; - bool is_song; + bool p_is_song; }; #endif // CHATLOGPIECE_H diff --git a/include/courtroom.h b/include/courtroom.h index 5d9a4b8..259a7ed 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -13,6 +13,7 @@ #include "aolineedit.h" #include "aomovie.h" #include "aomusicplayer.h" +#include "aooptionsdialog.h" #include "aopacket.h" #include "aoscene.h" #include "aosfxplayer.h" @@ -24,10 +25,12 @@ #include "file_functions.h" #include "hardware_functions.h" #include "lobby.h" +#include "scrolltext.h" #include #include #include +#include #include #include #include @@ -36,17 +39,24 @@ #include #include #include +#include #include #include #include +#include #include #include #include +#include +#include +#include +#include #include #include #include #include +//#include #include @@ -61,6 +71,8 @@ public: 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() { @@ -104,23 +116,47 @@ public: // 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); + void set_font(QWidget *widget, QString class_name, QString p_identifier); + + // Get the properly constructed font + QFont get_qfont(QString font_name, int f_pointsize, bool antialias = true); + + // actual operation of setting the font on a widget + void set_qfont(QWidget *widget, QString class_name, QFont font, + QColor f_color = Qt::black, bool bold = false); + // helper function that calls above function on the relevant widgets void set_fonts(); + // sets dropdown menu stylesheet + void set_dropdown(QWidget *widget); + + // 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); + // reads theme inis and returns the size and pos as defined by it + QPoint get_theme_pos(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); + void set_background(QString p_background, bool display = false); + + // sets the local character pos/side to use. + void set_side(QString p_side); + + // sets the pos dropdown + void set_pos_dropdown(QStringList pos_dropdowns); // sets the evidence list member variable to argument void set_evidence_list(QVector &p_evi_list); @@ -135,13 +171,7 @@ public: void set_pair_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(); - - // And gets the colour, too! - QColor get_text_color(QString color); + void set_scene(QString f_desk_mod, QString f_side); // takes in serverD-formatted IP list as prints a converted version to server // OOC admittedly poorly named @@ -159,8 +189,11 @@ public: QString get_current_char() { return current_char; } QString get_current_background() { return current_background; } + // updates character to p_cid and updates necessary ui elements + void update_character(int p_cid); + // properly sets up some varibles: resets user state - void enter_courtroom(int p_cid); + void enter_courtroom(); // helper function that populates ui_music_list with the contents of // music_list @@ -170,7 +203,7 @@ public: // 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, - QString p_colour); + QString p_color); // these functions handle chatmessages sequentially. // The process itself is very convoluted and merits separate documentation @@ -181,14 +214,14 @@ public: // This function filters out the common CC inline text trickery, for appending // to the IC chatlog. - QString filter_ic_text(QString p_text); + QString filter_ic_text(QString p_text, bool colorize = false, int pos = -1, + int default_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 // selected // or the user isn't already scrolled to the top - void append_ic_text(QString p_text, QString p_name = "", - bool is_songchange = false); + void append_ic_text(QString p_text, QString p_name = "", QString action = ""); // 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 @@ -230,12 +263,8 @@ private: bool first_message_sent = false; int maximumMessages = 0; - // This is for inline message-colouring. - - enum INLINE_COLOURS { INLINE_BLUE, INLINE_GREEN, INLINE_ORANGE, INLINE_GREY }; - - // A stack of inline colours. - std::stack inline_colour_stack; + QParallelAnimationGroup *screenshake_animation_group = + new QParallelAnimationGroup; bool next_character_is_not_special = false; // If true, write the // next character as it is. @@ -243,21 +272,16 @@ private: bool message_is_centered = false; int current_display_speed = 3; - int message_display_speed[7] = {30, 40, 50, 60, 75, 100, 120}; - - // This is for checking if the character should start talking again - // when an inline blue text ends. - bool entire_message_is_blue = false; - - // And this is the inline 'talking checker'. Counts how 'deep' we are - // in inline blues. - int inline_blue_depth = 0; + int message_display_speed[7] = {0, 10, 25, 40, 50, 70, 90}; // The character ID of the character this user wants to appear alongside with. int other_charid = -1; // The offset this user has given if they want to appear alongside someone. - int offset_with_pair = 0; + int char_offset = 0; + + // 0 = in front, 1 = behind + int pair_order = 0; QVector char_list; QVector evidence_list; @@ -271,10 +295,6 @@ private: QVector ic_chatlog_history; - // These map music row items and area row items to their actual IDs. - QVector music_row_to_number; - QVector area_row_to_number; - // triggers ping_server() every 60 seconds QTimer *keepalive_timer; @@ -283,13 +303,19 @@ private: // int chat_tick_interval = 60; // which tick position(character in chat message) we are at int tick_pos = 0; + // the actual document tick pos we gotta worry about for making the text + // scroll better + int real_tick_pos = 0; // used to determine how often blips sound - int blip_pos = 0; + int blip_ticker = 0; int blip_rate = 1; int rainbow_counter = 0; bool rainbow_appended = false; bool blank_blip = false; + // Whether or not is this message additive to the previous one + bool is_additive = false; + // Used for getting the current maximum blocks allowed in the IC chatlog. int log_maximum_blocks = 0; @@ -302,32 +328,33 @@ private: // 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; - // every time point in char.inis times this equals the final time const int time_mod = 40; - static const int chatmessage_size = 23; + // the amount of time non-animated objection/hold it/takethat images stay + // onscreen for in ms + const int shout_stay_time = 724; + + // the amount of time non-animated guilty/not guilty images stay onscreen for + // in ms + const int verdict_stay_time = 3000; + + // the amount of time non-animated witness testimony/cross-examination images + // stay onscreen for in ms + const int wtce_stay_time = 1500; + + // characters we consider punctuation + const QString punctuation_chars = ".,?!:;"; + + // amount by which we multiply the delay when we parse punctuation chars + const int punctuation_modifier = 3; + + static const int chatmessage_size = 30; QString m_chatmessage[chatmessage_size]; bool chatmessage_is_empty = false; QString previous_ic_message = ""; - - bool testimony_in_progress = false; - - // in milliseconds - const int testimony_show_time = 1500; - - // in milliseconds - const int testimony_hide_time = 500; + QString additive_previous = ""; // char id, muted or not QMap mute_map; @@ -340,6 +367,9 @@ private: // noniterrupting preanim int anim_state = 3; + // whether or not current color is a talking one + bool color_is_talking = true; + // state of text ticking, 0 = not yet ticking, 1 = ticking in progress, 2 = // ticking done int text_state = 2; @@ -351,9 +381,42 @@ private: int objection_state = 0; int realization_state = 0; + int screenshake_state = 0; int text_color = 0; + + // How many unique user colors are possible + static const int max_colors = 12; + + // Text Color-related optimization: + // Current color list indexes to real color references + QVector color_row_to_number; + + // List of associated RGB colors for this color index + QVector color_rgb_list; + + // List of markdown start characters, their index is tied to the color index + QStringList color_markdown_start_list; + + // List of markdown end characters, their index is tied to the color index + QStringList color_markdown_end_list; + + // Whether or not we're supposed to remove this char during parsing + QVector color_markdown_remove_list; + + // Whether or not this color allows us to play the talking animation + QVector color_markdown_talking_list; + // Text Color-related optimization END + + // List of all currently available pos + QStringList pos_dropdown_list; + bool is_presenting_evidence = false; + QString effect = ""; + + // Music effect flags we want to send to server when we play music + int music_flags = 0; + int defense_bar_state = 0; int prosecution_bar_state = 0; @@ -372,6 +435,11 @@ private: int max_emotes_on_page = 10; QVector local_evidence_list; + QVector private_evidence_list; + QVector global_evidence_list; + + // false = use private_evidence_list + bool current_evidence_global = true; int current_evidence_page = 0; int current_evidence = 0; @@ -387,6 +455,7 @@ private: bool server_ooc = true; QString current_background = "default"; + QString current_side = ""; AOMusicPlayer *music_player; AOSfxPlayer *sfx_player; @@ -407,9 +476,10 @@ private: AOEvidenceDisplay *ui_vp_evidence_display; AOImage *ui_vp_chatbox; QLabel *ui_vp_showname; + AOMovie *ui_vp_chat_arrow; QTextEdit *ui_vp_message; - AOImage *ui_vp_testimony; - AOImage *ui_vp_realization; + AOMovie *ui_vp_effect; + AOMovie *ui_vp_testimony; AOMovie *ui_vp_wtce; AOMovie *ui_vp_objection; @@ -419,14 +489,19 @@ private: AOTextArea *ui_server_chatlog; QListWidget *ui_mute_list; - QListWidget *ui_area_list; - QListWidget *ui_music_list; + QTreeWidget *ui_area_list; + QTreeWidget *ui_music_list; + + ScrollText *ui_music_name; + AOMovie *ui_music_display; AOButton *ui_pair_button; QListWidget *ui_pair_list; QSpinBox *ui_pair_offset_spinbox; - QLineEdit *ui_ic_chat_message; + QComboBox *ui_pair_order_dropdown; + + AOLineEdit *ui_ic_chat_message; QLineEdit *ui_ic_chat_name; QLineEdit *ui_ooc_chat_message; @@ -443,6 +518,14 @@ private: QComboBox *ui_emote_dropdown; QComboBox *ui_pos_dropdown; + QComboBox *ui_iniswap_dropdown; + AOButton *ui_iniswap_remove; + + QComboBox *ui_sfx_dropdown; + AOButton *ui_sfx_remove; + + QComboBox *ui_effects_dropdown; + AOImage *ui_defense_bar; AOImage *ui_prosecution_bar; @@ -470,6 +553,7 @@ private: QCheckBox *ui_pre; QCheckBox *ui_flip; + QCheckBox *ui_additive; QCheckBox *ui_guard; QCheckBox *ui_casing; @@ -478,6 +562,7 @@ private: AOButton *ui_custom_objection; AOButton *ui_realization; + AOButton *ui_screenshake; AOButton *ui_mute; AOButton *ui_defense_plus; @@ -494,9 +579,6 @@ private: AOImage *ui_muted; - QSpinBox *ui_log_limit_spinbox; - QLabel *ui_log_limit_label; - AOButton *ui_evidence_button; AOImage *ui_evidence; AOLineEdit *ui_evidence_name; @@ -510,6 +592,11 @@ private: AOLineEdit *ui_evidence_image_name; AOButton *ui_evidence_image_button; AOButton *ui_evidence_x; + AOButton *ui_evidence_ok; + AOButton *ui_evidence_switch; + AOButton *ui_evidence_transfer; + AOButton *ui_evidence_save; + AOButton *ui_evidence_load; AOTextEdit *ui_evidence_description; AOImage *ui_char_select_background; @@ -541,21 +628,23 @@ private: void put_button_in_place(int starting, int chars_on_this_page); void filter_character_list(); - void construct_emotes(); + void initialize_emotes(); + void refresh_emotes(); void set_emote_page(); void set_emote_dropdown(); - void construct_evidence(); + void initialize_evidence(); + void refresh_evidence(); void set_evidence_page(); public slots: void objection_done(); void preanim_done(); - - void realization_done(); - - void show_testimony(); - void hide_testimony(); + void do_screenshake(); + void do_flash(); + void do_effect(QString fx_path, QString fx_sound, QString p_char, + QString p_folder); + void play_char_sfx(QString sfx_name); void mod_called(QString p_ip); @@ -576,8 +665,14 @@ private slots: void on_ooc_return_pressed(); void on_music_search_edited(QString p_text); - void on_music_list_double_clicked(QModelIndex p_model); - void on_area_list_double_clicked(QModelIndex p_model); + void on_music_list_double_clicked(QTreeWidgetItem *p_item, int column); + void on_music_list_context_menu_requested(const QPoint &pos); + void music_fade_out(bool toggle); + void music_fade_in(bool toggle); + void music_synchronize(bool toggle); + void music_list_expand_all(); + void music_list_collapse_all(); + void on_area_list_double_clicked(QTreeWidgetItem *p_item, int column); void select_emote(int p_id); @@ -589,6 +684,28 @@ private slots: void on_emote_dropdown_changed(int p_index); void on_pos_dropdown_changed(int p_index); + void on_iniswap_dropdown_changed(int p_index); + void set_iniswap_dropdown(); + void on_iniswap_context_menu_requested(const QPoint &pos); + void on_iniswap_edit_requested(); + void on_iniswap_remove_clicked(); + + void on_sfx_dropdown_changed(int p_index); + void set_sfx_dropdown(); + void on_sfx_context_menu_requested(const QPoint &pos); + void on_sfx_edit_requested(); + void on_sfx_remove_clicked(); + + void set_effects_dropdown(); + void on_effects_context_menu_requested(const QPoint &pos); + void on_effects_edit_requested(); + void on_character_effects_edit_requested(); + void on_effects_dropdown_changed(int p_index); + bool effects_dropdown_find_and_set(QString effect); + + QString get_char_sfx(); + int get_char_sfx_delay(); + void on_evidence_name_edited(); void on_evidence_image_name_edited(); void on_evidence_image_button_clicked(); @@ -607,9 +724,11 @@ private slots: void on_custom_objection_clicked(); void on_realization_clicked(); + void on_screenshake_clicked(); void on_mute_clicked(); void on_pair_clicked(); + void on_pair_order_dropdown_changed(int p_index); void on_defense_minus_clicked(); void on_defense_plus_clicked(); @@ -617,6 +736,7 @@ private slots: void on_prosecution_plus_clicked(); void on_text_color_changed(int p_color); + void set_text_color_dropdown(); void on_music_slider_moved(int p_value); void on_sfx_slider_moved(int p_value); @@ -640,14 +760,28 @@ private slots: void on_pre_clicked(); void on_flip_clicked(); + void on_additive_clicked(); void on_guard_clicked(); void on_showname_enable_clicked(); + void on_evidence_name_double_clicked(); + void on_evidence_image_name_double_clicked(); void on_evidence_button_clicked(); void on_evidence_delete_clicked(); void on_evidence_x_clicked(); + void on_evidence_ok_clicked(); + void on_evidence_switch_clicked(); + void on_evidence_transfer_clicked(); + + void on_evidence_edited(); + + void evidence_close(); + void evidence_switch(bool global); + void on_evidence_save_clicked(); + void on_evidence_load_clicked(); + bool compare_evidence_changed(evi_type evi_a, evi_type evi_b); void on_back_to_lobby_clicked(); diff --git a/include/datatypes.h b/include/datatypes.h index 207f506..21ade04 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -91,9 +91,16 @@ enum CHAT_MESSAGE { SELF_OFFSET, OTHER_OFFSET, OTHER_FLIP, - NONINTERRUPTING_PRE + NONINTERRUPTING_PRE, + LOOPING_SFX, + SCREENSHAKE, + FRAME_SCREENSHAKE, + FRAME_REALIZATION, + FRAME_SFX, + ADDITIVE, + EFFECTS }; -enum COLOR { WHITE = 0, GREEN, RED, ORANGE, BLUE, YELLOW, RAINBOW, PINK, CYAN }; +enum MUSIC_EFFECT { FADE_IN = 1, FADE_OUT = 2, SYNC_POS = 4 }; #endif // DATATYPES_H diff --git a/include/discord_rich_presence.h b/include/discord_rich_presence.h index 77c0eff..42de59e 100644 --- a/include/discord_rich_presence.h +++ b/include/discord_rich_presence.h @@ -1,9 +1,12 @@ #ifndef DISCORD_RICH_PRESENCE_H #define DISCORD_RICH_PRESENCE_H +#include #include #include #include +#include + #include #include @@ -15,6 +18,8 @@ namespace AttorneyOnline { class Discord { + Q_DECLARE_TR_FUNCTIONS(Discord) + private: const char *APPLICATION_ID = "399779271737868288"; std::string server_name, server_id; diff --git a/include/lobby.h b/include/lobby.h index 833450d..42f5297 100644 --- a/include/lobby.h +++ b/include/lobby.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -31,6 +32,10 @@ public: 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_stylesheet(QWidget *widget, QString target_tag); + void set_stylesheets(); + void set_fonts(); + void set_font(QWidget *widget, QString p_identifier); void set_loading_text(QString p_text); void show_loading_overlay() { ui_loading_background->show(); } void hide_loading_overlay() { ui_loading_background->hide(); } @@ -59,7 +64,10 @@ private: QLabel *ui_version; AOButton *ui_about; - QListWidget *ui_server_list; + AOButton *ui_settings; + + QTreeWidget *ui_server_list; + QLineEdit *ui_server_search; QLabel *ui_player_count; AOTextArea *ui_description; @@ -74,6 +82,8 @@ private: QProgressBar *ui_progress_bar; AOButton *ui_cancel; + int last_index; + void set_size_and_pos(QWidget *p_widget, QString p_identifier); private slots: @@ -87,7 +97,10 @@ private slots: void on_connect_pressed(); void on_connect_released(); void on_about_clicked(); - void on_server_list_clicked(QModelIndex p_model); + void on_settings_clicked(); + void on_server_list_clicked(QTreeWidgetItem *p_item, int column); + void on_server_list_doubleclicked(QTreeWidgetItem *p_item, int column); + void on_server_search_edited(QString p_text); void on_chatfield_return_pressed(); }; diff --git a/include/scrolltext.h b/include/scrolltext.h new file mode 100644 index 0000000..9ed5ff7 --- /dev/null +++ b/include/scrolltext.h @@ -0,0 +1,47 @@ +#ifndef SCROLLTEXT_H +#define SCROLLTEXT_H + +#include +#include +#include +#include +#include + +class ScrollText : public QWidget { + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QString separator READ separator WRITE setSeparator) + +public: + explicit ScrollText(QWidget *parent = nullptr); + +public slots: + QString text() const; + void setText(QString text); + + QString separator() const; + void setSeparator(QString separator); + +protected: + virtual void paintEvent(QPaintEvent *); + virtual void resizeEvent(QResizeEvent *); + +private: + void updateText(); + QString _text; + QString _separator; + QStaticText staticText; + int singleTextWidth; + QSize wholeTextSize; + int leftMargin; + bool scrollEnabled; + int scrollPos; + QImage alphaChannel; + QImage buffer; + QTimer timer; + +private slots: + virtual void timer_timeout(); +}; + +#endif // SCROLLTEXT_H diff --git a/resource/logo.ico b/resource/logo.ico index b40e786..3c68adf 100644 Binary files a/resource/logo.ico and b/resource/logo.ico differ diff --git a/resource/logo.png b/resource/logo.png index f53fe30..d800123 100644 Binary files a/resource/logo.png and b/resource/logo.png differ diff --git a/resource/logo_ao2.ico b/resource/logo_ao2.ico new file mode 100644 index 0000000..b40e786 Binary files /dev/null and b/resource/logo_ao2.ico differ diff --git a/resource/logo_ao2.png b/resource/logo_ao2.png new file mode 100644 index 0000000..f53fe30 Binary files /dev/null and b/resource/logo_ao2.png differ diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 9bed218..e1e9e7f 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -126,7 +126,7 @@ void AOApplication::add_favorite_server(int p_server) void AOApplication::server_disconnected() { if (courtroom_constructed) { - call_notice("Disconnected from server."); + call_notice(tr("Disconnected from server.")); construct_lobby(); destruct_courtroom(); } @@ -149,18 +149,19 @@ void AOApplication::ms_connect_finished(bool connected, bool will_retry) if (will_retry) { if (lobby_constructed) w_lobby->append_error( - "Error connecting to master server. Will try again in " + - QString::number(net_manager->ms_reconnect_delay) + " seconds."); + tr("Error connecting to master server. Will try again in %1 " + "seconds.") + .arg(QString::number(net_manager->ms_reconnect_delay))); } 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."); + call_error(tr("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/src/aoblipplayer.cpp b/src/aoblipplayer.cpp index 0355414..39158fd 100644 --- a/src/aoblipplayer.cpp +++ b/src/aoblipplayer.cpp @@ -9,13 +9,17 @@ AOBlipPlayer::AOBlipPlayer(QWidget *parent, AOApplication *p_ao_app) void AOBlipPlayer::set_blips(QString p_sfx) { - QString f_path = ao_app->get_sounds_path(p_sfx); + QString f_path = ao_app->get_sfx_suffix(ao_app->get_sounds_path(p_sfx)); for (int n_stream = 0; n_stream < 5; ++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); + if (f_path.endsWith(".opus")) + m_stream_list[n_stream] = BASS_OPUS_StreamCreateFile( + FALSE, f_path.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); + else + m_stream_list[n_stream] = BASS_StreamCreateFile( + FALSE, f_path.utf16(), 0, 0, BASS_UNICODE | BASS_ASYNCFILE); } set_volume(m_volume); @@ -23,6 +27,10 @@ void AOBlipPlayer::set_blips(QString p_sfx) void AOBlipPlayer::blip_tick() { + if (delay.isValid() && delay.elapsed() < max_blip_ms) + return; + + delay.start(); int f_cycle = m_cycle++; if (m_cycle == 5) @@ -38,7 +46,9 @@ void AOBlipPlayer::set_volume(int p_value) { m_volume = p_value; - float volume = p_value / 100.0f; +void AOBlipPlayer::set_volume_internal(qreal p_value) +{ + float volume = static_cast(p_value); for (int n_stream = 0; n_stream < 5; ++n_stream) { BASS_ChannelSetAttribute(m_stream_list[n_stream], BASS_ATTRIB_VOL, volume); @@ -88,5 +98,7 @@ void AOBlipPlayer::set_blips(QString p_sfx) {} void AOBlipPlayer::blip_tick() {} -void AOBlipPlayer::set_volume(int p_value) {} +void AOBlipPlayer::set_volume(qreal p_value) {} + +void AOBlipPlayer::set_volume_internal(qreal p_value) {} #endif diff --git a/src/aobutton.cpp b/src/aobutton.cpp index fee946a..dc2d881 100644 --- a/src/aobutton.cpp +++ b/src/aobutton.cpp @@ -13,11 +13,26 @@ AOButton::~AOButton() {} void AOButton::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); + QString image_path = + ao_app->get_static_image_suffix(ao_app->get_theme_path(p_image)); + QString default_image_path = + ao_app->get_static_image_suffix(ao_app->get_default_theme_path(p_image)); - if (file_exists(image_path)) - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); + if (file_exists(image_path)) { + this->setText(""); + this->setStyleSheet("QPushButton { border-image: url(\"" + image_path + + "\") 0 0 0 0 stretch stretch; }" + "QToolTip { background-image: url(); color: #000000; " + "background-color: #ffffff; border: 0px; }"); + } + else if (file_exists(default_image_path)) { + this->setText(""); + this->setStyleSheet("QPushButton { border-image: url(\"" + + default_image_path + + "\"); }" + "QToolTip { background-image: url(); color: #000000; " + "background-color: #ffffff; border: 0px; }"); + } else - this->setStyleSheet("border-image:url(\"" + default_image_path + "\")"); + return; } diff --git a/src/aocharbutton.cpp b/src/aocharbutton.cpp index 6c27e3e..844c959 100644 --- a/src/aocharbutton.cpp +++ b/src/aocharbutton.cpp @@ -17,7 +17,7 @@ AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, ui_taken = new AOImage(this, ao_app); ui_taken->resize(60, 60); - ui_taken->set_image("char_taken.png"); + ui_taken->set_image("char_taken"); ui_taken->setAttribute(Qt::WA_TransparentForMouseEvents); ui_taken->hide(); @@ -30,7 +30,7 @@ AOCharButton::AOCharButton(QWidget *parent, AOApplication *p_ao_app, int x_pos, 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->set_image("char_selector"); ui_selector->setAttribute(Qt::WA_TransparentForMouseEvents); ui_selector->hide(); } @@ -59,14 +59,21 @@ 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 image_path = ao_app->get_static_image_suffix( + ao_app->get_character_path(p_character, "char_icon")); this->setText(""); - if (file_exists(image_path)) - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); + if (file_exists(image_path)) { + this->setStyleSheet("QPushButton { border-image: url(\"" + image_path + + "\") 0 0 0 0 stretch stretch; }" + "QToolTip { background-image: url(); color: #000000; " + "background-color: #ffffff; border: 0px; }"); + } else { - this->setStyleSheet("border-image:url()"); + this->setStyleSheet("QPushButton { border-image: url(); }" + "QToolTip { background-image: url(); color: #000000; " + "background-color: #ffffff; border: 0px; }"); this->setText(p_character); } } diff --git a/src/aocharmovie.cpp b/src/aocharmovie.cpp index 3e0a4de..252aab5 100644 --- a/src/aocharmovie.cpp +++ b/src/aocharmovie.cpp @@ -8,150 +8,274 @@ 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())); + 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::play(QString p_char, QString p_emote, QString emote_prefix) +void AOCharMovie::load_image(QString p_char, QString p_emote, + QString emote_prefix) { - QString original_path = - ao_app->get_character_path(p_char, emote_prefix + p_emote + ".gif"); - QString alt_path = ao_app->get_character_path(p_char, p_emote + ".png"); - QString apng_path = - ao_app->get_character_path(p_char, emote_prefix + p_emote + ".apng"); - 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; +#ifdef DEBUG_CHARMOVIE + actual_time.restart(); +#endif + QString emote_path; + QList 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_character_path( + p_char, p_emote + ".png"), // Non-animated path if emote_prefix fails + 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 + }; - if (file_exists(apng_path)) - gif_path = apng_path; - else 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; - - 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(); + for (QString path : pathlist) { + if (file_exists(path)) { + emote_path = path; + break; + } } - delete reader; + 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); + 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(); - m_movie->start(); + 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) { - QString gif_path = ao_app->get_character_path(p_char, p_emote); - - 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); - } - -#ifdef DEBUG_GIF - qDebug() << "full_duration: " << full_duration; - qDebug() << "real_duration: " << real_duration; -#endif - - 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; - } - -#ifdef DEBUG_GIF - qDebug() << "% mod: " << percentage_modifier; -#endif - - 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, ""); + 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) { - QString gif_path = ao_app->get_character_path(p_char, "(b)" + p_emote); - - m_movie->stop(); - this->clear(); - m_movie->setFileName(gif_path); - play_once = false; - m_movie->setSpeed(100); - play(p_char, p_emote, "(b)"); + load_image(p_char, p_emote, "(b)"); + play(); } void AOCharMovie::play_idle(QString p_char, QString p_emote) { - QString gif_path = ao_app->get_character_path(p_char, "(a)" + p_emote); - - m_movie->stop(); - this->clear(); - m_movie->setFileName(gif_path); - play_once = false; - m_movie->setSpeed(100); - play(p_char, p_emote, "(a)"); + 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 - m_movie->stop(); + 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); - m_movie->setScaledSize(f_size); +} + +int AOCharMovie::get_frame_delay(int delay) +{ + return static_cast(double(delay) * double(speed / 100)); } void AOCharMovie::move(int ax, int ay) @@ -161,31 +285,39 @@ void AOCharMovie::move(int ax, int ay) QLabel::move(x, y); } -void AOCharMovie::frame_change(int n_frame) +void AOCharMovie::movie_ticker() { - - if (movie_frames.size() > n_frame) { - QPixmap f_pixmap = QPixmap::fromImage(movie_frames.at(n_frame)); - auto aspect_ratio = Qt::KeepAspectRatio; - - if (f_pixmap.size().width() > f_pixmap.size().height()) - aspect_ratio = Qt::KeepAspectRatioByExpanding; - - if (f_pixmap.size().width() > this->size().width() || - f_pixmap.size().height() > this->size().height()) - this->setPixmap(f_pixmap.scaled(this->width(), this->height(), - aspect_ratio, Qt::SmoothTransformation)); + ++frame; + if (frame >= max_frames) { + if (play_once) { + preanim_done(); + return; + } else - this->setPixmap(f_pixmap.scaled(this->width(), this->height(), - aspect_ratio, Qt::FastTransformation)); - - QLabel::move(x + (this->width() - this->pixmap()->width()) / 2, y); + 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(); } - if (m_movie->frameCount() - 1 == n_frame && play_once) { - preanim_timer->start(m_movie->nextFrameDelay()); - m_movie->stop(); - } +#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::timer_done() { done(); } +void AOCharMovie::preanim_done() +{ + ticker->stop(); + preanim_timer->stop(); + done(); +} diff --git a/src/aoemotebutton.cpp b/src/aoemotebutton.cpp index 29329c9..abec526 100644 --- a/src/aoemotebutton.cpp +++ b/src/aoemotebutton.cpp @@ -3,32 +3,43 @@ #include "file_functions.h" AOEmoteButton::AOEmoteButton(QWidget *p_parent, AOApplication *p_ao_app, - int p_x, int p_y) + int p_x, int p_y, int p_w, int p_h) : QPushButton(p_parent) { parent = p_parent; ao_app = p_ao_app; this->move(p_x, p_y); - this->resize(40, 40); + this->resize(p_w, p_h); connect(this, SIGNAL(clicked()), this, SLOT(on_clicked())); } -void AOEmoteButton::set_image(QString p_char, int p_emote, QString suffix) +void AOEmoteButton::set_image(QString p_image, QString p_emote_comment) { - QString emotion_number = QString::number(p_emote + 1); - QString image_path = ao_app->get_character_path( - p_char, "emotions/button" + emotion_number + suffix); - - if (file_exists(image_path)) { + if (file_exists(p_image)) { this->setText(""); - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); + this->setStyleSheet( + "QPushButton { border-image: url(\"" + p_image + + "\") 0 0 0 0 stretch stretch; }" + "QToolTip { color: #000000; background-color: #ffffff; border: 0px; }"); } else { - this->setText(ao_app->get_emote_comment(p_char, p_emote)); - this->setStyleSheet("border-image:url(\"\")"); + this->setText(p_emote_comment); + this->setStyleSheet("QPushButton { border-image: url(); }" + "QToolTip { background-image: url(); color: #000000; " + "background-color: #ffffff; border: 0px; }"); } } +void AOEmoteButton::set_char_image(QString p_char, int p_emote, QString suffix) +{ + QString emotion_number = QString::number(p_emote + 1); + QString image_path = + ao_app->get_static_image_suffix(ao_app->get_character_path( + p_char, "emotions/button" + emotion_number + suffix)); + + this->set_image(image_path, ao_app->get_emote_comment(p_char, p_emote)); +} + void AOEmoteButton::on_clicked() { emote_clicked(m_id); } diff --git a/src/aoevidencebutton.cpp b/src/aoevidencebutton.cpp index a94519e..f85a223 100644 --- a/src/aoevidencebutton.cpp +++ b/src/aoevidencebutton.cpp @@ -3,51 +3,55 @@ #include "file_functions.h" AOEvidenceButton::AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, - int p_x, int p_y) + int p_x, int p_y, int p_w, int p_h) : QPushButton(p_parent) { ao_app = p_ao_app; m_parent = p_parent; - 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 = new AOImage(this, ao_app); + ui_selected->resize(p_w, p_h); + // ui_selected->move(p_x, p_y); + ui_selected->set_image("evidence_selected"); 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 = new AOImage(this, ao_app); + ui_selector->resize(p_w, p_h); + // ui_selector->move(p_x - 1, p_y - 1); + ui_selector->set_image("evidence_selector"); ui_selector->setAttribute(Qt::WA_TransparentForMouseEvents); ui_selector->hide(); this->move(p_x, p_y); - this->resize(70, 70); - this->setAcceptDrops(true); + this->resize(p_w, p_h); + // 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)) { + if (file_exists(p_image)) { this->setText(""); - this->setStyleSheet("border-image:url(\"" + image_path + "\")"); + this->setStyleSheet( + "QPushButton { border-image: url(\"" + p_image + + "\") 0 0 0 0 stretch stretch; }" + "QToolTip { color: #000000; background-color: #ffffff; border: 0px; }"); + } + else if (file_exists(image_path)) { + this->setText(""); + this->setStyleSheet( + "QPushButton { border-image: url(\"" + image_path + + "\") 0 0 0 0 stretch stretch; }" + "QToolTip { color: #000000; background-color: #ffffff; border: 0px; }"); } else { this->setText(p_image); - this->setStyleSheet(""); + this->setStyleSheet("QPushButton { border-image: url(); }" + "QToolTip { background-image: url(); color: #000000; " + "background-color: #ffffff; border: 0px; }"); } } @@ -63,8 +67,7 @@ void AOEvidenceButton::set_theme_image(QString p_image) else final_image_path = default_image_path; - this->setText(""); - this->setStyleSheet("border-image:url(\"" + final_image_path + "\")"); + this->set_image(final_image_path); } void AOEvidenceButton::set_selected(bool p_selected) diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp index 9dd062b..2ffea2c 100644 --- a/src/aoevidencedisplay.cpp +++ b/src/aoevidencedisplay.cpp @@ -8,13 +8,12 @@ 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))); + evidence_movie = new AOMovie(this, ao_app); + + connect(evidence_movie, SIGNAL(done()), this, SLOT(show_done())); } void AOEvidenceDisplay::show_evidence(QString p_evidence_image, @@ -24,64 +23,34 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, 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"; + gif_name = "evidence_appear_left"; } else { icon_identifier = "right_evidence_icon"; - gif_name = "evidence_appear_right.gif"; + gif_name = "evidence_appear_right"; } + QString f_evidence_path = ao_app->get_evidence_path(p_evidence_image); + QPixmap f_pixmap(f_evidence_path); + pos_size_type icon_dimensions = ao_app->get_element_dimensions(icon_identifier, "courtroom_design.ini"); + f_pixmap = f_pixmap.scaled(icon_dimensions.width, icon_dimensions.height); + evidence_icon->setPixmap(f_pixmap); + evidence_icon->resize(f_pixmap.size()); 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(); + evidence_movie->play(gif_name); 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(); @@ -90,4 +59,13 @@ void AOEvidenceDisplay::reset() this->clear(); } +void AOEvidenceDisplay::show_done() { evidence_icon->show(); } + QLabel *AOEvidenceDisplay::get_evidence_icon() { return evidence_icon; } + +void AOEvidenceDisplay::combo_resize(int w, int h) +{ + QSize f_size(w, h); + this->resize(f_size); + evidence_movie->combo_resize(w, h); +} diff --git a/src/aoimage.cpp b/src/aoimage.cpp index ffdf25a..2663ba0 100644 --- a/src/aoimage.cpp +++ b/src/aoimage.cpp @@ -10,37 +10,42 @@ AOImage::AOImage(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) AOImage::~AOImage() {} -void AOImage::set_image(QString p_image) +bool 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 theme_image_path = + ao_app->get_static_image_suffix(ao_app->get_theme_path(p_image)); + QString default_image_path = + ao_app->get_static_image_suffix(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 + else if (file_exists(default_image_path)) final_image_path = default_image_path; + else { + qDebug() << "Warning: Image" << p_image << "not found! Can't set!"; + return false; + } QPixmap f_pixmap(final_image_path); this->setPixmap( f_pixmap.scaled(this->width(), this->height(), Qt::IgnoreAspectRatio)); + return true; } -void AOImage::set_image_from_path(QString p_path) +bool AOImage::set_chatbox(QString p_path) { - QString default_path = ao_app->get_default_theme_path("chatmed.png"); + p_path = ao_app->get_static_image_suffix(p_path); + if (!file_exists(p_path)) { + qDebug() << "Warning: Chatbox" << p_path << "not found! Can't set!"; + return false; + } - QString final_path; - - if (file_exists(p_path)) - final_path = p_path; - else - final_path = default_path; - - QPixmap f_pixmap(final_path); + QPixmap f_pixmap(p_path); this->setPixmap( f_pixmap.scaled(this->width(), this->height(), Qt::IgnoreAspectRatio)); + return true; } diff --git a/src/aolineedit.cpp b/src/aolineedit.cpp index 211d9f7..f98d95f 100644 --- a/src/aolineedit.cpp +++ b/src/aolineedit.cpp @@ -1,18 +1,19 @@ #include "aolineedit.h" -AOLineEdit::AOLineEdit(QWidget *parent) : QLineEdit(parent) -{ - this->setReadOnly(true); - this->setFrame(false); - - connect(this, SIGNAL(returnPressed()), this, SLOT(on_enter_pressed())); -} +AOLineEdit::AOLineEdit(QWidget *parent) : QLineEdit(parent) {} void AOLineEdit::mouseDoubleClickEvent(QMouseEvent *e) { QLineEdit::mouseDoubleClickEvent(e); - this->setReadOnly(false); + double_clicked(); +} +void AOLineEdit::focusOutEvent(QFocusEvent *ev) +{ + int start = selectionStart(); + int len = selectionEnd() - start; // We're not using selectionLength because + // Linux build doesn't run qt5.10 + QLineEdit::focusOutEvent(ev); + if (p_selection && start != -1 && len != -1) + this->setSelection(start, len); } - -void AOLineEdit::on_enter_pressed() { this->setReadOnly(true); } diff --git a/src/aomovie.cpp b/src/aomovie.cpp index 8642688..ac94921 100644 --- a/src/aomovie.cpp +++ b/src/aomovie.cpp @@ -12,56 +12,58 @@ AOMovie::AOMovie(QWidget *p_parent, AOApplication *p_ao_app) : QLabel(p_parent) 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_gif, QString p_char, QString p_custom_theme) +void AOMovie::play(QString p_image, QString p_char, QString p_custom_theme, + int duration) { m_movie->stop(); - QString gif_path; + QString shout_path = p_image; + if (!file_exists(p_image)) { + QList pathlist; - QString custom_path; - if (p_gif == "custom") - custom_path = - ao_app->get_image_suffix(ao_app->get_character_path(p_char, p_gif)); - else - custom_path = ao_app->get_image_suffix( - ao_app->get_character_path(p_char, p_gif + "_bubble")); + 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 + }; - QString misc_path = ao_app->get_base_path() + "misc/" + p_custom_theme + "/" + - p_gif + "_bubble.gif"; - QString custom_theme_path = - ao_app->get_custom_theme_path(p_custom_theme, p_gif + ".gif"); - QString theme_path = ao_app->get_theme_path(p_gif + ".gif"); - QString default_theme_path = ao_app->get_default_theme_path(p_gif + ".gif"); - QString placeholder_path = ao_app->get_theme_path("placeholder.gif"); - QString default_placeholder_path = - ao_app->get_default_theme_path("placeholder.gif"); + for (QString path : pathlist) { + if (file_exists(path)) { + shout_path = path; + break; + } + } + } - if (file_exists(custom_path)) - gif_path = custom_path; - else if (file_exists(misc_path)) - gif_path = misc_path; - else if (file_exists(custom_theme_path)) - gif_path = custom_theme_path; - else if (file_exists(theme_path)) - gif_path = theme_path; - else if (file_exists(default_theme_path)) - gif_path = default_theme_path; - else if (file_exists(placeholder_path)) - gif_path = placeholder_path; - else if (file_exists(default_placeholder_path)) - gif_path = default_placeholder_path; - else - gif_path = ""; + m_movie->setFileName(shout_path); - m_movie->setFileName(gif_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() @@ -72,15 +74,21 @@ void AOMovie::stop() 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()); + // 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()); +} - this->stop(); - - // signal connected to courtroom object, let it figure out what to do - done(); - } +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) diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp index e61555a..bc1ab23 100644 --- a/src/aomusicplayer.cpp +++ b/src/aomusicplayer.cpp @@ -1,36 +1,173 @@ #include "aomusicplayer.h" -#if defined(BASSAUDIO) +#ifdef BASSAUDIO AOMusicPlayer::AOMusicPlayer(QWidget *parent, AOApplication *p_ao_app) { m_parent = parent; ao_app = p_ao_app; } -AOMusicPlayer::~AOMusicPlayer() { BASS_ChannelStop(m_stream); } - -void AOMusicPlayer::play(QString p_song) +AOMusicPlayer::~AOMusicPlayer() { - BASS_ChannelStop(m_stream); - - QString f_path = ao_app->get_music_path(p_song); - - m_stream = BASS_StreamCreateFile(FALSE, f_path.utf16(), 0, 0, - BASS_STREAM_AUTOFREE | BASS_UNICODE | - BASS_ASYNCFILE); - - this->set_volume(m_volume); - - if (ao_app->get_audio_output_device() != "default") - BASS_ChannelSetDevice(m_stream, BASS_GetDevice()); - BASS_ChannelPlay(m_stream, false); + for (int n_stream = 0; n_stream < m_channelmax; ++n_stream) { + BASS_ChannelStop(m_stream_list[n_stream]); + } } -void AOMusicPlayer::set_volume(int p_value) +void AOMusicPlayer::play(QString p_song, int channel, bool loop, + int effect_flags) { - m_volume = p_value; - float volume = m_volume / 100.0f; - BASS_ChannelSetAttribute(m_stream, BASS_ATTRIB_VOL, volume); + channel = channel % m_channelmax; + if (channel < 0) // wtf? + return; + QString f_path = ao_app->get_music_path(p_song); + + unsigned int flags = BASS_STREAM_PRESCAN | BASS_STREAM_AUTOFREE | + BASS_UNICODE | BASS_ASYNCFILE; + if (loop) + flags |= BASS_SAMPLE_LOOP; + + DWORD newstream; + if (f_path.endsWith(".opus")) + newstream = BASS_OPUS_StreamCreateFile(FALSE, f_path.utf16(), 0, 0, flags); + else + newstream = BASS_StreamCreateFile(FALSE, f_path.utf16(), 0, 0, flags); + + if (ao_app->get_audio_output_device() != "default") + BASS_ChannelSetDevice(m_stream_list[channel], BASS_GetDevice()); + + QString d_path = f_path + ".txt"; + + loop_start = 0; + loop_end = BASS_ChannelGetLength(newstream, BASS_POS_BYTE); + if (loop && file_exists(d_path)) // Contains loop/etc. information file + { + QStringList lines = ao_app->read_file(d_path).split("\n"); + foreach (QString line, lines) { + QStringList args = line.split("="); + if (args.size() < 2) + continue; + QString arg = args[0].trimmed(); + + float sample_rate; + BASS_ChannelGetAttribute(newstream, BASS_ATTRIB_FREQ, &sample_rate); + + // Grab number of bytes for sample size + int sample_size = 16 / 8; + + // number of channels (stereo/mono) + int num_channels = 2; + + // Calculate the bytes for loop_start/loop_end to use with the sync proc + QWORD bytes = static_cast(args[1].trimmed().toFloat() * + sample_size * num_channels); + if (arg == "loop_start") + loop_start = bytes; + else if (arg == "loop_length") + loop_end = loop_start + bytes; + else if (arg == "loop_end") + loop_end = bytes; + } + qDebug() << "Found data file for song" << p_song << "length" + << BASS_ChannelGetLength(newstream, BASS_POS_BYTE) << "loop start" + << loop_start << "loop end" << loop_end; + } + + if (BASS_ChannelIsActive(m_stream_list[channel]) == BASS_ACTIVE_PLAYING) { + DWORD oldstream = m_stream_list[channel]; + + if (effect_flags & SYNC_POS) { + BASS_ChannelLock(oldstream, true); + // Sync it with the new sample + BASS_ChannelSetPosition(newstream, + BASS_ChannelGetPosition(oldstream, BASS_POS_BYTE), + BASS_POS_BYTE); + BASS_ChannelLock(oldstream, false); + } + + if (effect_flags & FADE_OUT) { + // Fade out the other sample and stop it (due to -1) + BASS_ChannelSlideAttribute(oldstream, BASS_ATTRIB_VOL | BASS_SLIDE_LOG, + -1, 4000); + } + else + BASS_ChannelStop( + oldstream); // Stop the sample since we don't need it anymore + } + else + BASS_ChannelStop(m_stream_list[channel]); + + m_stream_list[channel] = newstream; + BASS_ChannelPlay(m_stream_list[channel], false); + if (effect_flags & FADE_IN) { + // Fade in our sample + BASS_ChannelSetAttribute(newstream, BASS_ATTRIB_VOL, 0); + BASS_ChannelSlideAttribute(newstream, BASS_ATTRIB_VOL, + static_cast(m_volume[channel] / 100.0f), + 1000); + } + else + this->set_volume(m_volume[channel], channel); + + this->set_looping(loop); // Have to do this here due to any + // crossfading-related changes, etc. +} + +void AOMusicPlayer::stop(int channel) +{ + BASS_ChannelStop(m_stream_list[channel]); +} + +void AOMusicPlayer::set_volume(int p_value, int channel) +{ + m_volume[channel] = p_value; + float volume = m_volume[channel] / 100.0f; + if (channel < 0) { + for (int n_stream = 0; n_stream < m_channelmax; ++n_stream) { + BASS_ChannelSetAttribute(m_stream_list[n_stream], BASS_ATTRIB_VOL, + volume); + } + } + else { + BASS_ChannelSetAttribute(m_stream_list[channel], BASS_ATTRIB_VOL, volume); + } +} + +void CALLBACK loopProc(HSYNC handle, DWORD channel, DWORD data, void *user) +{ + QWORD loop_start = *(static_cast(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; + if (!m_looping) { + if (BASS_ChannelFlags(m_stream_list[channel], 0, 0) & BASS_SAMPLE_LOOP) + BASS_ChannelFlags(m_stream_list[channel], 0, + BASS_SAMPLE_LOOP); // remove the LOOP flag + BASS_ChannelRemoveSync(m_stream_list[channel], loop_sync[channel]); + loop_sync[channel] = 0; + } + else { + BASS_ChannelFlags(m_stream_list[channel], BASS_SAMPLE_LOOP, + BASS_SAMPLE_LOOP); // set the LOOP flag + if (loop_sync[channel] != 0) { + BASS_ChannelRemoveSync(m_stream_list[channel], + loop_sync[channel]); // remove the sync + loop_sync[channel] = 0; + } + if (loop_start > 0) { + if (loop_end == 0) + loop_end = BASS_ChannelGetLength(m_stream_list[channel], BASS_POS_BYTE); + if (loop_end > 0) // Don't loop zero length songs even if we're asked to + loop_sync[channel] = BASS_ChannelSetSync( + m_stream_list[channel], BASS_SYNC_POS | BASS_SYNC_MIXTIME, loop_end, + loopProc, &loop_start); + } + } } #elif defined(QTAUDIO) AOMusicPlayer::AOMusicPlayer(QWidget *parent, AOApplication *p_ao_app) diff --git a/src/aooptionsdialog.cpp b/src/aooptionsdialog.cpp index 5653fd9..e911963 100644 --- a/src/aooptionsdialog.cpp +++ b/src/aooptionsdialog.cpp @@ -14,7 +14,7 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) ui_settings_buttons = new QDialogButtonBox(this); - QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Fixed); + QSizePolicy sizePolicy1(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); sizePolicy1.setHorizontalStretch(0); sizePolicy1.setVerticalStretch(0); sizePolicy1.setHeightForWidth( @@ -42,10 +42,11 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) // Let's add the tabs one by one. // First, we'll start with 'Gameplay'. ui_gameplay_tab = new QWidget(); + ui_gameplay_tab->setSizePolicy(sizePolicy1); ui_settings_tabs->addTab(ui_gameplay_tab, tr("Gameplay")); - ui_form_layout_widget = new QWidget(ui_gameplay_tab); - ui_form_layout_widget->setGeometry(QRect(10, 10, 361, 211)); + ui_form_layout_widget->setGeometry(QRect(10, 10, 361, 361)); + ui_form_layout_widget->setSizePolicy(sizePolicy1); ui_gameplay_form = new QFormLayout(ui_form_layout_widget); ui_gameplay_form->setLabelAlignment(Qt::AlignLeading | Qt::AlignLeft | @@ -53,6 +54,9 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) ui_gameplay_form->setFormAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignTop); ui_gameplay_form->setContentsMargins(0, 0, 0, 0); + ui_gameplay_form->setSpacing(2); + + int row = 0; ui_theme_label = new QLabel(ui_form_layout_widget); ui_theme_label->setText(tr("Theme:")); @@ -61,8 +65,7 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) "the lobby's look as well, you'll need to reload the " "lobby for the changes to take effect, such as by joining " "a server and leaving it.")); - ui_gameplay_form->setWidget(0, QFormLayout::LabelRole, ui_theme_label); - + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_theme_label); ui_theme_combobox = new QComboBox(ui_form_layout_widget); // Fill the combobox with the names of the themes. @@ -76,15 +79,18 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) ui_theme_combobox->setCurrentIndex(ui_theme_combobox->count() - 1); } - ui_gameplay_form->setWidget(0, QFormLayout::FieldRole, ui_theme_combobox); + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_theme_combobox); + row += 1; ui_theme_log_divider = new QFrame(ui_form_layout_widget); ui_theme_log_divider->setMidLineWidth(0); ui_theme_log_divider->setFrameShape(QFrame::HLine); ui_theme_log_divider->setFrameShadow(QFrame::Sunken); - ui_gameplay_form->setWidget(1, QFormLayout::FieldRole, ui_theme_log_divider); + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, + ui_theme_log_divider); + row += 1; ui_downwards_lbl = new QLabel(ui_form_layout_widget); ui_downwards_lbl->setText(tr("Log goes downwards:")); ui_downwards_lbl->setToolTip( @@ -92,47 +98,52 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) "the bottom (like the OOC chatlog). The traditional " "(AO1) behaviour is equivalent to this being unticked.")); - ui_gameplay_form->setWidget(2, QFormLayout::LabelRole, ui_downwards_lbl); + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_downwards_lbl); ui_downwards_cb = new QCheckBox(ui_form_layout_widget); ui_downwards_cb->setChecked(p_ao_app->get_log_goes_downwards()); - ui_gameplay_form->setWidget(2, QFormLayout::FieldRole, ui_downwards_cb); + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_downwards_cb); + row += 1; ui_length_lbl = new QLabel(ui_form_layout_widget); ui_length_lbl->setText(tr("Log length:")); ui_length_lbl->setToolTip(tr( "The amount of messages the IC chatlog will keep before " "deleting older messages. A value of 0 or below counts as 'infinite'.")); - ui_gameplay_form->setWidget(3, QFormLayout::LabelRole, ui_length_lbl); + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_length_lbl); ui_length_spinbox = new QSpinBox(ui_form_layout_widget); ui_length_spinbox->setMaximum(10000); ui_length_spinbox->setValue(p_ao_app->get_max_log_size()); - ui_gameplay_form->setWidget(3, QFormLayout::FieldRole, ui_length_spinbox); + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_length_spinbox); + row += 1; ui_log_names_divider = new QFrame(ui_form_layout_widget); ui_log_names_divider->setFrameShape(QFrame::HLine); ui_log_names_divider->setFrameShadow(QFrame::Sunken); - ui_gameplay_form->setWidget(4, QFormLayout::FieldRole, ui_log_names_divider); + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, + ui_log_names_divider); + row += 1; ui_username_lbl = new QLabel(ui_form_layout_widget); ui_username_lbl->setText(tr("Default username:")); ui_username_lbl->setToolTip( tr("Your OOC name will be automatically set to this value " "when you join a server.")); - ui_gameplay_form->setWidget(5, QFormLayout::LabelRole, ui_username_lbl); + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_username_lbl); ui_username_textbox = new QLineEdit(ui_form_layout_widget); ui_username_textbox->setMaxLength(30); ui_username_textbox->setText(p_ao_app->get_default_username()); - ui_gameplay_form->setWidget(5, QFormLayout::FieldRole, ui_username_textbox); + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_username_textbox); + row += 1; ui_showname_lbl = new QLabel(ui_form_layout_widget); ui_showname_lbl->setText(tr("Custom shownames:")); ui_showname_lbl->setToolTip( @@ -140,33 +151,36 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) "tickbox, which in turn determines whether the client should " "display custom in-character names.")); - ui_gameplay_form->setWidget(6, QFormLayout::LabelRole, ui_showname_lbl); + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_showname_lbl); ui_showname_cb = new QCheckBox(ui_form_layout_widget); ui_showname_cb->setChecked(p_ao_app->get_showname_enabled_by_default()); - ui_gameplay_form->setWidget(6, QFormLayout::FieldRole, ui_showname_cb); + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_showname_cb); + row += 1; ui_net_divider = new QFrame(ui_form_layout_widget); ui_net_divider->setFrameShape(QFrame::HLine); ui_net_divider->setFrameShadow(QFrame::Sunken); - ui_gameplay_form->setWidget(7, QFormLayout::FieldRole, ui_net_divider); + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_net_divider); + row += 1; ui_ms_lbl = new QLabel(ui_form_layout_widget); ui_ms_lbl->setText(tr("Backup MS:")); ui_ms_lbl->setToolTip( tr("If the built-in server lookups fail, the game will try the " "address given here and use it as a backup master server address.")); - ui_gameplay_form->setWidget(8, QFormLayout::LabelRole, ui_ms_lbl); + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_ms_lbl); QSettings *configini = ao_app->configini; ui_ms_textbox = new QLineEdit(ui_form_layout_widget); ui_ms_textbox->setText(configini->value("master", "").value()); - ui_gameplay_form->setWidget(8, QFormLayout::FieldRole, ui_ms_textbox); + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_ms_textbox); + row += 1; ui_discord_lbl = new QLabel(ui_form_layout_widget); ui_discord_lbl->setText(tr("Discord:")); ui_discord_lbl->setToolTip( @@ -174,12 +188,136 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) "what character are you playing, and how long you have " "been playing for.")); - ui_gameplay_form->setWidget(9, QFormLayout::LabelRole, ui_discord_lbl); + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_discord_lbl); ui_discord_cb = new QCheckBox(ui_form_layout_widget); ui_discord_cb->setChecked(ao_app->is_discord_enabled()); - ui_gameplay_form->setWidget(9, QFormLayout::FieldRole, ui_discord_cb); + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_discord_cb); + + row += 1; + ui_language_label = new QLabel(ui_form_layout_widget); + ui_language_label->setText(tr("Language:")); + ui_language_label->setToolTip( + tr("Sets the language if you don't want to use your system language.")); + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_language_label); + + ui_language_combobox = new QComboBox(ui_form_layout_widget); + ui_language_combobox->addItem( + configini->value("language", " ").value() + + " - Keep current setting"); + ui_language_combobox->addItem(" - Default"); + ui_language_combobox->addItem("en - English"); + ui_language_combobox->addItem("de - Deutsch"); + ui_language_combobox->addItem("es - Español"); + ui_language_combobox->addItem("jp - 日本語"); + ui_language_combobox->addItem("ru - Русский"); + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, + ui_language_combobox); + + row += 1; + ui_shake_lbl = new QLabel(ui_form_layout_widget); + ui_shake_lbl->setText(tr("Allow Screenshake:")); + ui_shake_lbl->setToolTip( + tr("Allows screenshaking. Disable this if you have concerns or issues " + "with photosensitivity and/or seizures.")); + + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_shake_lbl); + + ui_shake_cb = new QCheckBox(ui_form_layout_widget); + ui_shake_cb->setChecked(ao_app->is_shake_enabled()); + + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_shake_cb); + + row += 1; + ui_effects_lbl = new QLabel(ui_form_layout_widget); + ui_effects_lbl->setText(tr("Allow Effects:")); + ui_effects_lbl->setToolTip( + tr("Allows screen effects. Disable this if you have concerns or issues " + "with photosensitivity and/or seizures.")); + + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_effects_lbl); + + ui_effects_cb = new QCheckBox(ui_form_layout_widget); + ui_effects_cb->setChecked(ao_app->is_effects_enabled()); + + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_effects_cb); + + row += 1; + ui_framenetwork_lbl = new QLabel(ui_form_layout_widget); + ui_framenetwork_lbl->setText(tr("Network Frame Effects:")); + ui_framenetwork_lbl->setToolTip(tr( + "Send screen-shaking, flashes and sounds as defined in the char.ini over " + "the network. Only works for servers that support this functionality.")); + + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_framenetwork_lbl); + + ui_framenetwork_cb = new QCheckBox(ui_form_layout_widget); + ui_framenetwork_cb->setChecked(ao_app->is_frame_network_enabled()); + + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_framenetwork_cb); + + row += 1; + ui_colorlog_lbl = new QLabel(ui_form_layout_widget); + ui_colorlog_lbl->setText(tr("Colors in IC Log:")); + ui_colorlog_lbl->setToolTip( + tr("Use the markup colors in the server IC chatlog.")); + + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_colorlog_lbl); + + ui_colorlog_cb = new QCheckBox(ui_form_layout_widget); + ui_colorlog_cb->setChecked(ao_app->is_colorlog_enabled()); + + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_colorlog_cb); + + row += 1; + ui_stickysounds_lbl = new QLabel(ui_form_layout_widget); + ui_stickysounds_lbl->setText(tr("Sticky Sounds:")); + ui_stickysounds_lbl->setToolTip( + tr("Turn this on to prevent the sound dropdown from clearing the sound " + "after playing it.")); + + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_stickysounds_lbl); + + ui_stickysounds_cb = new QCheckBox(ui_form_layout_widget); + ui_stickysounds_cb->setChecked(ao_app->is_stickysounds_enabled()); + + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_stickysounds_cb); + + row += 1; + ui_stickyeffects_lbl = new QLabel(ui_form_layout_widget); + ui_stickyeffects_lbl->setText(tr("Sticky Effects:")); + ui_stickyeffects_lbl->setToolTip( + tr("Turn this on to prevent the effects dropdown from clearing the " + "effect after playing it.")); + + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, + ui_stickyeffects_lbl); + + ui_stickyeffects_cb = new QCheckBox(ui_form_layout_widget); + ui_stickyeffects_cb->setChecked(ao_app->is_stickyeffects_enabled()); + + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_stickyeffects_cb); + + row += 1; + ui_stickypres_lbl = new QLabel(ui_form_layout_widget); + ui_stickypres_lbl->setText(tr("Sticky Preanims:")); + ui_stickypres_lbl->setToolTip( + tr("Turn this on to prevent preanimation checkbox from clearing after " + "playing the emote.")); + + ui_gameplay_form->setWidget(row, QFormLayout::LabelRole, ui_stickypres_lbl); + + ui_stickypres_cb = new QCheckBox(ui_form_layout_widget); + ui_stickypres_cb->setChecked(ao_app->is_stickypres_enabled()); + + ui_gameplay_form->setWidget(row, QFormLayout::FieldRole, ui_stickypres_cb); + + QScrollArea *scroll = new QScrollArea; + scroll->setWidget(ui_form_layout_widget); + ui_gameplay_tab->setLayout(new QVBoxLayout); + ui_gameplay_tab->layout()->addWidget(scroll); + ui_gameplay_tab->show(); // Here we start the callwords tab. ui_callwords_tab = new QWidget(); @@ -218,8 +356,7 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) ui_callwords_layout->addWidget(ui_callwords_explain_lbl); -// The audio tab. -#ifdef BASSAUDIO + // The audio tab. ui_audio_tab = new QWidget(); ui_settings_tabs->addTab(ui_audio_tab, tr("Audio")); @@ -232,117 +369,163 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) ui_audio_layout->setFormAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignTop); ui_audio_layout->setContentsMargins(0, 0, 0, 0); + row = 0; ui_audio_device_lbl = new QLabel(ui_audio_widget); ui_audio_device_lbl->setText(tr("Audio device:")); ui_audio_device_lbl->setToolTip(tr("Sets the audio device for all sounds.")); - ui_audio_layout->setWidget(0, QFormLayout::LabelRole, ui_audio_device_lbl); + ui_audio_layout->setWidget(row, QFormLayout::LabelRole, ui_audio_device_lbl); ui_audio_device_combobox = new QComboBox(ui_audio_widget); // Let's fill out the combobox with the available audio devices. Or don't if // there is no audio int a = 0; - BASS_DEVICEINFO info; - if (needs_default_audiodev()) { + ui_audio_device_combobox->addItem("default"); } - +#ifdef BASSAUDIO + BASS_DEVICEINFO info; for (a = 0; BASS_GetDeviceInfo(a, &info); a++) { ui_audio_device_combobox->addItem(info.name); if (p_ao_app->get_audio_output_device() == info.name) ui_audio_device_combobox->setCurrentIndex( ui_audio_device_combobox->count() - 1); } - - ui_audio_layout->setWidget(0, QFormLayout::FieldRole, +#elif defined QTAUDIO + foreach (const QAudioDeviceInfo &deviceInfo, + QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) { + ui_audio_device_combobox->addItem(deviceInfo.deviceName()); + if (p_ao_app->get_audio_output_device() == deviceInfo.deviceName()) + ui_audio_device_combobox->setCurrentIndex( + ui_audio_device_combobox->count() - 1); + } +#endif + ui_audio_layout->setWidget(row, QFormLayout::FieldRole, ui_audio_device_combobox); + row += 1; ui_audio_volume_divider = new QFrame(ui_audio_widget); ui_audio_volume_divider->setFrameShape(QFrame::HLine); ui_audio_volume_divider->setFrameShadow(QFrame::Sunken); - ui_audio_layout->setWidget(1, QFormLayout::FieldRole, + ui_audio_layout->setWidget(row, QFormLayout::FieldRole, ui_audio_volume_divider); + row += 1; ui_music_volume_lbl = new QLabel(ui_audio_widget); ui_music_volume_lbl->setText(tr("Music:")); ui_music_volume_lbl->setToolTip(tr("Sets the music's default volume.")); - ui_audio_layout->setWidget(2, QFormLayout::LabelRole, ui_music_volume_lbl); + ui_audio_layout->setWidget(row, QFormLayout::LabelRole, ui_music_volume_lbl); ui_music_volume_spinbox = new QSpinBox(ui_audio_widget); ui_music_volume_spinbox->setValue(p_ao_app->get_default_music()); ui_music_volume_spinbox->setMaximum(100); ui_music_volume_spinbox->setSuffix("%"); - ui_audio_layout->setWidget(2, QFormLayout::FieldRole, + ui_audio_layout->setWidget(row, QFormLayout::FieldRole, ui_music_volume_spinbox); + row += 1; ui_sfx_volume_lbl = new QLabel(ui_audio_widget); ui_sfx_volume_lbl->setText(tr("SFX:")); ui_sfx_volume_lbl->setToolTip( tr("Sets the SFX's default volume. " "Interjections and actual sound effects count as 'SFX'.")); - - ui_audio_layout->setWidget(3, QFormLayout::LabelRole, ui_sfx_volume_lbl); + ui_audio_layout->setWidget(row, QFormLayout::LabelRole, ui_sfx_volume_lbl); ui_sfx_volume_spinbox = new QSpinBox(ui_audio_widget); ui_sfx_volume_spinbox->setValue(p_ao_app->get_default_sfx()); ui_sfx_volume_spinbox->setMaximum(100); ui_sfx_volume_spinbox->setSuffix("%"); - ui_audio_layout->setWidget(3, QFormLayout::FieldRole, ui_sfx_volume_spinbox); + ui_audio_layout->setWidget(row, QFormLayout::FieldRole, + ui_sfx_volume_spinbox); + row += 1; ui_blips_volume_lbl = new QLabel(ui_audio_widget); ui_blips_volume_lbl->setText(tr("Blips:")); ui_blips_volume_lbl->setToolTip( tr("Sets the volume of the blips, the talking sound effects.")); - ui_audio_layout->setWidget(4, QFormLayout::LabelRole, ui_blips_volume_lbl); + ui_audio_layout->setWidget(row, QFormLayout::LabelRole, ui_blips_volume_lbl); ui_blips_volume_spinbox = new QSpinBox(ui_audio_widget); ui_blips_volume_spinbox->setValue(p_ao_app->get_default_blip()); ui_blips_volume_spinbox->setMaximum(100); ui_blips_volume_spinbox->setSuffix("%"); - ui_audio_layout->setWidget(4, QFormLayout::FieldRole, + ui_audio_layout->setWidget(row, QFormLayout::FieldRole, ui_blips_volume_spinbox); + row += 1; ui_volume_blip_divider = new QFrame(ui_audio_widget); ui_volume_blip_divider->setFrameShape(QFrame::HLine); ui_volume_blip_divider->setFrameShadow(QFrame::Sunken); - ui_audio_layout->setWidget(5, QFormLayout::FieldRole, ui_volume_blip_divider); + ui_audio_layout->setWidget(row, QFormLayout::FieldRole, + ui_volume_blip_divider); + row += 1; ui_bliprate_lbl = new QLabel(ui_audio_widget); ui_bliprate_lbl->setText(tr("Blip rate:")); ui_bliprate_lbl->setToolTip( tr("Sets the delay between playing the blip sounds.")); - ui_audio_layout->setWidget(6, QFormLayout::LabelRole, ui_bliprate_lbl); + ui_audio_layout->setWidget(row, QFormLayout::LabelRole, ui_bliprate_lbl); ui_bliprate_spinbox = new QSpinBox(ui_audio_widget); ui_bliprate_spinbox->setValue(p_ao_app->read_blip_rate()); ui_bliprate_spinbox->setMinimum(1); + ui_bliprate_spinbox->setToolTip( + tr("Play a blip sound \"once per every X symbols\", where " + "X is the blip rate.")); - ui_audio_layout->setWidget(6, QFormLayout::FieldRole, ui_bliprate_spinbox); + ui_audio_layout->setWidget(row, QFormLayout::FieldRole, ui_bliprate_spinbox); + row += 1; ui_blank_blips_lbl = new QLabel(ui_audio_widget); ui_blank_blips_lbl->setText(tr("Blank blips:")); ui_blank_blips_lbl->setToolTip( tr("If true, the game will play a blip sound even " "when a space is 'being said'.")); - ui_audio_layout->setWidget(7, QFormLayout::LabelRole, ui_blank_blips_lbl); + ui_audio_layout->setWidget(row, QFormLayout::LabelRole, ui_blank_blips_lbl); ui_blank_blips_cb = new QCheckBox(ui_audio_widget); ui_blank_blips_cb->setChecked(p_ao_app->get_blank_blip()); - ui_audio_layout->setWidget(7, QFormLayout::FieldRole, ui_blank_blips_cb); -#endif + ui_audio_layout->setWidget(row, QFormLayout::FieldRole, ui_blank_blips_cb); + + row += 1; + ui_loopsfx_lbl = new QLabel(ui_audio_widget); + ui_loopsfx_lbl->setText(tr("Enable Looping SFX:")); + ui_loopsfx_lbl->setToolTip(tr("If true, the game will allow looping sound " + "effects to play on preanimations.")); + + ui_audio_layout->setWidget(row, QFormLayout::LabelRole, ui_loopsfx_lbl); + + ui_loopsfx_cb = new QCheckBox(ui_audio_widget); + ui_loopsfx_cb->setChecked(p_ao_app->get_looping_sfx()); + + ui_audio_layout->setWidget(row, QFormLayout::FieldRole, ui_loopsfx_cb); + + row += 1; + ui_objectmusic_lbl = new QLabel(ui_audio_widget); + ui_objectmusic_lbl->setText(tr("Kill Music On Objection:")); + ui_objectmusic_lbl->setToolTip( + tr("If true, AO2 will stop the music for you when you or someone else " + "does 'Objection!'.")); + + ui_audio_layout->setWidget(row, QFormLayout::LabelRole, ui_objectmusic_lbl); + + ui_objectmusic_cb = new QCheckBox(ui_audio_widget); + ui_objectmusic_cb->setChecked(p_ao_app->objection_stop_music()); + + ui_audio_layout->setWidget(row, QFormLayout::FieldRole, ui_objectmusic_cb); // The casing tab! ui_casing_tab = new QWidget(); @@ -357,6 +540,7 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) ui_casing_layout->setFormAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignTop); ui_casing_layout->setContentsMargins(0, 0, 0, 0); + row = 0; // -- SERVER SUPPORTS CASING @@ -368,126 +552,136 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) tr("This server does not support case alerts.")); ui_casing_supported_lbl->setToolTip(tr("Pretty self-explanatory.")); - ui_casing_layout->setWidget(0, QFormLayout::FieldRole, + ui_casing_layout->setWidget(row, QFormLayout::FieldRole, ui_casing_supported_lbl); // -- CASE ANNOUNCEMENTS + row += 1; ui_casing_enabled_lbl = new QLabel(ui_casing_widget); ui_casing_enabled_lbl->setText(tr("Casing:")); ui_casing_enabled_lbl->setToolTip( tr("If checked, you will get alerts about case " "announcements.")); - ui_casing_layout->setWidget(1, QFormLayout::LabelRole, ui_casing_enabled_lbl); + ui_casing_layout->setWidget(row, QFormLayout::LabelRole, + ui_casing_enabled_lbl); ui_casing_enabled_cb = new QCheckBox(ui_casing_widget); ui_casing_enabled_cb->setChecked(ao_app->get_casing_enabled()); - ui_casing_layout->setWidget(1, QFormLayout::FieldRole, ui_casing_enabled_cb); + ui_casing_layout->setWidget(row, QFormLayout::FieldRole, + ui_casing_enabled_cb); // -- DEFENSE ANNOUNCEMENTS + row += 1; ui_casing_def_lbl = new QLabel(ui_casing_widget); ui_casing_def_lbl->setText(tr("Defense:")); ui_casing_def_lbl->setToolTip(tr("If checked, you will get alerts about case " "announcements if a defense spot is open.")); - ui_casing_layout->setWidget(2, QFormLayout::LabelRole, ui_casing_def_lbl); + ui_casing_layout->setWidget(row, QFormLayout::LabelRole, ui_casing_def_lbl); ui_casing_def_cb = new QCheckBox(ui_casing_widget); ui_casing_def_cb->setChecked(ao_app->get_casing_defence_enabled()); - ui_casing_layout->setWidget(2, QFormLayout::FieldRole, ui_casing_def_cb); + ui_casing_layout->setWidget(row, QFormLayout::FieldRole, ui_casing_def_cb); // -- PROSECUTOR ANNOUNCEMENTS + row += 1; ui_casing_pro_lbl = new QLabel(ui_casing_widget); ui_casing_pro_lbl->setText(tr("Prosecution:")); ui_casing_pro_lbl->setToolTip( tr("If checked, you will get alerts about case " "announcements if a prosecutor spot is open.")); - ui_casing_layout->setWidget(3, QFormLayout::LabelRole, ui_casing_pro_lbl); + ui_casing_layout->setWidget(row, QFormLayout::LabelRole, ui_casing_pro_lbl); ui_casing_pro_cb = new QCheckBox(ui_casing_widget); ui_casing_pro_cb->setChecked(ao_app->get_casing_prosecution_enabled()); - ui_casing_layout->setWidget(3, QFormLayout::FieldRole, ui_casing_pro_cb); + ui_casing_layout->setWidget(row, QFormLayout::FieldRole, ui_casing_pro_cb); // -- JUDGE ANNOUNCEMENTS + row += 1; ui_casing_jud_lbl = new QLabel(ui_casing_widget); ui_casing_jud_lbl->setText(tr("Judge:")); ui_casing_jud_lbl->setToolTip(tr("If checked, you will get alerts about case " "announcements if the judge spot is open.")); - ui_casing_layout->setWidget(4, QFormLayout::LabelRole, ui_casing_jud_lbl); + ui_casing_layout->setWidget(row, QFormLayout::LabelRole, ui_casing_jud_lbl); ui_casing_jud_cb = new QCheckBox(ui_casing_widget); ui_casing_jud_cb->setChecked(ao_app->get_casing_judge_enabled()); - ui_casing_layout->setWidget(4, QFormLayout::FieldRole, ui_casing_jud_cb); + ui_casing_layout->setWidget(row, QFormLayout::FieldRole, ui_casing_jud_cb); // -- JUROR ANNOUNCEMENTS + row += 1; ui_casing_jur_lbl = new QLabel(ui_casing_widget); ui_casing_jur_lbl->setText(tr("Juror:")); ui_casing_jur_lbl->setToolTip(tr("If checked, you will get alerts about case " "announcements if a juror spot is open.")); - ui_casing_layout->setWidget(5, QFormLayout::LabelRole, ui_casing_jur_lbl); + ui_casing_layout->setWidget(row, QFormLayout::LabelRole, ui_casing_jur_lbl); ui_casing_jur_cb = new QCheckBox(ui_casing_widget); ui_casing_jur_cb->setChecked(ao_app->get_casing_juror_enabled()); - ui_casing_layout->setWidget(5, QFormLayout::FieldRole, ui_casing_jur_cb); + ui_casing_layout->setWidget(row, QFormLayout::FieldRole, ui_casing_jur_cb); // -- STENO ANNOUNCEMENTS + row += 1; ui_casing_steno_lbl = new QLabel(ui_casing_widget); ui_casing_steno_lbl->setText(tr("Stenographer:")); ui_casing_steno_lbl->setToolTip( tr("If checked, you will get alerts about case " "announcements if a stenographer spot is open.")); - ui_casing_layout->setWidget(6, QFormLayout::LabelRole, ui_casing_steno_lbl); + ui_casing_layout->setWidget(row, QFormLayout::LabelRole, ui_casing_steno_lbl); ui_casing_steno_cb = new QCheckBox(ui_casing_widget); ui_casing_steno_cb->setChecked(ao_app->get_casing_steno_enabled()); - ui_casing_layout->setWidget(6, QFormLayout::FieldRole, ui_casing_steno_cb); + ui_casing_layout->setWidget(row, QFormLayout::FieldRole, ui_casing_steno_cb); // -- CM ANNOUNCEMENTS + row += 1; ui_casing_cm_lbl = new QLabel(ui_casing_widget); ui_casing_cm_lbl->setText(tr("CM:")); ui_casing_cm_lbl->setToolTip( tr("If checked, you will appear amongst the potential " "CMs on the server.")); - ui_casing_layout->setWidget(7, QFormLayout::LabelRole, ui_casing_cm_lbl); + ui_casing_layout->setWidget(row, QFormLayout::LabelRole, ui_casing_cm_lbl); ui_casing_cm_cb = new QCheckBox(ui_casing_widget); ui_casing_cm_cb->setChecked(ao_app->get_casing_cm_enabled()); - ui_casing_layout->setWidget(7, QFormLayout::FieldRole, ui_casing_cm_cb); + ui_casing_layout->setWidget(row, QFormLayout::FieldRole, ui_casing_cm_cb); // -- CM CASES ANNOUNCEMENTS + row += 1; ui_casing_cm_cases_lbl = new QLabel(ui_casing_widget); ui_casing_cm_cases_lbl->setText(tr("Hosting cases:")); ui_casing_cm_cases_lbl->setToolTip( tr("If you're a CM, enter what cases you are " "willing to host.")); - ui_casing_layout->setWidget(8, QFormLayout::LabelRole, + ui_casing_layout->setWidget(row, QFormLayout::LabelRole, ui_casing_cm_cases_lbl); ui_casing_cm_cases_textbox = new QLineEdit(ui_casing_widget); ui_casing_cm_cases_textbox->setText(ao_app->get_casing_can_host_cases()); - ui_casing_layout->setWidget(8, QFormLayout::FieldRole, + ui_casing_layout->setWidget(row, QFormLayout::FieldRole, ui_casing_cm_cases_textbox); // When we're done, we should continue the updates! @@ -506,14 +700,19 @@ void AOOptionsDialog::save_pressed() configini->setValue("show_custom_shownames", ui_showname_cb->isChecked()); configini->setValue("master", ui_ms_textbox->text()); configini->setValue("discord", ui_discord_cb->isChecked()); + configini->setValue("language", ui_language_combobox->currentText().left(2)); + configini->setValue("shake", ui_shake_cb->isChecked()); + configini->setValue("effects", ui_effects_cb->isChecked()); + configini->setValue("framenetwork", ui_framenetwork_cb->isChecked()); + configini->setValue("colorlog", ui_colorlog_cb->isChecked()); + configini->setValue("stickysounds", ui_stickysounds_cb->isChecked()); + configini->setValue("stickyeffects", ui_stickyeffects_cb->isChecked()); + configini->setValue("stickypres", ui_stickypres_cb->isChecked()); QFile *callwordsini = new QFile(ao_app->get_base_path() + "callwords.ini"); - if (!callwordsini->open(QIODevice::WriteOnly | QIODevice::Truncate | - QIODevice::Text)) { - // Nevermind! - } - else { + if (callwordsini->open(QIODevice::WriteOnly | QIODevice::Truncate | + QIODevice::Text)) { QTextStream out(callwordsini); out << ui_callwords_textbox->toPlainText(); callwordsini->close(); @@ -526,6 +725,8 @@ void AOOptionsDialog::save_pressed() configini->setValue("default_blip", ui_blips_volume_spinbox->value()); configini->setValue("blip_rate", ui_bliprate_spinbox->value()); configini->setValue("blank_blip", ui_blank_blips_cb->isChecked()); + configini->setValue("looping_sfx", ui_loopsfx_cb->isChecked()); + configini->setValue("objection_stop_music", ui_objectmusic_cb->isChecked()); configini->setValue("casing_enabled", ui_casing_enabled_cb->isChecked()); configini->setValue("casing_defence_enabled", ui_casing_def_cb->isChecked()); diff --git a/src/aoscene.cpp b/src/aoscene.cpp index aad6c10..094d7a5 100644 --- a/src/aoscene.cpp +++ b/src/aoscene.cpp @@ -7,68 +7,122 @@ AOScene::AOScene(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) m_parent = parent; ao_app = p_ao_app; m_movie = new QMovie(this); + last_image = ""; } void AOScene::set_image(QString p_image) { - QString background_path = ao_app->get_background_path(p_image + ".png"); - QString animated_background_path = - ao_app->get_background_path(p_image + ".gif"); - QString default_path = ao_app->get_default_background_path(p_image + ".png"); + 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); - QPixmap background(background_path); - QPixmap default_bg(default_path); + m_movie->stop(); + last_image = ""; + return; + } - int w = this->width(); - int h = this->height(); + if (file_exists(background_path) && background_path == last_image) + return; this->clear(); this->setMovie(nullptr); m_movie->stop(); - m_movie->setFileName(animated_background_path); - m_movie->setScaledSize(QSize(w, h)); + m_movie->setFileName(background_path); - if (m_movie->isValid()) { + if (m_movie->isValid() && m_movie->frameCount() > 1) { + m_movie->jumpToNextFrame(); + float scale_factor = static_cast(f_h) / + static_cast(m_movie->frameRect().height()); + // preserve aspect ratio + int n_w = static_cast(m_movie->frameRect().width() * scale_factor); + int n_h = static_cast(m_movie->frameRect().height() * scale_factor); + + m_movie->setScaledSize(QSize(n_w, n_h)); + this->resize(m_movie->scaledSize()); this->setMovie(m_movie); + QLabel::move(x + (f_w - n_w) / 2, y + (f_h - n_h) / 2); // Center m_movie->start(); } - else if (file_exists(background_path)) { - this->setPixmap(background.scaled(w, h)); - } else { - this->setPixmap(default_bg.scaled(w, h)); + 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. - - 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 = static_cast(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)); + 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); } diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp index 543da5c..6db6f37 100644 --- a/src/aosfxplayer.cpp +++ b/src/aosfxplayer.cpp @@ -8,18 +8,44 @@ AOSfxPlayer::AOSfxPlayer(QWidget *parent, AOApplication *p_ao_app) ao_app = p_ao_app; } -void AOSfxPlayer::play(QString p_sfx, QString p_char, QString shout) +void AOSfxPlayer::clear() { - BASS_ChannelStop(m_stream); + for (int n_stream = 0; n_stream < m_channelmax; ++n_stream) { + BASS_ChannelStop(m_stream_list[n_stream]); + } + set_volume_internal(m_volume); +} + +void AOSfxPlayer::loop_clear() +{ + for (int n_stream = 0; n_stream < m_channelmax; ++n_stream) { + if ((BASS_ChannelFlags(m_stream_list[n_stream], 0, 0) & BASS_SAMPLE_LOOP)) + BASS_ChannelStop(m_stream_list[n_stream]); + } + set_volume_internal(m_volume); +} + +void AOSfxPlayer::play(QString p_sfx, QString p_char, QString shout, + int channel) +{ + if (channel == -1) { + if (BASS_ChannelIsActive(m_stream_list[channel]) == BASS_ACTIVE_PLAYING) + m_channel = (m_channel + 1) % m_channelmax; + channel = m_channel; + } + + BASS_ChannelStop(m_stream_list[channel]); QString misc_path = ""; QString char_path = ""; - QString sound_path = ao_app->get_sounds_path(p_sfx); + QString sound_path = ao_app->get_sfx_suffix(ao_app->get_sounds_path(p_sfx)); if (shout != "") - misc_path = ao_app->get_base_path() + "misc/" + shout + "/" + p_sfx; + misc_path = ao_app->get_sfx_suffix(ao_app->get_base_path() + "misc/" + + shout + "/" + p_sfx); if (p_char != "") - char_path = ao_app->get_character_path(p_char, p_sfx); + char_path = + ao_app->get_sfx_suffix(ao_app->get_character_path(p_char, p_sfx)); QString f_path; @@ -30,24 +56,60 @@ void AOSfxPlayer::play(QString p_sfx, QString p_char, QString shout) else f_path = sound_path; - m_stream = BASS_StreamCreateFile(FALSE, f_path.utf16(), 0, 0, - BASS_STREAM_AUTOFREE | BASS_UNICODE | - BASS_ASYNCFILE); + if (f_path.endsWith(".opus")) + m_stream_list[channel] = BASS_OPUS_StreamCreateFile( + FALSE, f_path.utf16(), 0, 0, + BASS_STREAM_AUTOFREE | BASS_UNICODE | BASS_ASYNCFILE); + else + m_stream_list[channel] = BASS_StreamCreateFile( + FALSE, f_path.utf16(), 0, 0, + BASS_STREAM_AUTOFREE | BASS_UNICODE | BASS_ASYNCFILE); set_volume(m_volume); if (ao_app->get_audio_output_device() != "default") - BASS_ChannelSetDevice(m_stream, BASS_GetDevice()); - BASS_ChannelPlay(m_stream, false); + BASS_ChannelSetDevice(m_stream_list[m_channel], BASS_GetDevice()); + BASS_ChannelPlay(m_stream_list[m_channel], false); } -void AOSfxPlayer::stop() { BASS_ChannelStop(m_stream); } +void AOSfxPlayer::stop(int channel) +{ + if (channel == -1) { + channel = m_channel; + } + BASS_ChannelStop(m_stream_list[channel]); +} + +void AOSfxPlayer::set_volume(qreal p_value) +{ + m_volume = p_value / 100; + set_volume_internal(m_volume); +} void AOSfxPlayer::set_volume(int p_value) { - m_volume = p_value; - float volume = p_value / 100.0f; - BASS_ChannelSetAttribute(m_stream, BASS_ATTRIB_VOL, volume); + float volume = static_cast(p_value); + for (int n_stream = 0; n_stream < m_channelmax; ++n_stream) { + BASS_ChannelSetAttribute(m_stream_list[n_stream], BASS_ATTRIB_VOL, volume); + } +} + +void AOSfxPlayer::set_looping(bool toggle, int channel) +{ + if (channel == -1) { + channel = m_channel; + } + m_looping = toggle; + if (BASS_ChannelFlags(m_stream_list[channel], 0, 0) & BASS_SAMPLE_LOOP) { + if (m_looping == false) + BASS_ChannelFlags(m_stream_list[channel], 0, + BASS_SAMPLE_LOOP); // remove the LOOP flag + } + else { + if (m_looping == true) + BASS_ChannelFlags(m_stream_list[channel], BASS_SAMPLE_LOOP, + BASS_SAMPLE_LOOP); // set the LOOP flag + } } #elif defined(QTAUDIO) // Using Qt's QSoundEffect class AOSfxPlayer::AOSfxPlayer(QWidget *parent, AOApplication *p_ao_app) @@ -82,7 +144,7 @@ void AOSfxPlayer::play(QString p_sfx, QString p_char, QString shout) { m_sfx.setSource(QUrl::fromLocalFile(f_path)); - set_volume(m_volume); + set_volume_internal(m_volume); m_sfx.play(); } @@ -90,7 +152,13 @@ void AOSfxPlayer::play(QString p_sfx, QString p_char, QString shout) void AOSfxPlayer::stop() { m_sfx.stop(); } -void AOSfxPlayer::set_volume(int p_value) +void AOSfxPlayer::set_volume(qreal p_value) +{ + m_volume = p_value / 100; + set_volume_internal(m_volume); +} + +void AOSfxPlayer::set_volume_internal(qreal p_value) { m_volume = p_value; m_sfx.setVolume(m_volume); @@ -106,5 +174,7 @@ void AOSfxPlayer::play(QString p_sfx, QString p_char, QString shout) {} void AOSfxPlayer::stop() {} -void AOSfxPlayer::set_volume(int p_value) {} +void AOSfxPlayer::set_volume(qreal p_value) {} + +void AOSfxPlayer::set_volume_internal(qreal p_value) {} #endif diff --git a/src/aotextarea.cpp b/src/aotextarea.cpp index ebc75e2..52e32f5 100644 --- a/src/aotextarea.cpp +++ b/src/aotextarea.cpp @@ -2,6 +2,14 @@ AOTextArea::AOTextArea(QWidget *p_parent) : QTextBrowser(p_parent) {} +void AOTextArea::append_linked(QString p_message) +{ + QString result = p_message.toHtmlEscaped() + .replace("\n", "
") + .replace(url_parser_regex, "\\1"); + this->insertHtml(result); +} + void AOTextArea::append_chatmessage(QString p_name, QString p_message, QString p_colour) { @@ -20,7 +28,7 @@ void AOTextArea::append_chatmessage(QString p_name, QString p_message, p_message += " "; QString result = p_message.toHtmlEscaped() .replace("\n", "
") - .replace(omnis_dank_url_regex, "\\1"); + .replace(url_parser_regex, "\\1"); this->insertHtml(result); @@ -40,7 +48,7 @@ void AOTextArea::append_error(QString p_message) p_message += " "; QString result = p_message.replace("\n", "
") - .replace(omnis_dank_url_regex, "\\1"); + .replace(url_parser_regex, "\\1"); this->insertHtml("" + result + ""); diff --git a/src/charselect.cpp b/src/charselect.cpp index c207d57..de9748b 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -70,15 +70,15 @@ void Courtroom::set_char_select() 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!"; + qDebug() << "W: did not find char_select 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"); + ui_char_select_background->set_image("charselect_background"); filter_character_list(); @@ -133,14 +133,17 @@ void Courtroom::char_clicked(int n_char) return; } - if (n_char == m_cid) { - enter_courtroom(m_cid); - } - else { + if (n_char != m_cid) { + ao_app->send_server_packet( + new AOPacket("PW#" + ui_char_password->text() + "#%")); ao_app->send_server_packet( new AOPacket("CC#" + QString::number(ao_app->s_pv) + "#" + QString::number(n_char) + "#" + get_hdid() + "#%")); } + else + update_character(n_char); + + enter_courtroom(); ui_ic_chat_name->setPlaceholderText(char_list.at(n_char).name); } @@ -225,8 +228,9 @@ void Courtroom::character_loading_finished() 100); ao_app->w_lobby->set_loading_value(loading_value); ao_app->w_lobby->set_loading_text( - "Generating chars:\n" + QString::number(ao_app->generated_chars) + - "/" + QString::number(ao_app->char_list_size)); + tr("Generating chars:\n%1/%2") + .arg(QString::number(ao_app->generated_chars)) + .arg(QString::number(ao_app->char_list_size))); } } diff --git a/src/chatlogpiece.cpp b/src/chatlogpiece.cpp index be3be4a..34a2fea 100644 --- a/src/chatlogpiece.cpp +++ b/src/chatlogpiece.cpp @@ -5,7 +5,7 @@ chatlogpiece::chatlogpiece() name = "UNKNOWN"; showname = "UNKNOWN"; message = "UNKNOWN"; - is_song = false; + p_is_song = false; datetime = QDateTime::currentDateTime().toUTC(); } @@ -15,7 +15,7 @@ chatlogpiece::chatlogpiece(QString p_name, QString p_showname, name = p_name; showname = p_showname; message = p_message; - is_song = p_song; + p_is_song = p_song; datetime = QDateTime::currentDateTime().toUTC(); } @@ -25,7 +25,7 @@ chatlogpiece::chatlogpiece(QString p_name, QString p_showname, name = p_name; showname = p_showname; message = p_message; - is_song = p_song; + p_is_song = p_song; datetime = p_datetime.toUTC(); } @@ -37,7 +37,7 @@ QString chatlogpiece::get_message() { return message; } QDateTime chatlogpiece::get_datetime() { return datetime; } -bool chatlogpiece::get_is_song() { return is_song; } +bool chatlogpiece::is_song() { return p_is_song; } QString chatlogpiece::get_datetime_as_string() { return datetime.toString(); } @@ -46,13 +46,15 @@ QString chatlogpiece::get_full() QString full = "["; full.append(get_datetime_as_string()); - full.append(" UTC] "); + full.append("] "); full.append(get_showname()); full.append(" ("); full.append(get_name()); full.append(")"); - if (is_song) + if (p_is_song) full.append(" has played a song: "); + else + full.append(": "); full.append(get_message()); return full; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index 25b4826..bcd3b47 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -1,4 +1,4 @@ -#include "courtroom.h" +#include "courtroom.h" Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() { @@ -25,8 +25,23 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() } } } +#elif defined QTAUDIO + + if (ao_app->get_audio_output_device() != "default") { + foreach (const QAudioDeviceInfo &deviceInfo, + QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) { + if (ao_app->get_audio_output_device() == deviceInfo.deviceName()) { + ao_app->QtAudioDevice = deviceInfo; + qDebug() << deviceInfo.deviceName() + << "was set as the default audio output device."; + break; + } + } + } #endif + qsrand(static_cast(QDateTime::currentMSecsSinceEpoch() / 1000)); + keepalive_timer = new QTimer(this); keepalive_timer->start(60000); @@ -38,15 +53,6 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() 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); - music_player = new AOMusicPlayer(this, ao_app); music_player->set_volume(0); @@ -74,20 +80,29 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_vp_desk = new AOScene(ui_viewport, ao_app); ui_vp_legacy_desk = new AOScene(ui_viewport, ao_app); - ui_vp_evidence_display = new AOEvidenceDisplay(this, ao_app); + ui_vp_evidence_display = new AOEvidenceDisplay(ui_viewport, 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_showname->setAlignment(Qt::AlignHCenter); + ui_vp_chat_arrow = new AOMovie(ui_vp_chatbox, ao_app); + ui_vp_chat_arrow->set_play_once(false); + + ui_vp_message = new QTextEdit(this); 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_testimony = new AOImage(this, ao_app); - ui_vp_realization = new AOImage(this, ao_app); + ui_vp_testimony = new AOMovie(this, ao_app); + ui_vp_testimony->set_play_once(false); + ui_vp_testimony->setAttribute(Qt::WA_TransparentForMouseEvents); + ui_vp_effect = new AOMovie(this, ao_app); + ui_vp_effect->setAttribute(Qt::WA_TransparentForMouseEvents); ui_vp_wtce = new AOMovie(this, ao_app); + ui_vp_wtce->setAttribute(Qt::WA_TransparentForMouseEvents); ui_vp_objection = new AOMovie(this, ao_app); + ui_vp_objection->setAttribute(Qt::WA_TransparentForMouseEvents); ui_ic_chatlog = new QTextEdit(this); ui_ic_chatlog->setReadOnly(true); @@ -104,17 +119,42 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_server_chatlog->setReadOnly(true); ui_server_chatlog->setOpenExternalLinks(true); - ui_area_list = new QListWidget(this); + ui_area_list = new QTreeWidget(this); + ui_area_list->setColumnCount(2); + ui_area_list->hideColumn(0); + ui_area_list->setHeaderHidden(true); + ui_area_list->header()->setStretchLastSection(false); + ui_area_list->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui_area_list->hide(); - ui_music_list = new QListWidget(this); + + ui_music_list = new QTreeWidget(this); + ui_music_list->setColumnCount(2); + ui_music_list->hideColumn(1); + ui_music_list->setHeaderHidden(true); + ui_music_list->header()->setStretchLastSection(false); + ui_music_list->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + ui_music_list->setContextMenuPolicy(Qt::CustomContextMenu); + + ui_music_display = new AOMovie(this, ao_app); + ui_music_display->set_play_once(false); + ui_music_display->setAttribute(Qt::WA_TransparentForMouseEvents); + + ui_music_name = new ScrollText(ui_music_display); + ui_music_name->setText(tr("None")); + ui_music_name->setAttribute(Qt::WA_TransparentForMouseEvents); ui_ic_chat_name = new QLineEdit(this); ui_ic_chat_name->setFrame(false); ui_ic_chat_name->setPlaceholderText(tr("Showname")); - ui_ic_chat_message = new QLineEdit(this); + ui_ic_chat_message = new AOLineEdit(this); ui_ic_chat_message->setFrame(false); ui_ic_chat_message->setPlaceholderText(tr("Message")); + ui_ic_chat_message->preserve_selection(true); + // ui_ic_chat_message->setValidator(new QRegExpValidator(QRegExp("^\\S+(?: + // \\S+)*$"), ui_ic_chat_message)); + // todo: filter out \n from showing up as that commonly breaks the chatlog and + // can be spammed to hell ui_muted = new AOImage(ui_ic_chat_message, ao_app); ui_muted->hide(); @@ -134,21 +174,20 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_music_search->setFrame(false); ui_music_search->setPlaceholderText(tr("Search")); - construct_emotes(); + initialize_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_pos_dropdown->addItem("jur"); - ui_pos_dropdown->addItem("sea"); + + ui_iniswap_dropdown = new QComboBox(this); + ui_iniswap_dropdown->setContextMenuPolicy(Qt::CustomContextMenu); + ui_iniswap_remove = new AOButton(this, ao_app); + + ui_sfx_dropdown = new QComboBox(this); + ui_sfx_dropdown->setContextMenuPolicy(Qt::CustomContextMenu); + ui_sfx_remove = new AOButton(this, ao_app); + + ui_effects_dropdown = new QComboBox(this); + ui_effects_dropdown->setContextMenuPolicy(Qt::CustomContextMenu); ui_defense_bar = new AOImage(this, ao_app); ui_prosecution_bar = new AOImage(this, ao_app); @@ -157,8 +196,6 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_sfx_label = new QLabel(this); ui_blip_label = new QLabel(this); - ui_log_limit_label = new QLabel(this); - ui_hold_it = new AOButton(this, ao_app); ui_objection = new AOButton(this, ao_app); ui_take_that = new AOButton(this, ao_app); @@ -187,6 +224,10 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_guard->setText("Guard"); ui_guard->hide(); + ui_additive = new QCheckBox(this); + ui_additive->setText(tr("Additive")); + ui_additive->hide(); + ui_casing = new QCheckBox(this); ui_casing->setChecked(ao_app->get_casing_enabled()); ui_casing->setText(tr("Casing")); @@ -202,6 +243,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_custom_objection = new AOButton(this, ao_app); ui_realization = new AOButton(this, ao_app); + ui_screenshake = new AOButton(this, ao_app); ui_mute = new AOButton(this, ao_app); ui_defense_plus = new AOButton(this, ao_app); @@ -211,15 +253,6 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() 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("Rainbow"); - ui_text_color->addItem("Pink"); - ui_text_color->addItem("Cyan"); ui_music_slider = new QSlider(Qt::Horizontal, this); ui_music_slider->setRange(0, 100); @@ -233,21 +266,22 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() ui_blip_slider->setRange(0, 100); ui_blip_slider->setValue(ao_app->get_default_blip()); - ui_log_limit_spinbox = new QSpinBox(this); - ui_log_limit_spinbox->setRange(0, 10000); - ui_log_limit_spinbox->setValue(ao_app->get_max_log_size()); - ui_mute_list = new QListWidget(this); ui_pair_list = new QListWidget(this); ui_pair_offset_spinbox = new QSpinBox(this); ui_pair_offset_spinbox->setRange(-100, 100); - ui_pair_offset_spinbox->setSuffix("% offset"); + ui_pair_offset_spinbox->setSuffix(tr("% offset")); + + ui_pair_order_dropdown = new QComboBox(this); + ui_pair_order_dropdown->addItem("To front"); + ui_pair_order_dropdown->addItem("To behind"); + ui_pair_button = new AOButton(this, ao_app); ui_evidence_button = new AOButton(this, ao_app); - construct_evidence(); + initialize_evidence(); construct_char_select(); @@ -255,6 +289,10 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() connect(ui_vp_objection, SIGNAL(done()), this, SLOT(objection_done())); connect(ui_vp_player_char, SIGNAL(done()), this, SLOT(preanim_done())); + connect(ui_vp_player_char, SIGNAL(shake()), this, SLOT(do_screenshake())); + connect(ui_vp_player_char, SIGNAL(flash()), this, SLOT(do_flash())); + connect(ui_vp_player_char, SIGNAL(play_sfx(QString)), this, + SLOT(play_char_sfx(QString))); connect(text_delay_timer, SIGNAL(timeout()), this, SLOT(start_chat_ticking())); @@ -262,23 +300,28 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() 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(currentIndexChanged(int)), this, SLOT(on_pos_dropdown_changed(int))); + connect(ui_iniswap_dropdown, SIGNAL(currentIndexChanged(int)), this, + SLOT(on_iniswap_dropdown_changed(int))); + connect(ui_iniswap_dropdown, SIGNAL(customContextMenuRequested(QPoint)), this, + SLOT(on_iniswap_context_menu_requested(QPoint))); + connect(ui_iniswap_remove, SIGNAL(clicked()), this, + SLOT(on_iniswap_remove_clicked())); + + connect(ui_sfx_dropdown, SIGNAL(currentIndexChanged(int)), this, + SLOT(on_sfx_dropdown_changed(int))); + connect(ui_sfx_dropdown, SIGNAL(customContextMenuRequested(QPoint)), this, + SLOT(on_sfx_context_menu_requested(QPoint))); + connect(ui_sfx_remove, SIGNAL(clicked()), this, + SLOT(on_sfx_remove_clicked())); + + connect(ui_effects_dropdown, SIGNAL(currentIndexChanged(int)), this, + SLOT(on_effects_dropdown_changed(int))); + connect(ui_effects_dropdown, SIGNAL(customContextMenuRequested(QPoint)), this, + SLOT(on_effects_context_menu_requested(QPoint))); + connect(ui_mute_list, SIGNAL(clicked(QModelIndex)), this, SLOT(on_mute_list_clicked(QModelIndex))); @@ -288,10 +331,13 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() connect(ui_ooc_chat_message, SIGNAL(returnPressed()), this, SLOT(on_ooc_return_pressed())); - 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))); + connect(ui_music_list, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), + this, SLOT(on_music_list_double_clicked(QTreeWidgetItem *, int))); + connect(ui_music_list, SIGNAL(customContextMenuRequested(QPoint)), this, + SLOT(on_music_list_context_menu_requested(QPoint))); + + connect(ui_area_list, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, + SLOT(on_area_list_double_clicked(QTreeWidgetItem *, int))); connect(ui_hold_it, SIGNAL(clicked()), this, SLOT(on_hold_it_clicked())); connect(ui_objection, SIGNAL(clicked()), this, SLOT(on_objection_clicked())); @@ -301,6 +347,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() connect(ui_realization, SIGNAL(clicked()), this, SLOT(on_realization_clicked())); + connect(ui_screenshake, SIGNAL(clicked()), this, + SLOT(on_screenshake_clicked())); connect(ui_mute, SIGNAL(clicked()), this, SLOT(on_mute_clicked())); @@ -323,9 +371,6 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() connect(ui_blip_slider, SIGNAL(valueChanged(int)), this, SLOT(on_blip_slider_moved(int))); - connect(ui_log_limit_spinbox, SIGNAL(valueChanged(int)), this, - SLOT(on_log_limit_changed(int))); - connect(ui_ooc_toggle, SIGNAL(clicked()), this, SLOT(on_ooc_toggle_clicked())); @@ -353,6 +398,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() connect(ui_pre, SIGNAL(clicked()), this, SLOT(on_pre_clicked())); connect(ui_flip, SIGNAL(clicked()), this, SLOT(on_flip_clicked())); + connect(ui_additive, SIGNAL(clicked()), this, SLOT(on_additive_clicked())); connect(ui_guard, SIGNAL(clicked()), this, SLOT(on_guard_clicked())); connect(ui_casing, SIGNAL(clicked()), this, SLOT(on_casing_clicked())); @@ -364,6 +410,8 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() SLOT(on_pair_list_clicked(QModelIndex))); connect(ui_pair_offset_spinbox, SIGNAL(valueChanged(int)), this, SLOT(on_pair_offset_changed(int))); + connect(ui_pair_order_dropdown, SIGNAL(currentIndexChanged(int)), this, + SLOT(on_pair_order_dropdown_changed(int))); connect(ui_evidence_button, SIGNAL(clicked()), this, SLOT(on_evidence_button_clicked())); @@ -435,7 +483,7 @@ void Courtroom::set_widgets() ui_background->move(0, 0); ui_background->resize(m_courtroom_width, m_courtroom_height); - ui_background->set_image("courtroombackground.png"); + ui_background->set_image("courtroombackground"); set_size_and_pos(ui_viewport, "viewport"); @@ -459,20 +507,20 @@ void Courtroom::set_widgets() if (ao_app->casing_alerts_enabled) { ui_announce_casing->show(); + ui_casing->show(); } else { ui_announce_casing->hide(); + ui_casing->hide(); } // We also show the non-server-dependent client additions. // Once again, if the theme can't display it, set_move_and_pos will catch // them. ui_settings->show(); - ui_log_limit_label->show(); - ui_log_limit_spinbox->show(); ui_vp_background->move(0, 0); - ui_vp_background->resize(ui_viewport->width(), ui_viewport->height()); + ui_vp_background->combo_resize(ui_viewport->width(), ui_viewport->height()); ui_vp_speedlines->move(0, 0); ui_vp_speedlines->combo_resize(ui_viewport->width(), ui_viewport->height()); @@ -486,7 +534,7 @@ void Courtroom::set_widgets() // the AO2 desk element ui_vp_desk->move(0, 0); - ui_vp_desk->resize(ui_viewport->width(), ui_viewport->height()); + ui_vp_desk->combo_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() @@ -497,24 +545,28 @@ void Courtroom::set_widgets() ui_vp_legacy_desk->hide(); ui_vp_evidence_display->move(0, 0); - ui_vp_evidence_display->resize(ui_viewport->width(), ui_viewport->height()); + ui_vp_evidence_display->combo_resize(ui_viewport->width(), + ui_viewport->height()); - set_size_and_pos(ui_vp_showname, "showname"); + ui_vp_chat_arrow->move(0, 0); + pos_size_type design_ini_result = + ao_app->get_element_dimensions("chat_arrow", "courtroom_design.ini"); - 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"); + if (design_ini_result.width < 0 || design_ini_result.height < 0) { + qDebug() << "W: could not find \"chat_arrow\" in courtroom_design.ini"; + ui_vp_chat_arrow->hide(); + } + else { + ui_vp_chat_arrow->move(design_ini_result.x, design_ini_result.y); + ui_vp_chat_arrow->combo_resize(design_ini_result.width, + design_ini_result.height); + } 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_testimony->combo_resize(ui_viewport->width(), ui_viewport->height()); - ui_vp_realization->move(ui_viewport->x(), ui_viewport->y()); - ui_vp_realization->resize(ui_viewport->width(), ui_viewport->height()); - ui_vp_realization->set_image("realizationflash.png"); - ui_vp_realization->hide(); + ui_vp_effect->move(ui_viewport->x(), ui_viewport->y()); + ui_vp_effect->combo_resize(ui_viewport->width(), ui_viewport->height()); ui_vp_wtce->move(ui_viewport->x(), ui_viewport->y()); ui_vp_wtce->combo_resize(ui_viewport->width(), ui_viewport->height()); @@ -523,25 +575,60 @@ void Courtroom::set_widgets() ui_vp_objection->combo_resize(ui_viewport->width(), ui_viewport->height()); set_size_and_pos(ui_ic_chatlog, "ic_chatlog"); + ui_ic_chatlog->setFrameShape(QFrame::NoFrame); set_size_and_pos(ui_ms_chatlog, "ms_chatlog"); + ui_ms_chatlog->setFrameShape(QFrame::NoFrame); set_size_and_pos(ui_server_chatlog, "server_chatlog"); + ui_server_chatlog->setFrameShape(QFrame::NoFrame); set_size_and_pos(ui_mute_list, "mute_list"); ui_mute_list->hide(); set_size_and_pos(ui_pair_list, "pair_list"); ui_pair_list->hide(); + ui_pair_list->setToolTip(tr("Select a character you wish to pair with.")); + set_size_and_pos(ui_pair_offset_spinbox, "pair_offset_spinbox"); ui_pair_offset_spinbox->hide(); + ui_pair_offset_spinbox->setToolTip( + tr("Change the percentage offset of your character's position from the " + "center of the screen.")); + + ui_pair_order_dropdown->hide(); + set_size_and_pos(ui_pair_order_dropdown, "pair_order_dropdown"); + ui_pair_offset_spinbox->setToolTip( + tr("Change the order of appearance for your character.")); + set_size_and_pos(ui_pair_button, "pair_button"); - ui_pair_button->set_image("pair_button.png"); + ui_pair_button->set_image("pair_button"); + ui_pair_button->setToolTip( + tr("Display the list of characters to pair with.")); set_size_and_pos(ui_area_list, "music_list"); - ui_area_list->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); + ui_area_list->header()->setMinimumSectionSize(ui_area_list->width()); set_size_and_pos(ui_music_list, "music_list"); + ui_music_list->header()->setMinimumSectionSize(ui_music_list->width()); + + set_size_and_pos(ui_music_name, "music_name"); + + ui_music_display->move(0, 0); + design_ini_result = + ao_app->get_element_dimensions("music_display", "courtroom_design.ini"); + + if (design_ini_result.width < 0 || design_ini_result.height < 0) { + qDebug() << "W: could not find \"music_name\" in courtroom_design.ini"; + ui_music_display->hide(); + } + else { + ui_music_display->move(design_ini_result.x, design_ini_result.y); + ui_music_display->combo_resize(design_ini_result.width, + design_ini_result.height); + } + + ui_music_display->play("music_display"); if (is_ao2_bg) { set_size_and_pos(ui_ic_chat_message, "ao2_ic_chat_message"); @@ -559,11 +646,23 @@ void Courtroom::set_widgets() ui_ic_chat_name->setStyleSheet( "QLineEdit{background-color: rgba(180, 180, 180, 255);}"); - ui_vp_chatbox->set_image("chatmed.png"); + ui_vp_chatbox->set_image("chatblank"); ui_vp_chatbox->hide(); + set_size_and_pos(ui_vp_showname, "showname"); + + set_size_and_pos(ui_vp_message, "message"); + ui_vp_message->hide(); + + // We detached the text as parent from the chatbox so it doesn't get affected + // by the screenshake. + ui_vp_message->move(ui_vp_message->x() + ui_vp_chatbox->x(), + ui_vp_message->y() + ui_vp_chatbox->y()); + ui_vp_message->setTextInteractionFlags(Qt::NoTextInteraction); + ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); - ui_muted->set_image("muted.png"); + ui_muted->set_image("muted"); + ui_muted->setToolTip(tr("Oops, you're muted!")); set_size_and_pos(ui_ooc_chat_message, "ooc_chat_message"); ui_ooc_chat_message->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); @@ -573,25 +672,71 @@ void Courtroom::set_widgets() // set_size_and_pos(ui_area_password, "area_password"); set_size_and_pos(ui_music_search, "music_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"); + ui_music_search->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); set_size_and_pos(ui_emote_dropdown, "emote_dropdown"); + ui_emote_dropdown->setToolTip( + tr("Set your character's emote to play on your next message.")); + set_size_and_pos(ui_pos_dropdown, "pos_dropdown"); + ui_pos_dropdown->setToolTip( + tr("Set your character's supplementary background.")); + + set_size_and_pos(ui_iniswap_dropdown, "iniswap_dropdown"); + ui_iniswap_dropdown->setEditable(true); + ui_iniswap_dropdown->setInsertPolicy(QComboBox::InsertAtBottom); + ui_iniswap_dropdown->setToolTip( + tr("Set an 'iniswap', or an alternative character folder to refer to " + "from your current character.\n" + "Edit by typing and pressing Enter, [X] to remove. This saves to your " + "base/characters//iniswaps.ini")); + + set_size_and_pos(ui_iniswap_remove, "iniswap_remove"); + ui_iniswap_remove->setText("X"); + ui_iniswap_remove->set_image("evidencex"); + ui_iniswap_remove->setToolTip( + tr("Remove the currently selected iniswap from the list and return to " + "the original character folder.")); + ui_iniswap_remove->hide(); + + set_size_and_pos(ui_sfx_dropdown, "sfx_dropdown"); + ui_sfx_dropdown->setEditable(true); + ui_sfx_dropdown->setInsertPolicy(QComboBox::InsertAtBottom); + ui_sfx_dropdown->setToolTip( + tr("Set a sound effect to play on your next 'Preanim'. Leaving it on " + "Default will use the emote-defined sound (if any).\n" + "Edit by typing and pressing Enter, [X] to remove. This saves to your " + "base/characters//soundlist.ini")); + + set_size_and_pos(ui_sfx_remove, "sfx_remove"); + ui_sfx_remove->setText("X"); + ui_sfx_remove->set_image("evidencex"); + ui_sfx_remove->setToolTip( + tr("Remove the currently selected iniswap from the list and return to " + "the original character folder.")); + ui_sfx_remove->hide(); + + set_size_and_pos(ui_effects_dropdown, "effects_dropdown"); + ui_effects_dropdown->setInsertPolicy(QComboBox::InsertAtBottom); + ui_effects_dropdown->setToolTip( + tr("Choose an effect to play on your next spoken message.\n" + "The effects are defined in your theme/effects/effects.ini. Your " + "character can define custom effects by\n" + "char.ini [Options] category, effects = 'miscname' where it referes " + "to misc//effects.ini to read the effects.")); + // Todo: recode this entire fucking system with these dumbass goddamn ini's + // why is everything so specifically coded for all these purposes is ABSTRACT + // CODING not a thing now huh what the FUCK why do I gotta do this pleASE FOR + // THE LOVE OF GOD SPARE ME FROM THIS FRESH HELL btw i still love coding. + QPoint p_point = ao_app->get_button_spacing("effects_icon_size", filename); + ui_effects_dropdown->setIconSize(QSize(p_point.x(), p_point.y())); set_size_and_pos(ui_defense_bar, "defense_bar"); - ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state) + - ".png"); + ui_defense_bar->set_image("defensebar" + QString::number(defense_bar_state)); set_size_and_pos(ui_prosecution_bar, "prosecution_bar"); - ui_prosecution_bar->set_image( - "prosecutionbar" + QString::number(prosecution_bar_state) + ".png"); + ui_prosecution_bar->set_image("prosecutionbar" + + QString::number(prosecution_bar_state)); set_size_and_pos(ui_music_label, "music_label"); ui_music_label->setText("Music"); @@ -600,183 +745,272 @@ void Courtroom::set_widgets() set_size_and_pos(ui_blip_label, "blip_label"); ui_blip_label->setText("Blips"); - set_size_and_pos(ui_log_limit_label, "log_limit_label"); - ui_log_limit_label->setText("Log limit"); - set_size_and_pos(ui_hold_it, "hold_it"); - ui_hold_it->set_image("holdit.png"); + ui_hold_it->setText(tr("Hold It!")); + ui_hold_it->setToolTip(tr("When this is turned on, your next in-character " + "message will be a shout!")); + ui_hold_it->set_image("holdit"); + set_size_and_pos(ui_objection, "objection"); - ui_objection->set_image("objection.png"); + ui_objection->setText(tr("Objection!")); + ui_objection->setToolTip(tr("When this is turned on, your next in-character " + "message will be a shout!")); + ui_objection->set_image("objection"); + set_size_and_pos(ui_take_that, "take_that"); - ui_take_that->set_image("takethat.png"); + ui_take_that->setText(tr("Take That!")); + ui_take_that->setToolTip(tr("When this is turned on, your next in-character " + "message will be a shout!")); + ui_take_that->set_image("takethat"); set_size_and_pos(ui_ooc_toggle, "ooc_toggle"); - ui_ooc_toggle->setText("Server"); + ui_ooc_toggle->setText(tr("Server")); + ui_ooc_toggle->setToolTip( + tr("Toggle between server chat and global AO2 chat.")); set_size_and_pos(ui_witness_testimony, "witness_testimony"); - ui_witness_testimony->set_image("witnesstestimony.png"); + ui_witness_testimony->set_image("witnesstestimony"); + ui_witness_testimony->setToolTip(tr("This will display the animation in the " + "viewport as soon as it is pressed.")); set_size_and_pos(ui_cross_examination, "cross_examination"); - ui_cross_examination->set_image("crossexamination.png"); + ui_cross_examination->set_image("crossexamination"); + ui_cross_examination->setToolTip(tr("This will display the animation in the " + "viewport as soon as it is pressed.")); set_size_and_pos(ui_guilty, "guilty"); - ui_guilty->set_image("guilty.png"); + ui_guilty->setText(tr("Guilty!")); + ui_guilty->set_image("guilty"); + ui_guilty->setToolTip(tr("This will display the animation in the viewport as " + "soon as it is pressed.")); set_size_and_pos(ui_not_guilty, "not_guilty"); - ui_not_guilty->set_image("notguilty.png"); + ui_not_guilty->set_image("notguilty"); + ui_not_guilty->setToolTip(tr("This will display the animation in the " + "viewport as soon as it is pressed.")); set_size_and_pos(ui_change_character, "change_character"); - ui_change_character->setText("Change character"); + ui_change_character->setText(tr("Change character")); + ui_change_character->set_image("change_character"); + ui_change_character->setToolTip( + tr("Bring up the Character Select Screen and change your character.")); set_size_and_pos(ui_reload_theme, "reload_theme"); - ui_reload_theme->setText("Reload theme"); + ui_reload_theme->setText(tr("Reload theme")); + ui_reload_theme->set_image("reload_theme"); + ui_reload_theme->setToolTip( + tr("Refresh the theme and update all of the ui elements to match.")); set_size_and_pos(ui_call_mod, "call_mod"); - ui_call_mod->setText("Call mod"); + ui_call_mod->setText(tr("Call mod")); + ui_call_mod->set_image("call_mod"); + ui_call_mod->setToolTip( + tr("Request the attention of the current server's moderator.")); set_size_and_pos(ui_settings, "settings"); - ui_settings->setText("Settings"); + ui_settings->setText(tr("Settings")); + ui_settings->set_image("settings"); + ui_settings->setToolTip( + tr("Allows you to change various aspects of the client.")); set_size_and_pos(ui_announce_casing, "casing_button"); - ui_announce_casing->setText("Casing"); + ui_announce_casing->setText(tr("Casing")); + ui_announce_casing->set_image("casing_button"); + ui_announce_casing->setToolTip( + tr("An interface to help you announce a case (you have to be a CM first " + "to be able to announce cases)")); set_size_and_pos(ui_switch_area_music, "switch_area_music"); - ui_switch_area_music->setText("A/M"); + ui_switch_area_music->setText(tr("A/M")); + ui_switch_area_music->set_image("switch_area_music"); + ui_switch_area_music->setToolTip(tr("Switch between Areas and Music lists")); set_size_and_pos(ui_pre, "pre"); - ui_pre->setText("Preanim"); + ui_pre->setText(tr("Preanim")); + ui_pre->setToolTip( + tr("Play a single-shot animation as defined by the emote when checked.")); set_size_and_pos(ui_pre_non_interrupt, "pre_no_interrupt"); + ui_pre_non_interrupt->setToolTip( + tr("If preanim is checked, display the input text immediately as the " + "animation plays concurrently.")); + set_size_and_pos(ui_flip, "flip"); + ui_flip->setToolTip(tr("Mirror your character's emotes when checked.")); + + set_size_and_pos(ui_additive, "additive"); + ui_additive->setToolTip( + tr("Add text to your last spoken message when checked.")); set_size_and_pos(ui_guard, "guard"); + ui_guard->setToolTip( + tr("Do not listen to mod calls when checked, preventing them from " + "playing sounds or focusing attention on the window.")); set_size_and_pos(ui_casing, "casing"); + ui_casing->setToolTip(tr("Lets you receive case alerts when enabled.\n" + "(You can set your preferences in the Settings!)")); set_size_and_pos(ui_showname_enable, "showname_enable"); + ui_showname_enable->setToolTip( + tr("Display customized shownames for all users when checked.")); set_size_and_pos(ui_custom_objection, "custom_objection"); - ui_custom_objection->set_image("custom.png"); + ui_custom_objection->setText(tr("Custom Shout!")); + ui_custom_objection->set_image("custom"); + ui_custom_objection->setToolTip( + tr("This will display the custom character-defined animation in the " + "viewport as soon as it is pressed.\n" + "To make one, your character's folder must contain " + "custom.[webp/apng/gif/png] and custom.[wav/ogg/opus] sound effect")); set_size_and_pos(ui_realization, "realization"); - ui_realization->set_image("realization.png"); + ui_realization->set_image("realization"); + ui_realization->setToolTip( + tr("Play realization sound and animation in the viewport on the next " + "spoken message when checked.")); + + set_size_and_pos(ui_screenshake, "screenshake"); + ui_screenshake->set_image("screenshake"); + ui_screenshake->setToolTip( + tr("Shake the screen on next spoken message when checked.")); set_size_and_pos(ui_mute, "mute_button"); - ui_mute->set_image("mute.png"); + ui_mute->setText("Mute"); + ui_mute->set_image("mute"); + ui_mute->setToolTip( + tr("Display the list of character folders you wish to mute.")); set_size_and_pos(ui_defense_plus, "defense_plus"); - ui_defense_plus->set_image("defplus.png"); + ui_defense_plus->set_image("defplus"); + ui_defense_plus->setToolTip(tr("Increase the health bar.")); set_size_and_pos(ui_defense_minus, "defense_minus"); - ui_defense_minus->set_image("defminus.png"); + ui_defense_minus->set_image("defminus"); + ui_defense_minus->setToolTip(tr("Decrease the health bar.")); set_size_and_pos(ui_prosecution_plus, "prosecution_plus"); - ui_prosecution_plus->set_image("proplus.png"); + ui_prosecution_plus->set_image("proplus"); + ui_prosecution_plus->setToolTip(tr("Increase the health bar.")); set_size_and_pos(ui_prosecution_minus, "prosecution_minus"); - ui_prosecution_minus->set_image("prominus.png"); + ui_prosecution_minus->set_image("prominus"); + ui_prosecution_minus->setToolTip(tr("Decrease the health bar.")); set_size_and_pos(ui_text_color, "text_color"); + ui_text_color->setToolTip( + tr("Change the text color of the spoken message.\n" + "You can also select a part of your currently typed message and use " + "the dropdown to change its color!")); + set_text_color_dropdown(); 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_log_limit_spinbox, "log_limit_spinbox"); - - 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->set_image("char_selector"); ui_selector->hide(); set_size_and_pos(ui_back_to_lobby, "back_to_lobby"); - ui_back_to_lobby->setText("Back to Lobby"); + ui_back_to_lobby->setText(tr("Back to Lobby")); + ui_back_to_lobby->setToolTip(tr("Return back to the server list.")); 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"); + ui_char_select_left->set_image("arrow_left"); set_size_and_pos(ui_char_select_right, "char_select_right"); - ui_char_select_right->set_image("arrow_right.png"); + ui_char_select_right->set_image("arrow_right"); set_size_and_pos(ui_spectator, "spectator"); + ui_spectator->setToolTip(tr("Become a spectator. You won't be able to " + "interact with the in-character screen.")); + + refresh_evidence(); } 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, "music_list"); + 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_music_name, "", "music_name"); - // Set color of labels and checkboxes - const QString design_file = "courtroom_fonts.ini"; - QColor f_color = ao_app->get_color("label_color", design_file); - QString color_string = "color: rgba(" + QString::number(f_color.red()) + - ", " + QString::number(f_color.green()) + ", " + - QString::number(f_color.blue()) + ", 255); }"; - QString style_sheet_string = "QLabel {" + color_string + - "}" - "QCheckBox {" + - color_string + "}"; - setStyleSheet(style_sheet_string); + set_dropdowns(); } -void Courtroom::set_font(QWidget *widget, QString p_identifier) +void Courtroom::set_font(QWidget *widget, QString class_name, + QString p_identifier) { QString design_file = "courtroom_fonts.ini"; - int f_weight = ao_app->get_font_size(p_identifier, design_file); - QString class_name = widget->metaObject()->className(); - - QString fontt = ao_app->get_font_name(p_identifier + "_font", design_file); - widget->setFont(QFont(fontt, f_weight)); - + int f_pointsize = ao_app->get_font_size(p_identifier, design_file); + QString font_name = + ao_app->get_font_name(p_identifier + "_font", design_file); QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); + bool bold = ao_app->get_font_size(p_identifier + "_bold", design_file) == + 1; // is the font bold or not? + bool antialias = + ao_app->get_font_size(p_identifier + "_sharp", design_file) != + 1; // is the font anti-aliased or not? + + this->set_qfont(widget, class_name, + get_qfont(font_name, f_pointsize, antialias), f_color, bold); +} + +QFont Courtroom::get_qfont(QString font_name, int f_pointsize, bool antialias) +{ + QFont font; + if (font_name.isEmpty()) + font_name = "Arial"; + + QFont::StyleStrategy style_strategy = QFont::PreferDefault; + if (!antialias) + style_strategy = QFont::NoAntialias; + + font = QFont(font_name, f_pointsize); + font.setStyleHint(QFont::SansSerif, style_strategy); + return font; +} + +void Courtroom::set_qfont(QWidget *widget, QString class_name, QFont font, + QColor f_color, bool bold) +{ + if (class_name.isEmpty()) + class_name = widget->metaObject()->className(); + + font.setBold(bold); + widget->setFont(font); 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); }"; - + ", " + QString::number(f_color.blue()) + ", 255);}"; widget->setStyleSheet(style_sheet_string); } +void Courtroom::set_dropdown(QWidget *widget) +{ + QString f_file = "courtroom_stylesheets.css"; + QString style_sheet_string = ao_app->get_stylesheet(f_file); + if (style_sheet_string != "") + widget->setStyleSheet(style_sheet_string); +} + +void Courtroom::set_dropdowns() +{ + set_dropdown( + this); // EXPERIMENTAL - Read the style-sheet as-is for maximum memeage + // set_dropdown(ui_text_color, "[TEXT COLOR]"); + // set_dropdown(ui_pos_dropdown, "[POS DROPDOWN]"); + // set_dropdown(ui_emote_dropdown, "[EMOTE DROPDOWN]"); + // set_dropdown(ui_mute_list, "[MUTE LIST]"); +} + void Courtroom::set_window_title(QString p_title) { this->setWindowTitle(p_title); @@ -816,6 +1050,22 @@ void Courtroom::set_taken(int n_char, bool p_taken) char_list.replace(n_char, f_char); } +QPoint Courtroom::get_theme_pos(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; + return QPoint(0, 0); + } + else { + return QPoint(design_ini_result.x, design_ini_result.y); + } +} + void Courtroom::done_received() { m_cid = -1; @@ -837,15 +1087,38 @@ void Courtroom::done_received() ui_spectator->show(); } -void Courtroom::set_background(QString p_background) +void Courtroom::set_background(QString p_background, bool display) { - testimony_in_progress = false; - + ui_vp_testimony->stop(); current_background = p_background; - is_ao2_bg = file_exists(ao_app->get_background_path("defensedesk.png")) && - file_exists(ao_app->get_background_path("prosecutiondesk.png")) && - file_exists(ao_app->get_background_path("stand.png")); + // welcome to hardcode central may I take your order of regularly scheduled + // CBT + QMap default_pos; + default_pos["defenseempty"] = "def"; + default_pos["helperstand"] = "hld"; + default_pos["prosecutorempty"] = "pro"; + default_pos["prohelperstand"] = "hlp"; + default_pos["witnessempty"] = "wit"; + default_pos["judgestand"] = "jud"; + default_pos["jurystand"] = "jur"; + default_pos["seancestand"] = "sea"; + + // Populate the dropdown list with all pos that exist on this bg + QStringList pos_list = {}; + for (QString key : default_pos.keys()) { + if (file_exists(ao_app->get_static_image_suffix( + ao_app->get_background_path(key)))) { + pos_list.append(default_pos[key]); + } + } + + // TODO: search through extra/custom pos and add them to the pos dropdown as + // well + + set_pos_dropdown(pos_list); + + is_ao2_bg = true; if (is_ao2_bg) { set_size_and_pos(ui_vp_chatbox, "ao2_chatbox"); @@ -855,10 +1128,74 @@ void Courtroom::set_background(QString p_background) set_size_and_pos(ui_vp_chatbox, "chatbox"); set_size_and_pos(ui_ic_chat_message, "ic_chat_message"); } + + if (display) { + ui_vp_speedlines->stop(); + ui_vp_player_char->stop(); + + ui_vp_sideplayer_char->stop(); + ui_vp_effect->stop(); + ui_vp_message->hide(); + ui_vp_chatbox->hide(); + + // Stop the chat arrow from animating + ui_vp_chat_arrow->stop(); + + text_state = 2; + anim_state = 3; + ui_vp_objection->stop(); + chat_tick_timer->stop(); + ui_vp_evidence_display->reset(); + set_scene( + QString::number(ao_app->get_desk_mod(current_char, current_emote)), + current_side); + } } -void Courtroom::enter_courtroom(int p_cid) +void Courtroom::set_side(QString p_side) { + if (p_side == "") + current_side = ao_app->get_char_side(current_char); + else + current_side = p_side; + + for (int i = 0; i < ui_pos_dropdown->count(); ++i) { + QString pos = ui_pos_dropdown->itemText(i); + if (pos == current_side) { + // Block the signals to prevent setCurrentIndex from triggering a pos + // change + ui_pos_dropdown->blockSignals(true); + + // Set the index on dropdown ui element to let you know what pos you're on + // right now + ui_pos_dropdown->setCurrentIndex(i); + + // Unblock the signals so the element can be used for setting pos again + ui_pos_dropdown->blockSignals(false); + + // alright we dun, jobs done here boyos + break; + } + } +} + +void Courtroom::set_pos_dropdown(QStringList pos_dropdowns) +{ + // Block the signals to prevent setCurrentIndex from triggering a pos change + ui_pos_dropdown->blockSignals(true); + pos_dropdown_list = pos_dropdowns; + ui_pos_dropdown->clear(); + ui_pos_dropdown->addItems(pos_dropdown_list); + // Unblock the signals so the element can be used for setting pos again + ui_pos_dropdown->blockSignals(false); + + qDebug() << pos_dropdown_list; +} + +void Courtroom::update_character(int p_cid) +{ + bool newchar = m_cid != p_cid; + m_cid = p_cid; QString f_char; @@ -876,6 +1213,7 @@ void Courtroom::enter_courtroom(int p_cid) } current_char = f_char; + current_side = ao_app->get_char_side(current_char); current_emote_page = 0; current_emote = 0; @@ -885,17 +1223,18 @@ void Courtroom::enter_courtroom(int p_cid) else ui_emotes->show(); + refresh_emotes(); set_emote_page(); set_emote_dropdown(); - current_evidence_page = 0; - current_evidence = 0; + set_sfx_dropdown(); + set_effects_dropdown(); - set_evidence_page(); + qDebug() << "update_character called"; + if (newchar) // Avoid infinite loop of death and suffering + set_iniswap_dropdown(); - QString side = ao_app->get_char_side(f_char); - - if (side == "jud") { + if (current_side == "jud") { ui_witness_testimony->show(); ui_cross_examination->show(); ui_not_guilty->show(); @@ -917,18 +1256,36 @@ void Courtroom::enter_courtroom(int p_cid) } if (ao_app->custom_objection_enabled && - (file_exists(ao_app->get_character_path(current_char, "custom.gif")) || - file_exists(ao_app->get_character_path(current_char, "custom.apng"))) && - file_exists(ao_app->get_character_path(current_char, "custom.wav"))) + file_exists(ao_app->get_image_suffix( + ao_app->get_character_path(current_char, "custom")))) ui_custom_objection->show(); else ui_custom_objection->hide(); + ui_char_select_background->hide(); + ui_ic_chat_message->setEnabled(m_cid != -1); + ui_ic_chat_message->setFocus(); +} + +void Courtroom::enter_courtroom() +{ + set_widgets(); + + current_evidence_page = 0; + current_evidence = 0; + + set_evidence_page(); + if (ao_app->flipping_enabled) ui_flip->show(); else ui_flip->hide(); + if (ao_app->additive_enabled) + ui_additive->show(); + else + ui_additive->hide(); + if (ao_app->casing_alerts_enabled) ui_casing->show(); else @@ -937,27 +1294,24 @@ void Courtroom::enter_courtroom(int p_cid) list_music(); list_areas(); - music_player->set_volume(ui_music_slider->value()); + music_player->set_volume(ui_music_slider->value(), 0); // set music + // Set the ambience and other misc. music layers + for (int i = 1; i < music_player->m_channelmax; ++i) { + music_player->set_volume(ui_sfx_slider->value(), i); + } sfx_player->set_volume(ui_sfx_slider->value()); objection_player->set_volume(ui_sfx_slider->value()); blip_player->set_volume(ui_blip_slider->value()); - testimony_in_progress = false; - - set_widgets(); - + ui_vp_testimony->stop(); // ui_server_chatlog->setHtml(ui_server_chatlog->toHtml()); - - ui_char_select_background->hide(); - - ui_ic_chat_message->setEnabled(m_cid != -1); - ui_ic_chat_message->setFocus(); } +// Todo: multithread this due to some servers having large as hell music list void Courtroom::list_music() { ui_music_list->clear(); - music_row_to_number.clear(); + // ui_music_search->setText(""); QString f_file = "courtroom_design.ini"; @@ -966,31 +1320,48 @@ void Courtroom::list_music() int n_listed_songs = 0; + QTreeWidgetItem *parent = nullptr; for (int n_song = 0; n_song < music_list.size(); ++n_song) { QString i_song = music_list.at(n_song); - QString i_song_listname = i_song; - i_song_listname = i_song_listname.left(i_song_listname.lastIndexOf(".")); + QString i_song_listname = i_song.left(i_song.lastIndexOf(".")); + i_song_listname = i_song_listname.right( + i_song_listname.length() - (i_song_listname.lastIndexOf("/") + 1)); - if (i_song.toLower().contains(ui_music_search->text().toLower())) { - ui_music_list->addItem(i_song_listname); - music_row_to_number.append(n_song); + QTreeWidgetItem *treeItem; + if (i_song_listname != i_song && + parent != nullptr) // not a category, parent exists + treeItem = new QTreeWidgetItem(parent); + else + treeItem = new QTreeWidgetItem(ui_music_list); + treeItem->setText(0, i_song_listname); + treeItem->setText(1, i_song); - QString song_path = ao_app->get_music_path(i_song); + QString song_path = ao_app->get_music_path(i_song); - if (file_exists(song_path)) - ui_music_list->item(n_listed_songs)->setBackground(found_brush); - else - ui_music_list->item(n_listed_songs)->setBackground(missing_brush); + if (file_exists(song_path)) + treeItem->setBackground(0, found_brush); + else + treeItem->setBackground(0, missing_brush); - ++n_listed_songs; - } + if (i_song_listname == + i_song) // Not supposed to be a song to begin with - a category? + parent = treeItem; + ++n_listed_songs; + } + + ui_music_list->expandAll(); // Needs to somehow remember which categories were + // expanded/collapsed if the music list didn't + // change since last time + if (ui_music_search->text() != "") { + on_music_search_edited(ui_music_search->text()); } } +// Todo: multithread this due to some servers having large as hell area list void Courtroom::list_areas() { ui_area_list->clear(); - area_row_to_number.clear(); + // ui_music_search->setText(""); QString f_file = "courtroom_design.ini"; @@ -1006,13 +1377,11 @@ void Courtroom::list_areas() for (int n_area = 0; n_area < area_list.size(); ++n_area) { QString i_area = ""; - i_area.append("["); - i_area.append(QString::number(n_area)); - i_area.append("] "); - i_area.append(area_list.at(n_area)); if (ao_app->arup_enabled) { + i_area.prepend("[" + QString::number(n_area) + "] "); // Give it the index + i_area.append("\n "); i_area.append(arup_statuses.at(n_area)); @@ -1027,35 +1396,38 @@ void Courtroom::list_areas() i_area.append(arup_locks.at(n_area)); } - if (i_area.toLower().contains(ui_music_search->text().toLower())) { - ui_area_list->addItem(i_area); - area_row_to_number.append(n_area); + QTreeWidgetItem *treeItem = new QTreeWidgetItem(ui_area_list); + treeItem->setText(0, area_list.at(n_area)); + treeItem->setText(1, i_area); - if (ao_app->arup_enabled) { - // Colouring logic here. - ui_area_list->item(n_listed_areas)->setBackground(free_brush); - if (arup_locks.at(n_area) == "LOCKED") { - ui_area_list->item(n_listed_areas)->setBackground(locked_brush); - } - else { - if (arup_statuses.at(n_area) == "LOOKING-FOR-PLAYERS") - ui_area_list->item(n_listed_areas)->setBackground(lfp_brush); - else if (arup_statuses.at(n_area) == "CASING") - ui_area_list->item(n_listed_areas)->setBackground(casing_brush); - else if (arup_statuses.at(n_area) == "RECESS") - ui_area_list->item(n_listed_areas)->setBackground(recess_brush); - else if (arup_statuses.at(n_area) == "RP") - ui_area_list->item(n_listed_areas)->setBackground(rp_brush); - else if (arup_statuses.at(n_area) == "GAMING") - ui_area_list->item(n_listed_areas)->setBackground(gaming_brush); - } + if (ao_app->arup_enabled) { + // Coloring logic here. + treeItem->setBackground(1, free_brush); + if (arup_locks.at(n_area) == "LOCKED") { + treeItem->setBackground(1, locked_brush); } else { - ui_area_list->item(n_listed_areas)->setBackground(free_brush); + if (arup_statuses.at(n_area) == "LOOKING-FOR-PLAYERS") + treeItem->setBackground(1, lfp_brush); + else if (arup_statuses.at(n_area) == "CASING") + treeItem->setBackground(1, casing_brush); + else if (arup_statuses.at(n_area) == "RECESS") + treeItem->setBackground(1, recess_brush); + else if (arup_statuses.at(n_area) == "RP") + treeItem->setBackground(1, rp_brush); + else if (arup_statuses.at(n_area) == "GAMING") + treeItem->setBackground(1, gaming_brush); } - - ++n_listed_areas; } + else { + treeItem->setBackground(1, free_brush); + } + + ++n_listed_areas; + } + + if (ui_music_search->text() != "") { + on_music_search_edited(ui_music_search->text()); } } @@ -1063,22 +1435,29 @@ void Courtroom::append_ms_chatmessage(QString f_name, QString f_message) { ui_ms_chatlog->append_chatmessage( f_name, f_message, - ao_app->get_color("ooc_default_color", "courtroom_design.ini").name()); + ao_app->get_color("ms_chatlog_sender_color", "courtroom_fonts.ini") + .name()); } void Courtroom::append_server_chatmessage(QString p_name, QString p_message, - QString p_colour) + QString p_color) { - QString colour = "#000000"; + QString color = "#000000"; - if (p_colour == "0") - colour = - ao_app->get_color("ooc_default_color", "courtroom_design.ini").name(); - if (p_colour == "1") - colour = - ao_app->get_color("ooc_server_color", "courtroom_design.ini").name(); + if (p_color == "0") + color = ao_app->get_color("ms_chatlog_sender_color", "courtroom_fonts.ini") + .name(); + if (p_color == "1") + color = + ao_app->get_color("server_chatlog_sender_color", "courtroom_fonts.ini") + .name(); + if (p_message == "Logged in as a moderator.") { + ui_guard->show(); + append_server_chatmessage( + "CLIENT", "You were granted the Disable Modcalls button.", "1"); + } - ui_server_chatlog->append_chatmessage(p_name, p_message, colour); + ui_server_chatlog->append_chatmessage(p_name, p_message, color); } void Courtroom::on_chat_return_pressed() @@ -1115,7 +1494,8 @@ void Courtroom::on_chat_return_pressed() QStringList packet_contents; - QString f_side = ao_app->get_char_side(current_char); + if (current_side == "") + current_side = ao_app->get_char_side(current_char); QString f_desk_mod = "chat"; @@ -1136,16 +1516,22 @@ void Courtroom::on_chat_return_pressed() packet_contents.append(ui_ic_chat_message->text()); - packet_contents.append(f_side); + packet_contents.append(current_side); - packet_contents.append(ao_app->get_sfx_name(current_char, current_emote)); + packet_contents.append(get_char_sfx()); + if (ui_pre->isChecked() && !ao_app->is_stickysounds_enabled()) { + ui_sfx_dropdown->blockSignals(true); + ui_sfx_dropdown->setCurrentIndex(0); + ui_sfx_dropdown->blockSignals(false); + ui_sfx_remove->hide(); + } 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 (objection_state > 0) { if (ui_pre->isChecked()) { - if (f_emote_mod == 5) + if (f_emote_mod == 4 || f_emote_mod == 5) f_emote_mod = 6; else f_emote_mod = 2; @@ -1167,8 +1553,7 @@ void Courtroom::on_chat_return_pressed() 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))); + packet_contents.append(QString::number(get_char_sfx_delay())); QString f_obj_state; @@ -1206,7 +1591,7 @@ void Courtroom::on_chat_return_pressed() if (text_color < 0) f_text_color = "0"; - else if (text_color > 8) + else if (text_color > max_colors) f_text_color = "0"; else f_text_color = QString::number(text_color); @@ -1227,13 +1612,17 @@ void Courtroom::on_chat_return_pressed() // Similarly, we send over whom we're paired with, unless we have chosen // ourselves. Or a charid of -1 or lower, through some means. if (other_charid > -1 && other_charid != m_cid) { - packet_contents.append(QString::number(other_charid)); - packet_contents.append(QString::number(offset_with_pair)); + QString packet = QString::number(other_charid); + if (ao_app->effects_enabled) // Only servers with effects enabled will + // support pair reordering + packet += "^" + QString::number(pair_order); + packet_contents.append(packet); } else { packet_contents.append("-1"); - packet_contents.append("0"); } + // Send the offset as it's gonna be used regardless + packet_contents.append(QString::number(char_offset)); // Finally, we send over if we want our pres to not interrupt. if (ui_pre_non_interrupt->isChecked() && ui_pre->isChecked()) { @@ -1244,6 +1633,55 @@ void Courtroom::on_chat_return_pressed() } } + // If the server we're on supports Looping SFX and Screenshake, use it if the + // emote uses it. + if (ao_app->looping_sfx_support_enabled) { + packet_contents.append( + ao_app->get_sfx_looping(current_char, current_emote)); + packet_contents.append(QString::number(screenshake_state)); + + QString pre_emote = ao_app->get_pre_emote(current_char, current_emote); + QString emote = ao_app->get_emote(current_char, current_emote); + QStringList emotes_to_check = {pre_emote, "(b)" + emote, "(a)" + emote}; + QStringList effects_to_check = {"_FrameScreenshake", "_FrameRealization", + "_FrameSFX"}; + + foreach (QString f_effect, effects_to_check) { + QString packet; + foreach (QString f_emote, emotes_to_check) { + packet += f_emote; + if (ao_app->is_frame_network_enabled()) { + QString sfx_frames = + ao_app + ->read_ini_tags( + ao_app->get_character_path(current_char, "char.ini"), + f_emote.append(f_effect)) + .join("|"); + if (sfx_frames != "") + packet += "|" + sfx_frames; + } + packet += "^"; + } + packet_contents.append(packet); + } + } + + if (ao_app->additive_enabled) { + packet_contents.append(ui_additive->isChecked() ? "1" : "0"); + } + if (ao_app->effects_enabled) { + QString fx_sound = ao_app->get_effect_sound(effect, current_char); + QString p_effect = + ao_app->read_char_ini(current_char, "effects", "Options"); + packet_contents.append(effect + "|" + p_effect + "|" + fx_sound); + if (!ao_app->is_stickyeffects_enabled()) { + ui_effects_dropdown->blockSignals(true); + ui_effects_dropdown->setCurrentIndex(0); + ui_effects_dropdown->blockSignals(false); + effect = ""; + } + } + ao_app->send_server_packet(new AOPacket("MS", packet_contents)); } @@ -1274,63 +1712,89 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) int f_char_id = m_chatmessage[CHAR_ID].toInt(); - if (f_char_id < 0 || f_char_id >= char_list.size()) + 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; - if (m_chatmessage[SHOWNAME].isEmpty() || !ui_showname_enable->isChecked()) { + if (f_char_id > 0 && + (m_chatmessage[SHOWNAME].isEmpty() || !ui_showname_enable->isChecked())) { 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_showname.trimmed() + .isEmpty()) // Pure whitespace showname, get outta here. + f_showname = m_chatmessage[CHAR_NAME]; - if (f_message == previous_ic_message) + QString f_message = f_showname + ": " + m_chatmessage[MESSAGE] + '\n'; + // Remove undesired newline chars + m_chatmessage[MESSAGE].remove("\n"); + chatmessage_is_empty = + m_chatmessage[MESSAGE] == " " || m_chatmessage[MESSAGE] == ""; + + if (f_char_id >= 0 && !chatmessage_is_empty && + f_message == previous_ic_message) // Not a system message return; + if (f_char_id <= -1) + previous_ic_message = + ""; // System messages don't care about repeating themselves + else + previous_ic_message = f_message; + + // Stop the chat arrow from animating + ui_vp_chat_arrow->stop(); + text_state = 0; anim_state = 0; ui_vp_objection->stop(); - ui_vp_player_char->stop(); chat_tick_timer->stop(); ui_vp_evidence_display->reset(); - chatmessage_is_empty = - m_chatmessage[MESSAGE] == " " || m_chatmessage[MESSAGE] == ""; - - if (m_chatmessage[MESSAGE] == ui_ic_chat_message->text() && + // Hey, our message showed up! Cool! + if (m_chatmessage[MESSAGE] == ui_ic_chat_message->text().remove("\n") && m_chatmessage[CHAR_ID].toInt() == m_cid) { ui_ic_chat_message->clear(); + if (ui_additive->isChecked()) + ui_ic_chat_message->insert(" "); objection_state = 0; realization_state = 0; + screenshake_state = 0; is_presenting_evidence = false; - ui_pre->setChecked(false); - ui_hold_it->set_image("holdit.png"); - ui_objection->set_image("objection.png"); - ui_take_that->set_image("takethat.png"); - ui_custom_objection->set_image("custom.png"); - ui_realization->set_image("realization.png"); - ui_evidence_present->set_image("present_disabled.png"); + if (!ao_app->is_stickypres_enabled()) + ui_pre->setChecked(false); + ui_hold_it->set_image("holdit"); + ui_objection->set_image("objection"); + ui_take_that->set_image("takethat"); + ui_custom_objection->set_image("custom"); + ui_realization->set_image("realization"); + ui_screenshake->set_image("screenshake"); + ui_evidence_present->set_image("present"); } + // Let the server handle actually checking if they're allowed to do this. + is_additive = m_chatmessage[ADDITIVE].toInt() == 1; + + QString f_charname = ""; + if (f_char_id >= 0) + f_charname = ao_app->get_showname(char_list.at(f_char_id).name); + chatlogpiece *temp = - new chatlogpiece(ao_app->get_showname(char_list.at(f_char_id).name), - f_showname, ": " + m_chatmessage[MESSAGE], false); + new chatlogpiece(f_charname, f_showname, m_chatmessage[MESSAGE], false); ic_chatlog_history.append(*temp); + ao_app->append_to_file(temp->get_full(), ao_app->log_filename, true); while (ic_chatlog_history.size() > log_maximum_blocks && log_maximum_blocks > 0) { ic_chatlog_history.removeFirst(); } - append_ic_text(": " + m_chatmessage[MESSAGE], f_showname); - - previous_ic_message = f_message; + append_ic_text(m_chatmessage[MESSAGE], f_showname); int objection_mod = m_chatmessage[OBJECTION_MOD].toInt(); QString f_char = m_chatmessage[CHAR_NAME]; @@ -1340,26 +1804,28 @@ void Courtroom::handle_chatmessage(QStringList *p_contents) if (objection_mod <= 4 && objection_mod >= 1) { switch (objection_mod) { case 1: - ui_vp_objection->play("holdit", f_char, f_custom_theme); - objection_player->play("holdit.wav", f_char, f_custom_theme); + ui_vp_objection->play("holdit_bubble", f_char, f_custom_theme, 724); + objection_player->play("holdit", f_char, f_custom_theme); break; case 2: - ui_vp_objection->play("objection", f_char, f_custom_theme); - objection_player->play("objection.wav", f_char, f_custom_theme); + ui_vp_objection->play("objection_bubble", f_char, f_custom_theme, 724); + objection_player->play("objection", f_char, f_custom_theme); + if (ao_app->objection_stop_music()) + music_player->stop(); break; case 3: - ui_vp_objection->play("takethat", f_char, f_custom_theme); - objection_player->play("takethat.wav", f_char, f_custom_theme); + ui_vp_objection->play("takethat_bubble", f_char, f_custom_theme, 724); + objection_player->play("takethat", f_char, f_custom_theme); break; // case 4 is AO2 only case 4: - ui_vp_objection->play("custom", f_char, f_custom_theme); - objection_player->play("custom.wav", f_char, f_custom_theme); + ui_vp_objection->play("custom", f_char, f_custom_theme, 724); + objection_player->play("custom", f_char, f_custom_theme); break; default: qDebug() << "W: Logic error in objection switch statement!"; } - + sfx_player->clear(); // Objection played! Cut all sfx. int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); if (emote_mod == 0) @@ -1375,9 +1841,25 @@ void Courtroom::handle_chatmessage_2() { ui_vp_speedlines->stop(); ui_vp_player_char->stop(); + ui_vp_effect->stop(); + // Clear all looping sfx to prevent obnoxiousness + sfx_player->loop_clear(); - if (m_chatmessage[SHOWNAME].isEmpty() || !ui_showname_enable->isChecked()) { - QString real_name = char_list.at(m_chatmessage[CHAR_ID].toInt()).name; + if (!m_chatmessage[FRAME_SFX].isEmpty() && + ao_app->is_frame_network_enabled()) { + // ORDER IS IMPORTANT!! + QStringList netstrings = {m_chatmessage[FRAME_SCREENSHAKE], + m_chatmessage[FRAME_REALIZATION], + m_chatmessage[FRAME_SFX]}; + ui_vp_player_char->network_strings = netstrings; + } + else + ui_vp_player_char->network_strings.clear(); + + int f_charid = m_chatmessage[CHAR_ID].toInt(); + if (f_charid >= 0 && + (m_chatmessage[SHOWNAME].isEmpty() || !ui_showname_enable->isChecked())) { + QString real_name = char_list.at(f_charid).name; QString f_showname = ao_app->get_showname(real_name); @@ -1387,38 +1869,92 @@ void Courtroom::handle_chatmessage_2() ui_vp_showname->setText(m_chatmessage[SHOWNAME]); } - ui_vp_message->clear(); + if (ui_vp_showname->text().trimmed().isEmpty()) // Whitespace showname + { + ui_vp_chatbox->set_image("chatblank"); + } + else // Aw yeah dude do some showname resizing magic + { + if (!ui_vp_chatbox->set_image("chat")) + ui_vp_chatbox->set_image("chatbox"); + + QFontMetrics fm(ui_vp_showname->font()); + int fm_width = fm.horizontalAdvance(ui_vp_showname->text()); + + QString chatbox_path = ao_app->get_theme_path("chat"); + QString chatbox = ao_app->get_chat(m_chatmessage[CHAR_NAME]); + if (chatbox != "") { + chatbox_path = ao_app->get_base_path() + "misc/" + chatbox + "/chat"; + if (!ui_vp_chatbox->set_chatbox(chatbox_path)) + ui_vp_chatbox->set_chatbox(chatbox_path + "box"); + + pos_size_type design_ini_result = ao_app->get_element_dimensions( + "chat_arrow", "courtroom_design.ini", m_chatmessage[CHAR_NAME]); + if (design_ini_result.width < 0 || design_ini_result.height < 0) { + qDebug() << "W: could not find \"chat_arrow\" in courtroom_design.ini"; + ui_vp_chat_arrow->hide(); + } + else { + ui_vp_chat_arrow->move(design_ini_result.x, design_ini_result.y); + ui_vp_chat_arrow->combo_resize(design_ini_result.width, + design_ini_result.height); + } + } + + pos_size_type default_width = ao_app->get_element_dimensions( + "showname", "courtroom_design.ini", m_chatmessage[CHAR_NAME]); + int extra_width = + ao_app + ->get_design_element("showname_extra_width", "courtroom_design.ini", + m_chatmessage[CHAR_NAME]) + .toInt(); + + if (extra_width > 0) { + if (fm_width > default_width.width && + ui_vp_chatbox->set_chatbox( + chatbox_path + + "med")) // This text be big. Let's do some shenanigans. + { + ui_vp_showname->resize(default_width.width + extra_width, + ui_vp_showname->height()); + if (fm_width > ui_vp_showname->width() && + ui_vp_chatbox->set_chatbox(chatbox_path + + "big")) // Biggest possible size for us. + { + ui_vp_showname->resize( + static_cast(default_width.width + (extra_width * 2)), + ui_vp_showname->height()); + } + } + else + ui_vp_showname->resize(default_width.width, ui_vp_showname->height()); + } + } + + ui_vp_message->hide(); ui_vp_chatbox->hide(); - QString chatbox = ao_app->get_chat(m_chatmessage[CHAR_NAME]); + // todo: put this in its own function or update + QString design_file = "courtroom_fonts.ini"; + int f_pointsize = ao_app->get_font_size("message", design_file); + QString font_name = ao_app->get_font_name("message_font", design_file); + QColor f_color = ao_app->get_color("message_color", design_file); + bool bold = ao_app->get_font_size("message_bold", design_file) == + 1; // is the font bold or not? + bool antialias = ao_app->get_font_size("message_sharp", design_file) != + 1; // is the font anti-aliased or not? - if (chatbox == "") - ui_vp_chatbox->set_image("chatmed.png"); - else { - QString chatbox_path = - ao_app->get_base_path() + "misc/" + chatbox + "/chatbox.png"; - ui_vp_chatbox->set_image_from_path(chatbox_path); - } + QString chatfont = ao_app->get_chat_font(m_chatmessage[CHAR_NAME]); + if (chatfont != "") + font_name = chatfont; - ui_vp_showname->setStyleSheet( - "QLabel { color : " + get_text_color("_showname").name() + "; }"); + int chatsize = ao_app->get_chat_size(m_chatmessage[CHAR_NAME]); + if (chatsize != -1) + f_pointsize = chatsize; + this->set_qfont(ui_vp_message, "", + get_qfont(font_name, f_pointsize, antialias), f_color, bold); - set_scene(); - set_text_color(); - - // Check if the message needs to be centered. - QString f_message = m_chatmessage[MESSAGE]; - if (f_message.size() >= 2) { - if (f_message.startsWith("~~")) { - message_is_centered = true; - } - else { - message_is_centered = false; - } - } - else { - ui_vp_message->setAlignment(Qt::AlignLeft); - } + set_scene(m_chatmessage[DESK_MOD], m_chatmessage[SIDE]); int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); @@ -1431,113 +1967,41 @@ void Courtroom::handle_chatmessage_2() // Making the second character appear. if (m_chatmessage[OTHER_CHARID].isEmpty()) { - // If there is no second character, hide 'em, and center the first. - ui_vp_sideplayer_char->hide(); + // If there is no second character, hide 'em + ui_vp_sideplayer_char->stop(); ui_vp_sideplayer_char->move(0, 0); - - ui_vp_player_char->move(0, 0); } else { bool ok; - int got_other_charid = m_chatmessage[OTHER_CHARID].toInt(&ok); + int got_other_charid = m_chatmessage[OTHER_CHARID].split("^")[0].toInt(&ok); if (ok) { if (got_other_charid > -1) { // If there is, show them! ui_vp_sideplayer_char->show(); - // Depending on where we are, we offset the characters, and reorder - // their stacking. - if (side == "def") { - // We also move the character down depending on how far the are to the - // right. - int hor_offset = m_chatmessage[SELF_OFFSET].toInt(); - int vert_offset = 0; - if (hor_offset > 0) { - vert_offset = hor_offset / 10; - } - ui_vp_player_char->move(ui_viewport->width() * hor_offset / 100, - ui_viewport->height() * vert_offset / 100); + int other_offset = m_chatmessage[OTHER_OFFSET].toInt(); + ui_vp_sideplayer_char->move(ui_viewport->width() * other_offset / 100, + 0); - // We do the same with the second character. - int hor2_offset = m_chatmessage[OTHER_OFFSET].toInt(); - int vert2_offset = 0; - if (hor2_offset > 0) { - vert2_offset = hor2_offset / 10; + QStringList args = m_chatmessage[OTHER_CHARID].split("^"); + if (args.size() > + 1) // This ugly workaround is so we don't make an extra packet just + // for this purpose. Rewrite pairing when? + { + // Change the order of appearance based on the pair order variable + int order = args.at(1).toInt(); + switch (order) { + case 0: + ui_vp_sideplayer_char->stackUnder(ui_vp_player_char); + break; + case 1: + ui_vp_player_char->stackUnder(ui_vp_sideplayer_char); + break; + default: + break; } - ui_vp_sideplayer_char->move(ui_viewport->width() * hor2_offset / 100, - ui_viewport->height() * vert2_offset / - 100); - - // Finally, we reorder them based on who is more to the left. - // The person more to the left is more in the front. - if (hor2_offset >= hor_offset) { - ui_vp_sideplayer_char->raise(); - ui_vp_player_char->raise(); - } - else { - ui_vp_player_char->raise(); - ui_vp_sideplayer_char->raise(); - } - ui_vp_desk->raise(); - ui_vp_legacy_desk->raise(); } - else if (side == "pro") { - // Almost the same thing happens here, but in reverse. - int hor_offset = m_chatmessage[SELF_OFFSET].toInt(); - int vert_offset = 0; - if (hor_offset < 0) { - // We don't want to RAISE the char off the floor. - vert_offset = -1 * hor_offset / 10; - } - ui_vp_player_char->move(ui_viewport->width() * hor_offset / 100, - ui_viewport->height() * vert_offset / 100); - // We do the same with the second character. - int hor2_offset = m_chatmessage[OTHER_OFFSET].toInt(); - int vert2_offset = 0; - if (hor2_offset < 0) { - vert2_offset = -1 * hor2_offset / 10; - } - ui_vp_sideplayer_char->move(ui_viewport->width() * hor2_offset / 100, - ui_viewport->height() * vert2_offset / - 100); - - // Finally, we reorder them based on who is more to the right. - if (hor2_offset <= hor_offset) { - ui_vp_sideplayer_char->raise(); - ui_vp_player_char->raise(); - } - else { - ui_vp_player_char->raise(); - ui_vp_sideplayer_char->raise(); - } - ui_vp_desk->raise(); - ui_vp_legacy_desk->raise(); - } - else { - // In every other case, the person more to the left is on top. - // These cases also don't move the characters down. - int hor_offset = m_chatmessage[SELF_OFFSET].toInt(); - ui_vp_player_char->move(ui_viewport->width() * hor_offset / 100, 0); - - // We do the same with the second character. - int hor2_offset = m_chatmessage[OTHER_OFFSET].toInt(); - ui_vp_sideplayer_char->move(ui_viewport->width() * hor2_offset / 100, - 0); - - // Finally, we reorder them based on who is more to the left. - // The person more to the left is more in the front. - if (hor2_offset >= hor_offset) { - ui_vp_sideplayer_char->raise(); - ui_vp_player_char->raise(); - } - else { - ui_vp_player_char->raise(); - ui_vp_sideplayer_char->raise(); - } - ui_vp_desk->raise(); - ui_vp_legacy_desk->raise(); - } // We should probably also play the other character's idle emote. if (ao_app->flipping_enabled && m_chatmessage[OTHER_FLIP].toInt() == 1) ui_vp_sideplayer_char->set_flipped(true); @@ -1551,11 +2015,17 @@ void Courtroom::handle_chatmessage_2() // really is no second character, hide 'em, and center the first. ui_vp_sideplayer_char->hide(); ui_vp_sideplayer_char->move(0, 0); - - ui_vp_player_char->move(0, 0); } } } + // Set ourselves according to SELF_OFFSET + + bool ok; + int self_offset = m_chatmessage[SELF_OFFSET].toInt(&ok); + if (ok) + ui_vp_player_char->move(ui_viewport->width() * self_offset / 100, 0); + else + ui_vp_player_char->move(0, 0); switch (emote_mod) { case 1: @@ -1575,6 +2045,91 @@ void Courtroom::handle_chatmessage_2() } } +void Courtroom::do_screenshake() +{ + if (!ao_app->is_shake_enabled()) + return; + + // This way, the animation is reset in such a way that last played screenshake + // would return to its "final frame" properly. This properly resets all UI + // elements without having to bother keeping track of "origin" positions. + // Works great wit the chat text being detached from the chat box! + screenshake_animation_group->setCurrentTime( + screenshake_animation_group->duration()); + screenshake_animation_group->clear(); + + QList affected_list = {ui_vp_background, ui_vp_player_char, + ui_vp_sideplayer_char, ui_vp_chatbox}; + + // I would prefer if this was its own "shake" function to be honest. + foreach (QWidget *ui_element, affected_list) { + QPropertyAnimation *screenshake_animation = + new QPropertyAnimation(ui_element, "pos", this); + QPoint pos_default = QPoint(ui_element->x(), ui_element->y()); + + int duration = 300; // How long does the screenshake last + int frequency = 20; // How often in ms is there a "jolt" frame + int maxframes = duration / frequency; + int max_x = 7; // Max deviation from origin on x axis + int max_y = 7; // Max deviation from origin on y axis + screenshake_animation->setDuration(duration); + for (int frame = 0; frame < maxframes; frame++) { + double fraction = double(frame * frequency) / duration; + int rng = qrand(); // QRandomGenerator::global()->generate(); + int rand_x = max_x - (int(rng) % (max_x * 2)); + int rand_y = max_y - (int(rng + 100) % (max_y * 2)); + screenshake_animation->setKeyValueAt( + fraction, QPoint(pos_default.x() + rand_x, pos_default.y() + rand_y)); + } + screenshake_animation->setEndValue(pos_default); + screenshake_animation->setEasingCurve(QEasingCurve::Linear); + screenshake_animation_group->addAnimation(screenshake_animation); + } + + screenshake_animation_group->start(); +} + +void Courtroom::do_flash() +{ + if (!ao_app->is_effects_enabled()) + return; + + QString f_char = m_chatmessage[CHAR_NAME]; + QString f_custom_theme = ao_app->get_char_shouts(f_char); + ui_vp_effect->play("realizationflash", f_char, f_custom_theme, 60); +} + +void Courtroom::do_effect(QString fx_name, QString fx_sound, QString p_char, + QString p_folder) +{ + + QString effect = ao_app->get_effect(fx_name, p_char, p_folder); + if (effect == "") + return; + + if (fx_sound != "") + sfx_player->play(fx_sound); + + // Only check if effects are disabled after playing the sound if it exists + if (!ao_app->is_effects_enabled()) + return; + + ui_vp_effect->set_play_once( + false); // The effects themselves dictate whether or not they're looping. + // Static effects will linger. + ui_vp_effect->play(effect); // It will set_play_once to true if the filepath + // provided is not designed to loop more than once +} + +void Courtroom::play_char_sfx(QString sfx_name) +{ + sfx_player->play(sfx_name); + // sfx_player->set_looping(false); + // if (ao_app->get_looping_sfx()) + // sfx_player->set_looping( + // ao_app->get_sfx_looping(current_char, current_emote) == "1"); +} + void Courtroom::handle_chatmessage_3() { start_chat_ticking(); @@ -1582,14 +2137,29 @@ void Courtroom::handle_chatmessage_3() int f_evi_id = m_chatmessage[EVIDENCE_ID].toInt(); QString f_side = m_chatmessage[SIDE]; + QString f_showname; + int f_char_id = m_chatmessage[CHAR_ID].toInt(); + if (f_char_id > 0 && + (m_chatmessage[SHOWNAME].isEmpty() || !ui_showname_enable->isChecked())) { + f_showname = ao_app->get_showname(char_list.at(f_char_id).name); + } + else { + f_showname = m_chatmessage[SHOWNAME]; + } + if (f_showname.trimmed() + .isEmpty()) // Pure whitespace showname, get outta here. + f_showname = m_chatmessage[CHAR_NAME]; + 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; + QString f_name = local_evidence_list.at(f_evi_id - 1).name; // 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" || f_side == "jur"); ui_vp_evidence_display->show_evidence(f_image, is_left_side, ui_sfx_slider->value()); + append_ic_text(f_name, f_showname, "has presented evidence"); } int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); @@ -1604,40 +2174,31 @@ void Courtroom::handle_chatmessage_3() ui_vp_sideplayer_char->hide(); ui_vp_player_char->move(0, 0); + QString f_char = m_chatmessage[CHAR_NAME]; + QString f_custom_theme = ao_app->get_char_shouts(f_char); if (side == "pro" || side == "hlp" || side == "wit") - ui_vp_speedlines->play("prosecution_speedlines"); + ui_vp_speedlines->play("prosecution_speedlines", f_char, f_custom_theme); else - ui_vp_speedlines->play("defense_speedlines"); + ui_vp_speedlines->play("defense_speedlines", f_char, f_custom_theme); } - int f_anim_state = 0; - // BLUE is from an enum in datatypes.h - bool text_is_blue = m_chatmessage[TEXT_COLOR].toInt() == BLUE; + // If this color is talking + color_is_talking = + color_markdown_talking_list.at(m_chatmessage[TEXT_COLOR].toInt()); - if (!text_is_blue && text_state == 1) { - // talking - f_anim_state = 2; - entire_message_is_blue = false; - } - else { - // idle - f_anim_state = 3; - entire_message_is_blue = true; - } - - 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]; - - if (f_anim_state == 2) { - ui_vp_player_char->play_talking(f_char, f_emote); + if (color_is_talking && text_state == 1 && + anim_state < 2) // Set it to talking as we're not on that already + { + ui_vp_player_char->stop(); + ui_vp_player_char->play_talking(m_chatmessage[CHAR_NAME], + m_chatmessage[EMOTE]); anim_state = 2; } - else { - ui_vp_player_char->play_idle(f_char, f_emote); + else if (anim_state < 3) // Set it to idle as we're not on that already + { + ui_vp_player_char->stop(); + ui_vp_player_char->play_idle(m_chatmessage[CHAR_NAME], + m_chatmessage[EMOTE]); anim_state = 3; } @@ -1654,121 +2215,250 @@ void Courtroom::handle_chatmessage_3() } } -QString Courtroom::filter_ic_text(QString p_text) +QString Courtroom::filter_ic_text(QString p_text, bool html, int target_pos, + int default_color) { - // Get rid of centering. - if (p_text.startsWith(": ~~")) { - // Don't forget, the p_text part actually everything after the name! - // Hence why we check for ': ~~'. + QString p_text_escaped; - // Let's remove those two tildes, then. - // : _ ~ ~ - // 0 1 2 3 - p_text.remove(2, 2); + int check_pos = 0; + int check_pos_escaped = 0; + bool ic_next_is_not_special = false; + std::stack ic_color_stack; + + // Text alignment shenanigans. Could make a dropdown for this later, too! + QString align; + if (p_text.trimmed().startsWith("~~")) { + p_text.remove(p_text.indexOf("~~"), 2); + if (target_pos != -1) { + target_pos = qMax(0, target_pos - 2); + } + align = "center"; + } + else if (p_text.trimmed().startsWith("~>")) { + p_text.remove(p_text.indexOf("~>"), 2); + if (target_pos != -1) { + target_pos = qMax(0, target_pos - 2); + } + align = "right"; + } + else if (p_text.trimmed().startsWith("<>")) { + p_text.remove(p_text.indexOf("<>"), 2); + if (target_pos != -1) { + target_pos = qMax(0, target_pos - 2); + } + align = "justify"; } - // Get rid of the inline-colouring. - // I know, I know, excessive code duplication. - // Nobody looks in here, I'm fine. - int trick_check_pos = 0; - bool ic_next_is_not_special = false; - QString f_character = p_text.at(trick_check_pos); - std::stack ic_colour_stack; - while (trick_check_pos < p_text.size()) { - f_character = p_text.at(trick_check_pos); + // If html is enabled, prepare this text to be all ready for it. + if (html) { + ic_color_stack.push(default_color); + QString appendage = ""; - // Escape character. - if (f_character == "\\" and !ic_next_is_not_special) { - ic_next_is_not_special = true; - p_text.remove(trick_check_pos, 1); - } + if (!align.isEmpty()) + appendage.prepend("
"); - // Text speed modifier. - else if (f_character == "{" and !ic_next_is_not_special) { - p_text.remove(trick_check_pos, 1); - } - else if (f_character == "}" and !ic_next_is_not_special) { - p_text.remove(trick_check_pos, 1); - } + p_text_escaped.insert(check_pos_escaped, appendage); + check_pos_escaped += appendage.size(); + } - // Orange inline colourisation. - else if (f_character == "|" and !ic_next_is_not_special) { - if (!ic_colour_stack.empty()) { - if (ic_colour_stack.top() == INLINE_ORANGE) { - ic_colour_stack.pop(); - p_text.remove(trick_check_pos, 1); - } - else { - ic_colour_stack.push(INLINE_ORANGE); - p_text.remove(trick_check_pos, 1); - } - } - else { - ic_colour_stack.push(INLINE_ORANGE); - p_text.remove(trick_check_pos, 1); - } - } + // Current issue: does not properly escape html stuff. + // Solution: probably parse p_text and export into a different string + // separately, perform some mumbo jumbo to properly adjust string indexes. + while (check_pos < p_text.size()) { + QString f_rest = p_text.right(p_text.size() - check_pos); + QTextBoundaryFinder tbf(QTextBoundaryFinder::Grapheme, f_rest); + QString f_character; + int f_char_length; - // Blue inline colourisation. - else if (f_character == "(" and !ic_next_is_not_special) { - ic_colour_stack.push(INLINE_BLUE); - trick_check_pos++; - } - else if (f_character == ")" and !ic_next_is_not_special and - !ic_colour_stack.empty()) { - if (ic_colour_stack.top() == INLINE_BLUE) { - ic_colour_stack.pop(); - trick_check_pos++; - } - else { + tbf.toNextBoundary(); + + if (tbf.position() == -1) + f_character = f_rest; + else + f_character = f_rest.left(tbf.position()); + + // if (f_character == "&") //oh shit it's probably an escaped html + // { + // //Skip escaped chars like you would graphemes + // QRegularExpression re("&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});", + // QRegularExpression::CaseInsensitiveOption); QRegularExpressionMatch + // match = re.match(f_rest); if (match.hasMatch()) //OH SHIT IT IS, + // PANIC, PANIC + // { + // f_character = match.captured(0); //Phew, we solved the big problem + // here. + // } + // } + + if (html) + f_character = f_character.toHtmlEscaped(); + + f_char_length = f_character.length(); + + bool color_update = false; + bool is_end = false; + bool skip = false; + + if (!ic_next_is_not_special) { + if (f_character == "\\") { ic_next_is_not_special = true; + skip = true; } - } - - // Grey inline colourisation. - else if (f_character == "[" and !ic_next_is_not_special) { - ic_colour_stack.push(INLINE_GREY); - trick_check_pos++; - } - else if (f_character == "]" and !ic_next_is_not_special and - !ic_colour_stack.empty()) { - if (ic_colour_stack.top() == INLINE_GREY) { - ic_colour_stack.pop(); - trick_check_pos++; + // Nothing related to colors here + else if (f_character == "{" || + f_character == + "}") //|| f_character == "@" || f_character == "$") + { + skip = true; } + // Parse markdown colors else { - ic_next_is_not_special = true; - } - } + for (int c = 0; c < max_colors; ++c) { + // Clear the stored optimization information + QString markdown_start = color_markdown_start_list.at(c); + QString markdown_end = color_markdown_end_list.at(c); + if (html) { + markdown_start = markdown_start.toHtmlEscaped(); + markdown_end = markdown_end.toHtmlEscaped(); + } + bool markdown_remove = color_markdown_remove_list.at(c); + if (markdown_start.isEmpty()) // Not defined + continue; - // Green inline colourisation. - else if (f_character == "`" and !ic_next_is_not_special) { - if (!ic_colour_stack.empty()) { - if (ic_colour_stack.top() == INLINE_GREEN) { - ic_colour_stack.pop(); - p_text.remove(trick_check_pos, 1); + if (markdown_end.isEmpty() || + markdown_end == markdown_start) //"toggle switch" type + { + if (f_character == markdown_start) { + if (html) { + if (!ic_color_stack.empty() && ic_color_stack.top() == c && + default_color != c) { + ic_color_stack.pop(); // Cease our coloring + is_end = true; + } + else { + ic_color_stack.push(c); // Begin our coloring + } + color_update = true; + } + skip = markdown_remove; + break; // Prevent it from looping forward for whatever reason + } + } + else if (f_character == markdown_start || + (f_character == markdown_end && !ic_color_stack.empty() && + ic_color_stack.top() == c)) { + if (html) { + if (f_character == markdown_end) { + ic_color_stack.pop(); // Cease our coloring + is_end = true; + } + else if (f_character == markdown_start) { + ic_color_stack.push(c); // Begin our coloring + } + color_update = true; + } + skip = markdown_remove; + break; // Prevent it from looping forward for whatever reason + } } - else { - ic_colour_stack.push(INLINE_GREEN); - p_text.remove(trick_check_pos, 1); + // Parse the newest color stack + if (color_update && (target_pos <= -1 || check_pos < target_pos)) { + if (!ic_next_is_not_special) { + QString appendage = ""; + + if (!ic_color_stack.empty()) + appendage += + ""; + + if (is_end && !skip) { + p_text_escaped.insert(check_pos_escaped, + f_character); // Add that char right now + check_pos_escaped += + f_char_length; // So the closing char is captured too + skip = true; + } + p_text_escaped.insert(check_pos_escaped, appendage); + check_pos_escaped += appendage.size(); + } } } - else { - ic_colour_stack.push(INLINE_GREEN); - p_text.remove(trick_check_pos, 1); - } } else { - trick_check_pos++; + if (f_character == "n") // \n, that's a line break son + { + QString appendage = "
"; + if (!html) { + // actual newline commented out + // appendage = "\n"; + // size = 1; //yeah guess what \n is a "single character" + // apparently + appendage = "\\n "; // visual representation of a newline + } + p_text_escaped.insert(check_pos_escaped, appendage); + check_pos_escaped += appendage.size(); + skip = true; + } + if (f_character == "s" || f_character == "f") // screenshake/flash + skip = true; + ic_next_is_not_special = false; } + + // Make all chars we're not supposed to see invisible + if (target_pos > -1 && check_pos == target_pos) { + QString appendage = ""; + if (!ic_color_stack.empty()) { + if (!is_end) // Was our last coloring char ending the color stack or nah + { + // God forgive me for my transgressions but I have refactored this + // whole thing about 25 times and having to refactor it again to more + // elegantly support this will finally make me go insane. + color_is_talking = + color_markdown_talking_list.at(ic_color_stack.top()); + } + + // Clean it up, we're done here + while (!ic_color_stack.empty()) + ic_color_stack.pop(); + + appendage += "
"; + } + ic_color_stack.push( + -1); // Dummy colorstack push for maximum appendage + appendage += ""; + p_text_escaped.insert(check_pos_escaped, appendage); + check_pos_escaped += appendage.size(); + } + if (!skip) { + p_text_escaped.insert(check_pos_escaped, f_character); + check_pos_escaped += f_char_length; + } + check_pos += 1; } - return p_text; + if (!ic_color_stack.empty() && html) { + p_text_escaped.append(""); + } + + if (html) { + // Example: https://regex101.com/r/oL4nM9/37 - this replaces + // excessive/trailing/etc. whitespace with non-breaking space. I WOULD use + // white-space: pre; stylesheet tag, but for whataver reason it doesn't work + // no matter where I try it. If somoene else can get that piece of HTML + // memery to work, please do. + p_text_escaped.replace(QRegularExpression("^\\s|(?<=\\s)\\s"), " "); + if (!align.isEmpty()) + p_text_escaped.append("
"); + } + + return p_text_escaped; } -void Courtroom::append_ic_text(QString p_text, QString p_name, - bool is_songchange) +void Courtroom::append_ic_text(QString p_text, QString p_name, QString p_action) { QTextCharFormat bold; QTextCharFormat normal; @@ -1779,8 +2469,9 @@ void Courtroom::append_ic_text(QString p_text, QString p_name, const QTextCursor old_cursor = ui_ic_chatlog->textCursor(); const int old_scrollbar_value = ui_ic_chatlog->verticalScrollBar()->value(); - if (!is_songchange) - p_text = filter_ic_text(p_text); + if (p_action == "") + p_text = filter_ic_text(p_text, ao_app->is_colorlog_enabled(), -1, + m_chatmessage[TEXT_COLOR].toInt()); if (log_goes_downwards) { const bool is_scrolled_down = @@ -1796,12 +2487,13 @@ void Courtroom::append_ic_text(QString p_text, QString p_name, ui_ic_chatlog->textCursor().insertText('\n' + p_name, bold); } - if (is_songchange) { - ui_ic_chatlog->textCursor().insertText(" has played a song: ", normal); + if (p_action != "") { + ui_ic_chatlog->textCursor().insertText(" " + p_action + ": ", normal); ui_ic_chatlog->textCursor().insertText(p_text + ".", italics); } else { - ui_ic_chatlog->textCursor().insertText(p_text, normal); + ui_ic_chatlog->textCursor().insertText(": ", normal); + ui_ic_chatlog->textCursor().insertHtml(p_text); } // If we got too many blocks in the current log, delete some from the top. @@ -1811,8 +2503,6 @@ void Courtroom::append_ic_text(QString p_text, QString p_name, ui_ic_chatlog->textCursor().select(QTextCursor::BlockUnderCursor); ui_ic_chatlog->textCursor().removeSelectedText(); ui_ic_chatlog->textCursor().deleteChar(); - // qDebug() << ui_ic_chatlog->document()->blockCount() << " < " << - // log_maximum_blocks; } if (old_cursor.hasSelection() || !is_scrolled_down) { @@ -1837,11 +2527,12 @@ void Courtroom::append_ic_text(QString p_text, QString p_name, ui_ic_chatlog->textCursor().insertText(p_name, bold); - if (is_songchange) { - ui_ic_chatlog->textCursor().insertText(" has played a song: ", normal); + if (p_action != "") { + ui_ic_chatlog->textCursor().insertText(" " + p_action + ": ", normal); ui_ic_chatlog->textCursor().insertText(p_text + "." + '\n', italics); } else { + ui_ic_chatlog->textCursor().insertText(": ", normal); ui_ic_chatlog->textCursor().insertText(p_text + '\n', normal); } @@ -1853,8 +2544,6 @@ void Courtroom::append_ic_text(QString p_text, QString p_name, ui_ic_chatlog->textCursor().select(QTextCursor::BlockUnderCursor); ui_ic_chatlog->textCursor().removeSelectedText(); ui_ic_chatlog->textCursor().deletePreviousChar(); - // qDebug() << ui_ic_chatlog->document()->blockCount() << " < " << - // log_maximum_blocks; } if (old_cursor.hasSelection() || !is_scrolled_up) { @@ -1882,7 +2571,7 @@ void Courtroom::play_preanim(bool noninterrupting) // 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 sfx_delay = m_chatmessage[SFX_DELAY].toInt() * time_mod; int preanim_duration; @@ -1894,7 +2583,7 @@ void Courtroom::play_preanim(bool noninterrupting) sfx_delay_timer->start(sfx_delay); QString anim_to_find = ao_app->get_image_suffix(ao_app->get_character_path(f_char, f_preanim)); - if (!file_exists(anim_to_find) || preanim_duration < 0) { + if (!file_exists(anim_to_find)) { if (noninterrupting) anim_state = 4; else @@ -1924,8 +2613,6 @@ void Courtroom::preanim_done() handle_chatmessage_3(); } -void Courtroom::realization_done() { ui_vp_realization->hide(); } - void Courtroom::start_chat_ticking() { // we need to ensure that the text isn't already ticking because this function @@ -1933,44 +2620,56 @@ void Courtroom::start_chat_ticking() if (text_state != 0) return; - if (m_chatmessage[REALIZATION] == "1") { - realization_timer->start(60); - ui_vp_realization->show(); + if (m_chatmessage[EFFECTS] != "") { + QStringList fx_list = m_chatmessage[EFFECTS].split("|"); + QString fx = fx_list[0]; + QString fx_sound; + QString fx_folder; + + if (fx_list.length() > 1) + fx_sound = fx_list[1]; + + if (fx_list.length() > 2) { + fx_folder = fx_list[1]; + fx_sound = fx_list[2]; + } + + this->do_effect(fx, fx_sound, m_chatmessage[CHAR_NAME], fx_folder); + } + else if (m_chatmessage[REALIZATION] == "1") { + this->do_flash(); sfx_player->play(ao_app->get_custom_realization(m_chatmessage[CHAR_NAME])); } - - ui_vp_message->clear(); - set_text_color(); - rainbow_counter = 0; - if (chatmessage_is_empty) { // since the message is empty, it's technically done ticking text_state = 2; return; } - // At this point, we'd do well to clear the inline colour stack. - // This stops it from flowing into next messages. - while (!inline_colour_stack.empty()) { - inline_colour_stack.pop(); + ui_vp_chatbox->show(); + ui_vp_message->show(); + + if (!is_additive) { + ui_vp_message->clear(); + real_tick_pos = 0; + additive_previous = ""; } - ui_vp_chatbox->show(); - tick_pos = 0; - blip_pos = 0; - - // Just in case we somehow got inline blue text left over from a previous - // message, let's set it to false. - inline_blue_depth = 0; + blip_ticker = 0; // At the start of every new message, we set the text speed to the default. current_display_speed = 3; - chat_tick_timer->start(message_display_speed[current_display_speed]); + chat_tick_timer->start(0); // Display the first char right away QString f_gender = ao_app->get_gender(m_chatmessage[CHAR_NAME]); - blip_player->set_blips(ao_app->get_sfx_suffix("sfx-blip" + f_gender)); + blip_player->set_blips(f_gender); + + int emote_mod = m_chatmessage[EMOTE_MOD].toInt(); // text meme bonanza + if ((emote_mod == 0 || emote_mod == 5) && m_chatmessage[SCREENSHAKE] == "1") { + this->do_screenshake(); + } // means text is currently ticking text_state = 1; @@ -1978,362 +2677,261 @@ void Courtroom::start_chat_ticking() void Courtroom::chat_tick() { - // note: this is called fairly often(every 60 ms when char is talking) + // note: this is called fairly often // do not perform heavy operations here QString f_message = m_chatmessage[MESSAGE]; - f_message.remove(0, tick_pos); // Due to our new text speed system, we always need to stop the timer now. chat_tick_timer->stop(); - // Stops blips from playing when we have a formatting option. - bool formatting_char = false; - - // If previously, we have detected that the message is centered, now - // is the time to remove those two tildes at the start. - if (message_is_centered) { - f_message.remove(0, 2); - } - - if (f_message.size() == 0) { + if (tick_pos >= f_message.size()) { text_state = 2; - if (anim_state != 4) { + if (anim_state < 3) { anim_state = 3; ui_vp_player_char->play_idle(m_chatmessage[CHAR_NAME], m_chatmessage[EMOTE]); } + QString f_char = m_chatmessage[CHAR_NAME]; + QString f_custom_theme = ao_app->get_chat(f_char); + ui_vp_chat_arrow->play( + "chat_arrow", f_char, + f_custom_theme); // Chat stopped being processed, indicate that. + additive_previous = + additive_previous + + filter_ic_text(f_message, true, -1, m_chatmessage[TEXT_COLOR].toInt()); + real_tick_pos = ui_vp_message->toPlainText().size(); + return; } - else { - QTextBoundaryFinder tbf(QTextBoundaryFinder::Grapheme, f_message); - QString f_character; - int f_char_length; + // Stops blips from playing when we have a formatting option. + bool formatting_char = false; - tbf.toNextBoundary(); + QString f_rest = f_message; - if (tbf.position() == -1) - f_character = f_message; - else - f_character = f_message.left(tbf.position()); + // Alignment characters + if (tick_pos < 2) { + if (f_rest.startsWith("~~")) { + tick_pos = f_rest.indexOf("~~"); + f_rest.remove(tick_pos, 2); + tick_pos += 2; + } + else if (f_rest.startsWith("~>")) { + tick_pos = f_rest.indexOf("~>"); + f_rest.remove(tick_pos, 2); + tick_pos += 2; + } + else if (f_rest.startsWith("<>")) { + tick_pos = f_rest.indexOf("<>"); + f_rest.remove(tick_pos, 2); + tick_pos += 2; + } + } + f_rest.remove(0, tick_pos); + QTextBoundaryFinder tbf(QTextBoundaryFinder::Grapheme, f_rest); + QString f_character; + int f_char_length; - f_char_length = f_character.length(); - f_character = f_character.toHtmlEscaped(); + tbf.toNextBoundary(); - if (f_character == " ") - ui_vp_message->insertPlainText(" "); + if (tbf.position() == -1) + f_character = f_rest; + else + f_character = f_rest.left(tbf.position()); - // Escape character. - else if (f_character == "\\" and !next_character_is_not_special) { + f_char_length = f_character.length(); + tick_pos += f_char_length; + + // Escape character. + if (!next_character_is_not_special) { + if (f_character == "\\") { next_character_is_not_special = true; formatting_char = true; } // Text speed modifier. - else if (f_character == "{" and !next_character_is_not_special) { + else if (f_character == "{") { // ++, because it INCREASES delay! current_display_speed++; formatting_char = true; } - else if (f_character == "}" and !next_character_is_not_special) { + else if (f_character == "}") { current_display_speed--; formatting_char = true; } - // Orange inline colourisation. - else if (f_character == "|" and !next_character_is_not_special) { - if (!inline_colour_stack.empty()) { - if (inline_colour_stack.top() == INLINE_ORANGE) { - inline_colour_stack.pop(); - } - else { - inline_colour_stack.push(INLINE_ORANGE); - } - } - else { - inline_colour_stack.push(INLINE_ORANGE); - } - formatting_char = true; - } - - // Blue inline colourisation. - else if (f_character == "(" and !next_character_is_not_special) { - inline_colour_stack.push(INLINE_BLUE); - ui_vp_message->insertHtml("" + f_character + ""); - - // Increase how deep we are in inline blues. - inline_blue_depth++; - - // Here, we check if the entire message is blue. - // If it isn't, we stop talking. - if (!entire_message_is_blue and anim_state != 4) { - QString f_char = m_chatmessage[CHAR_NAME]; - QString f_emote = m_chatmessage[EMOTE]; - ui_vp_player_char->play_idle(f_char, f_emote); - } - } - else if (f_character == ")" and !next_character_is_not_special and - !inline_colour_stack.empty()) { - if (inline_colour_stack.top() == INLINE_BLUE) { - inline_colour_stack.pop(); - ui_vp_message->insertHtml("" + f_character + ""); - - // Decrease how deep we are in inline blues. - // Just in case, we do a check if we're above zero, but we should be. - if (inline_blue_depth > 0) { - inline_blue_depth--; - // Here, we check if the entire message is blue. - // If it isn't, we start talking if we have completely climbed out of - // inline blues. - if (!entire_message_is_blue) { - // We should only go back to talking if we're out of inline blues, - // not during a non. int. pre, and not on the last character. - if (inline_blue_depth == 0 and anim_state != 4 and - !(tick_pos + 1 >= f_message.size())) { - QString f_char = m_chatmessage[CHAR_NAME]; - QString f_emote = m_chatmessage[EMOTE]; - ui_vp_player_char->play_talking(f_char, f_emote); - } - } - } - } - else { - next_character_is_not_special = true; - tick_pos -= f_char_length; - } - } - - // Grey inline colourisation. - else if (f_character == "[" and !next_character_is_not_special) { - inline_colour_stack.push(INLINE_GREY); - ui_vp_message->insertHtml("" + - f_character + ""); - } - else if (f_character == "]" and !next_character_is_not_special and - !inline_colour_stack.empty()) { - if (inline_colour_stack.top() == INLINE_GREY) { - inline_colour_stack.pop(); - ui_vp_message->insertHtml("" + f_character + ""); - } - else { - next_character_is_not_special = true; - tick_pos -= f_char_length; - } - } - - // Green inline colourisation. - else if (f_character == "`" and !next_character_is_not_special) { - if (!inline_colour_stack.empty()) { - if (inline_colour_stack.top() == INLINE_GREEN) { - inline_colour_stack.pop(); - formatting_char = true; - } - else { - inline_colour_stack.push(INLINE_GREEN); - formatting_char = true; - } - } - else { - inline_colour_stack.push(INLINE_GREEN); - formatting_char = true; - } - } else { - next_character_is_not_special = false; - if (!inline_colour_stack.empty()) { - switch (inline_colour_stack.top()) { - case INLINE_ORANGE: - ui_vp_message->insertHtml( - "" + - f_character + ""); - break; - case INLINE_BLUE: - ui_vp_message->insertHtml( - "" + f_character + ""); - break; - case INLINE_GREEN: - ui_vp_message->insertHtml( - "" + f_character + ""); - break; - case INLINE_GREY: - ui_vp_message->insertHtml("" + f_character + ""); + // Parse markdown colors + for (int c = 0; c < max_colors; ++c) { + QString markdown_start = color_markdown_start_list.at(c); + QString markdown_end = color_markdown_end_list.at(c); + bool markdown_remove = color_markdown_remove_list.at(c); + if (markdown_start.isEmpty()) + continue; + + if (f_character == markdown_start || f_character == markdown_end) { + if (markdown_remove) + formatting_char = true; break; } } - else { - if (m_chatmessage[TEXT_COLOR].toInt() == RAINBOW) { - QString html_color; - - switch (rainbow_counter) { - case 0: - html_color = get_text_color(QString::number(RED)).name(); - break; - case 1: - html_color = get_text_color(QString::number(ORANGE)).name(); - break; - case 2: - html_color = get_text_color(QString::number(YELLOW)).name(); - break; - case 3: - html_color = get_text_color(QString::number(GREEN)).name(); - break; - default: - html_color = get_text_color(QString::number(BLUE)).name(); - rainbow_counter = -1; - } - - ++rainbow_counter; - - ui_vp_message->insertHtml("" + - f_character + ""); - } - else - ui_vp_message->insertHtml(f_character); - } - - if (message_is_centered) { - ui_vp_message->setAlignment(Qt::AlignCenter); - } - else { - ui_vp_message->setAlignment(Qt::AlignLeft); - } - } - - QScrollBar *scroll = ui_vp_message->verticalScrollBar(); - scroll->setValue(scroll->maximum()); - - if (blank_blip) - qDebug() << "blank_blip found true"; - - if (f_character != ' ' || blank_blip) { - - if (blip_pos % blip_rate == 0 && !formatting_char) { - blip_pos = 0; - blip_player->blip_tick(); - } - - ++blip_pos; - } - - tick_pos += f_char_length; - - // Restart the timer, but according to the newly set speeds, if there were - // any. Keep the speed at bay. - if (current_display_speed < 0) { - current_display_speed = 0; - } - - if (current_display_speed > 6) { - current_display_speed = 6; - } - - // If we had a formatting char, we shouldn't wait so long again, as it won't - // appear! - if (formatting_char) { - chat_tick_timer->start(1); - } - else { - chat_tick_timer->start(message_display_speed[current_display_speed]); } } -} + else { + if (f_character == "n") + formatting_char = true; // it's a newline + if (f_character == "s") // Screenshake. + { + this->do_screenshake(); + formatting_char = true; + } + if (f_character == "f") // Flash. + { + this->do_flash(); + formatting_char = true; + } + next_character_is_not_special = false; + } -void Courtroom::show_testimony() -{ - if (!testimony_in_progress || m_chatmessage[SIDE] != "wit") - return; + if ((message_display_speed[current_display_speed] <= 0 && + tick_pos < f_message.size() - 1) || + formatting_char) { + chat_tick_timer->start(0); // Don't bother rendering anything out as we're + // doing the SPEED. (there's latency otherwise) + if (!formatting_char || f_character == "n" || f_character == "f" || + f_character == "s") + real_tick_pos += f_char_length; // Adjust the tick position for the + // scrollbar convenience + } + else { + int msg_delay = message_display_speed[current_display_speed]; + // Do the colors, gradual showing, etc. in here + ui_vp_message->setHtml(additive_previous + + filter_ic_text(f_message, true, tick_pos, + m_chatmessage[TEXT_COLOR].toInt())); - ui_vp_testimony->show(); + // This should always be done AFTER setHtml. Scroll the chat window with the + // text. - testimony_show_timer->start(testimony_show_time); -} + // Make the cursor follow the message + QTextCursor cursor = ui_vp_message->textCursor(); + cursor.setPosition(real_tick_pos); + ui_vp_message->setTextCursor(cursor); + real_tick_pos += f_char_length; -void Courtroom::hide_testimony() -{ - ui_vp_testimony->hide(); + ui_vp_message->ensureCursorVisible(); - if (!testimony_in_progress) - return; + // Keep the speed at bay. + if (current_display_speed < 0) + current_display_speed = 0; + else if (current_display_speed > 6) + current_display_speed = 6; - testimony_hide_timer->start(testimony_hide_time); + // Blip player and real tick pos ticker + if (!formatting_char && (f_character != ' ' || blank_blip)) { + if (blip_ticker % blip_rate == 0) { + blip_player->blip_tick(); + } + ++blip_ticker; + } + + // Punctuation delayer + if (punctuation_chars.contains(f_character)) { + msg_delay *= punctuation_modifier; + } + + // If this color is talking + if (color_is_talking && anim_state != 2 && + anim_state < + 4) // Set it to talking as we're not on that already (though we have + // to avoid interrupting a non-interrupted preanim) + { + ui_vp_player_char->stop(); + ui_vp_player_char->play_talking(m_chatmessage[CHAR_NAME], + m_chatmessage[EMOTE]); + anim_state = 2; + } + else if (!color_is_talking && anim_state < 3 && + anim_state != 3) // Set it to idle as we're not on that already + { + ui_vp_player_char->stop(); + ui_vp_player_char->play_idle(m_chatmessage[CHAR_NAME], + m_chatmessage[EMOTE]); + anim_state = 3; + } + // Continue ticking + chat_tick_timer->start(msg_delay); + } } void Courtroom::play_sfx() { QString sfx_name = m_chatmessage[SFX_NAME]; - + if (m_chatmessage[SCREENSHAKE] == + "1") // Screenshake dependant on preanim sfx delay meme + { + this->do_screenshake(); + } if (sfx_name == "1") return; - sfx_player->play(ao_app->get_sfx_suffix(sfx_name)); + sfx_player->play(sfx_name); + if (ao_app->get_looping_sfx()) + sfx_player->set_looping( + ao_app->get_sfx_looping(current_char, current_emote) == "1"); } -void Courtroom::set_scene() +void Courtroom::set_scene(QString f_desk_mod, QString f_side) { - 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]; - if (f_side == "def") { + if (f_side == "def" && file_exists(ao_app->get_image_suffix( + ao_app->get_background_path("defenseempty")))) { f_background = "defenseempty"; - if (is_ao2_bg) - f_desk_image = "defensedesk"; - else - f_desk_image = "bancodefensa"; + f_desk_image = "defensedesk"; } - else if (f_side == "pro") { + else if (f_side == "pro" && + file_exists(ao_app->get_image_suffix( + ao_app->get_background_path("prosecutorempty")))) { f_background = "prosecutorempty"; - if (is_ao2_bg) - f_desk_image = "prosecutiondesk"; - else - f_desk_image = "bancoacusacion"; + f_desk_image = "prosecutiondesk"; } - else if (f_side == "jud") { + else if (f_side == "jud" && file_exists(ao_app->get_image_suffix( + ao_app->get_background_path("judgestand")))) { f_background = "judgestand"; f_desk_image = "judgedesk"; } - else if (f_side == "hld") { + else if (f_side == "hld" && + file_exists(ao_app->get_image_suffix( + ao_app->get_background_path("helperstand")))) { f_background = "helperstand"; f_desk_image = "helperdesk"; } - else if (f_side == "hlp") { + else if (f_side == "hlp" && + file_exists(ao_app->get_image_suffix( + ao_app->get_background_path("prohelperstand")))) { f_background = "prohelperstand"; f_desk_image = "prohelperdesk"; } - else if (f_side == "jur" && - (file_exists(ao_app->get_background_path("jurystand.png")) || - file_exists(ao_app->get_background_path("jurystand.gif")))) { + else if (f_side == "jur" && file_exists(ao_app->get_image_suffix( + ao_app->get_background_path("jurystand")))) { f_background = "jurystand"; f_desk_image = "jurydesk"; } else if (f_side == "sea" && - (file_exists(ao_app->get_background_path("seancestand.png")) || - file_exists(ao_app->get_background_path("seancestand.gif")))) { + file_exists(ao_app->get_image_suffix( + ao_app->get_background_path("seancestand")))) { f_background = "seancestand"; f_desk_image = "seancedesk"; } - else { - if (is_ao2_bg) - f_desk_image = "stand"; - else - f_desk_image = "estrado"; + + if (file_exists(ao_app->get_image_suffix( + ao_app->get_background_path(f_side)))) // Unique pos path + { + f_background = f_side; + f_desk_image = f_side + "_overlay"; } ui_vp_background->set_image(f_background); @@ -2346,47 +2944,10 @@ void Courtroom::set_scene() ui_vp_desk->hide(); ui_vp_legacy_desk->hide(); } - else if (is_ao2_bg || - (f_side == "jud" || f_side == "hld" || f_side == "hlp")) { + else { 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(); - } - } -} - -void Courtroom::set_text_color() -{ - QColor textcolor = ao_app->get_chat_color( - m_chatmessage[TEXT_COLOR], ao_app->get_chat(m_chatmessage[CHAR_NAME])); - - ui_vp_message->setTextBackgroundColor(QColor(0, 0, 0, 0)); - ui_vp_message->setTextColor(textcolor); - - QString style = "background-color: rgba(0, 0, 0, 0);"; - style.append("color: rgb("); - style.append(QString::number(textcolor.red())); - style.append(", "); - style.append(QString::number(textcolor.green())); - style.append(", "); - style.append(QString::number(textcolor.blue())); - style.append(")"); - - ui_vp_message->setStyleSheet(style); -} - -QColor Courtroom::get_text_color(QString color) -{ - return ao_app->get_chat_color(color, - ao_app->get_chat(m_chatmessage[CHAR_NAME])); } void Courtroom::set_ip_list(QString p_list) @@ -2409,7 +2970,7 @@ void Courtroom::set_mute(bool p_muted, int p_cid) } ui_muted->resize(ui_ic_chat_message->width(), ui_ic_chat_message->height()); - ui_muted->set_image("muted.png"); + ui_muted->set_image("muted"); is_muted = p_muted; ui_ic_chat_message->setEnabled(!p_muted); @@ -2434,32 +2995,74 @@ void Courtroom::handle_song(QStringList *p_contents) return; QString f_song = f_contents.at(0); - QString f_song_clear = f_song; - f_song_clear = f_song_clear.left(f_song_clear.lastIndexOf(".")); + QString f_song_clear = f_song.left(f_song.lastIndexOf(".")); + f_song_clear = f_song_clear.right(f_song_clear.length() - + (f_song_clear.lastIndexOf("/") + 1)); int n_char = f_contents.at(1).toInt(); + bool looping = true; + int channel = 0; + int effect_flags = 0; if (n_char < 0 || n_char >= char_list.size()) { - music_player->play(f_song); + int channel = 0; + if (p_contents->length() > 3 && p_contents->at(3) != "-1") + looping = false; + + if (p_contents->length() > + 4) // eyyy we want to change this song's CHANNEL huh + channel = p_contents->at(4).toInt(); // let the music player handle it if + // it's bigger than the channel list + + if (p_contents->length() > 5) // Flags provided to us by server such as Fade + // In, Fade Out, Sync Pos etc. + { + effect_flags = p_contents->at(5).toInt(); + } + + music_player->play(f_song, channel, looping, effect_flags); + if (channel == 0) + ui_music_name->setText(f_song_clear); } else { QString str_char = char_list.at(n_char).name; QString str_show = char_list.at(n_char).name; if (p_contents->length() > 2) { - str_show = p_contents->at(2); + if (p_contents->at(2) != "") { + str_show = p_contents->at(2); + } + } + if (p_contents->length() > 3 && p_contents->at(3) != "-1") { + // I am really confused why "-1" is "loop this song" and why anything else + // passes as "don't loop" (if we even have this length) but alright + looping = false; + } + if (p_contents->length() > + 4) // eyyy we want to change this song's CHANNEL huh + channel = p_contents->at(4).toInt(); // let the music player handle it if + // it's bigger than the channel list + + if (p_contents->length() > 5) // Flags provided to us by server such as Fade + // In, Fade Out, Sync Pos etc. + { + effect_flags = p_contents->at(5).toInt(); } if (!mute_map.value(n_char)) { chatlogpiece *temp = new chatlogpiece(str_char, str_show, f_song, true); ic_chatlog_history.append(*temp); + ao_app->append_to_file(temp->get_full(), ao_app->log_filename, true); while (ic_chatlog_history.size() > log_maximum_blocks && log_maximum_blocks > 0) { ic_chatlog_history.removeFirst(); } - append_ic_text(f_song_clear, str_show, true); - music_player->play(f_song); + append_ic_text(f_song_clear, str_show, "has played a song"); + + music_player->play(f_song, channel, looping, effect_flags); + if (channel == 0) + ui_music_name->setText(f_song_clear); } } } @@ -2471,26 +3074,25 @@ void Courtroom::handle_wtce(QString p_wtce, int variant) // witness testimony if (p_wtce == "testimony1") { sfx_player->play(ao_app->get_sfx("witness_testimony")); - ui_vp_wtce->play("witnesstestimony"); - testimony_in_progress = true; - show_testimony(); + ui_vp_wtce->play("witnesstestimony", "", "", 1500); + ui_vp_testimony->play("testimony"); } // cross examination else if (p_wtce == "testimony2") { sfx_player->play(ao_app->get_sfx("cross_examination")); - ui_vp_wtce->play("crossexamination"); - testimony_in_progress = false; + ui_vp_wtce->play("crossexamination", "", "", 1500); + ui_vp_testimony->stop(); } else if (p_wtce == "judgeruling") { if (variant == 0) { sfx_player->play(ao_app->get_sfx("not_guilty")); - ui_vp_wtce->play("notguilty"); - testimony_in_progress = false; + ui_vp_wtce->play("notguilty", "", "", 3000); + ui_vp_testimony->stop(); } else if (variant == 1) { sfx_player->play(ao_app->get_sfx("guilty")); - ui_vp_wtce->play("guilty"); - testimony_in_progress = false; + ui_vp_wtce->play("guilty", "", "", 3000); + ui_vp_testimony->stop(); } } } @@ -2501,12 +3103,11 @@ void Courtroom::set_hp_bar(int p_bar, int p_state) return; if (p_bar == 1) { - ui_defense_bar->set_image("defensebar" + QString::number(p_state) + ".png"); + ui_defense_bar->set_image("defensebar" + QString::number(p_state)); defense_bar_state = p_state; } else if (p_bar == 2) { - ui_prosecution_bar->set_image("prosecutionbar" + QString::number(p_state) + - ".png"); + ui_prosecution_bar->set_image("prosecutionbar" + QString::number(p_state)); prosecution_bar_state = p_state; } } @@ -2538,7 +3139,7 @@ void Courtroom::toggle_judge_buttons(bool is_on) void Courtroom::mod_called(QString p_ip) { ui_server_chatlog->append(p_ip); - if (ui_guard->isChecked()) { + if (!ui_guard->isChecked()) { modcall_player->play(ao_app->get_sfx("mod_call")); ao_app->alert(this); } @@ -2575,24 +3176,11 @@ void Courtroom::on_ooc_return_pressed() toggle_judge_buttons(false); } } - else if (ooc_message.startsWith("/login")) { - ui_guard->show(); - append_server_chatmessage("CLIENT", "You were granted the Guard button.", - "1"); - } - 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; - append_server_chatmessage("CLIENT", "This does nohing, but there you go.", - "1"); - return; - } else if (ooc_message.startsWith("/settings")) { ui_ooc_chat_message->clear(); ao_app->call_settings_menu(); - append_server_chatmessage("CLIENT", "You opened the settings menu.", "1"); + append_server_chatmessage("CLIENT", tr("You opened the settings menu."), + "1"); return; } else if (ooc_message.startsWith("/pair")) { @@ -2611,14 +3199,14 @@ void Courtroom::on_ooc_return_pressed() } else { other_charid = -1; - append_server_chatmessage("CLIENT", - "You are no longer paired with anyone.", "1"); + append_server_chatmessage( + "CLIENT", tr("You are no longer paired with anyone."), "1"); } } else { append_server_chatmessage("CLIENT", - "Are you sure you typed that well? The char ID " - "could not be recognised.", + tr("Are you sure you typed that well? The char " + "ID could not be recognised."), "1"); } return; @@ -2631,36 +3219,36 @@ void Courtroom::on_ooc_return_pressed() int off = ooc_message.toInt(&ok); if (ok) { if (off >= -100 && off <= 100) { - offset_with_pair = off; - QString msg = "You have set your offset to "; + char_offset = off; + QString msg = tr("You have set your offset to "); msg.append(QString::number(off)); msg.append("%."); append_server_chatmessage("CLIENT", msg, "1"); } else { append_server_chatmessage( - "CLIENT", "Your offset must be between -100% and 100%!", "1"); + "CLIENT", tr("Your offset must be between -100% and 100%!"), "1"); } } else { - append_server_chatmessage("CLIENT", "That offset does not look like one.", - "1"); + append_server_chatmessage("CLIENT", + tr("That offset does not look like one."), "1"); } return; } else if (ooc_message.startsWith("/switch_am")) { - append_server_chatmessage("CLIENT", - "You switched your music and area list.", "1"); + append_server_chatmessage( + "CLIENT", tr("You switched your music and area list."), "1"); on_switch_area_music_clicked(); ui_ooc_chat_message->clear(); return; } else if (ooc_message.startsWith("/enable_blocks")) { - append_server_chatmessage( - "CLIENT", - "You have forcefully enabled features that the server may not support. " - "You may not be able to talk IC, or worse, because of this.", - "1"); + append_server_chatmessage("CLIENT", + tr("You have forcefully enabled features that " + "the server may not support. You may not be " + "able to talk IC, or worse, because of this."), + "1"); ao_app->cccc_ic_support_enabled = true; ao_app->arup_enabled = true; ao_app->modcall_reason_enabled = true; @@ -2670,11 +3258,11 @@ void Courtroom::on_ooc_return_pressed() } else if (ooc_message.startsWith("/non_int_pre")) { if (ui_pre_non_interrupt->isChecked()) - append_server_chatmessage("CLIENT", - "Your pre-animations interrupt again.", "1"); + append_server_chatmessage( + "CLIENT", tr("Your pre-animations interrupt again."), "1"); else append_server_chatmessage( - "CLIENT", "Your pre-animations will not interrupt text.", "1"); + "CLIENT", tr("Your pre-animations will not interrupt text."), "1"); ui_pre_non_interrupt->setChecked(!ui_pre_non_interrupt->isChecked()); ui_ooc_chat_message->clear(); return; @@ -2685,7 +3273,7 @@ void Courtroom::on_ooc_return_pressed() if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) { append_server_chatmessage( - "CLIENT", "Couldn't open chatlog.txt to write into.", "1"); + "CLIENT", tr("Couldn't open chatlog.txt to write into."), "1"); ui_ooc_chat_message->clear(); return; } @@ -2698,7 +3286,8 @@ void Courtroom::on_ooc_return_pressed() file.close(); - append_server_chatmessage("CLIENT", "The IC chatlog has been saved.", "1"); + append_server_chatmessage("CLIENT", tr("The IC chatlog has been saved."), + "1"); ui_ooc_chat_message->clear(); return; } @@ -2710,9 +3299,9 @@ void Courtroom::on_ooc_return_pressed() QDir::current().mkdir("base/" + casefolder.dirName()); append_server_chatmessage( "CLIENT", - "You don't have a `base/cases/` folder! It was just made for you, " - "but seeing as it WAS just made for you, it's likely the case file " - "you're looking for can't be found in there.", + tr("You don't have a `base/cases/` folder! It was just made for you, " + "but seeing as it WAS just made for you, it's likely the case " + "file you're looking for can't be found in there."), "1"); ui_ooc_chat_message->clear(); return; @@ -2725,20 +3314,21 @@ void Courtroom::on_ooc_return_pressed() if (command.size() < 2) { append_server_chatmessage( "CLIENT", - "You need to give a filename to load (extension not needed)! Make " - "sure that it is in the `base/cases/` folder, and that it is a " - "correctly formatted ini.\nCases you can load: " + - caseslist.join(", "), + tr("You need to give a filename to load (extension not needed)! Make " + "sure that it is in the `base/cases/` folder, and that it is a " + "correctly formatted ini.\nCases you can load: %1") + .arg(caseslist.join(", ")), "1"); ui_ooc_chat_message->clear(); return; } if (command.size() > 2) { - append_server_chatmessage("CLIENT", - "Too many arguments to load a case! You only " - "need one filename, without extension.", - "1"); + append_server_chatmessage( + "CLIENT", + tr("Too many arguments to load a case! You only need one filename, " + "without extension."), + "1"); ui_ooc_chat_message->clear(); return; } @@ -2752,7 +3342,7 @@ void Courtroom::on_ooc_return_pressed() QString casestatus = casefile.value("status", "").value(); if (!caseauth.isEmpty()) - append_server_chatmessage("CLIENT", "Case made by " + caseauth + ".", + append_server_chatmessage("CLIENT", tr("Case made by %1.").arg(caseauth), "1"); if (!casedoc.isEmpty()) ao_app->send_server_packet(new AOPacket("CT#" + ui_ooc_chat_name->text() + @@ -2762,7 +3352,7 @@ void Courtroom::on_ooc_return_pressed() "#/status " + casestatus + "#%")); if (!cmdoc.isEmpty()) append_server_chatmessage( - "CLIENT", "Navigate to " + cmdoc + " for the CM doc.", "1"); + "CLIENT", tr("Navigate to %1 for the CM doc.").arg(cmdoc), "1"); for (int i = local_evidence_list.size() - 1; i >= 0; i--) { ao_app->send_server_packet( @@ -2786,7 +3376,7 @@ void Courtroom::on_ooc_return_pressed() } append_server_chatmessage( - "CLIENT", "Your case \"" + command[1] + "\" was loaded!", "1"); + "CLIENT", tr("Your case \"%1\" was loaded!").arg(command[1]), "1"); ui_ooc_chat_message->clear(); return; } @@ -2798,9 +3388,9 @@ void Courtroom::on_ooc_return_pressed() QDir::current().mkdir("base/" + casefolder.dirName()); append_server_chatmessage( "CLIENT", - "You don't have a `base/cases/` folder! It was just made for you, " - "but seeing as it WAS just made for you, it's likely that you " - "somehow deleted it.", + tr("You don't have a `base/cases/` folder! It was just made for you, " + "but seeing as it WAS just made for you, it's likely that you " + "somehow deleted it."), "1"); ui_ooc_chat_message->clear(); return; @@ -2813,8 +3403,8 @@ void Courtroom::on_ooc_return_pressed() if (command.size() < 3) { append_server_chatmessage( "CLIENT", - "You need to give a filename to save (extension not needed) and the " - "courtroom status!", + tr("You need to give a filename to save (extension not needed) and " + "the courtroom status!"), "1"); ui_ooc_chat_message->clear(); return; @@ -2823,8 +3413,8 @@ void Courtroom::on_ooc_return_pressed() if (command.size() > 3) { append_server_chatmessage( "CLIENT", - "Too many arguments to save a case! You only need a filename without " - "extension and the courtroom status!", + tr("Too many arguments to save a case! You only need a filename " + "without extension and the courtroom status!"), "1"); ui_ooc_chat_message->clear(); return; @@ -2836,7 +3426,7 @@ void Courtroom::on_ooc_return_pressed() casefile.setValue("doc", ""); casefile.setValue("status", command[2]); casefile.sync(); - for (int i = local_evidence_list.size() - 1; i >= 0; i--) { + for (int i = 0; i < local_evidence_list.size(); i++) { QString clean_evidence_dsc = local_evidence_list[i].description.replace( QRegularExpression("..."), ""); clean_evidence_dsc = clean_evidence_dsc.replace( @@ -2850,7 +3440,7 @@ void Courtroom::on_ooc_return_pressed() } casefile.sync(); append_server_chatmessage( - "CLIENT", "Succesfully saved, edit doc and cmdoc link on the ini!", + "CLIENT", tr("Succesfully saved, edit doc and cmdoc link on the ini!"), "1"); ui_ooc_chat_message->clear(); return; @@ -2877,67 +3467,405 @@ void Courtroom::on_ooc_toggle_clicked() if (server_ooc) { ui_ms_chatlog->show(); ui_server_chatlog->hide(); - ui_ooc_toggle->setText("Master"); + ui_ooc_toggle->setText(tr("Master")); server_ooc = false; } else { ui_ms_chatlog->hide(); ui_server_chatlog->show(); - ui_ooc_toggle->setText("Server"); + ui_ooc_toggle->setText(tr("Server")); server_ooc = true; } } +// Todo: multithread this due to some servers having large as hell music list void Courtroom::on_music_search_edited(QString p_text) { - // preventing compiler warnings - p_text += "a"; - list_music(); - list_areas(); + // Iterate through all QTreeWidgetItem items + if (!ui_music_list->isHidden()) { + QTreeWidgetItemIterator it(ui_music_list); + while (*it) { + (*it)->setHidden(p_text != ""); + ++it; + } + } + + if (!ui_area_list->isHidden()) { + QTreeWidgetItemIterator ait(ui_area_list); + while (*ait) { + (*ait)->setHidden(p_text != ""); + ++ait; + } + } + + if (p_text != "") { + if (!ui_music_list->isHidden()) { + // Search in metadata + QList clist = ui_music_list->findItems( + ui_music_search->text(), Qt::MatchContains | Qt::MatchRecursive, 1); + foreach (QTreeWidgetItem *item, clist) { + if (item->parent() != nullptr) // So the category shows up too + item->parent()->setHidden(false); + item->setHidden(false); + } + } + + if (!ui_area_list->isHidden()) { + // Search in metadata + QList alist = ui_area_list->findItems( + ui_music_search->text(), Qt::MatchContains | Qt::MatchRecursive, 1); + foreach (QTreeWidgetItem *item, alist) { + if (item->parent() != nullptr) // So the category shows up too + item->parent()->setHidden(false); + item->setHidden(false); + } + } + } } void Courtroom::on_pos_dropdown_changed(int p_index) { - ui_ic_chat_message->setFocus(); - - if (p_index < 0 || p_index > 5) + if (p_index < 0 || p_index > 7) return; toggle_judge_buttons(false); - QString f_pos; + QString f_pos = ui_pos_dropdown->itemText(p_index); - 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"; - toggle_judge_buttons(true); - break; - case 4: - f_pos = "hld"; - break; - case 5: - f_pos = "hlp"; - break; - default: - f_pos = ""; - } - - if (f_pos == "" || ui_ooc_chat_name->text() == "") + if (f_pos == "") return; - ao_app->send_server_packet( - new AOPacket("CT#" + ui_ooc_chat_name->text() + "#/pos " + f_pos + "#%")); + if (f_pos == "jud") + toggle_judge_buttons(true); + + // YEAH SENDING LIKE 20 PACKETS IF THE USER SCROLLS THROUGH, GREAT IDEA + // how about this instead + set_side(f_pos); +} + +void Courtroom::set_iniswap_dropdown() +{ + ui_iniswap_dropdown->blockSignals(true); + ui_iniswap_dropdown->clear(); + if (m_cid == -1) { + ui_iniswap_dropdown->hide(); + ui_iniswap_remove->hide(); + return; + } + QStringList iniswaps = ao_app->get_list_file( + ao_app->get_character_path(char_list.at(m_cid).name, "iniswaps.ini")); + iniswaps.prepend(char_list.at(m_cid).name); + if (iniswaps.size() <= 0) { + ui_iniswap_dropdown->hide(); + ui_iniswap_remove->hide(); + return; + } + ui_iniswap_dropdown->show(); + ui_iniswap_dropdown->addItems(iniswaps); + + for (int i = 0; i < iniswaps.size(); ++i) { + if (iniswaps.at(i) == current_char) { + ui_iniswap_dropdown->setCurrentIndex(i); + if (i != 0) + ui_iniswap_remove->show(); + else + ui_iniswap_remove->hide(); + break; + } + } + ui_iniswap_dropdown->blockSignals(false); +} + +void Courtroom::on_iniswap_dropdown_changed(int p_index) +{ + ui_ic_chat_message->setFocus(); + QString iniswap = ui_iniswap_dropdown->itemText(p_index); + ao_app->set_char_ini(char_list.at(m_cid).name, iniswap, "name", "Options"); + + QStringList swaplist; + for (int i = 0; i < ui_iniswap_dropdown->count(); ++i) { + QString entry = ui_iniswap_dropdown->itemText(i); + if (!swaplist.contains(entry) && entry != char_list.at(m_cid).name) + swaplist.append(entry); + } + ao_app->write_to_file( + swaplist.join("\n"), + ao_app->get_character_path(char_list.at(m_cid).name, "iniswaps.ini")); + ui_iniswap_dropdown->blockSignals(true); + ui_iniswap_dropdown->setCurrentIndex(p_index); + ui_iniswap_dropdown->blockSignals(false); + update_character(m_cid); + if (p_index != 0) + ui_iniswap_remove->show(); + else + ui_iniswap_remove->hide(); +} + +void Courtroom::on_iniswap_context_menu_requested(const QPoint &pos) +{ + QMenu *menu = ui_iniswap_dropdown->lineEdit()->createStandardContextMenu(); + + menu->addSeparator(); + if (file_exists(ao_app->get_character_path(current_char, "char.ini"))) + menu->addAction(QString("Edit " + current_char + "/char.ini"), this, + SLOT(on_iniswap_edit_requested())); + if (ui_iniswap_dropdown->itemText(ui_iniswap_dropdown->currentIndex()) != + char_list.at(m_cid).name) + menu->addAction(QString("Remove " + current_char), this, + SLOT(on_iniswap_remove_clicked())); + menu->popup(ui_iniswap_dropdown->mapToGlobal(pos)); +} +void Courtroom::on_iniswap_edit_requested() +{ + QString p_path = ao_app->get_character_path(current_char, "char.ini"); + if (!file_exists(p_path)) + return; + QDesktopServices::openUrl(QUrl::fromLocalFile(p_path)); +} + +void Courtroom::on_iniswap_remove_clicked() +{ + if (ui_iniswap_dropdown->count() <= 0) { + ui_iniswap_remove->hide(); // We're not supposed to see it. Do this or the + // client will crash + return; + } + if (ui_iniswap_dropdown->itemText(ui_iniswap_dropdown->currentIndex()) != + char_list.at(m_cid).name) { + ui_iniswap_dropdown->removeItem(ui_iniswap_dropdown->currentIndex()); + on_iniswap_dropdown_changed(0); // Reset back to original + update_character(m_cid); + } +} + +void Courtroom::set_sfx_dropdown() +{ + ui_sfx_dropdown->blockSignals(true); + ui_sfx_dropdown->clear(); + if (m_cid == -1) { + ui_sfx_dropdown->hide(); + ui_sfx_remove->hide(); + return; + } + QStringList soundlist = ao_app->get_list_file( + ao_app->get_character_path(current_char, "soundlist.ini")); + + if (soundlist.size() <= 0) { + soundlist = ao_app->get_list_file( + ao_app->get_theme_path("character_soundlist.ini")); + if (soundlist.size() <= 0) { + soundlist = ao_app->get_list_file( + ao_app->get_default_theme_path("character_soundlist.ini")); + } + } + + if (soundlist.size() <= 0) { + ui_sfx_dropdown->hide(); + ui_sfx_remove->hide(); + return; + } + soundlist.prepend("Default"); + + ui_sfx_dropdown->show(); + ui_sfx_dropdown->addItems(soundlist); + ui_sfx_dropdown->setCurrentIndex(0); + ui_sfx_remove->hide(); + ui_sfx_dropdown->blockSignals(false); +} + +void Courtroom::on_sfx_dropdown_changed(int p_index) +{ + ui_ic_chat_message->setFocus(); + + QStringList soundlist; + for (int i = 0; i < ui_sfx_dropdown->count(); ++i) { + QString entry = ui_sfx_dropdown->itemText(i); + if (!soundlist.contains(entry) && entry != "Default") + soundlist.append(entry); + } + + QStringList defaultlist = + ao_app->get_list_file(ao_app->get_theme_path("character_soundlist.ini")); + if (defaultlist.size() <= 0) { + defaultlist = ao_app->get_list_file( + ao_app->get_default_theme_path("character_soundlist.ini")); + } + + if (defaultlist.size() > 0 && + defaultlist.toSet().subtract(soundlist.toSet()).size() > + 0) // There's a difference from the default configuration + ao_app->write_to_file( + soundlist.join("\n"), + ao_app->get_character_path(current_char, + "soundlist.ini")); // Create a new sound list + + ui_sfx_dropdown->blockSignals(true); + ui_sfx_dropdown->setCurrentIndex(p_index); + ui_sfx_dropdown->blockSignals(false); + if (p_index != 0) + ui_sfx_remove->show(); + else + ui_sfx_remove->hide(); +} + +void Courtroom::on_sfx_context_menu_requested(const QPoint &pos) +{ + QMenu *menu = ui_sfx_dropdown->lineEdit()->createStandardContextMenu(); + + menu->addSeparator(); + if (file_exists(ao_app->get_character_path(current_char, "soundlist.ini"))) + menu->addAction(QString("Edit " + current_char + "/soundlist.ini"), this, + SLOT(on_sfx_edit_requested())); + else + menu->addAction(QString("Edit theme's character_soundlist.ini"), this, + SLOT(on_sfx_edit_requested())); + if (ui_sfx_dropdown->currentIndex() != 0) + menu->addAction(QString("Remove " + ui_sfx_dropdown->itemText( + ui_sfx_dropdown->currentIndex())), + this, SLOT(on_sfx_remove_clicked())); + menu->popup(ui_sfx_dropdown->mapToGlobal(pos)); +} +void Courtroom::on_sfx_edit_requested() +{ + QString p_path = ao_app->get_character_path(current_char, "soundlist.ini"); + if (!file_exists(p_path)) { + p_path = ao_app->get_theme_path("character_soundlist.ini"); + if (!file_exists(p_path)) { + p_path = ao_app->get_default_theme_path("character_soundlist.ini"); + if (!file_exists(p_path)) { + return; + } + } + } + QDesktopServices::openUrl(QUrl::fromLocalFile(p_path)); +} + +void Courtroom::on_sfx_remove_clicked() +{ + if (ui_sfx_dropdown->count() <= 0) { + ui_sfx_remove->hide(); // We're not supposed to see it. Do this or the + // client will crash + return; + } + if (ui_sfx_dropdown->itemText(ui_sfx_dropdown->currentIndex()) != "Default") { + ui_sfx_dropdown->removeItem(ui_sfx_dropdown->currentIndex()); + on_sfx_dropdown_changed(0); // Reset back to original + } +} + +void Courtroom::set_effects_dropdown() +{ + ui_effects_dropdown->blockSignals(true); + ui_effects_dropdown->clear(); + if (m_cid == -1) { + ui_effects_dropdown->hide(); + return; + } + QStringList effectslist = ao_app->get_effects(current_char); + + if (effectslist.size() <= 0) { + ui_effects_dropdown->hide(); + return; + } + + effectslist.prepend("None"); + + ui_effects_dropdown->show(); + ui_effects_dropdown->addItems(effectslist); + + // ICON-MAKING HELL + QString p_effect = ao_app->read_char_ini(current_char, "effects", "Options"); + QString custom_path = + ao_app->get_base_path() + "misc/" + p_effect + "/icons/"; + QString theme_path = ao_app->get_theme_path("effects/icons/"); + QString default_path = ao_app->get_default_theme_path("effects/icons/"); + for (int i = 0; i < ui_effects_dropdown->count(); ++i) { + QString entry = ui_effects_dropdown->itemText(i); + QString iconpath = ao_app->get_static_image_suffix(custom_path + entry); + if (!file_exists(iconpath)) { + iconpath = ao_app->get_static_image_suffix(theme_path + entry); + if (!file_exists(iconpath)) { + iconpath = ao_app->get_static_image_suffix(default_path + entry); + if (!file_exists(iconpath)) + continue; + } + } + ui_effects_dropdown->setItemIcon(i, QIcon(iconpath)); + } + + ui_effects_dropdown->setCurrentIndex(0); + ui_effects_dropdown->blockSignals(false); +} + +void Courtroom::on_effects_context_menu_requested(const QPoint &pos) +{ + QMenu *menu = new QMenu(); + + if (!ao_app->read_char_ini(current_char, "effects", "Options").isEmpty()) + menu->addAction( + QString("Open misc/" + + ao_app->read_char_ini(current_char, "effects", "Options") + + " folder"), + this, SLOT(on_character_effects_edit_requested())); + menu->addAction(QString("Open theme's effects folder"), this, + SLOT(on_effects_edit_requested())); + menu->popup(ui_effects_dropdown->mapToGlobal(pos)); +} +void Courtroom::on_effects_edit_requested() +{ + QString p_path = ao_app->get_theme_path("effects/"); + if (!dir_exists(p_path)) { + p_path = ao_app->get_default_theme_path("effects/"); + if (!dir_exists(p_path)) { + return; + } + } + QDesktopServices::openUrl(QUrl::fromLocalFile(p_path)); +} +void Courtroom::on_character_effects_edit_requested() +{ + QString p_effect = ao_app->read_char_ini(current_char, "effects", "Options"); + QString p_path = ao_app->get_base_path() + "misc/" + p_effect + "/"; + if (!dir_exists(p_path)) + return; + + QDesktopServices::openUrl(QUrl::fromLocalFile(p_path)); +} + +void Courtroom::on_effects_dropdown_changed(int p_index) +{ + effect = ui_effects_dropdown->itemText(p_index); + ui_ic_chat_message->setFocus(); +} + +bool Courtroom::effects_dropdown_find_and_set(QString effect) +{ + for (int i = 0; i < ui_effects_dropdown->count(); ++i) { + QString entry = ui_effects_dropdown->itemText(i); + if (entry == effect) { + ui_effects_dropdown->setCurrentIndex(i); + return true; + } + } + return false; +} + +QString Courtroom::get_char_sfx() +{ + QString sfx = ui_sfx_dropdown->itemText(ui_sfx_dropdown->currentIndex()); + if (sfx != "" && sfx != "Default") + return sfx; + return ao_app->get_sfx_name(current_char, current_emote); +} + +int Courtroom::get_char_sfx_delay() +{ + // QString sfx = ui_sfx_dropdown->itemText(ui_sfx_dropdown->currentIndex()); + // if (sfx != "" && sfx != "Default") + // return 0; //todo: a way to define this + return ao_app->get_sfx_delay(current_char, current_emote); } void Courtroom::on_mute_list_clicked(QModelIndex p_index) @@ -3015,46 +3943,115 @@ void Courtroom::on_pair_list_clicked(QModelIndex p_index) } } -void Courtroom::on_music_list_double_clicked(QModelIndex p_model) +void Courtroom::on_music_list_double_clicked(QTreeWidgetItem *p_item, + int column) { if (is_muted) return; - QString p_song = music_list.at(music_row_to_number.at(p_model.row())); + column = 1; // Column 1 is always the metadata (which we want) + QString p_song = p_item->text(column); - if (!ui_ic_chat_name->text().isEmpty() && ao_app->cccc_ic_support_enabled) { - ao_app->send_server_packet(new AOPacket("MC#" + p_song + "#" + - QString::number(m_cid) + "#" + - ui_ic_chat_name->text() + "#%"), - false); - } - else { - ao_app->send_server_packet( - new AOPacket("MC#" + p_song + "#" + QString::number(m_cid) + "#%"), - false); - } + QStringList packet_contents; + packet_contents.append(p_song); + packet_contents.append(QString::number(m_cid)); + if ((!ui_ic_chat_name->text().isEmpty() && ao_app->cccc_ic_support_enabled) || + ao_app->effects_enabled) + packet_contents.append(ui_ic_chat_name->text()); + if (ao_app->effects_enabled) + packet_contents.append(QString::number(music_flags)); + ao_app->send_server_packet(new AOPacket("MC", packet_contents), false); } -void Courtroom::on_area_list_double_clicked(QModelIndex p_model) +void Courtroom::on_music_list_context_menu_requested(const QPoint &pos) { - QString p_area = area_list.at(area_row_to_number.at(p_model.row())); - ao_app->send_server_packet( - new AOPacket("MC#" + p_area + "#" + QString::number(m_cid) + "#%"), - false); + QMenu *menu = new QMenu(); + + menu->addAction(QString("Expand All Categories"), this, + SLOT(music_list_expand_all())); + menu->addAction(QString("Collapse All Categories"), this, + SLOT(music_list_collapse_all())); + menu->addSeparator(); + + menu->addAction(new QAction("Fade Out Previous", this)); + menu->actions().back()->setCheckable(true); + menu->actions().back()->setChecked(music_flags & FADE_OUT); + connect(menu->actions().back(), SIGNAL(toggled(bool)), this, + SLOT(music_fade_out(bool))); + + menu->addAction(new QAction("Fade In", this)); + menu->actions().back()->setCheckable(true); + menu->actions().back()->setChecked(music_flags & FADE_IN); + connect(menu->actions().back(), SIGNAL(toggled(bool)), this, + SLOT(music_fade_in(bool))); + + menu->addAction(new QAction("Synchronize", this)); + menu->actions().back()->setCheckable(true); + menu->actions().back()->setChecked(music_flags & SYNC_POS); + connect(menu->actions().back(), SIGNAL(toggled(bool)), this, + SLOT(music_synchronize(bool))); + + menu->popup(ui_music_list->mapToGlobal(pos)); +} + +void Courtroom::music_fade_out(bool toggle) +{ + if (toggle) + music_flags |= FADE_OUT; + else + music_flags &= ~FADE_OUT; +} + +void Courtroom::music_fade_in(bool toggle) +{ + if (toggle) + music_flags |= FADE_IN; + else + music_flags &= ~FADE_IN; +} + +void Courtroom::music_synchronize(bool toggle) +{ + if (toggle) + music_flags |= SYNC_POS; + else + music_flags &= ~SYNC_POS; +} + +void Courtroom::music_list_expand_all() { ui_music_list->expandAll(); } +void Courtroom::music_list_collapse_all() +{ + ui_music_list->collapseAll(); + QTreeWidgetItem *current = ui_music_list->selectedItems()[0]; + if (current->parent() != nullptr) + current = current->parent(); + ui_music_list->setCurrentItem(current); +} + +void Courtroom::on_area_list_double_clicked(QTreeWidgetItem *p_item, int column) +{ + column = 0; // The metadata + QString p_area = p_item->text(0); + + QStringList packet_contents; + packet_contents.append(p_area); + packet_contents.append(QString::number(m_cid)); + qDebug() << packet_contents; + ao_app->send_server_packet(new AOPacket("MC", packet_contents), false); } void Courtroom::on_hold_it_clicked() { if (objection_state == 1) { - ui_hold_it->set_image("holdit.png"); + ui_hold_it->set_image("holdit"); objection_state = 0; } else { - ui_objection->set_image("objection.png"); - ui_take_that->set_image("takethat.png"); - ui_custom_objection->set_image("custom.png"); + ui_objection->set_image("objection"); + ui_take_that->set_image("takethat"); + ui_custom_objection->set_image("custom"); - ui_hold_it->set_image("holdit_selected.png"); + ui_hold_it->set_image("holdit_selected"); objection_state = 1; } @@ -3064,15 +4061,15 @@ void Courtroom::on_hold_it_clicked() void Courtroom::on_objection_clicked() { if (objection_state == 2) { - ui_objection->set_image("objection.png"); + ui_objection->set_image("objection"); objection_state = 0; } else { - ui_hold_it->set_image("holdit.png"); - ui_take_that->set_image("takethat.png"); - ui_custom_objection->set_image("custom.png"); + ui_hold_it->set_image("holdit"); + ui_take_that->set_image("takethat"); + ui_custom_objection->set_image("custom"); - ui_objection->set_image("objection_selected.png"); + ui_objection->set_image("objection_selected"); objection_state = 2; } @@ -3082,15 +4079,15 @@ void Courtroom::on_objection_clicked() void Courtroom::on_take_that_clicked() { if (objection_state == 3) { - ui_take_that->set_image("takethat.png"); + ui_take_that->set_image("takethat"); objection_state = 0; } else { - ui_objection->set_image("objection.png"); - ui_hold_it->set_image("holdit.png"); - ui_custom_objection->set_image("custom.png"); + ui_objection->set_image("objection"); + ui_hold_it->set_image("holdit"); + ui_custom_objection->set_image("custom"); - ui_take_that->set_image("takethat_selected.png"); + ui_take_that->set_image("takethat_selected"); objection_state = 3; } @@ -3100,15 +4097,15 @@ void Courtroom::on_take_that_clicked() void Courtroom::on_custom_objection_clicked() { if (objection_state == 4) { - ui_custom_objection->set_image("custom.png"); + ui_custom_objection->set_image("custom"); objection_state = 0; } else { - ui_objection->set_image("objection.png"); - ui_take_that->set_image("takethat.png"); - ui_hold_it->set_image("holdit.png"); + ui_objection->set_image("objection"); + ui_take_that->set_image("takethat"); + ui_hold_it->set_image("holdit"); - ui_custom_objection->set_image("custom_selected.png"); + ui_custom_objection->set_image("custom_selected"); objection_state = 4; } @@ -3119,11 +4116,30 @@ void Courtroom::on_realization_clicked() { if (realization_state == 0) { realization_state = 1; - ui_realization->set_image("realization_pressed.png"); + if (effects_dropdown_find_and_set("realization")) + on_effects_dropdown_changed(ui_effects_dropdown->currentIndex()); + + ui_realization->set_image("realization_pressed"); } else { realization_state = 0; - ui_realization->set_image("realization.png"); + ui_effects_dropdown->setCurrentIndex(0); + on_effects_dropdown_changed(ui_effects_dropdown->currentIndex()); + ui_realization->set_image("realization"); + } + + ui_ic_chat_message->setFocus(); +} + +void Courtroom::on_screenshake_clicked() +{ + if (screenshake_state == 0) { + screenshake_state = 1; + ui_screenshake->set_image("screenshake_pressed"); + } + else { + screenshake_state = 0; + ui_screenshake->set_image("screenshake"); } ui_ic_chat_message->setFocus(); @@ -3135,12 +4151,13 @@ void Courtroom::on_mute_clicked() ui_mute_list->show(); ui_pair_list->hide(); ui_pair_offset_spinbox->hide(); - ui_pair_button->set_image("pair_button.png"); - ui_mute->set_image("mute_pressed.png"); + ui_pair_order_dropdown->hide(); + ui_pair_button->set_image("pair_button"); + ui_mute->set_image("mute_pressed"); } else { ui_mute_list->hide(); - ui_mute->set_image("mute.png"); + ui_mute->set_image("mute"); } } @@ -3149,17 +4166,24 @@ void Courtroom::on_pair_clicked() if (ui_pair_list->isHidden()) { ui_pair_list->show(); ui_pair_offset_spinbox->show(); + ui_pair_order_dropdown->show(); ui_mute_list->hide(); - ui_mute->set_image("mute.png"); - ui_pair_button->set_image("pair_button_pressed.png"); + ui_mute->set_image("mute"); + ui_pair_button->set_image("pair_button_pressed"); } else { ui_pair_list->hide(); ui_pair_offset_spinbox->hide(); - ui_pair_button->set_image("pair_button.png"); + ui_pair_order_dropdown->hide(); + ui_pair_button->set_image("pair_button"); } } +void Courtroom::on_pair_order_dropdown_changed(int p_index) +{ + pair_order = p_index; +} + void Courtroom::on_defense_minus_clicked() { int f_state = defense_bar_state - 1; @@ -3196,21 +4220,96 @@ void Courtroom::on_prosecution_plus_clicked() new AOPacket("HP#2#" + QString::number(f_state) + "#%")); } +void Courtroom::set_text_color_dropdown() +{ + // Clear the lists + ui_text_color->clear(); + color_row_to_number.clear(); + + // Clear the stored optimization information + color_rgb_list.clear(); + color_markdown_start_list.clear(); + color_markdown_end_list.clear(); + color_markdown_remove_list.clear(); + color_markdown_talking_list.clear(); + + // Update markdown colors. TODO: make a loading function that only loads the + // config file once instead of several times + for (int c = 0; c < max_colors; ++c) { + QColor color = ao_app->get_chat_color(QString::number(c), current_char); + color_rgb_list.append(color); + color_markdown_start_list.append(ao_app->get_chat_markdown( + "c" + QString::number(c) + "_start", current_char)); + color_markdown_end_list.append(ao_app->get_chat_markdown( + "c" + QString::number(c) + "_end", current_char)); + color_markdown_remove_list.append( + ao_app->get_chat_markdown("c" + QString::number(c) + "_remove", + current_char) == "1"); + color_markdown_talking_list.append( + ao_app->get_chat_markdown("c" + QString::number(c) + "_talking", + current_char) == "1"); + + QString color_name = ao_app->get_chat_markdown( + "c" + QString::number(c) + "_name", current_char); + if (color_name.isEmpty()) // Not defined + { + if (c > 0) + continue; + color_name = tr("Default"); + } + ui_text_color->addItem(color_name); + QPixmap pixmap(16, 16); + pixmap.fill(color); + ui_text_color->setItemIcon(ui_text_color->count() - 1, QIcon(pixmap)); + color_row_to_number.append(c); + } +} + void Courtroom::on_text_color_changed(int p_color) { - text_color = p_color; + if (ui_ic_chat_message->selectionStart() != -1) // We have a selection! + { + int c = color_row_to_number.at(p_color); + QString markdown_start = color_markdown_start_list.at(c); + if (markdown_start.isEmpty()) { + qDebug() << "W: Color list dropdown selected a non-existent markdown " + "start character"; + return; + } + QString markdown_end = color_markdown_end_list.at(c); + if (markdown_end.isEmpty()) + markdown_end = markdown_start; + int start = ui_ic_chat_message->selectionStart(); + int end = ui_ic_chat_message->selectionEnd() + 1; + ui_ic_chat_message->setCursorPosition(start); + ui_ic_chat_message->insert(markdown_start); + ui_ic_chat_message->setCursorPosition(end); + ui_ic_chat_message->insert(markdown_end); + // ui_ic_chat_message->end(false); + ui_text_color->setCurrentIndex(0); + } + else { + if (p_color != -1 && p_color < color_row_to_number.size()) + text_color = color_row_to_number.at(p_color); + else + text_color = 0; + } ui_ic_chat_message->setFocus(); } void Courtroom::on_music_slider_moved(int p_value) { - music_player->set_volume(p_value); + music_player->set_volume(p_value, 0); // Set volume on music layer ui_ic_chat_message->setFocus(); } void Courtroom::on_sfx_slider_moved(int p_value) { sfx_player->set_volume(p_value); + // Set the ambience and other misc. music layers + for (int i = 1; i < music_player->m_channelmax; ++i) { + music_player->set_volume(p_value, i); + } objection_player->set_volume(p_value); ui_ic_chat_message->setFocus(); } @@ -3223,7 +4322,7 @@ void Courtroom::on_blip_slider_moved(int p_value) void Courtroom::on_log_limit_changed(int value) { log_maximum_blocks = value; } -void Courtroom::on_pair_offset_changed(int value) { offset_with_pair = value; } +void Courtroom::on_pair_offset_changed(int value) { char_offset = value; } void Courtroom::on_witness_testimony_clicked() { @@ -3267,8 +4366,6 @@ void Courtroom::on_guilty_clicked() void Courtroom::on_change_character_clicked() { - music_player->set_volume(0); - sfx_player->set_volume(0); sfx_player->set_volume(0); blip_player->set_volume(0); @@ -3282,12 +4379,14 @@ void Courtroom::on_reload_theme_clicked() { ao_app->reload_theme(); - // to update status on the background - set_background(current_background); - enter_courtroom(m_cid); + enter_courtroom(); + update_character(m_cid); anim_state = 4; text_state = 3; + + // to update status on the background + set_background(current_background); } void Courtroom::on_back_to_lobby_clicked() @@ -3308,14 +4407,7 @@ void Courtroom::on_char_select_right_clicked() set_char_select_page(); } -void Courtroom::on_spectator_clicked() -{ - enter_courtroom(-1); - - ui_emotes->hide(); - - ui_char_select_background->hide(); -} +void Courtroom::on_spectator_clicked() { update_character(-1); } void Courtroom::on_call_mod_clicked() { @@ -3337,7 +4429,7 @@ void Courtroom::on_call_mod_clicked() return; } else if (text.length() > 256) { - errorBox.critical(nullptr, "Error", "The message is too long."); + errorBox.critical(nullptr, tr("Error"), tr("The message is too long.")); return; } @@ -3364,6 +4456,17 @@ void Courtroom::on_pre_clicked() { ui_ic_chat_message->setFocus(); } void Courtroom::on_flip_clicked() { ui_ic_chat_message->setFocus(); } +void Courtroom::on_additive_clicked() +{ + if (ui_additive->isChecked()) { + ui_ic_chat_message->home(false); // move cursor to the start of the message + ui_ic_chat_message->insert(" "); // preface the message by whitespace + ui_ic_chat_message->end(false); // move cursor to the end of the message + // without selecting anything + } + ui_ic_chat_message->setFocus(); +} + void Courtroom::on_guard_clicked() { ui_ic_chat_message->setFocus(); } void Courtroom::on_showname_enable_clicked() @@ -3373,14 +4476,16 @@ void Courtroom::on_showname_enable_clicked() foreach (chatlogpiece item, ic_chatlog_history) { if (ui_showname_enable->isChecked()) { - if (item.get_is_song()) - append_ic_text(item.get_message(), item.get_showname(), true); + if (item.is_song()) + append_ic_text(item.get_message(), item.get_showname(), + "has played a song"); else append_ic_text(item.get_message(), item.get_showname()); } else { - if (item.get_is_song()) - append_ic_text(item.get_message(), item.get_name(), true); + if (item.is_song()) + append_ic_text(item.get_message(), item.get_name(), + "has played a song"); else append_ic_text(item.get_message(), item.get_name()); } @@ -3402,6 +4507,8 @@ void Courtroom::on_evidence_button_clicked() void Courtroom::on_switch_area_music_clicked() { + ui_music_search->setText(""); + on_music_search_edited(ui_music_search->text()); if (ui_area_list->isHidden()) { ui_area_list->show(); ui_music_list->hide(); diff --git a/src/debug_functions.cpp b/src/debug_functions.cpp index f33a2c6..b832164 100644 --- a/src/debug_functions.cpp +++ b/src/debug_functions.cpp @@ -1,11 +1,16 @@ +#include +#include + #include "debug_functions.h" void call_error(QString p_message) { QMessageBox *msgBox = new QMessageBox; - msgBox->setText("Error: " + p_message); - msgBox->setWindowTitle("Error"); + msgBox->setText(QCoreApplication::translate("debug_functions", "Error: %1") + .arg(p_message)); + msgBox->setWindowTitle( + QCoreApplication::translate("debug_functions", "Error")); // msgBox->setWindowModality(Qt::NonModal); msgBox->exec(); @@ -16,7 +21,8 @@ void call_notice(QString p_message) QMessageBox *msgBox = new QMessageBox; msgBox->setText(p_message); - msgBox->setWindowTitle("Notice"); + msgBox->setWindowTitle( + QCoreApplication::translate("debug_functions", "Notice")); // msgBox->setWindowModality(Qt::NonModal); msgBox->exec(); diff --git a/src/emotes.cpp b/src/emotes.cpp index bbe2f5c..8de5524 100644 --- a/src/emotes.cpp +++ b/src/emotes.cpp @@ -2,20 +2,48 @@ #include "aoemotebutton.h" -void Courtroom::construct_emotes() +void Courtroom::initialize_emotes() { ui_emotes = new QWidget(this); + ui_emote_left = new AOButton(this, ao_app); + ui_emote_right = new AOButton(this, ao_app); + + ui_emote_dropdown = new QComboBox(this); + + 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))); +} + +void Courtroom::refresh_emotes() +{ + // Should properly refresh the emote list + qDeleteAll(ui_emote_list.begin(), ui_emote_list.end()); + ui_emote_list.clear(); + set_size_and_pos(ui_emotes, "emotes"); + set_size_and_pos(ui_emote_left, "emote_left"); + ui_emote_left->set_image("arrow_left"); + + set_size_and_pos(ui_emote_right, "emote_right"); + ui_emote_right->set_image("arrow_right"); + QPoint f_spacing = ao_app->get_button_spacing("emote_button_spacing", "courtroom_design.ini"); + QPoint p_point = + ao_app->get_button_spacing("emote_button_size", "courtroom_design.ini"); - const int button_width = 40; + const int button_width = p_point.x(); int x_spacing = f_spacing.x(); int x_mod_count = 0; - const int button_height = 40; + const int button_height = p_point.y(); int y_spacing = f_spacing.y(); int y_mod_count = 0; @@ -30,7 +58,8 @@ void Courtroom::construct_emotes() 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); + AOEmoteButton *f_emote = new AOEmoteButton(ui_emotes, ao_app, x_pos, y_pos, + button_width, button_height); ui_emote_list.append(f_emote); @@ -82,16 +111,19 @@ void Courtroom::set_emote_page() if (current_emote_page > 0) ui_emote_left->show(); - for (int n_emote = 0; n_emote < emotes_on_page; ++n_emote) { + for (int n_emote = 0; + n_emote < emotes_on_page && n_emote < ui_emote_list.size(); ++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"); + f_emote->set_char_image(current_char, n_real_emote, "_on"); else - f_emote->set_image(current_char, n_real_emote, "_off.png"); + f_emote->set_char_image(current_char, n_real_emote, "_off"); f_emote->show(); + f_emote->setToolTip(QString::number(n_real_emote + 1) + ": " + + ao_app->get_emote_comment(current_char, n_real_emote)); } } @@ -103,7 +135,8 @@ void Courtroom::set_emote_dropdown() QStringList emote_list; for (int n = 0; n < total_emotes; ++n) { - emote_list.append(ao_app->get_emote_comment(current_char, n)); + emote_list.append(QString::number(n + 1) + ": " + + ao_app->get_emote_comment(current_char, n)); } ui_emote_dropdown->addItems(emote_list); @@ -116,7 +149,7 @@ void Courtroom::select_emote(int 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, "_off.png"); + ->set_char_image(current_char, current_emote, "_off"); int old_emote = current_emote; @@ -124,17 +157,19 @@ void Courtroom::select_emote(int 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"); + ->set_char_image(current_char, current_emote, "_on"); int emote_mod = ao_app->get_emote_mod(current_char, current_emote); if (old_emote == current_emote) { ui_pre->setChecked(!ui_pre->isChecked()); } - else if (emote_mod == 1) - ui_pre->setChecked(true); - else - ui_pre->setChecked(false); + else if (!ao_app->is_stickypres_enabled()) { + if (emote_mod == 1 || emote_mod == 4) + ui_pre->setChecked(true); + else + ui_pre->setChecked(false); + } ui_emote_dropdown->setCurrentIndex(current_emote); diff --git a/src/evidence.cpp b/src/evidence.cpp index 128156c..dffe848 100644 --- a/src/evidence.cpp +++ b/src/evidence.cpp @@ -1,45 +1,194 @@ #include "courtroom.h" -void Courtroom::construct_evidence() +void Courtroom::initialize_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_name->setFrame(false); 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_present->setToolTip(tr("Present this piece of evidence to " + "everyone on your next spoken message")); + + ui_evidence_switch = new AOButton(ui_evidence, ao_app); + ui_evidence_transfer = new AOButton(ui_evidence, ao_app); + + ui_evidence_save = new AOButton(ui_evidence, ao_app); + ui_evidence_save->setToolTip(tr("Save evidence to an .ini file.")); + ui_evidence_load = new AOButton(ui_evidence, ao_app); + ui_evidence_load->setToolTip(tr("Load evidence from an .ini file.")); ui_evidence_overlay = new AOImage(ui_evidence, ao_app); ui_evidence_delete = new AOButton(ui_evidence_overlay, ao_app); + ui_evidence_delete->setToolTip(tr("Destroy this piece of evidence")); 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_x->setToolTip( + tr("Close the evidence display/editing overlay.\n" + "You will be prompted if there's any unsaved changes.")); + ui_evidence_ok = new AOButton(ui_evidence_overlay, ao_app); + ui_evidence_ok->setToolTip(tr("Save any changes made to this piece of " + "evidence and send them to server.")); ui_evidence_description = new AOTextEdit(ui_evidence_overlay); ui_evidence_description->setStyleSheet("background-color: rgba(0, 0, 0, 0);" "color: white;"); + ui_evidence_description->setFrameStyle(QFrame::NoFrame); + ui_evidence_description->setToolTip( + "Double-click to edit. Press [X] to update your changes."); + + connect(ui_evidence_name, SIGNAL(returnPressed()), this, + SLOT(on_evidence_name_edited())); + connect(ui_evidence_name, SIGNAL(double_clicked()), this, + SLOT(on_evidence_name_double_clicked())); + 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_switch, SIGNAL(clicked()), this, + SLOT(on_evidence_switch_clicked())); + connect(ui_evidence_transfer, SIGNAL(clicked()), this, + SLOT(on_evidence_transfer_clicked())); + connect(ui_evidence_save, SIGNAL(clicked()), this, + SLOT(on_evidence_save_clicked())); + connect(ui_evidence_load, SIGNAL(clicked()), this, + SLOT(on_evidence_load_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_name, SIGNAL(double_clicked()), this, + SLOT(on_evidence_image_name_double_clicked())); + 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())); + connect(ui_evidence_ok, SIGNAL(clicked()), this, + SLOT(on_evidence_ok_clicked())); + + connect(ui_evidence_name, SIGNAL(textChanged(QString)), this, + SLOT(on_evidence_edited())); + connect(ui_evidence_image_name, SIGNAL(textChanged(QString)), this, + SLOT(on_evidence_edited())); + connect(ui_evidence_description, SIGNAL(textChanged()), this, + SLOT(on_evidence_edited())); + + ui_evidence->hide(); +} + +void Courtroom::refresh_evidence() +{ + set_font(ui_evidence_name, "", "evidence_name"); + set_font(ui_evidence_image_name, "", "evidence_image_name"); + set_font(ui_evidence_description, "", "evidence_description"); + + // Should properly refresh the evidence list + qDeleteAll(ui_evidence_list.begin(), ui_evidence_list.end()); + ui_evidence_list.clear(); + + set_size_and_pos(ui_evidence_button, "evidence_button"); + ui_evidence_button->set_image("evidence_button"); + ui_evidence_button->setToolTip(tr("Bring up the Evidence screen.")); set_size_and_pos(ui_evidence, "evidence_background"); + if (current_evidence_global) + ui_evidence->set_image("evidence_background"); + else + ui_evidence->set_image("evidence_background_private"); + + 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"); + + set_size_and_pos(ui_evidence_right, "evidence_right"); + ui_evidence_right->set_image("arrow_right"); + + set_size_and_pos(ui_evidence_present, "evidence_present"); + ui_evidence_present->set_image("present"); + + set_size_and_pos(ui_evidence_overlay, "evidence_overlay"); + if (current_evidence_global) + ui_evidence_overlay->set_image("evidence_overlay"); + else + ui_evidence_overlay->set_image("evidence_overlay_private"); + + set_size_and_pos(ui_evidence_delete, "evidence_delete"); + ui_evidence_delete->set_image("evidence_delete"); + + 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("evidence_x"); + + set_size_and_pos(ui_evidence_ok, "evidence_ok"); + ui_evidence_ok->set_image("evidence_ok"); + + set_size_and_pos(ui_evidence_switch, "evidence_switch"); + if (current_evidence_global) { + ui_evidence_switch->set_image("evidence_global"); + ui_evidence_switch->setToolTip(tr("Switch evidence to private inventory.")); + } + else { + ui_evidence_switch->set_image("evidence_private"); + ui_evidence_switch->setToolTip(tr("Switch evidence to global inventory.")); + } + + set_size_and_pos(ui_evidence_transfer, "evidence_transfer"); + if (current_evidence_global) { + ui_evidence_transfer->set_image("evidence_transfer"); + ui_evidence_transfer->setToolTip( + tr("Transfer evidence to private inventory.")); + } + else { + ui_evidence_transfer->set_image("evidence_transfer_private"); + ui_evidence_transfer->setToolTip( + tr("Transfer evidence to global inventory.")); + } + + set_size_and_pos(ui_evidence_save, "evidence_save"); + ui_evidence_save->set_image("evidence_save"); + if (current_evidence_global) + ui_evidence_save->hide(); + else + ui_evidence_save->show(); + + set_size_and_pos(ui_evidence_load, "evidence_load"); + ui_evidence_load->set_image("evidence_load"); + if (current_evidence_global) + ui_evidence_load->hide(); + else + ui_evidence_load->show(); + + set_size_and_pos(ui_evidence_description, "evidence_description"); + QPoint f_spacing = ao_app->get_button_spacing("evidence_button_spacing", "courtroom_design.ini"); + QPoint p_point = ao_app->get_button_spacing("evidence_button_size", + "courtroom_design.ini"); - const int button_width = 70; + const int button_width = p_point.x(); int x_spacing = f_spacing.x(); int x_mod_count = 0; - const int button_height = 70; + const int button_height = p_point.y(); int y_spacing = f_spacing.y(); int y_mod_count = 0; @@ -56,8 +205,8 @@ void Courtroom::construct_evidence() 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); + AOEvidenceButton *f_evidence = new AOEvidenceButton( + ui_evidence_buttons, ao_app, x_pos, y_pos, button_width, button_height); ui_evidence_list.append(f_evidence); @@ -77,33 +226,65 @@ void Courtroom::construct_evidence() 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) { + global_evidence_list = p_evi_list; + if (!current_evidence_global) + return; // We're on private evidence editing, wait for user to do their + // thing + + QVector old_list = local_evidence_list; local_evidence_list.clear(); local_evidence_list = p_evi_list; set_evidence_page(); + + if (ui_evidence_overlay + ->isVisible()) // Update the currently edited evidence for this user + { + if (current_evidence >= local_evidence_list.size()) { + evidence_close(); + ui_evidence_name->setText(""); + } + else if (ui_evidence_description->isReadOnly()) // We haven't double clicked + // to edit it or anything + { + on_evidence_double_clicked(current_evidence); + } + // Todo: make a function that compares two pieces of evidence for any + // differences + else if (compare_evidence_changed( + old_list.at(current_evidence), + local_evidence_list.at(current_evidence))) { + QMessageBox *msgBox = new QMessageBox; + + msgBox->setText("The piece of evidence you've been editing has changed."); + msgBox->setInformativeText("Do you wish to keep your changes?"); + msgBox->setDetailedText( + "Name: " + local_evidence_list.at(current_evidence).name + + "\nImage: " + local_evidence_list.at(current_evidence).image + + "\nDescription:\n" + + local_evidence_list.at(current_evidence).description); + msgBox->setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox->setDefaultButton(QMessageBox::LastButton); + // msgBox->setWindowModality(Qt::NonModal); + int ret = msgBox->exec(); + switch (ret) { + case QMessageBox::Yes: + // "Keep changes" + break; + case QMessageBox::No: + // "Discard changes and keep theirs" + on_evidence_double_clicked(current_evidence); + break; + default: + // should never be reached + break; + } + } + } } void Courtroom::set_evidence_page() @@ -114,7 +295,7 @@ void Courtroom::set_evidence_page() ui_evidence_right->hide(); for (AOEvidenceButton *i_button : ui_evidence_list) { - i_button->reset(); + i_button->hide(); } // to account for the "add evidence" button @@ -147,17 +328,21 @@ void Courtroom::set_evidence_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_selected(false); + f_evidence_button->setToolTip(""); + 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); + + f_evidence_button->setToolTip( + QString::number(n_real_evidence + 1) + ": " + + local_evidence_list.at(n_real_evidence).name); } else f_evidence_button->set_image(""); @@ -168,36 +353,31 @@ void Courtroom::set_evidence_page() void Courtroom::on_evidence_name_edited() { + ui_evidence_name->setReadOnly(true); if (current_evidence >= local_evidence_list.size()) return; +} - QStringList f_contents; +void Courtroom::on_evidence_name_double_clicked() +{ + if (ui_evidence_overlay->isVisible()) { + ui_evidence_name->setReadOnly(false); + } + else { + ui_evidence_name->setReadOnly(true); + } +} - 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_double_clicked() +{ + ui_evidence_image_name->setReadOnly(false); } void Courtroom::on_evidence_image_name_edited() { + ui_evidence_image_name->setReadOnly(true); 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() @@ -230,8 +410,19 @@ void Courtroom::on_evidence_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()) { - ao_app->send_server_packet( - new AOPacket("PE###empty.png#%")); + if (current_evidence_global) + ao_app->send_server_packet( + new AOPacket("PE###empty.png#%")); + else { + evi_type f_evi; + f_evi.name = ""; + f_evi.description = ""; + f_evi.image = "empty.png"; + + local_evidence_list.append(f_evi); + private_evidence_list = local_evidence_list; + set_evidence_page(); + } return; } else if (f_real_id > local_evidence_list.size()) @@ -246,7 +437,7 @@ void Courtroom::on_evidence_clicked(int p_id) current_evidence = f_real_id; - ui_ic_chat_message->setFocus(); + // ui_ic_chat_message->setFocus(); } void Courtroom::on_evidence_double_clicked(int p_id) @@ -262,10 +453,18 @@ void Courtroom::on_evidence_double_clicked(int p_id) ui_evidence_description->clear(); ui_evidence_description->appendPlainText(f_evi.description); + ui_evidence_description->setReadOnly(true); + ui_evidence_description->setToolTip("Double-click to edit..."); + ui_evidence_name->setText(f_evi.name); + ui_evidence_name->setReadOnly(true); + ui_evidence_name->setToolTip("Double-click to edit..."); ui_evidence_image_name->setText(f_evi.image); + ui_evidence_image_name->setReadOnly(true); + ui_evidence_image_name->setToolTip("Double-click to edit..."); ui_evidence_overlay->show(); + ui_evidence_ok->hide(); ui_ic_chat_message->setFocus(); } @@ -307,10 +506,15 @@ void Courtroom::on_evidence_right_clicked() void Courtroom::on_evidence_present_clicked() { + if (!current_evidence_global) { + ui_evidence_present->hide(); + is_presenting_evidence = false; + return; // otherwise we get force-disconnected + } if (is_presenting_evidence) - ui_evidence_present->set_image("present_disabled.png"); + ui_evidence_present->set_image("present"); else - ui_evidence_present->set_image("present.png"); + ui_evidence_present->set_image("present_disabled"); is_presenting_evidence = !is_presenting_evidence; @@ -319,11 +523,15 @@ void Courtroom::on_evidence_present_clicked() 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) + "#%")); + evidence_close(); + if (current_evidence_global) + ao_app->send_server_packet( + new AOPacket("DE#" + QString::number(current_evidence) + "#%")); + else { + local_evidence_list.remove(current_evidence); + private_evidence_list = local_evidence_list; + set_evidence_page(); + } current_evidence = 0; @@ -332,22 +540,238 @@ void Courtroom::on_evidence_delete_clicked() void Courtroom::on_evidence_x_clicked() { - ui_evidence_description->setReadOnly(true); - ui_evidence_overlay->hide(); + if (current_evidence >= + local_evidence_list.size()) // Should never happen but you never know. + return; + evi_type fake_evidence; + fake_evidence.name = ui_evidence_name->text(); + fake_evidence.description = ui_evidence_description->toPlainText(); + fake_evidence.image = ui_evidence_image_name->text(); + if (!compare_evidence_changed(fake_evidence, + local_evidence_list.at(current_evidence))) { + evidence_close(); + return; + } + QMessageBox *msgBox = new QMessageBox; + msgBox->setText("Evidence has been modified."); + msgBox->setInformativeText("Do you want to save your changes?"); + msgBox->setStandardButtons(QMessageBox::Save | QMessageBox::Discard | + QMessageBox::Cancel); + msgBox->setDefaultButton(QMessageBox::Save); + int ret = msgBox->exec(); + switch (ret) { + case QMessageBox::Save: + evidence_close(); + on_evidence_ok_clicked(); + break; + case QMessageBox::Discard: + evidence_close(); + break; + case QMessageBox::Cancel: + // Cancel was clicked, do nothing + break; + default: + // should never be reached + break; + } +} + +void Courtroom::on_evidence_ok_clicked() +{ + ui_evidence_name->setReadOnly(true); + ui_evidence_description->setReadOnly(true); + ui_evidence_image_name->setReadOnly(true); + if (current_evidence < local_evidence_list.size()) { + evi_type f_evi = local_evidence_list.at(current_evidence); + if (current_evidence_global) { + QStringList f_contents; + f_contents.append(QString::number(current_evidence)); + f_contents.append(ui_evidence_name->text()); + f_contents.append(ui_evidence_description->toPlainText()); + f_contents.append(ui_evidence_image_name->text()); + + ao_app->send_server_packet(new AOPacket("EE", f_contents)); + } + else { + f_evi.name = ui_evidence_name->text(); + f_evi.description = ui_evidence_description->toPlainText(); + f_evi.image = ui_evidence_image_name->text(); + local_evidence_list.replace(current_evidence, f_evi); + private_evidence_list = local_evidence_list; + ui_evidence_ok->hide(); + set_evidence_page(); + } + } +} + +void Courtroom::on_evidence_switch_clicked() +{ + evidence_close(); + evidence_switch(!current_evidence_global); + if (current_evidence_global) { + ui_evidence_switch->set_image("evidence_global"); + ui_evidence->set_image("evidence_background"); + ui_evidence_overlay->set_image("evidence_overlay"); + ui_evidence_transfer->set_image("evidence_transfer"); + ui_evidence_transfer->setToolTip( + tr("Transfer evidence to private inventory.")); + ui_evidence_switch->setToolTip( + tr("Current evidence is global. Click to switch to private.")); + } + else { + ui_evidence_switch->set_image("evidence_private"); + ui_evidence->set_image("evidence_background_private"); + ui_evidence_overlay->set_image("evidence_overlay_private"); + ui_evidence_transfer->set_image("evidence_transfer_private"); + ui_evidence_transfer->setToolTip( + tr("Transfer evidence to global inventory.")); + ui_evidence_switch->setToolTip( + tr("Current evidence is private. Click to switch to global.")); + } +} + +void Courtroom::on_evidence_transfer_clicked() +{ if (current_evidence >= local_evidence_list.size()) return; - QStringList f_contents; + QString name; + if (!current_evidence_global) // Transfer private evidence to global + { + evi_type f_evi = local_evidence_list.at(current_evidence); - evi_type f_evi = local_evidence_list.at(current_evidence); + QStringList f_contents; + f_contents.append(f_evi.name); + f_contents.append(f_evi.description); + f_contents.append(f_evi.image); - 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); + name = f_evi.name; + ao_app->send_server_packet(new AOPacket("PE", f_contents)); + } + else // Transfer global evidence to private + { + evi_type f_evi = local_evidence_list.at(current_evidence); + name = f_evi.name; + private_evidence_list.append(f_evi); + } - ao_app->send_server_packet(new AOPacket("EE", f_contents)); + QMessageBox *msgBox = new QMessageBox; + msgBox->setText("\"" + name + "\" has been transferred."); + msgBox->setStandardButtons(QMessageBox::Ok); + msgBox->setDefaultButton(QMessageBox::Ok); + msgBox->exec(); +} +void Courtroom::on_evidence_edited() +{ + if (current_evidence >= + local_evidence_list.size()) // Should never happen but you never know. + return; + evi_type fake_evidence; + fake_evidence.name = ui_evidence_name->text(); + fake_evidence.description = ui_evidence_description->toPlainText(); + fake_evidence.image = ui_evidence_image_name->text(); + if (compare_evidence_changed(fake_evidence, + local_evidence_list.at(current_evidence))) + ui_evidence_ok->show(); + else + ui_evidence_ok->hide(); +} + +void Courtroom::evidence_close() +{ + ui_evidence_description->setReadOnly(true); + ui_evidence_description->setToolTip(""); + ui_evidence_name->setReadOnly(true); + ui_evidence_name->setToolTip(""); + ui_evidence_image_name->setReadOnly(true); + ui_evidence_image_name->setToolTip(""); + ui_evidence_overlay->hide(); ui_ic_chat_message->setFocus(); } + +void Courtroom::evidence_switch(bool global) +{ + current_evidence_global = global; + is_presenting_evidence = false; + ui_evidence_present->set_image("present"); + local_evidence_list.clear(); + if (current_evidence_global) { + local_evidence_list = global_evidence_list; + ui_evidence_present->show(); + ui_evidence_save->hide(); + ui_evidence_load->hide(); + } + else { + local_evidence_list = private_evidence_list; + ui_evidence_present->hide(); + ui_evidence_save->show(); + ui_evidence_load->show(); + } + set_evidence_page(); +} + +void Courtroom::on_evidence_save_clicked() +{ + if (current_evidence_global) + return; // Don't allow saving/loading operations when in global inventory + // mode for now + + QString p_path = QFileDialog::getSaveFileName( + this, tr("Save Inventory"), "base/inventories/", tr("Ini Files (*.ini)")); + if (p_path.isEmpty()) + return; + + evidence_close(); + ui_evidence_name->setText(""); + + QSettings inventory(p_path, QSettings::IniFormat); + inventory.clear(); + for (int i = 0; i < local_evidence_list.size(); i++) { + inventory.beginGroup(QString::number(i)); + inventory.setValue("name", local_evidence_list[i].name); + inventory.setValue("description", local_evidence_list[i].description); + inventory.setValue("image", local_evidence_list[i].image); + inventory.endGroup(); + } + inventory.sync(); +} + +void Courtroom::on_evidence_load_clicked() +{ + if (current_evidence_global) + return; // Don't allow saving/loading operations when in global inventory + // mode for now + + QString p_path = QFileDialog::getOpenFileName( + this, tr("Open Inventory"), "base/inventories/", tr("Ini Files (*.ini)")); + if (p_path.isEmpty()) + return; + + evidence_close(); + ui_evidence_name->setText(""); + + QSettings inventory(p_path, QSettings::IniFormat); + local_evidence_list.clear(); + foreach (QString evi, inventory.childGroups()) { + if (evi == "General") + continue; + + evi_type f_evi; + f_evi.name = inventory.value(evi + "/name", "UNKNOWN").value(); + f_evi.description = + inventory.value(evi + "/description", "UNKNOWN").value(); + f_evi.image = + inventory.value(evi + "/image", "UNKNOWN.png").value(); + local_evidence_list.append(f_evi); + } + private_evidence_list = local_evidence_list; + set_evidence_page(); +} + +bool Courtroom::compare_evidence_changed(evi_type evi_a, evi_type evi_b) +{ + return evi_a.name != evi_b.name || evi_a.image != evi_b.image || + evi_a.description != evi_b.description; +} diff --git a/src/hardware_functions.cpp b/src/hardware_functions.cpp index 4dbc438..c898aef 100644 --- a/src/hardware_functions.cpp +++ b/src/hardware_functions.cpp @@ -51,8 +51,25 @@ QString get_hdid() #elif defined __APPLE__ QString get_hdid() { - // hdids are broken at this point anyways - return "just a mac passing by"; + 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 diff --git a/src/lobby.cpp b/src/lobby.cpp index 79a37be..81e4086 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -20,9 +20,19 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() 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_settings = new AOButton(this, ao_app); + + ui_server_list = new QTreeWidget(this); + ui_server_list->setHeaderLabels({"#", "Name"}); //, "Players"}); + ui_server_list->hideColumn(0); + + ui_server_search = new QLineEdit(this); + ui_server_search->setFrame(false); + ui_server_search->setPlaceholderText(tr("Search")); + ui_player_count = new QLabel(this); ui_description = new AOTextArea(this); + ui_description->setOpenExternalLinks(true); ui_chatbox = new AOTextArea(this); ui_chatbox->setOpenExternalLinks(true); ui_chatname = new QLineEdit(this); @@ -49,8 +59,13 @@ Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() 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_settings, SIGNAL(clicked()), this, SLOT(on_settings_clicked())); + connect(ui_server_list, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, + SLOT(on_server_list_clicked(QTreeWidgetItem *, int))); + connect(ui_server_list, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), + this, SLOT(on_server_list_doubleclicked(QTreeWidgetItem *, int))); + connect(ui_server_search, SIGNAL(textChanged(QString)), this, + SLOT(on_server_search_edited(QString))); connect(ui_chatmessage, SIGNAL(returnPressed()), this, SLOT(on_chatfield_return_pressed())); connect(ui_cancel, SIGNAL(clicked()), ao_app, SLOT(loading_cancelled())); @@ -75,9 +90,10 @@ void Lobby::set_widgets() 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?"); + call_notice( + tr("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); } @@ -86,33 +102,42 @@ void Lobby::set_widgets() } set_size_and_pos(ui_background, "lobby"); - ui_background->set_image("lobbybackground.png"); + ui_background->set_image("lobbybackground"); set_size_and_pos(ui_public_servers, "public_servers"); - ui_public_servers->set_image("publicservers_selected.png"); + ui_public_servers->set_image("publicservers_selected"); set_size_and_pos(ui_favorites, "favorites"); - ui_favorites->set_image("favorites.png"); + ui_favorites->set_image("favorites"); set_size_and_pos(ui_refresh, "refresh"); - ui_refresh->set_image("refresh.png"); + ui_refresh->set_image("refresh"); set_size_and_pos(ui_add_to_fav, "add_to_fav"); - ui_add_to_fav->set_image("addtofav.png"); + ui_add_to_fav->set_image("addtofav"); set_size_and_pos(ui_connect, "connect"); - ui_connect->set_image("connect.png"); + ui_connect->set_image("connect"); set_size_and_pos(ui_version, "version"); - ui_version->setText("Version: " + ao_app->get_version_string()); + ui_version->setText(tr("Version: KFO%1").arg(ao_app->get_version_string())); set_size_and_pos(ui_about, "about"); - ui_about->set_image("about.png"); + ui_about->set_image("about"); + + set_size_and_pos(ui_settings, "settings"); + ui_settings->setText(tr("Settings")); + ui_settings->set_image("settings"); + ui_settings->setToolTip( + tr("Allows you to change various aspects of the client.")); 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_server_search, "server_search"); + ui_server_search->setStyleSheet("background-color: rgba(0, 0, 0, 0);"); + set_size_and_pos(ui_player_count, "player_count"); ui_player_count->setText("Offline"); ui_player_count->setStyleSheet("font: bold;" @@ -139,7 +164,7 @@ void Lobby::set_widgets() "selection-background-color: rgba(0, 0, 0, 0);"); ui_loading_background->resize(this->width(), this->height()); - ui_loading_background->set_image("loadingbackground.png"); + ui_loading_background->set_image("loadingbackground"); set_size_and_pos(ui_loading_text, "loading_label"); ui_loading_text->setFont(QFont("Arial", 20, QFont::Bold)); @@ -155,6 +180,9 @@ void Lobby::set_widgets() ui_cancel->setText("Cancel"); ui_loading_background->hide(); + + set_fonts(); + set_stylesheets(); } void Lobby::set_size_and_pos(QWidget *p_widget, QString p_identifier) @@ -174,6 +202,71 @@ void Lobby::set_size_and_pos(QWidget *p_widget, QString p_identifier) } } +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_tagged_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 = ao_app->get_font_size("use_custom_fonts", design_file) == 1; + if (use) { + widget->setFont(font); + QColor f_color = ao_app->get_color(p_identifier + "_color", design_file); + bool bold = ao_app->get_font_size(p_identifier + "_bold", design_file) == + 1; // is the font bold or not? + bool center = + ao_app->get_font_size(p_identifier + "_center", design_file) == + 1; // 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(); @@ -188,7 +281,10 @@ QString Lobby::get_chatlog() return return_value; } -int Lobby::get_selected_server() { return ui_server_list->currentRow(); } +int Lobby::get_selected_server() +{ + return ui_server_list->currentItem()->text(0).toInt(); +} void Lobby::set_loading_value(int p_value) { @@ -197,8 +293,8 @@ void Lobby::set_loading_value(int p_value) void Lobby::on_public_servers_clicked() { - ui_public_servers->set_image("publicservers_selected.png"); - ui_favorites->set_image("favorites.png"); + ui_public_servers->set_image("publicservers_selected"); + ui_favorites->set_image("favorites"); list_servers(); @@ -207,8 +303,8 @@ void Lobby::on_public_servers_clicked() void Lobby::on_favorites_clicked() { - ui_favorites->set_image("favorites_selected.png"); - ui_public_servers->set_image("publicservers.png"); + ui_favorites->set_image("favorites_selected"); + ui_public_servers->set_image("publicservers"); ao_app->set_favorite_list(); // ao_app->favorite_list = read_serverlist_txt(); @@ -218,14 +314,11 @@ void Lobby::on_favorites_clicked() public_servers_selected = false; } -void Lobby::on_refresh_pressed() -{ - ui_refresh->set_image("refresh_pressed.png"); -} +void Lobby::on_refresh_pressed() { ui_refresh->set_image("refresh_pressed"); } void Lobby::on_refresh_released() { - ui_refresh->set_image("refresh.png"); + ui_refresh->set_image("refresh"); AOPacket *f_packet = new AOPacket("ALL#%"); @@ -234,28 +327,25 @@ void Lobby::on_refresh_released() void Lobby::on_add_to_fav_pressed() { - ui_add_to_fav->set_image("addtofav_pressed.png"); + ui_add_to_fav->set_image("addtofav_pressed"); } void Lobby::on_add_to_fav_released() { - ui_add_to_fav->set_image("addtofav.png"); + ui_add_to_fav->set_image("addtofav"); // you cant add favorites from favorites m8 if (!public_servers_selected) return; - ao_app->add_favorite_server(ui_server_list->currentRow()); + ao_app->add_favorite_server(get_selected_server()); } -void Lobby::on_connect_pressed() -{ - ui_connect->set_image("connect_pressed.png"); -} +void Lobby::on_connect_pressed() { ui_connect->set_image("connect_pressed"); } void Lobby::on_connect_released() { - ui_connect->set_image("connect.png"); + ui_connect->set_image("connect"); AOPacket *f_packet; @@ -274,48 +364,91 @@ void Lobby::on_about_clicked() "https://github.com/AttorneyOnline/AO2-Client" "

Major development:
" "OmniTroid, stonedDiscord, longbyte1, gameboyprinter, Cerapter" + "

2.8 Major Release development:
" + "Crystalwarrior, Iamgoofball" + "

2.8 Quality Assurance:
" + "WillDean, Captain N, Mr M, Riel, Seimmet, Fury McFlurry," + "CedricDewitt, Chewable Tablets, Fantos, Futugaze," + "Geck, Minx, Pandae, Sierra, CrazyJC, CaseyMayCazy," + "GreenBowers, Robotic Overlord, Veritas, Gin-Gi" "

Special thanks:
" "Remy, Iamgoofball, Hibiki, Qubrick (webAO), Ruekasu (UI design), " "Draxirch (UI design), Unishred, Argoneus (tsuserver), Fiercy, " - "Noevain, Cronnicossy") + "Noevain, Cronnicossy, the AO2 community, server hosts, game masters," + "case makers, content creators and players!") .arg(ao_app->get_version_string()); QMessageBox::about(this, "About", msg); } -void Lobby::on_server_list_clicked(QModelIndex p_model) +void Lobby::on_settings_clicked() { ao_app->call_settings_menu(); } + +// clicked on an item in the serverlist +void Lobby::on_server_list_clicked(QTreeWidgetItem *p_item, int column) { - server_type f_server; - int n_server = p_model.row(); + column = 0; + if (p_item->text(column).toInt() != last_index) { + server_type f_server; + int n_server = p_item->text(column).toInt(); + last_index = n_server; - 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()) + if (n_server < 0) return; - f_server = f_server_list.at(p_model.row()); - } - else { - if (n_server >= ao_app->get_favorite_list().size()) - return; + if (public_servers_selected) { + QVector f_server_list = ao_app->get_server_list(); - f_server = ao_app->get_favorite_list().at(p_model.row()); + if (n_server >= f_server_list.size()) + return; + + f_server = f_server_list.at(n_server); + } + else { + if (n_server >= ao_app->get_favorite_list().size()) + return; + + f_server = ao_app->get_favorite_list().at(n_server); + } + + ui_description->clear(); + ui_description->append_linked(f_server.desc); + + ui_description->moveCursor(QTextCursor::Start); + ui_description->ensureCursorVisible(); + + ui_player_count->setText(tr("Offline")); + + ui_connect->setEnabled(false); + + ao_app->net_manager->connect_to_server(f_server); + } +} + +// doubleclicked on an item in the serverlist so we'll connect right away +void Lobby::on_server_list_doubleclicked(QTreeWidgetItem *p_item, int column) +{ + on_server_list_clicked(p_item, column); + on_connect_released(); +} + +void Lobby::on_server_search_edited(QString p_text) +{ + // Iterate through all QTreeWidgetItem items + QTreeWidgetItemIterator it(ui_server_list); + while (*it) { + (*it)->setHidden(p_text != ""); + ++it; } - ui_description->clear(); - ui_description->append(f_server.desc); - - ui_description->moveCursor(QTextCursor::Start); - ui_description->ensureCursorVisible(); - - ui_player_count->setText("Offline"); - - ui_connect->setEnabled(false); - - ao_app->net_manager->connect_to_server(f_server); + if (p_text != "") { + // Search in metadata + QList clist = ui_server_list->findItems( + ui_server_search->text(), Qt::MatchContains | Qt::MatchRecursive, 1); + foreach (QTreeWidgetItem *item, clist) { + if (item->parent() != nullptr) // So the category shows up too + item->parent()->setHidden(false); + item->setHidden(false); + } + } } void Lobby::on_chatfield_return_pressed() @@ -337,23 +470,38 @@ void Lobby::on_chatfield_return_pressed() void Lobby::list_servers() { public_servers_selected = true; - ui_favorites->set_image("favorites.png"); - ui_public_servers->set_image("publicservers_selected.png"); + ui_favorites->set_image("favorites"); + ui_public_servers->set_image("publicservers_selected"); + ui_server_list->setSortingEnabled(false); ui_server_list->clear(); + ui_server_search->setText(""); + + int i = 0; for (server_type i_server : ao_app->get_server_list()) { - ui_server_list->addItem(i_server.name); + QTreeWidgetItem *treeItem = new QTreeWidgetItem(ui_server_list); + treeItem->setText(0, QString::number(i)); + treeItem->setText(1, i_server.name); + i++; } + ui_server_list->setSortingEnabled(true); } void Lobby::list_favorites() { + ui_server_list->setSortingEnabled(false); ui_server_list->clear(); + int i = 0; for (server_type i_server : ao_app->get_favorite_list()) { - ui_server_list->addItem(i_server.name); + QTreeWidgetItem *treeItem = new QTreeWidgetItem(ui_server_list); + treeItem->setText(0, QString::number(i)); + treeItem->setText(1, i_server.name); + // treeItem->setText(2, "-"); + i++; } + ui_server_list->setSortingEnabled(true); } void Lobby::append_chatmessage(QString f_name, QString f_message) @@ -370,8 +518,9 @@ void Lobby::append_error(QString 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); + QString f_string = tr("Online: %1/%2") + .arg(QString::number(players_online)) + .arg(QString::number(max_players)); ui_player_count->setText(f_string); } diff --git a/src/main.cpp b/src/main.cpp index 1caa394..364377b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,7 +6,10 @@ #include "lobby.h" #include "networkmanager.h" #include +#include #include +#include + int main(int argc, char *argv[]) { #if QT_VERSION > QT_VERSION_CHECK(5, 6, 0) @@ -17,6 +20,32 @@ int main(int argc, char *argv[]) #endif AOApplication main_app(argc, argv); + + QSettings *configini = main_app.configini; + + QPluginLoader apngPlugin("qapng"); + if (!apngPlugin.load()) + qCritical() << "QApng plugin could not be loaded"; + + QPluginLoader webpPlugin("qwebp"); + if (!webpPlugin.load()) + qCritical() << "QWebp plugin could not be loaded"; + + QString p_language = + configini->value("language", QLocale::system().name()).toString(); + if (p_language == " " || p_language == "") + p_language = QLocale::system().name(); + + QTranslator qtTranslator; + qtTranslator.load("qt_" + p_language, + QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + main_app.installTranslator(&qtTranslator); + + QTranslator appTranslator; + qDebug() << ":/resource/translations/ao_" + p_language; + appTranslator.load("ao_" + p_language, ":/resource/translations/"); + main_app.installTranslator(&appTranslator); + main_app.construct_lobby(); main_app.net_manager->connect_to_master(); main_app.w_lobby->show(); diff --git a/src/packet_distribution.cpp b/src/packet_distribution.cpp index baba0ea..a7bdbab 100644 --- a/src/packet_distribution.cpp +++ b/src/packet_distribution.cpp @@ -91,14 +91,9 @@ void AOApplication::ms_packet_received(AOPacket *p_packet) } } - 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."); + call_notice(tr("Outdated version! Your version: %1\n" + "Please go to aceattorneyonline.com to update.") + .arg(get_version_string())); destruct_courtroom(); destruct_lobby(); } @@ -141,6 +136,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) arup_enabled = false; casing_alerts_enabled = false; modcall_reason_enabled = false; + looping_sfx_support_enabled = false; + additive_enabled = false; + effects_enabled = false; // workaround for tsuserver4 if (f_contents.at(0) == "NOENCRYPT") @@ -177,6 +175,8 @@ void AOApplication::server_packet_received(AOPacket *p_packet) else if (header == "FL") { if (f_packet.contains("yellowtext", Qt::CaseInsensitive)) yellow_text_enabled = true; + if (f_packet.contains("prezoom", Qt::CaseInsensitive)) + prezoom_enabled = true; if (f_packet.contains("flipping", Qt::CaseInsensitive)) flipping_enabled = true; if (f_packet.contains("customobjections", Qt::CaseInsensitive)) @@ -197,8 +197,12 @@ void AOApplication::server_packet_received(AOPacket *p_packet) casing_alerts_enabled = true; if (f_packet.contains("modcall_reason", Qt::CaseInsensitive)) modcall_reason_enabled = true; - - w_lobby->enable_connect_button(); + if (f_packet.contains("looping_sfx", Qt::CaseInsensitive)) + looping_sfx_support_enabled = true; + if (f_packet.contains("additive", Qt::CaseInsensitive)) + additive_enabled = true; + if (f_packet.contains("effects", Qt::CaseInsensitive)) + effects_enabled = true; } else if (header == "PN") { if (f_contents.size() < 2) @@ -236,7 +240,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (selected_server >= 0 && selected_server < server_list.size()) { auto info = server_list.at(selected_server); server_name = info.name; - server_address = QString("%1:%2").arg(info.ip, info.port); + server_address = + QString("%1:%2").arg(info.ip, QString::number(info.port)); + qDebug() << server_address; window_title += ": " + server_name; } } @@ -244,7 +250,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) 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; + server_address = + QString("%1:%2").arg(info.ip, QString::number(info.port)); + qDebug() << server_address; window_title += ": " + server_name; } } @@ -264,6 +272,15 @@ void AOApplication::server_packet_received(AOPacket *p_packet) send_server_packet(f_packet); + // Remove any characters not accepted in folder names for the server_name + // here + this->log_filename = QDateTime::currentDateTime().toUTC().toString( + "'logs/" + server_name.remove(QRegExp("[\\\\/:*?\"<>|\']")) + + "/'ddd MMMM yyyy hh.mm.ss t'.log'"); + this->write_to_file("Joined server " + server_name + " on address " + + server_address + " on " + + QDateTime::currentDateTime().toUTC().toString(), + log_filename, true); QCryptographicHash hash(QCryptographicHash::Algorithm::Sha256); hash.addData(server_address.toUtf8()); if (is_discord_enabled()) @@ -343,9 +360,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) ++loaded_evidence; - w_lobby->set_loading_text("Loading evidence:\n" + - QString::number(loaded_evidence) + "/" + - QString::number(evidence_list_size)); + w_lobby->set_loading_text(tr("Loading evidence:\n%1/%2") + .arg(QString::number(loaded_evidence)) + .arg(QString::number(evidence_list_size))); w_courtroom->append_evidence(f_evi); @@ -378,9 +395,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) ++loaded_music; - w_lobby->set_loading_text("Loading music:\n" + - QString::number(loaded_music) + "/" + - QString::number(music_list_size)); + w_lobby->set_loading_text(tr("Loading music:\n%1/%2") + .arg(QString::number(loaded_music)) + .arg(QString::number(music_list_size))); if (musics_time) { w_courtroom->append_music(f_music); @@ -445,9 +462,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) ++loaded_chars; - w_lobby->set_loading_text("Loading chars:\n" + - QString::number(loaded_chars) + "/" + - QString::number(char_list_size)); + w_lobby->set_loading_text(tr("Loading chars:\n%1/%2") + .arg(QString::number(loaded_chars)) + .arg(QString::number(char_list_size))); w_courtroom->append_char(f_char); @@ -472,9 +489,9 @@ void AOApplication::server_packet_received(AOPacket *p_packet) 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)); + w_lobby->set_loading_text(tr("Loading music:\n%1/%2") + .arg(QString::number(loaded_music)) + .arg(QString::number(music_list_size))); if (musics_time) { w_courtroom->append_music(f_contents.at(n_element)); @@ -511,6 +528,32 @@ void AOApplication::server_packet_received(AOPacket *p_packet) send_server_packet(new AOPacket("RD#%")); } + else if (header == "FM") // Fetch music ONLY + { + if (!courtroom_constructed) + goto end; + + w_courtroom->clear_music(); + + for (int n_element = 0; n_element < f_contents.size(); ++n_element) { + w_courtroom->append_music(f_contents.at(n_element)); + } + + w_courtroom->list_music(); + } + else if (header == "FA") // Fetch areas ONLY + { + if (!courtroom_constructed) + goto end; + + w_courtroom->clear_areas(); + + for (int n_element = 0; n_element < f_contents.size(); ++n_element) { + w_courtroom->append_area(f_contents.at(n_element)); + } + + w_courtroom->list_areas(); + } else if (header == "DONE") { if (!courtroom_constructed) goto end; @@ -529,8 +572,28 @@ void AOApplication::server_packet_received(AOPacket *p_packet) if (f_contents.size() < 1) goto end; - if (courtroom_constructed) - w_courtroom->set_background(f_contents.at(0)); + if (courtroom_constructed) { + if (f_contents.size() >= + 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); + } + } + else if (header == "SP") { + if (f_contents.size() < 1) + goto end; + + if (courtroom_constructed) // We were sent a "set position" packet + { + w_courtroom->set_side(f_contents.at(0)); + } + } + else if (header == "SD") // Send pos dropdown + { + if (f_contents.size() < 1) + goto end; + + w_courtroom->set_pos_dropdown(f_contents.at(0).split("*")); } // server accepting char request(CC) packet else if (header == "PV") { @@ -538,7 +601,7 @@ void AOApplication::server_packet_received(AOPacket *p_packet) goto end; if (courtroom_constructed) - w_courtroom->enter_courtroom(f_contents.at(2).toInt()); + w_courtroom->update_character(f_contents.at(2).toInt()); } else if (header == "MS") { if (courtroom_constructed && courtroom_loaded) @@ -608,22 +671,23 @@ void AOApplication::server_packet_received(AOPacket *p_packet) } else if (header == "KK") { if (courtroom_constructed && f_contents.size() >= 1) { - call_notice("You have been kicked from the server.\nReason: " + - f_contents.at(0)); + call_notice(tr("You have been kicked from the server.\nReason: %1") + .arg(f_contents.at(0))); construct_lobby(); destruct_courtroom(); } } else if (header == "KB") { if (courtroom_constructed && f_contents.size() >= 1) { - call_notice("You have been banned from the server.\nReason: " + - f_contents.at(0)); + call_notice(tr("You have been banned from the server.\nReason: %1") + .arg(f_contents.at(0))); construct_lobby(); destruct_courtroom(); } } else if (header == "BD") { - call_notice("You are banned on this server.\nReason: " + f_contents.at(0)); + call_notice( + tr("You are banned on this server.\nReason: %1").arg(f_contents.at(0))); } else if (header == "ZZ") { if (courtroom_constructed && f_contents.size() > 0) diff --git a/src/scrolltext.cpp b/src/scrolltext.cpp new file mode 100644 index 0000000..afd0aab --- /dev/null +++ b/src/scrolltext.cpp @@ -0,0 +1,125 @@ +#include "scrolltext.h" + +ScrollText::ScrollText(QWidget *parent) : QWidget(parent), scrollPos(0) +{ + staticText.setTextFormat(Qt::PlainText); + + // setFixedHeight(fontMetrics().height()*2); //The theme sets this + leftMargin = height() / 3; + + setSeparator(" --- "); + + connect(&timer, SIGNAL(timeout()), this, SLOT(timer_timeout())); + timer.setInterval(50); +} + +QString ScrollText::text() const { return _text; } + +void ScrollText::setText(QString text) +{ + _text = text; + updateText(); + update(); +} + +QString ScrollText::separator() const { return _separator; } + +void ScrollText::setSeparator(QString separator) +{ + _separator = separator; + updateText(); + update(); +} + +void ScrollText::updateText() +{ + timer.stop(); + + singleTextWidth = fontMetrics().horizontalAdvance(_text); + scrollEnabled = (singleTextWidth > width() - leftMargin * 2); + + if (scrollEnabled) { + scrollPos = -64; + staticText.setText(_text + _separator); + timer.start(); + } + else + staticText.setText(_text); + + staticText.prepare(QTransform(), font()); + wholeTextSize = QSize(fontMetrics().horizontalAdvance(staticText.text()), + fontMetrics().height()); +} + +void ScrollText::paintEvent(QPaintEvent *) +{ + QPainter p(this); + + if (scrollEnabled) { + buffer.fill(qRgba(0, 0, 0, 0)); + QPainter pb(&buffer); + pb.setPen(p.pen()); + pb.setFont(p.font()); + + int x = qMin(-scrollPos, 0) + leftMargin; + while (x < width()) { + pb.drawStaticText(QPointF(x, (height() - wholeTextSize.height()) / 2), + staticText); + x += wholeTextSize.width(); + } + + // Apply Alpha Channel + pb.setCompositionMode(QPainter::CompositionMode_DestinationIn); + pb.setClipRect(width() - 15, 0, 15, height()); + pb.drawImage(0, 0, alphaChannel); + pb.setClipRect(0, 0, 15, height()); + // initial situation: don't apply alpha channel in the left half of the + // image at all; apply it more and more until scrollPos gets positive + if (scrollPos < 0) + pb.setOpacity(static_cast((qMax(-8, scrollPos) + 8) / 8.0)); + pb.drawImage(0, 0, alphaChannel); + + // pb.end(); + p.drawImage(0, 0, buffer); + } + else { + p.drawStaticText( + QPointF(leftMargin, (height() - wholeTextSize.height()) / 2), + staticText); + } +} + +void ScrollText::resizeEvent(QResizeEvent *) +{ + // When the widget is resized, we need to update the alpha channel. + + alphaChannel = QImage(size(), QImage::Format_ARGB32_Premultiplied); + buffer = QImage(size(), QImage::Format_ARGB32_Premultiplied); + + // Create Alpha Channel: + if (width() > 64) { + // create first scanline + QRgb *scanline1 = reinterpret_cast(alphaChannel.scanLine(0)); + for (int x = 1; x < 16; ++x) + scanline1[x - 1] = scanline1[width() - x] = qRgba(0, 0, 0, x << 4); + for (int x = 15; x < width() - 15; ++x) + scanline1[x] = qRgb(0, 0, 0); + // copy scanline to the other ones + for (int y = 1; y < height(); ++y) + memcpy(alphaChannel.scanLine(y), scanline1, + static_cast(width() * 4)); + } + else + alphaChannel.fill(qRgb(0, 0, 0)); + + // Update scrolling state + bool newScrollEnabled = (singleTextWidth > width() - leftMargin); + if (newScrollEnabled != scrollEnabled) + updateText(); +} + +void ScrollText::timer_timeout() +{ + scrollPos = (scrollPos + 2) % wholeTextSize.width(); + update(); +} diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index 18f0e4c..1689cd3 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -8,7 +8,7 @@ QString AOApplication::read_theme() int AOApplication::read_blip_rate() { - int result = configini->value("blip_rate", 1).toInt(); + int result = configini->value("blip_rate", 2).toInt(); if (result < 1) return 1; @@ -49,7 +49,7 @@ int AOApplication::get_max_log_size() bool AOApplication::get_log_goes_downwards() { QString result = - configini->value("log_goes_downwards", "false").value(); + configini->value("log_goes_downwards", "true").value(); return result.startsWith("true"); } @@ -77,17 +77,22 @@ QString AOApplication::get_audio_output_device() } QStringList AOApplication::get_call_words() +{ + return get_list_file(get_base_path() + "callwords.ini"); +} + +QStringList AOApplication::get_list_file(QString p_file) { QStringList return_value; - QFile callwords_ini; + QFile p_ini; - callwords_ini.setFileName(get_base_path() + "callwords.ini"); + p_ini.setFileName(p_file); - if (!callwords_ini.open(QIODevice::ReadOnly)) + if (!p_ini.open(QIODevice::ReadOnly)) return return_value; - QTextStream in(&callwords_ini); + QTextStream in(&p_ini); while (!in.atEnd()) { QString line = in.readLine(); @@ -97,6 +102,71 @@ QStringList AOApplication::get_call_words() return return_value; } +QString AOApplication::read_file(QString filename) +{ + QFile f_log(filename); + + if (!f_log.open(QIODevice::ReadOnly | QIODevice::Text)) { + qDebug() << "Couldn't open" << filename; + return ""; + } + + QTextStream in(&f_log); + QString text = in.readAll(); + f_log.close(); + return text; +} + +bool AOApplication::write_to_file(QString p_text, QString p_file, bool make_dir) +{ + QString path = QFileInfo(p_file).path(); + if (make_dir) { + // Create the dir if it doesn't exist yet + QDir dir(path); + if (!dir.exists()) + if (!dir.mkpath(".")) + return false; + } + + QFile f_log(p_file); + if (f_log.open(QIODevice::WriteOnly | QIODevice::Text | + QIODevice::Truncate)) { + QTextStream out(&f_log); + + out << p_text; + + f_log.flush(); + f_log.close(); + return true; + } + return false; +} + +bool AOApplication::append_to_file(QString p_text, QString p_file, + bool make_dir) +{ + QString path = QFileInfo(p_file).path(); + // Create the dir if it doesn't exist yet + if (make_dir) { + QDir dir(path); + if (!dir.exists()) + if (!dir.mkpath(".")) + return false; + } + + QFile f_log(p_file); + if (f_log.open(QIODevice::WriteOnly | QIODevice::Append)) { + QTextStream out(&f_log); + + out << "\r\n" << p_text; + + f_log.flush(); + f_log.close(); + return true; + } + return false; +} + void AOApplication::write_to_serverlist_txt(QString p_line) { QFile serverlist_txt; @@ -192,11 +262,14 @@ QPoint AOApplication::get_button_spacing(QString p_identifier, QString p_file) } pos_size_type AOApplication::get_element_dimensions(QString p_identifier, - QString p_file) + 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, design_ini_path); + QString f_result = read_design_ini(p_identifier, char_ini_path); pos_size_type return_value; @@ -206,10 +279,13 @@ pos_size_type AOApplication::get_element_dimensions(QString p_identifier, return_value.height = -1; if (f_result == "") { - f_result = read_design_ini(p_identifier, default_path); + 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; + if (f_result == "") + return return_value; + } } QStringList sub_line_elements = f_result.split(","); @@ -224,6 +300,21 @@ pos_size_type AOApplication::get_element_dimensions(QString p_identifier, return return_value; } +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); + } + return f_result; +} QString AOApplication::get_font_name(QString p_identifier, QString p_file) { QString design_ini_path = get_theme_path(p_file); @@ -232,7 +323,7 @@ QString AOApplication::get_font_name(QString p_identifier, QString p_file) if (f_result == "") { f_result = read_design_ini(p_identifier, default_path); if (f_result == "") - return "Sans"; + return ""; } return f_result; } @@ -279,48 +370,120 @@ QColor AOApplication::get_color(QString p_identifier, QString p_file) return return_color; } +QString AOApplication::get_stylesheet(QString p_file) +{ + QString design_ini_path = get_theme_path(p_file); + QString default_path = get_default_theme_path(p_file); + + QFile design_ini; + + design_ini.setFileName(design_ini_path); + + if (!design_ini.open(QIODevice::ReadOnly)) { + design_ini.setFileName(default_path); + if (!design_ini.open(QIODevice::ReadOnly)) + return ""; + } + + QTextStream in(&design_ini); + + QString f_text; + + while (!in.atEnd()) { + f_text.append(in.readLine()); + } + + design_ini.close(); + return f_text; +} + +QString AOApplication::get_tagged_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; +} + +QString AOApplication::get_chat_markdown(QString p_identifier, QString p_chat) +{ + QString design_ini_path = get_base_path() + "misc/" + 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); + + return f_result.toLatin1(); +} + QColor AOApplication::get_chat_color(QString p_identifier, QString p_chat) { QColor return_color(255, 255, 255); - if (p_identifier == "_inline_grey") { + switch (p_identifier.toInt()) { + case 0: // White + return_color = QColor(255, 255, 255); + break; + case 1: // Green + return_color = QColor(0, 255, 0); + break; + case 2: // Red + return_color = QColor(255, 0, 0); + break; + case 3: // Orange + return_color = QColor(255, 165, 0); + break; + case 4: // Blue + return_color = QColor(45, 150, 255); + break; + case 5: // Yellow + return_color = QColor(255, 255, 0); + break; + case 6: // Pink + return_color = QColor(255, 192, 203); + break; + case 7: // Cyan + return_color = QColor(0, 255, 255); + break; + case 8: // Grey return_color = QColor(187, 187, 187); + break; + default: + return_color = QColor(255, 255, 255); + break; } - else { - switch (p_identifier.toInt()) { - case 1: - return_color = QColor(0, 255, 0); - break; - case 2: - return_color = QColor(255, 0, 0); - break; - case 3: - return_color = QColor(255, 165, 0); - break; - case 4: - return_color = QColor(45, 150, 255); - break; - case 5: - return_color = QColor(255, 255, 0); - break; - case 7: - return_color = QColor(255, 192, 203); - break; - case 8: - return_color = QColor(0, 255, 255); - break; - case 0: - case 6: // 6 is rainbow. - default: - return_color = QColor(255, 255, 255); - break; - } - } - - p_identifier = p_identifier.prepend("c"); QString design_ini_path = get_base_path() + "misc/" + 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); + QString f_result = read_design_ini("c" + p_identifier, design_ini_path); if (f_result == "") { f_result = read_design_ini(p_identifier, default_path); @@ -363,24 +526,35 @@ QString AOApplication::get_sfx(QString p_identifier) QString AOApplication::get_sfx_suffix(QString sound_to_check) { - QString mp3_check = get_sounds_path(sound_to_check + ".mp3"); - QString opus_check = get_sounds_path(sound_to_check + ".opus"); - if (file_exists(opus_check)) { + if (file_exists(sound_to_check)) + return sound_to_check; + if (file_exists(sound_to_check + ".opus")) return sound_to_check + ".opus"; - } - else if (file_exists(mp3_check)) { + if (file_exists(sound_to_check + ".ogg")) + return sound_to_check + ".ogg"; + if (file_exists(sound_to_check + ".mp3")) return sound_to_check + ".mp3"; - } + if (file_exists(sound_to_check + ".mp4")) + return sound_to_check + ".mp4"; return sound_to_check + ".wav"; } QString AOApplication::get_image_suffix(QString path_to_check) { - QString apng_check = path_to_check + ".apng"; - if (file_exists(apng_check)) { - return apng_check; - } - return path_to_check + ".gif"; + if (file_exists(path_to_check)) + return path_to_check; + if (file_exists(path_to_check + ".webp")) + return path_to_check + ".webp"; + if (file_exists(path_to_check + ".apng")) + return path_to_check + ".apng"; + if (file_exists(path_to_check + ".gif")) + return path_to_check + ".gif"; + return path_to_check + ".png"; +} + +QString AOApplication::get_static_image_suffix(QString path_to_check) +{ + return path_to_check + ".png"; } // returns whatever is to the right of "search_line =" within target_tag and @@ -397,14 +571,40 @@ QString AOApplication::read_char_ini(QString p_char, QString p_search_line, return value; } +void AOApplication::set_char_ini(QString p_char, QString value, + QString p_search_line, QString target_tag) +{ + QSettings settings(get_character_path(p_char, "char.ini"), + QSettings::IniFormat); + settings.beginGroup(target_tag); + settings.setValue(p_search_line, value); + settings.endGroup(); +} + +// returns all the values of target_tag +QStringList AOApplication::read_ini_tags(QString p_path, QString target_tag) +{ + QStringList r_values; + QSettings settings(p_path, QSettings::IniFormat); + if (!target_tag.isEmpty()) + settings.beginGroup(target_tag); + QStringList keys = settings.allKeys(); + foreach (QString key, keys) { + QString value = settings.value(key).toString(); + r_values << key + "=" + value; + } + if (!settings.group().isEmpty()) + settings.endGroup(); + return r_values; +} + QString AOApplication::get_char_name(QString p_char) { QString f_result = read_char_ini(p_char, "name", "Options"); if (f_result == "") return p_char; - else - return f_result; + return f_result; } QString AOApplication::get_showname(QString p_char) @@ -416,8 +616,7 @@ QString AOApplication::get_showname(QString p_char) return ""; if (f_result == "") return p_char; - else - return f_result; + return f_result; } QString AOApplication::get_char_side(QString p_char) @@ -426,8 +625,7 @@ QString AOApplication::get_char_side(QString p_char) if (f_result == "") return "wit"; - else - return f_result; + return f_result; } QString AOApplication::get_gender(QString p_char) @@ -435,9 +633,15 @@ QString AOApplication::get_gender(QString p_char) QString f_result = read_char_ini(p_char, "gender", "Options"); if (f_result == "") - return "male"; - else - return f_result; + return "sfx-blipmale"; + + if (!file_exists(get_sfx_suffix(get_sounds_path(f_result)))) { + if (file_exists(get_sfx_suffix(get_sounds_path("../blips/" + f_result)))) + return "../blips/" + f_result; // Return the cool kids variant + + return "sfx-blip" + f_result; // Return legacy variant + } + return f_result; } QString AOApplication::get_chat(QString p_char) @@ -449,13 +653,28 @@ QString AOApplication::get_chat(QString p_char) return f_result; } +QString AOApplication::get_chat_font(QString p_char) +{ + QString f_result = read_char_ini(p_char, "chat_font", "Options"); + + return f_result; +} + +int AOApplication::get_chat_size(QString p_char) +{ + QString f_result = read_char_ini(p_char, "chat_size", "Options"); + + if (f_result == "") + return -1; + return f_result.toInt(); +} + QString AOApplication::get_char_shouts(QString p_char) { QString f_result = read_char_ini(p_char, "shouts", "Options"); if (f_result == "") return "default"; - else - return f_result; + return f_result; } int AOApplication::get_preanim_duration(QString p_char, QString p_emote) @@ -464,8 +683,7 @@ int AOApplication::get_preanim_duration(QString p_char, QString p_emote) if (f_result == "") return -1; - else - return f_result.toInt(); + return f_result.toInt(); } int AOApplication::get_ao2_preanim_duration(QString p_char, QString p_emote) @@ -474,8 +692,7 @@ int AOApplication::get_ao2_preanim_duration(QString p_char, QString p_emote) if (f_result == "") return -1; - else - return f_result.toInt(); + return f_result.toInt(); } int AOApplication::get_emote_number(QString p_char) @@ -484,8 +701,7 @@ int AOApplication::get_emote_number(QString p_char) if (f_result == "") return 0; - else - return f_result.toInt(); + return f_result.toInt(); } QString AOApplication::get_emote_comment(QString p_char, int p_emote) @@ -499,8 +715,7 @@ QString AOApplication::get_emote_comment(QString p_char, int p_emote) qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; return "normal"; } - else - return result_contents.at(0); + return result_contents.at(0); } QString AOApplication::get_pre_emote(QString p_char, int p_emote) @@ -514,8 +729,7 @@ QString AOApplication::get_pre_emote(QString p_char, int p_emote) qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; return ""; } - else - return result_contents.at(1); + return result_contents.at(1); } QString AOApplication::get_emote(QString p_char, int p_emote) @@ -529,8 +743,7 @@ QString AOApplication::get_emote(QString p_char, int p_emote) qDebug() << "W: misformatted char.ini: " << p_char << ", " << p_emote; return "normal"; } - else - return result_contents.at(2); + return result_contents.at(2); } int AOApplication::get_emote_mod(QString p_char, int p_emote) @@ -545,8 +758,7 @@ int AOApplication::get_emote_mod(QString p_char, int p_emote) << QString::number(p_emote); return 0; } - else - return result_contents.at(3).toInt(); + return result_contents.at(3).toInt(); } int AOApplication::get_desk_mod(QString p_char, int p_emote) @@ -563,8 +775,7 @@ int AOApplication::get_desk_mod(QString p_char, int p_emote) if (string_result == "") return -1; - else - return string_result.toInt(); + return string_result.toInt(); } QString AOApplication::get_sfx_name(QString p_char, int p_emote) @@ -574,8 +785,14 @@ QString AOApplication::get_sfx_name(QString p_char, int p_emote) if (f_result == "") return "1"; - else - return f_result; + return f_result; +} + +QString AOApplication::get_emote_blip(QString p_char, int p_emote) +{ + QString f_result = + read_char_ini(p_char, QString::number(p_emote + 1), "SoundB"); + return f_result; } int AOApplication::get_sfx_delay(QString p_char, int p_emote) @@ -585,8 +802,52 @@ int AOApplication::get_sfx_delay(QString p_char, int p_emote) if (f_result == "") return 1; + return f_result.toInt(); +} + +QString AOApplication::get_sfx_looping(QString p_char, int p_emote) +{ + QString f_result = + read_char_ini(p_char, QString::number(p_emote + 1), "SoundL"); + + qDebug() << f_result; + if (f_result == "") + return "0"; else - return f_result.toInt(); + return f_result; +} + +QString AOApplication::get_sfx_frame(QString p_char, QString p_emote, + int n_frame) +{ + QString f_result = read_char_ini(p_char, QString::number(n_frame), + p_emote.append("_FrameSFX")); + + if (f_result == "") + return ""; + return f_result; +} + +QString AOApplication::get_screenshake_frame(QString p_char, QString p_emote, + int n_frame) +{ + QString f_result = read_char_ini(p_char, QString::number(n_frame), + p_emote.append("_FrameScreenshake")); + + if (f_result == "") + return ""; + return f_result; +} + +QString AOApplication::get_flash_frame(QString p_char, QString p_emote, + int n_frame) +{ + QString f_result = read_char_ini(p_char, QString::number(n_frame), + p_emote.append("_FrameRealization")); + + if (f_result == "") + return ""; + return f_result; } int AOApplication::get_text_delay(QString p_char, QString p_emote) @@ -595,8 +856,91 @@ int AOApplication::get_text_delay(QString p_char, QString p_emote) if (f_result == "") return -1; - else - return f_result.toInt(); + return f_result.toInt(); +} + +QStringList AOApplication::get_theme_effects() +{ + QString p_path = get_theme_path("effects/effects.ini"); + QString default_path = get_default_theme_path("effects/effects.ini"); + + QStringList effects; + if (!file_exists(p_path)) { + p_path = default_path; + if (!file_exists(p_path)) + return effects; + } + + QStringList lines = read_file(p_path).split("\n"); + foreach (QString effect, lines) { + effect = effect.split("=")[0].trimmed(); + if (!effect.isEmpty() && !effects.contains(effect)) + effects.append(effect); + } + return effects; +} + +QStringList AOApplication::get_effects(QString p_char) +{ + QString p_effect = read_char_ini(p_char, "effects", "Options"); + QString p_path = get_base_path() + "misc/" + p_effect + "/effects.ini"; + + QStringList effects = get_theme_effects(); + if (!file_exists(p_path)) + return effects; + + QStringList lines = read_file(p_path).split("\n"); + foreach (QString effect, lines) { + effect = effect.split("=")[0].trimmed(); + if (!effect.isEmpty() && !effects.contains(effect)) + effects.append(effect); + } + + return effects; +} + +QString AOApplication::get_effect(QString effect, QString p_char, + QString p_folder) +{ + QString p_effect = p_folder; + if (p_folder == "") + p_effect = read_char_ini(p_char, "effects", "Options"); + + QString p_path = + get_image_suffix(get_base_path() + "misc/" + p_effect + "/" + effect); + QString design_ini_path = + get_image_suffix(get_theme_path("effects/" + effect)); + QString default_path = + get_image_suffix(get_default_theme_path("effects/" + effect)); + + if (!file_exists(p_path)) { + p_path = design_ini_path; + if (!file_exists(p_path)) { + p_path = default_path; + if (!file_exists(p_path)) { + return ""; + } + } + } + + return p_path; +} + +QString AOApplication::get_effect_sound(QString fx_name, QString p_char) +{ + 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); + if (f_result == "") { + f_result = read_design_ini(fx_name, design_ini_path); + if (f_result == "") { + f_result = read_design_ini(fx_name, default_path); + } + } + return f_result; } QString AOApplication::get_custom_realization(QString p_char) @@ -606,7 +950,7 @@ QString AOApplication::get_custom_realization(QString p_char) if (f_result == "") return get_sfx("realization"); else - return f_result; + return get_sfx_suffix(get_sounds_path(f_result)); } bool AOApplication::get_blank_blip() @@ -615,12 +959,67 @@ bool AOApplication::get_blank_blip() return result.startsWith("true"); } +bool AOApplication::get_looping_sfx() +{ + QString result = configini->value("looping_sfx", "true").value(); + return result.startsWith("true"); +} + +bool AOApplication::objection_stop_music() +{ + QString result = + configini->value("objection_stop_music", "false").value(); + return result.startsWith("true"); +} + bool AOApplication::is_discord_enabled() { QString result = configini->value("discord", "true").value(); return result.startsWith("true"); } +bool AOApplication::is_shake_enabled() +{ + QString result = configini->value("shake", "true").value(); + return result.startsWith("true"); +} + +bool AOApplication::is_effects_enabled() +{ + QString result = configini->value("effects", "true").value(); + return result.startsWith("true"); +} + +bool AOApplication::is_frame_network_enabled() +{ + QString result = configini->value("framenetwork", "true").value(); + return result.startsWith("true"); +} + +bool AOApplication::is_colorlog_enabled() +{ + QString result = configini->value("colorlog", "true").value(); + return result.startsWith("true"); +} + +bool AOApplication::is_stickysounds_enabled() +{ + QString result = configini->value("stickysounds", "false").value(); + return result.startsWith("true"); +} + +bool AOApplication::is_stickyeffects_enabled() +{ + QString result = configini->value("stickyeffects", "false").value(); + return result.startsWith("true"); +} + +bool AOApplication::is_stickypres_enabled() +{ + QString result = configini->value("stickypres", "false").value(); + return result.startsWith("true"); +} + bool AOApplication::get_casing_enabled() { QString result = configini->value("casing_enabled", "false").value();