Merge branch 'master' into 'bass-optional'

# Conflicts:
#   Attorney_Online.pro
#   include/aoapplication.h
This commit is contained in:
stonedDiscord 2019-07-20 16:20:05 +00:00
commit 4a2a167f7d
32 changed files with 905 additions and 95 deletions

1
.gitignore vendored
View File

@ -5,6 +5,7 @@
*.so
*.pro.autosave
base_override.h
.DS_Store
base-full/
base/

196
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,196 @@
stages:
- build
- deploy
- publish
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- lib/
before_script:
- echo Current working directory is $(pwd)
build linux x86_64:
image: ubuntu
stage: build
tags:
- docker
- linux
script:
# Install dependencies
- apt-get update
- >
apt-get install --no-install-recommends -y qt5-default qtmultimedia5-dev
clang make git sudo curl ca-certificates pkg-config upx unzip
# Print versions
- qmake --version
- clang --version
# Extract BASS
- mkdir bass
- cd bass
- curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip
- unzip bass.zip
- cp x64/libbass.so ../lib
- curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip
- unzip bassopus.zip
- cp x64/libbassopus.so ../lib
- cd ..
# Extract Discord RPC
- mkdir discord-rpc
- cd discord-rpc
- curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc_linux.zip
- unzip discord_rpc_linux.zip
- cp discord-rpc/linux-dynamic/lib/libdiscord-rpc.so ../lib
- cd ..
# Extract QtApng
- mkdir qtapng
- cd qtapng
- curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.0-5/build_gcc_64_5.12.0.tar.xz -o apng.tar.xz
- tar -xvf apng.tar.xz
- cp gcc_64/plugins/imageformats/libqapng.so ../lib
- cd ..
# Build
- qmake -spec linux-clang
- make -j4
# Post-processing
- upx --lzma -9 --force bin/Attorney_Online
artifacts:
paths:
- bin/
build windows i686:
image: ${CI_REGISTRY_IMAGE}/builder-windows-i686
stage: build
tags:
- docker
- linux
script:
# Install dependencies
- apt-get update
- apt-get install --no-install-recommends -y make curl ca-certificates upx unzip
# Extract BASS
- mkdir bass
- cd bass
- curl http://www.un4seen.com/files/bass24.zip -o bass.zip
- unzip bass.zip
- cp bass.dll ../lib
- curl http://www.un4seen.com/files/bassopus24.zip -o bassopus.zip
- unzip bassopus.zip
- cp bassopus.dll ../lib
- cd ..
# Build
- /opt/mxe/usr/${TARGET_SPEC}/qt5/bin/qmake
- make -j4
# Post-processing
- upx --lzma -9 --force bin/Attorney_Online.exe
artifacts:
paths:
- bin/
# Base folder
.deploy_base: &deploy_base |
mkdir base
mkdir base/themes
cp -a ../base/themes/default base/themes/
cp -a ../base/config.ini base/config.sample.ini
cp -a ../base/serverlist.txt base/serverlist.sample.txt
# Miscellaneous files
.deploy_misc: &deploy_misc |
cp -a ../README.md README.md.txt
cp -a ../LICENSE.MIT LICENSE.txt
deploy linux x86_64:
stage: deploy
dependencies:
- build linux x86_64
tags:
- docker
- linux
script:
- mkdir artifact
- cd artifact
- *deploy_base
- *deploy_misc
# Platform-specific
- cp -a ../lib/*.so .
- cp -a ../bin/Attorney_Online .
- echo "#!/bin/sh" >> ./run.sh
- echo "LD_LIBRARY_PATH=.:\$LD_LIBRARY_PATH ./Attorney_Online" >> ./run.sh
- chmod +x ./run.sh
# Zipping
# zip -r -9 -l Attorney_Online_$(git describe --tags)_linux_x86_64.zip .
- mkdir ../zip
- tar cavf ../zip/Attorney_Online_$(git describe --tags)_linux_x64.tar.xz *
- sha1sum ../zip/*
artifacts:
paths:
- zip/
deploy windows i686:
image: ubuntu
stage: deploy
dependencies:
- build windows i686
tags:
- docker
- linux
script:
- apt-get update
- apt-get install --no-install-recommends -y zip git
- mkdir artifact
- cd artifact
- *deploy_base
- *deploy_misc
# Platform-specific
- cp -a ../lib/*.dll .
- cp -a ../bin/Attorney_Online.exe .
# Zipping
# -r: recursive; -9: max compression; -l: convert to CR LF
- mkdir ../zip
- zip -r -9 -l ../zip/Attorney_Online_$(git describe --tags)_windows_x86.zip .
- sha1sum ../zip/*
artifacts:
paths:
- zip/
publish linux x86_64:
image: ubuntu
stage: publish
dependencies:
- deploy linux x86_64
when: manual
script:
- cd zip
- ../scripts/wasabi.sh
variables:
MANIFEST: program_linux_x86_64.json
ARTIFACT_SUFFIX: _linux_x64.tar.xz
publish windows i686:
image: ubuntu
stage: publish
dependencies:
- deploy windows i686
when: manual
script:
- cd zip
- ../scripts/wasabi.sh
variables:
MANIFEST: program_winnt_i386.json
ARTIFACT_SUFFIX: _windows_x86.zip

11
.travis.yml Normal file
View File

@ -0,0 +1,11 @@
language: cpp
os: osx
addons:
homebrew:
packages:
- qt5
script:
- ./scripts/macos_build.sh
- ./scripts/macos_post_build.sh

View File

@ -16,13 +16,13 @@ HEADERS += $$files($$PWD/include/*.h)
LIBS += -L$$PWD/lib
#DEFINES += DISCORD
DEFINES += DISCORD
contains(DEFINES, DISCORD) {
LIBS += -ldiscord-rpc
}
#DEFINES += BASSAUDIO
DEFINES += BASSAUDIO
contains(DEFINES, BASSAUDIO) {
LIBS += -lbass
@ -34,6 +34,9 @@ contains(DEFINES, QTAUDIO) {
QT += multimedia
}
macx:LIBS += -framework CoreFoundation -framework Foundation -framework CoreServices
CONFIG += c++14
RESOURCES += resources.qrc

View File

@ -8,22 +8,40 @@ This program has five main dependencies
* Discord Rich Presence (https://github.com/discordapp/discord-rpc/releases)
* Qt Apng Plugin (https://github.com/Skycoder42/QtApng/releases)
### Help
If you're having issues with any of this, ask in the offical Discord: https://discord.gg/wWvQ3pw
Alternatively, you can ask OmniTroid#4004 on Discord.
### How to build dynamically (the easy way)
#### General preparation
What you want to do is first download the latest version of Qt from the first link. (get the prebuilt dynamic version)
If you're on Ubuntu, go to the scripts/ folder and run configure_ubuntu.sh. This should fetch all the required dependencies automatically.
If not, go to each one of the links above and find the right dynamic library for your platform:
* Windows: .dll
* Linux: .so
* Mac: .dylib
After going through the OS-specific steps below, compiling in Qt creator should work.
And put them in BOTH lib/ and the repository root (lib/ is required for linking and root is required for runtime)
#### Windows
Launch Qt creator, open the .pro file and try running it. Ask in the Discord if you're having issues: https://discord.gg/wWvQ3pw
If you're on Windows, you need to go find all the dependencies (see above) and put them in the lib/ folder.
#### MacOS
If you're on MacOS, you can simply go to terminal and run ./scripts/configure_macos.sh
This will automatically fetch all the required dependencies. Additionally, if you need to create a standalone release, just run ./scripts/release_macos.sh
This will make the .app bundle in bin/ able to execute as a standalone.
#### Ubuntu
If you're on Ubuntu, just go to terminal and run ./scripts/configure_ubuntu.sh
This should fetch all the required dependencies automatically.
#### Other Linux
With some tweaks to the ubuntu script, it shouldn't be a big hassle to compile it on a modern linux. Look in the script and see what you may have to modify.
### How to build statically (the hard way)
You're gonna have a bad time.
You're gonna have a bad time.
Building statically means you can distribute the final program without needing to pack alongside a lot of dynamic libraries.
This is a tricky process and is not recommended unless you know what you're doing.

View File

@ -22,6 +22,7 @@
#include <QTextStream>
#include <QStringList>
#include <QColor>
#include <QScreen>
class NetworkManager;
class Lobby;

View File

@ -35,8 +35,10 @@ protected:
void enterEvent(QEvent *e);
void leaveEvent(QEvent *e);
void mouseDoubleClickEvent(QMouseEvent *e);
/*
void dragLeaveEvent(QMouseEvent *e);
void dragEnterEvent(QMouseEvent *e);
*/
signals:
void evidence_clicked(int p_id);

View File

@ -35,7 +35,6 @@
#include <QSlider>
#include <QVector>
#include <QCloseEvent>
#include <QSignalMapper>
#include <QMap>
#include <QTextBrowser>
#include <QSpinBox>
@ -48,6 +47,7 @@
#include <QFont>
#include <QInputDialog>
#include <QFileDialog>
#include <QTextBoundaryFinder>
#include <stack>
@ -272,8 +272,6 @@ private:
QVector<QString> arup_cms;
QVector<QString> arup_locks;
QSignalMapper *char_button_mapper;
QVector<chatlogpiece> ic_chatlog_history;
// These map music row items and area row items to their actual IDs.
@ -537,6 +535,7 @@ private:
void construct_char_select();
void set_char_select();
void set_char_select_page();
void char_clicked(int n_char);
void put_button_in_place(int starting, int chars_on_this_page);
void filter_character_list();
@ -651,14 +650,12 @@ private slots:
void on_char_select_left_clicked();
void on_char_select_right_clicked();
void on_char_search_changed(const QString& newtext);
void on_char_taken_clicked(int newstate);
void on_char_passworded_clicked(int newstate);
void on_char_search_changed();
void on_char_taken_clicked();
void on_char_passworded_clicked();
void on_spectator_clicked();
void char_clicked(int n_char);
void on_switch_area_music_clicked();
void on_casing_clicked();

View File

@ -44,13 +44,14 @@ public:
QString ms_nosrv_hostname = "master.aceattorneyonline.com";
#endif
const int ms_port = 27016;
const quint16 ms_port = 27016;
const int timeout_milliseconds = 2000;
const int ms_reconnect_delay_ms = 7000;
// in seconds
const int ms_reconnect_delay = 7;
// kind of arbitrary max buffer size
const size_t buffer_max_size = 16384;
#define BUFFER_MAX_SIZE 16384
bool ms_partial_packet = false;
QString ms_temp_packet = "";

6
scripts/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
node_modules/
# Cursed file
package-lock.json
s3_keys.sh

42
scripts/configure_macos.sh Executable file
View File

@ -0,0 +1,42 @@
#!/bin/sh
# This script fetches all build dependencies for MacOS
# Tested on MacOS 10.14 (Mojave), Qt 5.13 and XCode 10.2
# Exit on errors and unset variables
set -eu
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/"
cd ${ROOT_DIR}
LIB_TARGET="../../lib"
BASS_LINK="http://uk.un4seen.com/files/bass24-osx.zip"
BASSOPUS_LINK="http://www.un4seen.com/files/bassopus24-osx.zip"
DISCORD_RPC_LINK="https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip"
APNG_LINK="https://github.com/Skycoder42/QtApng/releases/download/1.1.2-2/qtapng_clang_64_5.13.0.tar.xz"
# Easier if we don't need to worry about an existing tmp folder tbh smh
# v Add a slash here for free tmp folder cleanup in true javascript community style
rm -rf tmp
mkdir tmp
cd tmp
curl -Ls ${BASS_LINK} -o bass.zip
unzip -qq bass.zip
cp libbass.dylib ${LIB_TARGET}
curl -Ls ${BASSOPUS_LINK} -o bassopus.zip
unzip -qq bassopus.zip
cp libbassopus.dylib ${LIB_TARGET}
curl -Ls ${DISCORD_RPC_LINK} -o discord_rpc.zip
unzip -qq discord_rpc.zip
cp discord-rpc/osx-dynamic/lib/libdiscord-rpc.dylib ${LIB_TARGET}
curl -Ls ${APNG_LINK} -o apng.tar.xz
tar -xf apng.tar.xz
cp clang_64/plugins/imageformats/libqapng.dylib ../../lib
cd ..
rm -rf tmp

View File

@ -1,6 +1,13 @@
#!/bin/bash
#!/bin/sh
#assumes a somewhat recent 64-bit ubuntu
# Assumes a somewhat recent 64-bit ubuntu
# Exit on errors and unset variables
set -eu
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/"
cd ${ROOT_DIR}
#need some openGL stuff
sudo apt install libgl1-mesa-dev

33
scripts/macos_build.sh Executable file
View File

@ -0,0 +1,33 @@
#!/bin/sh
set -Eexo pipefail
export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/local/opt/openssl/lib/pkgconfig
export PATH=$PATH:/usr/local/opt/qt5/bin:/usr/local/bin
mkdir bass
cd bass
curl http://www.un4seen.com/files/bass24-osx.zip -o bass.zip
unzip bass.zip
cp libbass.dylib ../lib
curl http://www.un4seen.com/files/bassopus24-osx.zip -o bassopus.zip
unzip bassopus.zip
cp libbassopus.dylib ../lib
cd ..
mkdir discord-rpc
cd discord-rpc
curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-osx.zip -o discord_rpc_osx.zip
unzip discord_rpc_osx.zip
cp discord-rpc/osx-static/lib/libdiscord-rpc.a ../lib
cd ..
mkdir qtapng
cd qtapng
curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.0-5/build_clang_64_5.12.0.tar.xz -o apng.tar.xz
tar -xvf apng.tar.xz
cp clang_64/plugins/imageformats/libqapng.dylib ../lib
cd ..
qmake && make -j2

View File

@ -1,16 +1,22 @@
#!/bin/bash
#!/bin/sh
DST_FOLDER="./bin/Attorney_Online.app/Contents/Frameworks"
# This script prepares the compiled bundle for shipping as a standalone release
# Assumes the Qt bin folder is in PATH
# Should be used on a "Release" build from QT creator
# Note that this DOES NOT add the base/ folder
cd ..
# Exit on errors and unset variables
set -eu
mkdir $DST_FOLDER
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/"
cp ./lib/libbass.dylib $DST_FOLDER
cp ./lib/libbassopus.dylib $DST_FOLDER
cd ${ROOT_DIR}
install_name_tool -id @executable_path/../Frameworks/libbass.dylib $DST_FOLDER/libbass.dylib
# This thing basically does all the work
macdeployqt ../bin/Attorney_Online.app
install_name_tool -id @executable_path/../Frameworks/libbassopus.dylib $DST_FOLDER/libbassopus.dylib
# Need to add the dependencies
cp ../lib/* ../bin/Attorney_Online.app/Contents/Frameworks
install_name_tool -change @loader_path/libbass.dylib @executable_path/../Frameworks/libbass.dylib ./bin/Attorney_Online.app/Contents/MacOS/Attorney_Online
# libbass has a funny path for some reason, just use rpath
install_name_tool -change @loader_path/libbass.dylib @rpath/libbass.dylib ../bin/Attorney_Online.app/Contents/MacOS/Attorney_Online

9
scripts/package.json Normal file
View File

@ -0,0 +1,9 @@
{
"name": "ao-ci-scripts",
"version": "1.0.0",
"main": "update_manifest.js",
"dependencies": {
"argparse": "^1.0.10"
},
"license": "ISC"
}

22
scripts/release_macos.sh Executable file
View File

@ -0,0 +1,22 @@
#!/bin/sh
# This script prepares the compiled bundle for shipping as a standalone release
# Assumes the Qt bin folder is in PATH
# Should be used on a "Release" build from QT creator
# Note that this DOES NOT add the base/ folder
# Exit on errors and unset variables
set -eu
ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/"
cd ${ROOT_DIR}
# This thing basically does all the work
macdeployqt ../bin/Attorney_Online.app
# Need to add the dependencies
cp ../lib/* ../bin/Attorney_Online.app/Contents/Frameworks
# libbass has a funny path for some reason, just use rpath
install_name_tool -change @loader_path/libbass.dylib @rpath/libbass.dylib ../bin/Attorney_Online.app/Contents/MacOS/Attorney_Online

151
scripts/update_manifest.js Executable file
View File

@ -0,0 +1,151 @@
#!/usr/bin/env node
const fs = require("fs");
const crypto = require("crypto");
const path = require("path");
const ArgumentParser = require("argparse").ArgumentParser;
function isFile(file) {
if (!fs.existsSync(file)) {
console.error(`File '${file}' not found. Try again.`);
throw Error();
}
return file;
}
const argParser = new ArgumentParser({
addHelp: true,
description: "Adds a new latest version to the manifest file based on the " +
"provided zip file, including an incremental update."
});
argParser.addArgument("manifestFile", {
metavar: "<manifest file>", type: isFile
});
argParser.addArgument("version", {
metavar: "<version>"
});
argParser.addArgument([ "-f", "--full" ], {
metavar: "<full zip file>", type: isFile, nargs: 1,
dest: "fullZipFileArgs"
});
argParser.addArgument([ "-i", "--incremental" ], {
type: isFile, nargs: 2, dest: "incrementalArgs",
metavar: ["<incremental zip file>", "<file containing list of changed files>"]
});
argParser.addArgument([ "-e", "--executable" ], {
metavar: "[executable file]", nargs: 1,
dest: "executableArgs"
});
const {
manifestFile,
version,
fullZipFileArgs,
incrementalArgs,
executableArgs
} = argParser.parseArgs();
const [incrementalZipFile, changesFile] = incrementalArgs || [];
const [fullZipFile] = fullZipFileArgs || [];
const [executable] = executableArgs || [];
// Do one final check
if (!incrementalZipFile && !fullZipFile) {
console.error("No download archive specified! Abort.");
process.exit(1);
}
// Do a quick litmus test to prevent deleting everything incorrectly
if (changesFile && !fs.existsSync("base")) {
console.error("The working directory must be set to an " +
"asset folder in order for deleted directories " +
"to be calculated correctly. Abort.");
process.exit(1);
}
const manifest = JSON.parse(fs.readFileSync(manifestFile));
const dirsDeleted = new Set();
const specialActions = changesFile ?
fs.readFileSync(changesFile)
.toString()
.trim()
.split("\n")
.map(line => line.split("\t"))
.map(([mode, target, source]) => {
switch (mode[0]) {
case "D": // Deleted
// Check if the folder exists relative to the working
// directory, and if not, add it to the dirsDeleted list.
// Keep going up the tree to see how many directories were
// deleted.
let dir = path.dirname(target);
while (!dirsDeleted.has(dir) && !fs.existsSync(dir)) {
dirsDeleted.add(dir);
dir = path.dirname(dir);
}
return { action: "delete", target };
case "R": // Renamed
// NOTE: Make sure that the launcher's implementation of
// the move action also creates directories when needed.
return { action: "move", source, target};
default:
return null;
}
})
// Remove ignored file mode changes
.filter(action => action !== null)
// Create actions based on directories to be deleted.
// Always have deeper directories first, to guarantee that deleting
// higher-level directories will succeed.
.concat(Array.from(dirsDeleted.values())
.sort((a, b) => b.split("/").length - a.split("/").length)
.map(dir => {
return { action: "deleteDir", target: dir };
}))
: [];
const urlBase = "https://s3.wasabisys.com/ao-downloads/";
const versionEntry = {
version,
executable,
prev: manifest.versions[0] ? manifest.versions[0].version : undefined,
full: fullZipFile ? [
{
action: "dl",
url: urlBase + encodeURIComponent(path.basename(fullZipFile)),
hash: crypto.createHash("sha1")
.update(fs.readFileSync(fullZipFile))
.digest("hex")
}
] : undefined,
update: incrementalArgs ? [
...specialActions,
{
action: "dl",
url: urlBase + encodeURIComponent(path.basename(incrementalZipFile)),
hash: crypto.createHash("sha1")
.update(fs.readFileSync(incrementalZipFile))
.digest("hex")
}
] : undefined
};
console.log("Generated version entry:", versionEntry);
const existingVersions = manifest.versions.filter(v => v.version == version);
if (existingVersions.length > 0) {
console.warn(`Warning: version ${version} already exists. Adding new values.`);
// Don't overwrite prev - it will cause headaches
delete versionEntry.prev;
Object.assign(existingVersions[0], versionEntry);
console.log("Merged version entry:", existingVersions[0]);
} else {
manifest.versions = [versionEntry, ...manifest.versions];
}
fs.writeFileSync(manifestFile, JSON.stringify(manifest, null, 4));

View File

@ -0,0 +1,39 @@
#!/usr/bin/env node
const fs = require("fs");
const crypto = require("crypto");
const [ _nodeExe, _jsPath, manifestFile, version, zipFile ] = process.argv;
if (!manifestFile || !version || !zipFile) {
console.log(`Usage: update_program_manifest <manifest file> <version> <zip file>`);
console.log(`Adds a new latest version to the manifest file based on the ` +
`provided zip file.`);
process.exit(1);
}
if (!fs.existsSync(manifestFile)) {
console.error(`Manifest file '${manifestFile}' not found. Try again.`);
process.exit(2);
}
if (!fs.existsSync(zipFile)) {
console.error(`Zip file '${zipFile}' not found. Try again.`);
process.exit(2);
}
const manifest = JSON.parse(fs.readFileSync(manifestFile));
manifest.versions = [{
version,
executable: "Attorney_Online.exe",
full: [
{
action: "dl",
url: "https://s3.wasabisys.com/ao-downloads/" + encodeURIComponent(zipFile),
hash: crypto.createHash("sha1").update(fs.readFileSync(zipFile)).digest("hex")
}
]
}, ...manifest.versions];
fs.writeFileSync(manifestFile, JSON.stringify(manifest, null, 4));

73
scripts/wasabi.sh Executable file
View File

@ -0,0 +1,73 @@
#!/bin/bash
# Updates the specified program manifest to a new archive and version
# and uploads the new archive and manifest to S3/Wasabi.
#
# Requires:
# MANIFEST: name of the manifest file
# S3_ACCESS_KEY, S3_SECRET_KEY: S3 credentials
# S3_MANIFESTS, S3_ARCHIVES: S3 paths to manifests and downloads
# ARCHIVE_FULL: name of the full archive (if desired)
# ARCHIVE_INCR: name of the incremental archive (if desired)
# VERSION: name of the new version
# EXECUTABLE: name of the executable (if program manifest)
# -E: inherit ERR trap by shell functions
# -e: stop script on ERR trap
# -u: stop script on unbound variables
# -x: print command before running it
# -o pipefail: fail if any command in a pipeline fails
set -Eeuxo pipefail
aws configure set aws_access_key_id ${S3_ACCESS_KEY}
aws configure set aws_secret_access_key ${S3_SECRET_KEY}
aws configure set default.region us-east-1
export S3_COPY="aws s3 cp --endpoint-url=https://s3.wasabisys.com"
export ARCHIVE_FULL_ARG=""
export ARCHIVE_INCR_ARG=""
export EXECUTABLE_ARG=""
export LAST_TAGGED_VERSION=$(git rev-list --tags --skip=1 --max-count=1)
echo "Previous tagged version: ${LAST_TAGGED_VERSION}"
echo "Current tagged version: ${VERSION}"
if [[ -n $ARCHIVE_INCR && -n $LAST_TAGGED_VERSION ]]; then
echo "Incremental archive: ${ARCHIVE_INCR}"
# Get all files
export CHANGES_FILE="changes.txt"
git diff --name-status ${LAST_TAGGED_VERSION}..HEAD > ${CHANGES_FILE}
# Get added/modified files
git diff --name-only --diff-filter=dr ${LAST_TAGGED_VERSION}..HEAD | \
zip ${ARCHIVE_INCR} -@
export ARCHIVE_INCR_ARG="-i ${ARCHIVE_INCR} ${CHANGES_FILE}"
elif [[ -n $ARCHIVE_INCR && -z $LAST_TAGGED_VERSION ]]; then
echo "Incremental archive was requested, but there is no previous version"
fi
if [[ -n $ARCHIVE_FULL ]]; then
echo "Full archive: ${ARCHIVE_INCR}"
export ARCHIVE_FULL_ARG="-f ${ARCHIVE_FULL}"
fi
if [[ -v EXECUTABLE ]]; then
export EXECUTABLE_ARG="-e ${EXECUTABLE}"
fi
${S3_COPY} ${S3_MANIFESTS}/${MANIFEST} .
node $(dirname $0)/update_manifest.js ${MANIFEST} ${VERSION} \
${ARCHIVE_FULL_ARG} ${ARCHIVE_INCR_ARG} ${EXECUTABLE_ARG}
if [[ -n $ARCHIVE_INCR_ARG ]]; then
${S3_COPY} ${ARCHIVE_INCR} ${S3_ARCHIVES}
fi
if [[ -n $ARCHIVE_FULL_ARG ]]; then
${S3_COPY} ${ARCHIVE_FULL} ${S3_ARCHIVES}
fi
${S3_COPY} ${MANIFEST} ${S3_MANIFESTS}

35
scripts/wasabi_program.sh Executable file
View File

@ -0,0 +1,35 @@
#!/bin/sh
# Updates the specified program manifest to a new archive and version
# and uploads the new archive and manifest to S3/Wasabi.
#
# Requires:
# MANIFEST: name of the manifest file
# ARTIFACT_SUFFIX: suffix of the archive to be uploaded (including extension)
# S3_ACCESS_KEY and S3_SECRET_KEY
# -E: inherit ERR trap by shell functions
# -e: stop script on ERR trap
# -u: stop script on unbound variables
# -x: print command before running it
# -o pipefail: fail if any command in a pipeline fails
set -Eeuxo pipefail
aws configure set aws_access_key_id ${S3_ACCESS_KEY}
aws configure set aws_secret_access_key ${S3_SECRET_KEY}
aws configure set default.region us-east-1
export S3_COPY="aws s3 cp --endpoint-url=https://s3.wasabisys.com"
export S3_MANIFESTS="s3://ao-manifests"
export S3_ARCHIVES="s3://ao-downloads"
export VERSION=$(git describe --tags)
export ARCHIVE="Attorney_Online_${VERSION}_${ARTIFACT_SUFFIX}"
${S3_COPY} ${S3_MANIFESTS}/${MANIFEST} .
node $(dirname $0)/update_manifest.js ${MANIFEST} ${VERSION} \
-f ${ARCHIVE} -e Attorney_Online.exe
${S3_COPY} ${ARCHIVE} ${S3_ARCHIVES}
${S3_COPY} ${MANIFEST} ${S3_MANIFESTS}
rm -f ${MANIFEST}

View File

@ -0,0 +1,17 @@
FROM oldmud0/mxe-qt:5.12.1-win32-static-posix
#FROM fffaraz/qt:windows
ENV TARGET_SPEC i686-w64-mingw32.static.posix
# Build Discord RPC statically
RUN git clone https://github.com/discordapp/discord-rpc
WORKDIR discord-rpc/build
RUN /opt/mxe/usr/bin/${TARGET_SPEC}-cmake .. -DCMAKE_INSTALL_PREFIX=/opt/mxe/usr/${TARGET_SPEC}
RUN /opt/mxe/usr/bin/${TARGET_SPEC}-cmake --build . --config Release --target install
WORKDIR ../..
# Build QtApng statically
RUN git clone https://github.com/Skycoder42/QtApng
WORKDIR QtApng
RUN /opt/mxe/usr/${TARGET_SPEC}/qt5/bin/qmake && make qmake_all && make && make install
WORKDIR ..

View File

@ -0,0 +1,44 @@
FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y \
autoconf \
automake \
autopoint \
bash \
bison \
bzip2 \
flex \
g++ \
g++-multilib \
gettext \
git \
gperf \
intltool \
libc6-dev-i386 \
libgdk-pixbuf2.0-dev \
libltdl-dev \
libssl-dev \
libtool-bin \
libxml-parser-perl \
lzip \
make \
openssl \
p7zip-full \
patch \
perl \
pkg-config \
python \
ruby \
sed \
unzip \
wget \
xz-utils
RUN git clone https://github.com/mxe/mxe.git
RUN mv mxe /opt/mxe
WORKDIR /opt/mxe
RUN make -j4 MXE_TARGETS="i686-w64-mingw32.static.posix" qtbase qtmultimedia
ENV PATH=/opt/mxe/usr/bin:$PATH
WORKDIR /

View File

@ -0,0 +1,19 @@
When you want to build a new version of Qt:
```docker
docker build -t mxe-windows-static . -f Dockerfile-mxe
docker tag mxe-windows-static oldmud0/mxe-qt:5.12.1-win32-static-posix
docker push oldmud0/mxe-qt:5.12.1-win32-static-posix
```
Remember to log into Docker Hub before attempting to push.
When you want to build a new version of any dependency required for building AO:
```docker
docker build -t mxe-windows-static-ao . -f Dockerfile
docker tag mxe-windows-static-ao registry.gitlab.com/attorneyonline/ao2-client/builder-windows-i686
docker push registry.gitlab.com/attorneyonline/ao2-client/builder-windows-i686
```
Remember to create an access token in GitLab before attempting to push.
GitLab CI depends on `builder-windows-i686` image to be present in the repository's registry in order for the Windows build to succeed.

View File

@ -37,9 +37,9 @@ void AOApplication::construct_lobby()
w_lobby = new Lobby(this);
lobby_constructed = true;
QRect screenGeometry = QApplication::desktop()->screenGeometry();
int x = (screenGeometry.width()-w_lobby->width()) / 2;
int y = (screenGeometry.height()-w_lobby->height()) / 2;
QRect geometry = QGuiApplication::primaryScreen()->geometry();
int x = (geometry.width()-w_lobby->width()) / 2;
int y = (geometry.height()-w_lobby->height()) / 2;
w_lobby->move(x, y);
if (is_discord_enabled())
@ -72,9 +72,9 @@ void AOApplication::construct_courtroom()
w_courtroom = new Courtroom(this);
courtroom_constructed = true;
QRect screenGeometry = QApplication::desktop()->screenGeometry();
int x = (screenGeometry.width()-w_courtroom->width()) / 2;
int y = (screenGeometry.height()-w_courtroom->height()) / 2;
QRect geometry = QGuiApplication::primaryScreen()->geometry();
int x = (geometry.width()-w_courtroom->width()) / 2;
int y = (geometry.height()-w_courtroom->height()) / 2;
w_courtroom->move(x, y);
}
@ -161,7 +161,7 @@ void AOApplication::ms_connect_finished(bool connected, bool 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_ms / 1000.f) + " seconds.");
+ QString::number(net_manager->ms_reconnect_delay) + " seconds.");
}
else
{

View File

@ -86,6 +86,7 @@ void AOEvidenceButton::mouseDoubleClickEvent(QMouseEvent *e)
evidence_double_clicked(m_id);
}
/*
void AOEvidenceButton::dragLeaveEvent(QMouseEvent *e)
{
//QWidget::dragLeaveEvent(e);
@ -99,6 +100,7 @@ void AOEvidenceButton::dragEnterEvent(QMouseEvent *e)
qDebug() << "drag enter event";
}
*/
void AOEvidenceButton::enterEvent(QEvent * e)
{

View File

@ -44,7 +44,6 @@ void Courtroom::construct_char_select()
set_size_and_pos(ui_char_buttons, "char_buttons");
connect (char_button_mapper, SIGNAL(mapped(int)), this, SLOT(char_clicked(int)));
connect(ui_back_to_lobby, SIGNAL(clicked()), this, SLOT(on_back_to_lobby_clicked()));
connect(ui_char_select_left, SIGNAL(clicked()), this, SLOT(on_char_select_left_clicked()));
@ -52,9 +51,9 @@ void Courtroom::construct_char_select()
connect(ui_spectator, SIGNAL(clicked()), this, SLOT(on_spectator_clicked()));
connect(ui_char_search, SIGNAL(textEdited(const QString&)), this, SLOT(on_char_search_changed(const QString&)));
connect(ui_char_passworded, SIGNAL(stateChanged(int)), this, SLOT(on_char_passworded_clicked(int)));
connect(ui_char_taken, SIGNAL(stateChanged(int)), this, SLOT(on_char_taken_clicked(int)));
connect(ui_char_search, SIGNAL(textEdited(const QString&)), this, SLOT(on_char_search_changed()));
connect(ui_char_passworded, SIGNAL(stateChanged(int)), this, SLOT(on_char_passworded_clicked()));
connect(ui_char_taken, SIGNAL(stateChanged(int)), this, SLOT(on_char_taken_clicked()));
}
void Courtroom::set_char_select()
@ -197,14 +196,15 @@ void Courtroom::character_loading_finished()
// Later on, we'll be revealing buttons as we need them.
for (int n = 0; n < char_list.size(); n++)
{
AOCharButton* character = new AOCharButton(ui_char_buttons, ao_app, 0, 0, char_list.at(n).taken);
character->reset();
character->hide();
character->set_image(char_list.at(n).name);
ui_char_button_list.append(character);
AOCharButton* char_button = new AOCharButton(ui_char_buttons, ao_app, 0, 0, char_list.at(n).taken);
char_button->reset();
char_button->hide();
char_button->set_image(char_list.at(n).name);
ui_char_button_list.append(char_button);
connect(character, SIGNAL(clicked()), char_button_mapper, SLOT(map()));
char_button_mapper->setMapping(character, ui_char_button_list.size() - 1);
connect(char_button, &AOCharButton::clicked, [this, n](){
this->char_clicked(n);
});
// This part here serves as a way of showing to the player that the game is still running, it is
// just loading the pictures of the characters.
@ -252,17 +252,17 @@ void Courtroom::filter_character_list()
set_char_select_page();
}
void Courtroom::on_char_search_changed(const QString& newtext)
void Courtroom::on_char_search_changed()
{
filter_character_list();
}
void Courtroom::on_char_passworded_clicked(int newstate)
void Courtroom::on_char_passworded_clicked()
{
filter_character_list();
}
void Courtroom::on_char_taken_clicked(int newstate)
void Courtroom::on_char_taken_clicked()
{
filter_character_list();
}

View File

@ -6,7 +6,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow()
#ifdef BASSAUDIO
// Change the default audio output device to be the one the user has given
// in his config.ini file for now.
int a = 0;
unsigned int a = 0;
BASS_DEVICEINFO info;
if (ao_app->get_audio_output_device() == "default")
@ -21,7 +21,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow()
if (ao_app->get_audio_output_device() == info.name)
{
BASS_SetDevice(a);
BASS_Init(a, 48000, BASS_DEVICE_LATENCY, nullptr, nullptr);
BASS_Init(static_cast<int>(a), 48000, BASS_DEVICE_LATENCY, nullptr, nullptr);
load_bass_opus_plugin();
qDebug() << info.name << "was set as the default audio output device.";
break;
@ -50,8 +50,6 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow()
testimony_hide_timer = new QTimer(this);
testimony_hide_timer->setSingleShot(true);
char_button_mapper = new QSignalMapper(this);
music_player = new AOMusicPlayer(this, ao_app);
music_player->set_volume(0);
sfx_player = new AOSfxPlayer(this, ao_app);
@ -2025,6 +2023,7 @@ void Courtroom::chat_tick()
//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();
@ -2039,7 +2038,7 @@ void Courtroom::chat_tick()
f_message.remove(0,2);
}
if (tick_pos >= f_message.size())
if (f_message.size() == 0)
{
text_state = 2;
if (anim_state != 4)
@ -2051,9 +2050,21 @@ void Courtroom::chat_tick()
else
{
QString f_character = f_message.at(tick_pos);
QTextBoundaryFinder tbf(QTextBoundaryFinder::Grapheme, f_message);
QString f_character;
int f_char_length;
tbf.toNextBoundary();
if (tbf.position() == -1)
f_character = f_message;
else
f_character = f_message.left(tbf.position());
f_char_length = f_character.length();
f_character = f_character.toHtmlEscaped();
if (f_character == " ")
ui_vp_message->insertPlainText(" ");
@ -2146,7 +2157,7 @@ void Courtroom::chat_tick()
else
{
next_character_is_not_special = true;
tick_pos--;
tick_pos -= f_char_length;
}
}
@ -2167,7 +2178,7 @@ void Courtroom::chat_tick()
else
{
next_character_is_not_special = true;
tick_pos--;
tick_pos -= f_char_length;
}
}
@ -2211,11 +2222,7 @@ void Courtroom::chat_tick()
case INLINE_GREY:
ui_vp_message->insertHtml("<font color=\""+ get_text_color("_inline_grey").name() +"\">" + f_character + "</font>");
break;
default:
ui_vp_message->insertHtml(f_character);
break;
}
}
else
{
@ -2266,7 +2273,7 @@ void Courtroom::chat_tick()
if(blank_blip)
qDebug() << "blank_blip found true";
if (f_message.at(tick_pos) != ' ' || blank_blip)
if (f_character != ' ' || blank_blip)
{
if (blip_pos % blip_rate == 0 && !formatting_char)
@ -2278,7 +2285,7 @@ void Courtroom::chat_tick()
++blip_pos;
}
++tick_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.
@ -2305,6 +2312,7 @@ void Courtroom::chat_tick()
}
}
void Courtroom::show_testimony()
{
if (!testimony_in_progress || m_chatmessage[SIDE] != "wit")
@ -2697,6 +2705,7 @@ void Courtroom::on_ooc_return_pressed()
}
else
{
other_charid = -1;
append_server_chatmessage("CLIENT", "You are no longer paired with anyone.", "1");
}
}
@ -2853,6 +2862,60 @@ void Courtroom::on_ooc_return_pressed()
ui_ooc_chat_message->clear();
return;
}
else if(ooc_message.startsWith("/save_case"))
{
QStringList command = ooc_message.split(" ", QString::SkipEmptyParts);
QDir casefolder("base/cases");
if (!casefolder.exists())
{
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.", "1");
ui_ooc_chat_message->clear();
return;
}
QStringList caseslist = casefolder.entryList();
caseslist.removeOne(".");
caseslist.removeOne("..");
caseslist.replaceInStrings(".ini","");
if (command.size() < 3)
{
append_server_chatmessage("CLIENT", "You need to give a filename to save (extension not needed) and the courtroom status!", "1");
ui_ooc_chat_message->clear();
return;
}
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!", "1");
ui_ooc_chat_message->clear();
return;
}
QSettings casefile("base/cases/" + command[1] + ".ini", QSettings::IniFormat);
casefile.setValue("author",ui_ooc_chat_name->text());
casefile.setValue("cmdoc","");
casefile.setValue("doc", "");
casefile.setValue("status",command[2]);
casefile.sync();
for(int i = local_evidence_list.size() - 1; i >= 0; i--)
{
QString clean_evidence_dsc = local_evidence_list[i].description.replace(QRegularExpression("<owner = ...>..."), "");
clean_evidence_dsc = clean_evidence_dsc.replace(clean_evidence_dsc.lastIndexOf(">"), 1, "");
casefile.beginGroup(QString::number(i));
casefile.sync();
casefile.setValue("name",local_evidence_list[i].name);
casefile.setValue("description",local_evidence_list[i].description);
casefile.setValue("image",local_evidence_list[i].image);
casefile.endGroup();
}
casefile.sync();
append_server_chatmessage("CLIENT", "Succesfully saved, edit doc and cmdoc link on the ini!", "1");
ui_ooc_chat_message->clear();
return;
}
QStringList packet_contents;
packet_contents.append(ui_ooc_chat_name->text());
@ -2982,6 +3045,7 @@ void Courtroom::on_pair_list_clicked(QModelIndex p_index)
QListWidgetItem *f_item = ui_pair_list->item(p_index.row());
QString f_char = f_item->text();
QString real_char;
int f_cid = -1;
if (f_char.endsWith(" [x]"))
{
@ -2989,17 +3053,19 @@ void Courtroom::on_pair_list_clicked(QModelIndex p_index)
f_item->setText(real_char);
}
else
real_char = f_char;
int f_cid = -1;
for (int n_char = 0 ; n_char < char_list.size() ; n_char++)
{
real_char = f_char;
for (int n_char = 0 ; n_char < char_list.size() ; n_char++)
{
if (char_list.at(n_char).name == real_char)
f_cid = n_char;
}
}
if (f_cid < 0 || f_cid >= char_list.size())
if (f_cid < -2 || f_cid >= char_list.size())
{
qDebug() << "W: " << real_char << " not present in char_list";
return;
@ -3018,8 +3084,10 @@ void Courtroom::on_pair_list_clicked(QModelIndex p_index)
for (int i = 0; i < ui_pair_list->count(); i++) {
ui_pair_list->item(i)->setText(sorted_pair_list.at(i));
}
f_item->setText(real_char + " [x]");
if(other_charid != -1)
{
f_item->setText(real_char + " [x]");
}
}
void Courtroom::on_music_list_double_clicked(QModelIndex p_model)

View File

@ -12,10 +12,10 @@ Discord::Discord()
qInfo() << "Discord RPC ready";
};
handlers.disconnected = [](int errorCode, const char* message) {
qInfo() << "Discord RPC disconnected! " << message;
qInfo() << "Discord RPC disconnected! " << message << errorCode;
};
handlers.errored = [](int errorCode, const char* message) {
qWarning() << "Discord RPC errored out! " << message;
qWarning() << "Discord RPC errored out! " << message << errorCode;
};
qInfo() << "Initializing Discord RPC";
Discord_Initialize(APPLICATION_ID, &handlers, 1, nullptr);

View File

@ -188,11 +188,12 @@ void Courtroom::on_evidence_image_name_edited()
void Courtroom::on_evidence_image_button_clicked()
{
QDir dir(ao_app->get_base_path() + "evidence");
QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::ExistingFile);
dialog.setNameFilter(tr("Images (*.png)"));
dialog.setViewMode(QFileDialog::List);
dialog.setDirectory(ao_app->get_base_path() + "evidence");
dialog.setDirectory(dir);
QStringList filenames;
@ -203,13 +204,8 @@ void Courtroom::on_evidence_image_button_clicked()
return;
QString filename = filenames.at(0);
QStringList split_filename = filename.split("/");
filename = split_filename.at(split_filename.size() - 1);
filename = dir.relativeFilePath(filename);
ui_evidence_image_name->setText(filename);
on_evidence_image_name_edited();
}

View File

@ -5,12 +5,12 @@
#if (defined (_WIN32) || defined (_WIN64))
#include <windows.h>
DWORD dwVolSerial;
BOOL bIsRetrieved;
static DWORD dwVolSerial;
static BOOL bIsRetrieved;
QString get_hdid()
{
bIsRetrieved = GetVolumeInformation(TEXT("C:\\"), NULL, NULL, &dwVolSerial, NULL, NULL, NULL, NULL);
bIsRetrieved = GetVolumeInformation(TEXT("C:\\"), nullptr, 0, &dwVolSerial, nullptr, nullptr, nullptr, 0);
if (bIsRetrieved)
return QString::number(dwVolSerial, 16);
@ -18,7 +18,6 @@ QString get_hdid()
//a totally random string
//what could possibly go wrong
return "gxsps32sa9fnwic92mfbs0";
}
#elif (defined (LINUX) || defined (__linux__))

View File

@ -78,9 +78,9 @@ void NetworkManager::ship_server_packet(QString p_packet)
void NetworkManager::handle_ms_packet()
{
char buffer[buffer_max_size];
std::memset(buffer, 0, buffer_max_size);
ms_socket->read(buffer, buffer_max_size);
char buffer[BUFFER_MAX_SIZE];
std::memset(buffer, 0, BUFFER_MAX_SIZE);
ms_socket->read(buffer, BUFFER_MAX_SIZE);
QString in_data = buffer;
@ -137,7 +137,9 @@ void NetworkManager::on_srv_lookup()
for (const QDnsServiceRecord &record : srv_records)
{
#ifdef DEBUG_NETWORK
qDebug() << "Connecting to " << record.target() << ":" << record.port();
#endif
ms_socket->connectToHost(record.target(), record.port());
QTime timer;
timer.start();
@ -206,7 +208,7 @@ void NetworkManager::on_ms_socket_error(QAbstractSocket::SocketError error)
emit ms_connect_finished(false, true);
ms_reconnect_timer->start(ms_reconnect_delay_ms);
ms_reconnect_timer->start(ms_reconnect_delay * 1000);
}
void NetworkManager::retry_ms_connect()
@ -217,9 +219,9 @@ void NetworkManager::retry_ms_connect()
void NetworkManager::handle_server_packet()
{
char buffer[buffer_max_size];
std::memset(buffer, 0, buffer_max_size);
server_socket->read(buffer, buffer_max_size);
char buffer[BUFFER_MAX_SIZE];
std::memset(buffer, 0, BUFFER_MAX_SIZE);
server_socket->read(buffer, BUFFER_MAX_SIZE);
QString in_data = buffer;

View File

@ -14,8 +14,10 @@ void AOApplication::ms_packet_received(AOPacket *p_packet)
QString header = p_packet->get_header();
QStringList f_contents = p_packet->get_contents();
#ifdef DEBUG_NETWORK
if (header != "CHECK")
qDebug() << "R(ms):" << p_packet->to_string();
#endif
if (header == "ALL")
{
@ -127,8 +129,10 @@ void AOApplication::server_packet_received(AOPacket *p_packet)
QStringList f_contents = p_packet->get_contents();
QString f_packet = p_packet->to_string();
#ifdef DEBUG_NETWORK
if (header != "checkconnection")
qDebug() << "R:" << f_packet;
#endif
if (header == "decryptor")
{
@ -677,7 +681,9 @@ void AOApplication::send_ms_packet(AOPacket *p_packet)
net_manager->ship_ms_packet(f_packet);
#ifdef DEBUG_NETWORK
qDebug() << "S(ms):" << f_packet;
#endif
delete p_packet;
}
@ -691,14 +697,18 @@ void AOApplication::send_server_packet(AOPacket *p_packet, bool encoded)
if (encryption_needed)
{
#ifdef DEBUG_NETWORK
qDebug() << "S(e):" << f_packet;
#endif
p_packet->encrypt_header(s_decryptor);
f_packet = p_packet->to_string();
}
else
{
#ifdef DEBUG_NETWORK
qDebug() << "S:" << f_packet;
#endif
}
net_manager->ship_server_packet(f_packet);