forked from mirror/openmw-tes3mp
Compare commits
314 commits
0.7.0
...
sol2-serve
Author | SHA1 | Date | |
---|---|---|---|
|
9029533ec1 | ||
|
7fadc533c7 | ||
|
cf33695447 | ||
|
dbd6ccaf97 | ||
|
702d5b44c9 | ||
|
8b482e19ec | ||
|
d5b3d08ec9 | ||
|
524363702c | ||
|
b3c398605f | ||
|
176aa62b15 | ||
|
6bb2a7e24a | ||
|
058d67dbd0 | ||
|
49bf605e8e | ||
|
07b781cd32 | ||
|
08915c44a0 | ||
|
e12330b038 | ||
|
5c41350532 | ||
|
fc4d3fe3fa | ||
|
569243c987 | ||
|
7180b009bd | ||
|
3f7e163c92 | ||
|
7083cb0359 | ||
|
ad4214d3e2 | ||
|
83fe29d10f | ||
|
3a68f4bd8f | ||
|
8a393d2984 | ||
|
b5922d18fd | ||
|
98eece808b | ||
|
d4dbfdfdb6 | ||
|
672bb707a7 | ||
|
7c8dd7380f | ||
|
c5f33e451f | ||
|
4846497078 | ||
|
502751cae0 | ||
|
d58efde3f1 | ||
|
e355dca4dd | ||
|
ed15d9ebf5 | ||
|
2390744b45 | ||
|
c3b44e11fb | ||
|
f50637bdd4 | ||
|
6131ada0ba | ||
|
5bb09d3bed | ||
|
2aaf9105af | ||
|
3d5860d6f4 | ||
|
7ec08e125b | ||
|
b2a3dd9d60 | ||
|
2ac01dc02a | ||
|
4aff1f1833 | ||
|
017956366f | ||
|
afbafdf806 | ||
|
d0eef7c98e | ||
|
7deff7a42a | ||
|
23da0b16ea | ||
|
6f7771d97e | ||
|
24ba4ae404 | ||
|
73d030b779 | ||
|
4e869a2974 | ||
|
e85d0db771 | ||
|
44c549211e | ||
|
2bfd4627ed | ||
|
9dae748a76 | ||
|
bb7c5ee34c | ||
|
54945b537d | ||
|
4bde7d80f5 | ||
|
f2a88e6a37 | ||
|
410eb353e8 | ||
|
a9614ad28e | ||
|
69436714f9 | ||
|
3b865244d0 | ||
|
4e9cac96c7 | ||
|
ac374a8ef9 | ||
|
bdafa8e9ab | ||
|
f1ba9253b0 | ||
|
5858e05362 | ||
|
1a8a518897 | ||
|
569911121d | ||
|
5cf71a4e67 | ||
|
d70b93e095 | ||
|
6197636fac | ||
|
edd883853d | ||
|
0e97b769f9 | ||
|
4cfb04aa7f | ||
|
7e5b929ea2 | ||
|
29ba07fe8c | ||
|
de0bb3cdab | ||
|
cb86557aca | ||
|
0780329a59 | ||
|
1f1cbf53f9 | ||
|
b63bf258ff | ||
|
c2578918f2 | ||
|
e2e197d84a | ||
|
f1e2bc01f6 | ||
|
1e25a122c9 | ||
|
a037193e79 | ||
|
05fac2f67d | ||
|
043eb224e2 | ||
|
35b771b19e | ||
|
92060bd6b6 | ||
|
1de9f30449 | ||
|
45d3b24c17 | ||
|
7248a5d037 | ||
|
ba4d2bd5fe | ||
|
b6a7377692 | ||
|
fcd4d8b842 | ||
|
e2103d0bea | ||
|
35922e4898 | ||
|
ef0384b296 | ||
|
5b8f4f3e92 | ||
|
647661daf9 | ||
|
2f6e3b4cda | ||
|
c4949ac5d9 | ||
|
8f5d31cb03 | ||
|
4ab338bbb1 | ||
|
5777759aae | ||
|
2019128d92 | ||
|
51a92bcf8f | ||
|
09958681cd | ||
|
8bb764c6d6 | ||
|
dddd2f1cc7 | ||
|
a84c4c7ecc | ||
|
f2eca2566f | ||
|
077a3d06b3 | ||
|
f9c4b847aa | ||
|
c5388e49f2 | ||
|
ba07d7820f | ||
|
77d14211c9 | ||
|
ecbe0127b0 | ||
|
fd721143e2 | ||
|
25b7095396 | ||
|
44dc153ebe | ||
|
1ef6ad6215 | ||
|
aff1859759 | ||
|
122a30c183 | ||
|
2726d94d10 | ||
|
d98bb74b80 | ||
|
bfdf348a6c | ||
|
f11473da87 | ||
|
4d0072a74c | ||
|
cd620e17ec | ||
|
dd352f0a91 | ||
|
f35d35741e | ||
|
585c24cee8 | ||
|
ff8b5061b4 | ||
|
e97dac7793 | ||
|
7748e582a8 | ||
|
2cb0ea20f0 | ||
|
de77ee3126 | ||
|
ad61d88cb1 | ||
|
051f65a4d5 | ||
|
7eecbfd08e | ||
|
1c7330635b | ||
|
392e645fe5 | ||
|
91398c5dcc | ||
|
aa183e6844 | ||
|
c55f0f73b8 | ||
|
85a9181c12 | ||
|
94f3eaa980 | ||
|
456bcee68a | ||
|
cfb5835e17 | ||
|
cd03d59056 | ||
|
2bc79fcdf4 | ||
|
c2f330d4f1 | ||
|
fc5e883160 | ||
|
993cc3dfd6 | ||
|
d1ad0c91f8 | ||
|
7e322f1f8b | ||
|
bd9e8bd10f | ||
|
711bdf187a | ||
|
720ef5f6c5 | ||
|
eff3504b05 | ||
|
b55bb445dd | ||
|
a546d99000 | ||
|
1841562553 | ||
|
744b8cf168 | ||
|
e44fcdc0b3 | ||
|
901fe72471 | ||
|
a796f81444 | ||
|
46d55816b8 | ||
|
4ebfcc4a21 | ||
|
729f6e745e | ||
|
fef3764eb1 | ||
|
382f56178c | ||
|
e657934cef | ||
|
c276ff4bd9 | ||
|
0a35b897be | ||
|
b0965f094a | ||
|
ef79a98544 | ||
|
64b57983f0 | ||
|
606ddff813 | ||
|
cac4684986 | ||
|
07d75abdf8 | ||
|
1ee460bba8 | ||
|
d33254f287 | ||
|
010a80ceca | ||
|
947b3f76be | ||
|
6f822f54aa | ||
|
a3e2ab4d4e | ||
|
4cc0216e0a | ||
|
80be664139 | ||
|
57a0415ba3 | ||
|
494b10b97e | ||
|
ba161ddddd | ||
|
7788821a69 | ||
|
068f733d1e | ||
|
1272b03f25 | ||
|
b4e8560698 | ||
|
926106cf8c | ||
|
ac7a588632 | ||
|
a21f5d18d6 | ||
|
a8261bb385 | ||
|
700e4d032e | ||
|
4dbada69bf | ||
|
ca7f3f7450 | ||
|
64b531aa3c | ||
|
f377164db9 | ||
|
7ab01b66e4 | ||
|
d15c674584 | ||
|
0da44f69ad | ||
|
062d6a1824 | ||
|
29cb51cdce | ||
|
1d111fdca8 | ||
|
bd7082f57e | ||
|
71c921faa7 | ||
|
5653d07c7b | ||
|
948090676a | ||
|
61db22f5ae | ||
|
b7e5e77166 | ||
|
378d30834b | ||
|
14d47213ef | ||
|
3495fd43f4 | ||
|
e7a5919477 | ||
|
1aa630e4a9 | ||
|
e8915f8ec5 | ||
|
14fdec2478 | ||
|
b801cf2c9e | ||
|
878294e4fe | ||
|
d44848ecbb | ||
|
05abb8ace3 | ||
|
04a844a9c0 | ||
|
dad0b38f25 | ||
|
a3d5fbbdcd | ||
|
916ada108f | ||
|
bece095579 | ||
|
d6dc75e94b | ||
|
76a4abd7c0 | ||
|
0e73571111 | ||
|
8a93631e08 | ||
|
ba8613a179 | ||
|
fb67180809 | ||
|
4530370e52 | ||
|
ce6a4e4032 | ||
|
fc3f2483ee | ||
|
dffd3bfa7d | ||
|
7a0b45d456 | ||
|
66283943c5 | ||
|
d702845026 | ||
|
0a0c9893b1 | ||
|
6e7c033a5d | ||
|
bbac26294f | ||
|
b20df4b7cd | ||
|
17a8e32782 | ||
|
1fd16ba69c | ||
|
62588ce088 | ||
|
6cb9c3c713 | ||
|
3839a2dcfd | ||
|
ed75563a94 | ||
|
01a5196a92 | ||
|
15723adb9a | ||
|
57353cdfff | ||
|
fe9a3088bd | ||
|
5c79e7106f | ||
|
4845599bda | ||
|
7a38a0b223 | ||
|
cbabc91b06 | ||
|
846f83e3e4 | ||
|
841a4f90c1 | ||
|
3284769fef | ||
|
b5ce3cebbc | ||
|
73aa83aa03 | ||
|
5fcdff843c | ||
|
76f1a61538 | ||
|
d591180e99 | ||
|
381a5fabc4 | ||
|
9838cc680a | ||
|
98195b5e3c | ||
|
3124e627cf | ||
|
c2286482ae | ||
|
f814ae795d | ||
|
50ef9677fe | ||
|
838e05521e | ||
|
cccbe753d7 | ||
|
6decd148e5 | ||
|
045dc566ea | ||
|
bb183457a6 | ||
|
dc18916f46 | ||
|
f99dafbf51 | ||
|
aee6fb1265 | ||
|
d1388cdf84 | ||
|
b869fe0b76 | ||
|
0e2817da88 | ||
|
fc8232f943 | ||
|
26324c2578 | ||
|
4e93905350 | ||
|
510e657c93 | ||
|
060ebe3d4a | ||
|
1c0adc47ee | ||
|
66fdba957b | ||
|
1d16958910 | ||
|
b18c6dec9d | ||
|
991a1fe8d8 | ||
|
60fc0bedb8 | ||
|
7717f9bece | ||
|
fe2dd1bad4 | ||
|
2d0840cb3a |
945 changed files with 17501 additions and 40618 deletions
|
@ -9,8 +9,3 @@ insert_final_newline = true
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[*.glsl]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
insert_final_newline = false
|
|
|
@ -1,69 +0,0 @@
|
||||||
stages:
|
|
||||||
- build
|
|
||||||
|
|
||||||
Debian:
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
- linux
|
|
||||||
image: gcc
|
|
||||||
cache:
|
|
||||||
key: apt-cache
|
|
||||||
paths:
|
|
||||||
- apt-cache/
|
|
||||||
before_script:
|
|
||||||
- export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR
|
|
||||||
- apt-get update -yq
|
|
||||||
- apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev
|
|
||||||
# - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old
|
|
||||||
- curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb
|
|
||||||
- curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb
|
|
||||||
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb
|
|
||||||
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb
|
|
||||||
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb
|
|
||||||
- dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb
|
|
||||||
stage: build
|
|
||||||
script:
|
|
||||||
- cores_to_use=$((`nproc`-2)); if (( $cores_to_use < 1 )); then cores_to_use=1; fi
|
|
||||||
- mkdir build; cd build; cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../
|
|
||||||
- make -j$cores_to_use
|
|
||||||
- DESTDIR=artifacts make install
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- build/artifacts/
|
|
||||||
MacOS:
|
|
||||||
tags:
|
|
||||||
- macos
|
|
||||||
- xcode
|
|
||||||
except:
|
|
||||||
- branches # because our CI VMs are not public, MRs can't use them and timeout
|
|
||||||
stage: build
|
|
||||||
allow_failure: true
|
|
||||||
script:
|
|
||||||
- rm -fr build/* # remove anything in the build directory
|
|
||||||
- CI/before_install.osx.sh
|
|
||||||
- CI/before_script.osx.sh
|
|
||||||
- cd build; make -j2 package
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- build/OpenMW-*.dmg
|
|
||||||
|
|
||||||
Windows:
|
|
||||||
tags:
|
|
||||||
- win10
|
|
||||||
- msvc2017
|
|
||||||
except:
|
|
||||||
- branches # because our CI VMs are not public, MRs can't use them and timeout
|
|
||||||
stage: build
|
|
||||||
allow_failure: true
|
|
||||||
script:
|
|
||||||
# - env # turn on for debugging
|
|
||||||
- sh %CI_PROJECT_DIR%/CI/before_script.msvc.sh -c Release -p x64 -v 2017 -V
|
|
||||||
- SET msBuildLocation="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\msbuild.exe"
|
|
||||||
- call %msBuildLocation% MSVC2017_64\OpenMW.sln /t:Build /p:Configuration=Release /m:%NUMBER_OF_PROCESSORS%
|
|
||||||
- 7z a OpenMW_MSVC2017_64_%CI_BUILD_REF_NAME%_%CI_BUILD_ID%.zip %CI_PROJECT_DIR%\MSVC2017_64\Release\
|
|
||||||
cache:
|
|
||||||
paths:
|
|
||||||
- deps
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- "*.zip"
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +1,6 @@
|
||||||
[submodule "extern/breakpad"]
|
[submodule "extern/breakpad"]
|
||||||
path = extern/breakpad
|
path = extern/breakpad
|
||||||
url = https://chromium.googlesource.com/breakpad/breakpad
|
url = https://chromium.googlesource.com/breakpad/breakpad
|
||||||
|
[submodule "extern/sol"]
|
||||||
|
path = extern/sol
|
||||||
|
url = https://github.com/ThePhD/sol2
|
||||||
|
|
31
.travis.yml
31
.travis.yml
|
@ -1,10 +1,6 @@
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
# - osx
|
|
||||||
osx_image: xcode9.4
|
|
||||||
language: cpp
|
language: cpp
|
||||||
sudo: required
|
sudo: required
|
||||||
dist: xenial
|
dist: trusty
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
|
@ -15,18 +11,19 @@ env:
|
||||||
global:
|
global:
|
||||||
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
|
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
|
||||||
# via the "travis encrypt" command using the project repo's public key
|
# via the "travis encrypt" command using the project repo's public key
|
||||||
- secure: 1QK0yVyoOB+gf2I7XzvhXu9w/5lq4stBXIwJbVCTjz4Q4XVHCosURaW1MAgKzMrPnbFEwjyn5uQ8BwsvvfkuN1AZD0YXITgc7gyI+J1wQ/p/ljxRxglakU6WEgsTs2J5z9UmGac4YTXg+quK7YP3rv+zuGim2I2rhzImejyzp0Ym3kRCnNcy+SGBsiRaevRJMe00Ch8zGAbEhduQGeSoS6W0rcu02DNlQKiq5NktWsXR+TWWWVfIeIlQR/lbPsCd0pdxMaMv2QCY0rVbwrYxWJwr/Qe45dAdWp+8/C3PbXpeMSGxlLa33nJNX4Lf/djxbjm8KWk6edaXPajrjR/0iwcpwq0jg2Jt6XfEdnJt35F1gpXlc04sxStjG45uloOKCFYT0wdhIO1Lq+hDP54wypQl+JInd5qC001O7pwhVxO36EgKWqo8HD+BqGDBwsNj2engy9Qcp3wO6G0rLBPB3CrZsk9wrHVv5cSiQSLMhId3Xviu3ZI2qEDA+kgTvxrKrsnMj4bILVCyG5Ka2Mj22wIDW9e8oIab9oTdujax3DTN1GkD6QuOAGzwDsNwGASsgfoeZ+FUhgM75RlBWGMilgkmnF7EJ0oAXLEpjtABnEr2d4qHv+y08kOuTDBLB9ExzCIj024dYYYNLZrqPKx0ncHuCMG2QNj2aJAJEZtj1rQ=
|
- secure: NZmvVuA0O9NJXVQ12tXQZHDJC2mbFgYNFcsicw0DgW1It2Nk5hxIkF0pfu4/Z59mhQuOPgRVjl5b0FKy2Axh0gkWc1DJEXGwNaiW5lpTMNWR1LJG5rxa8LrDUpFkycpbzfAFuTUZu5z3iYVv64XzELvBuqNGhPMu1LeBnrlech0jFNjkR9p5qtJGWb8zYcPMCC57rig8a9g1ABoVYS6UXjrKpx0946ZLRsE5ukc9pXsypGwPmOMyfzZkxxzIqFaxoE5JIEdaJTWba/6Za315ozYYIi/N35ROI1YAv5GHRe/Iw9XAa4vQpbDzjM7ZSsZdTvvQsSU598gD2xC6jFUKSrpW6GZKwM2x236fZLGnOk5Uw7DUbG+AwpcEmxBwoy9PjBl9ZF3tJykI0gROewCy8MODhdsVMKr1HGIMVBIJySm/RnNqtoDbYV8mYnSl5b8rwJiCajoiR8Zuv4CIfGneeH1a3DOQDPH/qkDsU6ilzF4ANsBlMUUpgY653KBMBmTlNuVZSH527tnD7Fg6JgHVuSQkTbRa1vSkR7Zcre604RZcAoaEdbX3bhVDasPPghU/I742L0RH3oQNlR09pPBDZ8kG7ydl4aPHwpCWnvXNM1vgxtGvnYLztwrse7IoaRXRYiMFmrso78WhMWUDKgvY4wV9aeUu0DtnMezZVIQwCKg=
|
||||||
|
- macos_qt_formula=qt
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
sources:
|
sources:
|
||||||
- sourceline: 'ppa:openmw/openmw'
|
- sourceline: 'ppa:openmw/openmw'
|
||||||
- sourceline: 'ppa:rakhimov/boost'
|
- sourceline: 'ppa:rakhimov/boost'
|
||||||
- ubuntu-toolchain-r-test
|
- ubuntu-toolchain-r-test
|
||||||
|
- llvm-toolchain-precise-3.8
|
||||||
packages: [
|
packages: [
|
||||||
# Dev
|
# Dev
|
||||||
cmake, clang-6.0, libunshield-dev, libtinyxml-dev,
|
cmake, clang-3.8, libunshield-dev, libtinyxml-dev,
|
||||||
g++-8,
|
g++-6,
|
||||||
# Tests
|
# Tests
|
||||||
libgtest-dev, google-mock,
|
libgtest-dev, google-mock,
|
||||||
# Boost
|
# Boost
|
||||||
|
@ -45,7 +42,7 @@ addons:
|
||||||
project:
|
project:
|
||||||
name: "TES3MP/openmw-tes3mp"
|
name: "TES3MP/openmw-tes3mp"
|
||||||
description: "<Your project description here>"
|
description: "<Your project description here>"
|
||||||
notification_email: koncord@tes3mp.com
|
notification_email: stas5978@gmail.com
|
||||||
build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE -DBUILD_BSATOOL=FALSE -DBUILD_ESMTOOL=FALSE -DBUILD_MWINIIMPORTER=FALSE -DBUILD_LAUNCHER=FALSE"
|
build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE -DBUILD_BSATOOL=FALSE -DBUILD_ESMTOOL=FALSE -DBUILD_MWINIIMPORTER=FALSE -DBUILD_LAUNCHER=FALSE"
|
||||||
build_command: "make -j3"
|
build_command: "make -j3"
|
||||||
branch_pattern: coverity_scan
|
branch_pattern: coverity_scan
|
||||||
|
@ -53,21 +50,19 @@ matrix:
|
||||||
include:
|
include:
|
||||||
- os: linux
|
- os: linux
|
||||||
env:
|
env:
|
||||||
- ANALYZE="scan-build-6.0 --use-cc clang-6.0 --use-c++ clang++-6.0 "
|
- ANALYZE="scan-build-3.8 --use-cc clang-3.8 --use-c++ clang++-3.8 "
|
||||||
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
|
- MATRIX_CC="CC=clang-3.8 && CXX=clang++-3.8"
|
||||||
compiler: clang
|
compiler: clang
|
||||||
- os: linux
|
- os: linux
|
||||||
env:
|
env:
|
||||||
- MATRIX_CC="CC=gcc-8 && CXX=g++-8"
|
- MATRIX_CC="CC=gcc-6 && CXX=g++-6"
|
||||||
- os: linux
|
- os: linux
|
||||||
env:
|
env:
|
||||||
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
|
- MATRIX_CC="CC=clang-3.8 && CXX=clang++-3.8"
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- env:
|
- env:
|
||||||
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
|
- ANALYZE="scan-build-3.8 --use-cc clang-3.8 --use-c++ clang++-3.8 "
|
||||||
- env:
|
- MATRIX_CC="CC=clang-3.8 && CXX=clang++-3.8"
|
||||||
- ANALYZE="scan-build-6.0 --use-cc clang-6.0 --use-c++ clang++-6.0 "
|
|
||||||
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
|
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- ./CI/before_install.${TRAVIS_OS_NAME}.sh
|
- ./CI/before_install.${TRAVIS_OS_NAME}.sh
|
||||||
|
|
|
@ -22,7 +22,6 @@ Programmers
|
||||||
alexanderkjall
|
alexanderkjall
|
||||||
Alexander Nadeau (wareya)
|
Alexander Nadeau (wareya)
|
||||||
Alexander Olofsson (Ace)
|
Alexander Olofsson (Ace)
|
||||||
Alex S (docwest)
|
|
||||||
Allofich
|
Allofich
|
||||||
Andrei Kortunov (akortunov)
|
Andrei Kortunov (akortunov)
|
||||||
AnyOldName3
|
AnyOldName3
|
||||||
|
@ -38,7 +37,6 @@ Programmers
|
||||||
Britt Mathis (galdor557)
|
Britt Mathis (galdor557)
|
||||||
Capostrophic
|
Capostrophic
|
||||||
cc9cii
|
cc9cii
|
||||||
Cédric Mocquillon
|
|
||||||
Chris Boyce (slothlife)
|
Chris Boyce (slothlife)
|
||||||
Chris Robinson (KittyCat)
|
Chris Robinson (KittyCat)
|
||||||
Cory F. Cohen (cfcohen)
|
Cory F. Cohen (cfcohen)
|
||||||
|
@ -64,8 +62,6 @@ Programmers
|
||||||
Evgeniy Mineev (sandstranger)
|
Evgeniy Mineev (sandstranger)
|
||||||
Federico Guerra (FedeWar)
|
Federico Guerra (FedeWar)
|
||||||
Fil Krynicki (filkry)
|
Fil Krynicki (filkry)
|
||||||
Finbar Crago (finbar-crago)
|
|
||||||
Florian Weber (Florianjw)
|
|
||||||
Gašper Sedej
|
Gašper Sedej
|
||||||
gugus/gus
|
gugus/gus
|
||||||
Hallfaer Tuilinn
|
Hallfaer Tuilinn
|
||||||
|
@ -143,7 +139,6 @@ Programmers
|
||||||
Rohit Nirmal
|
Rohit Nirmal
|
||||||
Roman Melnik (Kromgart)
|
Roman Melnik (Kromgart)
|
||||||
Roman Proskuryakov (kpp)
|
Roman Proskuryakov (kpp)
|
||||||
Roman Siromakha (elsid)
|
|
||||||
Sandy Carter (bwrsandman)
|
Sandy Carter (bwrsandman)
|
||||||
Scott Howard
|
Scott Howard
|
||||||
scrawl
|
scrawl
|
||||||
|
@ -176,7 +171,6 @@ Programmers
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
Adam Bowen (adamnbowen)
|
|
||||||
Alejandro Sanchez (HiPhish)
|
Alejandro Sanchez (HiPhish)
|
||||||
Bodillium
|
Bodillium
|
||||||
Bret Curtis (psi29a)
|
Bret Curtis (psi29a)
|
||||||
|
|
107
CHANGELOG.md
107
CHANGELOG.md
|
@ -1,114 +1,8 @@
|
||||||
0.45.0
|
0.45.0
|
||||||
------
|
------
|
||||||
|
|
||||||
Bug #1990: Sunrise/sunset not set correct
|
|
||||||
Bug #2131: Lustidrike's spell misses the player every time
|
|
||||||
Bug #2222: Fatigue's effect on selling price is backwards
|
|
||||||
Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped
|
|
||||||
Bug #2455: Creatures attacks degrade armor
|
|
||||||
Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash
|
|
||||||
Bug #2626: Resurrecting the player does not resume the game
|
|
||||||
Bug #2772: Non-existing class or faction freezes the game
|
|
||||||
Bug #2835: Player able to slowly move when overencumbered
|
|
||||||
Bug #2852: No murder bounty when a player follower commits murder
|
|
||||||
Bug #2862: [macOS] Can't quit launcher using Command-Q or OpenMW->Quit
|
|
||||||
Bug #2872: Tab completion in console doesn't work with explicit reference
|
|
||||||
Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y
|
|
||||||
Bug #3249: Fixed revert function not updating views properly
|
|
||||||
Bug #3374: Touch spells not hitting kwama foragers
|
|
||||||
Bug #3486: [Mod] NPC Commands does not work
|
|
||||||
Bug #3591: Angled hit distance too low
|
|
||||||
Bug #3629: DB assassin attack never triggers creature spawning
|
|
||||||
Bug #3876: Landscape texture painting is misaligned
|
|
||||||
Bug #3897: Have Goodbye give all choices the effects of Goodbye
|
|
||||||
Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters
|
|
||||||
Bug #3950: FLATTEN_STATIC_TRANSFORMS optimization breaks animated collision shapes
|
|
||||||
Bug #3993: Terrain texture blending map is not upscaled
|
|
||||||
Bug #3997: Almalexia doesn't pace
|
|
||||||
Bug #4036: Weird behaviour of AI packages if package target has non-unique ID
|
|
||||||
Bug #4047: OpenMW not reporting its version number in MacOS; OpenMW-CS not doing it fully
|
|
||||||
Bug #4110: Fixed undo / redo menu text losing the assigned shortcuts
|
|
||||||
Bug #4125: OpenMW logo cropped on bugtracker
|
|
||||||
Bug #4215: OpenMW shows book text after last EOL tag
|
|
||||||
Bug #4221: Characters get stuck in V-shaped terrain
|
|
||||||
Bug #4230: AiTravel package issues break some Tribunal quests
|
|
||||||
Bug #4251: Stationary NPCs do not return to their position after combat
|
|
||||||
Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+
|
|
||||||
Bug #4286: Scripted animations can be interrupted
|
|
||||||
Bug #4291: Non-persistent actors that started the game as dead do not play death animations
|
|
||||||
Bug #4293: Faction members are not aware of faction ownerships in barter
|
Bug #4293: Faction members are not aware of faction ownerships in barter
|
||||||
Bug #4307: World cleanup should remove dead bodies only if death animation is finished
|
|
||||||
Bug #4311: OpenMW does not handle RootCollisionNode correctly
|
|
||||||
Bug #4327: Missing animations during spell/weapon stance switching
|
|
||||||
Bug #4358: Running animation is interrupted when magic mode is toggled
|
|
||||||
Bug #4368: Settings window ok button doesn't have key focus by default
|
|
||||||
Bug #4378: On-self absorb spells restore stats
|
|
||||||
Bug #4393: NPCs walk back to where they were after using ResetActors
|
|
||||||
Bug #4416: Handle exception if we try to play non-music file
|
|
||||||
Bug #4419: MRK NiStringExtraData is handled incorrectly
|
|
||||||
Bug #4426: RotateWorld behavior is incorrect
|
Bug #4426: RotateWorld behavior is incorrect
|
||||||
Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2
|
|
||||||
Bug #4431: "Lock 0" console command is a no-op
|
|
||||||
Bug #4432: Guards behaviour is incorrect if they do not have AI packages
|
|
||||||
Bug #4433: Guard behaviour is incorrect with Alarm = 0
|
Bug #4433: Guard behaviour is incorrect with Alarm = 0
|
||||||
Bug #4451: Script fails to compile when using "Begin, [ScriptName]" syntax
|
|
||||||
Bug #4452: Default terrain texture bleeds through texture transitions
|
|
||||||
Bug #4453: Quick keys behaviour is invalid for equipment
|
|
||||||
Bug #4454: AI opens doors too slow
|
|
||||||
Bug #4457: Item without CanCarry flag prevents shield autoequipping in dark areas
|
|
||||||
Bug #4458: AiWander console command handles idle chances incorrectly
|
|
||||||
Bug #4459: NotCell dialogue condition doesn't support partial matches
|
|
||||||
Bug #4460: Script function "Equip" doesn't bypass beast restrictions
|
|
||||||
Bug #4461: "Open" spell from non-player caster isn't a crime
|
|
||||||
Bug #4464: OpenMW keeps AiState cached storages even after we cancel AI packages
|
|
||||||
Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal
|
|
||||||
Bug #4474: No fallback when getVampireHead fails
|
|
||||||
Bug #4475: Scripted animations should not cause movement
|
|
||||||
Bug #4479: "Game" category on Advanced page is getting too long
|
|
||||||
Bug #4480: Segfault in QuickKeysMenu when item no longer in inventory
|
|
||||||
Bug #4489: Goodbye doesn't block dialogue hyperlinks
|
|
||||||
Bug #4490: PositionCell on player gives "Error: tried to add local script twice"
|
|
||||||
Bug #4494: Training cap based off Base Skill instead of Modified Skill
|
|
||||||
Bug #4495: Crossbow animations blending is buggy
|
|
||||||
Bug #4496: SpellTurnLeft and SpellTurnRight animation groups are unused
|
|
||||||
Bug #4497: File names starting with x or X are not classified as animation
|
|
||||||
Bug #4503: Cast and ExplodeSpell commands increase alteration skill
|
|
||||||
Bug #4510: Division by zero in MWMechanics::CreatureStats::setAttribute
|
|
||||||
Bug #4519: Knockdown does not discard movement in the 1st-person mode
|
|
||||||
Bug #4539: Paper Doll is affected by GUI scaling
|
|
||||||
Bug #4545: Creatures flee from werewolves
|
|
||||||
Bug #4551: Replace 0 sound range with default range separately
|
|
||||||
Bug #4553: Forcegreeting on non-actor opens a dialogue window which cannot be closed
|
|
||||||
Bug #4557: Topics with reserved names are handled differently from vanilla
|
|
||||||
Bug #4558: Mesh optimizer: check for reserved node name is case-sensitive
|
|
||||||
Bug #4563: Fast travel price logic checks destination cell instead of service actor cell
|
|
||||||
Bug #4565: Underwater view distance should be limited
|
|
||||||
Bug #4573: Player uses headtracking in the 1st-person mode
|
|
||||||
Bug #4574: Player turning animations are twitchy
|
|
||||||
Bug #4575: Weird result of attack animation blending with movement animations
|
|
||||||
Bug #4576: Reset of idle animations when attack can not be started
|
|
||||||
Feature #2606: Editor: Implemented (optional) case sensitive global search
|
|
||||||
Feature #3083: Play animation when NPC is casting spell via script
|
|
||||||
Feature #3103: Provide option for disposition to get increased by successful trade
|
|
||||||
Feature #3276: Editor: Search - Show number of (remaining) search results and indicate a search without any results
|
|
||||||
Feature #3641: Editor: Limit FPS in 3d preview window
|
|
||||||
Feature #3703: Ranged sneak attack criticals
|
|
||||||
Feature #4012: Editor: Write a log file if OpenCS crashes
|
|
||||||
Feature #4222: 360° screenshots
|
|
||||||
Feature #4256: Implement ToggleBorders (TB) console command
|
|
||||||
Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts
|
|
||||||
Feature #4345: Add equivalents for the command line commands to Launcher
|
|
||||||
Feature #4404: Editor: All EnumDelegate fields should have their items sorted alphabetically
|
|
||||||
Feature #4444: Per-group KF-animation files support
|
|
||||||
Feature #4466: Editor: Add option to ignore "Base" records when running verifier
|
|
||||||
Feature #4488: Make water shader rougher during rain
|
|
||||||
Feature #4509: Show count of enchanted items in stack in the spells list
|
|
||||||
Feature #4512: Editor: Use markers for lights and creatures levelled lists
|
|
||||||
Feature #4548: Weapon priority: use the actual chance to hit the target instead of weapon skill
|
|
||||||
Feature #4549: Weapon priority: use the actual damage in weapon rating calculations
|
|
||||||
Feature #4550: Weapon priority: make ranged weapon bonus more sensible
|
|
||||||
Task #2490: Don't open command prompt window on Release-mode builds automatically
|
|
||||||
Task #4545: Enable is_pod string test
|
|
||||||
|
|
||||||
0.44.0
|
0.44.0
|
||||||
------
|
------
|
||||||
|
@ -194,7 +88,6 @@
|
||||||
Bug #4412: openmw-iniimporter ignores data paths from config
|
Bug #4412: openmw-iniimporter ignores data paths from config
|
||||||
Bug #4413: Moving with 0 strength uses all of your fatigue
|
Bug #4413: Moving with 0 strength uses all of your fatigue
|
||||||
Bug #4420: Camera flickering when I open up and close menus while sneaking
|
Bug #4420: Camera flickering when I open up and close menus while sneaking
|
||||||
Bug #4424: [macOS] Cursor is either empty or garbage when compiled against macOS 10.13 SDK
|
|
||||||
Bug #4435: Item health is considered a signed integer
|
Bug #4435: Item health is considered a signed integer
|
||||||
Bug #4441: Adding items to currently disabled weapon-wielding creatures crashes the game
|
Bug #4441: Adding items to currently disabled weapon-wielding creatures crashes the game
|
||||||
Feature #1786: Round up encumbrance value in the encumbrance bar
|
Feature #1786: Round up encumbrance value in the encumbrance bar
|
||||||
|
|
|
@ -15,8 +15,7 @@ sudo ln -s /usr/src/gtest/build/libgtest.so /usr/lib/libgtest.so
|
||||||
sudo ln -s /usr/src/gtest/build/libgtest_main.so /usr/lib/libgtest_main.so
|
sudo ln -s /usr/src/gtest/build/libgtest_main.so /usr/lib/libgtest_main.so
|
||||||
|
|
||||||
cd ~/
|
cd ~/
|
||||||
git clone https://github.com/TES3MP/CrabNet
|
git clone https://github.com/TES3MP/RakNet
|
||||||
cd CrabNet
|
cd RakNet
|
||||||
cmake . -DCRABNET_ENABLE_DLL=OFF -DCRABNET_ENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=Release
|
cmake . -DRAKNET_ENABLE_DLL=OFF -DRAKNET_ENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=Release
|
||||||
make -j3
|
make -j3
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ brew update
|
||||||
|
|
||||||
brew outdated cmake || brew upgrade cmake
|
brew outdated cmake || brew upgrade cmake
|
||||||
brew outdated pkgconfig || brew upgrade pkgconfig
|
brew outdated pkgconfig || brew upgrade pkgconfig
|
||||||
brew install qt
|
brew install $macos_qt_formula
|
||||||
|
|
||||||
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-100d2e0.zip -o ~/openmw-deps.zip
|
curl https://downloads.openmw.org/osx/dependencies/openmw-deps-c40905f.zip -o ~/openmw-deps.zip
|
||||||
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null
|
unzip ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null
|
||||||
|
|
|
@ -9,7 +9,7 @@ if [ ! -z "${MATRIX_CC}" ]; then
|
||||||
eval "${MATRIX_CC}"
|
eval "${MATRIX_CC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export RAKNET_ROOT=~/CrabNet
|
export RAKNET_ROOT=~/RakNet
|
||||||
|
|
||||||
export CODE_COVERAGE=0
|
export CODE_COVERAGE=0
|
||||||
if [ ! -z "${ANALYZE}" ]; then
|
if [ ! -z "${ANALYZE}" ]; then
|
||||||
|
@ -35,5 +35,5 @@ ${ANALYZE}cmake .. \
|
||||||
-DBINDIR=/usr/games \
|
-DBINDIR=/usr/games \
|
||||||
-DCMAKE_BUILD_TYPE="None" \
|
-DCMAKE_BUILD_TYPE="None" \
|
||||||
-DUSE_SYSTEM_TINYXML=TRUE \
|
-DUSE_SYSTEM_TINYXML=TRUE \
|
||||||
-DRakNet_LIBRARY_RELEASE=~/CrabNet/lib/libRakNetLibStatic.a \
|
-DRakNet_LIBRARY_RELEASE=~/RakNet/lib/libRakNetLibStatic.a \
|
||||||
-DRakNet_LIBRARY_DEBUG=~/CrabNet/lib/libRakNetLibStatic.a
|
-DRakNet_LIBRARY_DEBUG=~/RakNet/lib/libRakNetLibStatic.a \
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# set -x # turn-on for debugging
|
|
||||||
|
|
||||||
MISSINGTOOLS=0
|
MISSINGTOOLS=0
|
||||||
|
|
||||||
|
@ -77,6 +76,7 @@ while [ $# -gt 0 ]; do
|
||||||
h )
|
h )
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: $0 [-cdehkpuvV]
|
Usage: $0 [-cdehkpuvV]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-c <Release/Debug>
|
-c <Release/Debug>
|
||||||
Set the configuration, can also be set with environment variable CONFIGURATION.
|
Set the configuration, can also be set with environment variable CONFIGURATION.
|
||||||
|
@ -232,9 +232,10 @@ fi
|
||||||
case $VS_VERSION in
|
case $VS_VERSION in
|
||||||
15|15.0|2017 )
|
15|15.0|2017 )
|
||||||
GENERATOR="Visual Studio 15 2017"
|
GENERATOR="Visual Studio 15 2017"
|
||||||
TOOLSET="vc141"
|
TOOLSET="vc140"
|
||||||
|
TOOLSET_REAL="vc141"
|
||||||
MSVC_REAL_VER="15"
|
MSVC_REAL_VER="15"
|
||||||
MSVC_VER="14.1"
|
MSVC_VER="14"
|
||||||
MSVC_YEAR="2015"
|
MSVC_YEAR="2015"
|
||||||
MSVC_DISPLAY_YEAR="2017"
|
MSVC_DISPLAY_YEAR="2017"
|
||||||
;;
|
;;
|
||||||
|
@ -242,8 +243,9 @@ case $VS_VERSION in
|
||||||
14|14.0|2015 )
|
14|14.0|2015 )
|
||||||
GENERATOR="Visual Studio 14 2015"
|
GENERATOR="Visual Studio 14 2015"
|
||||||
TOOLSET="vc140"
|
TOOLSET="vc140"
|
||||||
|
TOOLSET_REAL="vc140"
|
||||||
MSVC_REAL_VER="14"
|
MSVC_REAL_VER="14"
|
||||||
MSVC_VER="14.0"
|
MSVC_VER="14"
|
||||||
MSVC_YEAR="2015"
|
MSVC_YEAR="2015"
|
||||||
MSVC_DISPLAY_YEAR="2015"
|
MSVC_DISPLAY_YEAR="2015"
|
||||||
;;
|
;;
|
||||||
|
@ -251,8 +253,9 @@ case $VS_VERSION in
|
||||||
12|12.0|2013 )
|
12|12.0|2013 )
|
||||||
GENERATOR="Visual Studio 12 2013"
|
GENERATOR="Visual Studio 12 2013"
|
||||||
TOOLSET="vc120"
|
TOOLSET="vc120"
|
||||||
|
TOOLSET_REAL="vc120"
|
||||||
MSVC_REAL_VER="12"
|
MSVC_REAL_VER="12"
|
||||||
MSVC_VER="12.0"
|
MSVC_VER="12"
|
||||||
MSVC_YEAR="2013"
|
MSVC_YEAR="2013"
|
||||||
MSVC_DISPLAY_YEAR="2013"
|
MSVC_DISPLAY_YEAR="2013"
|
||||||
;;
|
;;
|
||||||
|
@ -322,9 +325,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
|
||||||
|
|
||||||
# Boost
|
# Boost
|
||||||
if [ -z $APPVEYOR ]; then
|
if [ -z $APPVEYOR ]; then
|
||||||
download "Boost 1.67.0" \
|
download "Boost 1.61.0" \
|
||||||
"https://sourceforge.net/projects/boost/files/boost-binaries/1.67.0/boost_1_67_0-msvc-${MSVC_VER}-${BITS}.exe" \
|
"https://sourceforge.net/projects/boost/files/boost-binaries/1.61.0/boost_1_61_0-msvc-${MSVC_VER}.0-${BITS}.exe" \
|
||||||
"boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe"
|
"boost-1.61.0-msvc${MSVC_YEAR}-win${BITS}.exe"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Bullet
|
# Bullet
|
||||||
|
@ -362,8 +365,8 @@ if [ -z $SKIP_DOWNLOAD ]; then
|
||||||
QT_SUFFIX=""
|
QT_SUFFIX=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
download "Qt 5.7.0" \
|
download "Qt 5.7.2" \
|
||||||
"https://download.qt.io/archive/qt/5.7/5.7.0/qt-opensource-windows-x86-msvc${MSVC_YEAR}${QT_SUFFIX}-5.7.0.exe" \
|
"https://download.qt.io/official_releases/qt/5.7/5.7.0/qt-opensource-windows-x86-msvc${MSVC_YEAR}${QT_SUFFIX}-5.7.0.exe" \
|
||||||
"qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" \
|
"qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" \
|
||||||
"https://www.lysator.liu.se/~ace/OpenMW/deps/qt-5-install.qs" \
|
"https://www.lysator.liu.se/~ace/OpenMW/deps/qt-5-install.qs" \
|
||||||
"qt-5-install.qs"
|
"qt-5-install.qs"
|
||||||
|
@ -400,9 +403,9 @@ echo
|
||||||
|
|
||||||
# Boost
|
# Boost
|
||||||
if [ -z $APPVEYOR ]; then
|
if [ -z $APPVEYOR ]; then
|
||||||
printf "Boost 1.67.0... "
|
printf "Boost 1.61.0... "
|
||||||
else
|
else
|
||||||
if [ $MSVC_VER -eq 12.0 ]; then
|
if [ $MSVC_VER -eq 12 ]; then
|
||||||
printf "Boost 1.58.0 AppVeyor... "
|
printf "Boost 1.58.0 AppVeyor... "
|
||||||
else
|
else
|
||||||
printf "Boost 1.67.0 AppVeyor... "
|
printf "Boost 1.67.0 AppVeyor... "
|
||||||
|
@ -414,28 +417,17 @@ fi
|
||||||
|
|
||||||
BOOST_SDK="$(real_pwd)/Boost"
|
BOOST_SDK="$(real_pwd)/Boost"
|
||||||
|
|
||||||
# Boost's installer is still based on ms-dos API that doesn't support larger than 260 char path names
|
if [ -d Boost ] && grep "BOOST_VERSION 106100" Boost/boost/version.hpp > /dev/null; then
|
||||||
# We work around this by installing to root of the current working drive and then move it to our deps
|
|
||||||
# get the current working drive's root, we'll install to that temporarily
|
|
||||||
CWD_DRIVE_ROOT="$(powershell -command '(get-location).Drive.Root')Boost_temp"
|
|
||||||
CWD_DRIVE_ROOT_BASH=$(echo "$CWD_DRIVE_ROOT" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,")
|
|
||||||
if [ -d CWD_DRIVE_ROOT_BASH ]; then
|
|
||||||
printf "Cannot continue, ${CWD_DRIVE_ROOT_BASH} aka ${CWD_DRIVE_ROOT} already exists. Please remove before re-running. ";
|
|
||||||
exit 1;
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d ${BOOST_SDK} ] && grep "BOOST_VERSION 106700" Boost/boost/version.hpp > /dev/null; then
|
|
||||||
printf "Exists. "
|
printf "Exists. "
|
||||||
elif [ -z $SKIP_EXTRACT ]; then
|
elif [ -z $SKIP_EXTRACT ]; then
|
||||||
rm -rf Boost
|
rm -rf Boost
|
||||||
CI_EXTRA_INNO_OPTIONS=""
|
"${DEPS}/boost-1.61.0-msvc${MSVC_YEAR}-win${BITS}.exe" //dir="$(echo $BOOST_SDK | sed s,/,\\\\,g)" //verysilent
|
||||||
[ -n "$CI" ] && CI_EXTRA_INNO_OPTIONS="//SUPPRESSMSGBOXES //LOG='boost_install.log'"
|
|
||||||
"${DEPS}/boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}" //VERYSILENT //NORESTART ${CI_EXTRA_INNO_OPTIONS}
|
|
||||||
mv "${CWD_DRIVE_ROOT_BASH}" "${BOOST_SDK}"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
|
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
|
||||||
-DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}"
|
-DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.0"
|
||||||
add_cmake_opts -DBoost_COMPILER="-${TOOLSET}"
|
add_cmake_opts -DBoost_COMPILER="-${TOOLSET}"
|
||||||
|
|
||||||
echo Done.
|
echo Done.
|
||||||
else
|
else
|
||||||
# Appveyor unstable has all the boost we need already
|
# Appveyor unstable has all the boost we need already
|
||||||
|
@ -452,17 +444,19 @@ fi
|
||||||
|
|
||||||
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
|
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
|
||||||
-DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.${LIB_SUFFIX}"
|
-DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.${LIB_SUFFIX}"
|
||||||
add_cmake_opts -DBoost_COMPILER="-${TOOLSET}"
|
add_cmake_opts -DBoost_COMPILER="-${TOOLSET_REAL}"
|
||||||
|
|
||||||
echo Done.
|
echo Done.
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
cd $DEPS
|
cd $DEPS
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# Bullet
|
# Bullet
|
||||||
printf "Bullet 2.86... "
|
printf "Bullet 2.86... "
|
||||||
{
|
{
|
||||||
cd $DEPS_INSTALL
|
cd $DEPS_INSTALL
|
||||||
|
|
||||||
if [ -d Bullet ]; then
|
if [ -d Bullet ]; then
|
||||||
printf -- "Exists. (No version checking) "
|
printf -- "Exists. (No version checking) "
|
||||||
elif [ -z $SKIP_EXTRACT ]; then
|
elif [ -z $SKIP_EXTRACT ]; then
|
||||||
|
@ -470,38 +464,49 @@ printf "Bullet 2.86... "
|
||||||
eval 7z x -y "${DEPS}/Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
|
eval 7z x -y "${DEPS}/Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
|
||||||
mv "Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}" Bullet
|
mv "Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}" Bullet
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export BULLET_ROOT="$(real_pwd)/Bullet"
|
export BULLET_ROOT="$(real_pwd)/Bullet"
|
||||||
|
|
||||||
echo Done.
|
echo Done.
|
||||||
}
|
}
|
||||||
cd $DEPS
|
cd $DEPS
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# FFmpeg
|
# FFmpeg
|
||||||
printf "FFmpeg 3.2.4... "
|
printf "FFmpeg 3.2.4... "
|
||||||
{
|
{
|
||||||
cd $DEPS_INSTALL
|
cd $DEPS_INSTALL
|
||||||
|
|
||||||
if [ -d FFmpeg ] && grep "FFmpeg version: 3.2.4" FFmpeg/README.txt > /dev/null; then
|
if [ -d FFmpeg ] && grep "FFmpeg version: 3.2.4" FFmpeg/README.txt > /dev/null; then
|
||||||
printf "Exists. "
|
printf "Exists. "
|
||||||
elif [ -z $SKIP_EXTRACT ]; then
|
elif [ -z $SKIP_EXTRACT ]; then
|
||||||
rm -rf FFmpeg
|
rm -rf FFmpeg
|
||||||
|
|
||||||
eval 7z x -y "${DEPS}/ffmpeg-3.2.4-win${BITS}.zip" $STRIP
|
eval 7z x -y "${DEPS}/ffmpeg-3.2.4-win${BITS}.zip" $STRIP
|
||||||
eval 7z x -y "${DEPS}/ffmpeg-3.2.4-dev-win${BITS}.zip" $STRIP
|
eval 7z x -y "${DEPS}/ffmpeg-3.2.4-dev-win${BITS}.zip" $STRIP
|
||||||
|
|
||||||
mv "ffmpeg-3.2.4-win${BITS}-shared" FFmpeg
|
mv "ffmpeg-3.2.4-win${BITS}-shared" FFmpeg
|
||||||
cp -r "ffmpeg-3.2.4-win${BITS}-dev/"* FFmpeg/
|
cp -r "ffmpeg-3.2.4-win${BITS}-dev/"* FFmpeg/
|
||||||
rm -rf "ffmpeg-3.2.4-win${BITS}-dev"
|
rm -rf "ffmpeg-3.2.4-win${BITS}-dev"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export FFMPEG_HOME="$(real_pwd)/FFmpeg"
|
export FFMPEG_HOME="$(real_pwd)/FFmpeg"
|
||||||
add_runtime_dlls "$(pwd)/FFmpeg/bin/"{avcodec-57,avformat-57,avutil-55,swresample-2,swscale-4}.dll
|
add_runtime_dlls "$(pwd)/FFmpeg/bin/"{avcodec-57,avformat-57,avutil-55,swresample-2,swscale-4}.dll
|
||||||
|
|
||||||
if [ $BITS -eq 32 ]; then
|
if [ $BITS -eq 32 ]; then
|
||||||
add_cmake_opts "-DCMAKE_EXE_LINKER_FLAGS=\"/machine:X86 /safeseh:no\""
|
add_cmake_opts "-DCMAKE_EXE_LINKER_FLAGS=\"/machine:X86 /safeseh:no\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo Done.
|
echo Done.
|
||||||
}
|
}
|
||||||
cd $DEPS
|
cd $DEPS
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# MyGUI
|
# MyGUI
|
||||||
printf "MyGUI 3.2.2... "
|
printf "MyGUI 3.2.2... "
|
||||||
{
|
{
|
||||||
cd $DEPS_INSTALL
|
cd $DEPS_INSTALL
|
||||||
|
|
||||||
if [ -d MyGUI ] && \
|
if [ -d MyGUI ] && \
|
||||||
grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \
|
grep "MYGUI_VERSION_MAJOR 3" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \
|
||||||
grep "MYGUI_VERSION_MINOR 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \
|
grep "MYGUI_VERSION_MINOR 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null && \
|
||||||
|
@ -513,17 +518,21 @@ printf "MyGUI 3.2.2... "
|
||||||
eval 7z x -y "${DEPS}/MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
|
eval 7z x -y "${DEPS}/MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
|
||||||
mv "MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}" MyGUI
|
mv "MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}" MyGUI
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export MYGUI_HOME="$(real_pwd)/MyGUI"
|
export MYGUI_HOME="$(real_pwd)/MyGUI"
|
||||||
|
|
||||||
if [ $CONFIGURATION == "Debug" ]; then
|
if [ $CONFIGURATION == "Debug" ]; then
|
||||||
SUFFIX="_d"
|
SUFFIX="_d"
|
||||||
else
|
else
|
||||||
SUFFIX=""
|
SUFFIX=""
|
||||||
fi
|
fi
|
||||||
add_runtime_dlls "$(pwd)/MyGUI/bin/${CONFIGURATION}/MyGUIEngine${SUFFIX}.dll"
|
add_runtime_dlls "$(pwd)/MyGUI/bin/${CONFIGURATION}/MyGUIEngine${SUFFIX}.dll"
|
||||||
|
|
||||||
echo Done.
|
echo Done.
|
||||||
}
|
}
|
||||||
cd $DEPS
|
cd $DEPS
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# OpenAL
|
# OpenAL
|
||||||
printf "OpenAL-Soft 1.17.2... "
|
printf "OpenAL-Soft 1.17.2... "
|
||||||
{
|
{
|
||||||
|
@ -533,18 +542,24 @@ printf "OpenAL-Soft 1.17.2... "
|
||||||
rm -rf openal-soft-1.17.2-bin
|
rm -rf openal-soft-1.17.2-bin
|
||||||
eval 7z x -y OpenAL-Soft-1.17.2.zip $STRIP
|
eval 7z x -y OpenAL-Soft-1.17.2.zip $STRIP
|
||||||
fi
|
fi
|
||||||
|
|
||||||
OPENAL_SDK="$(real_pwd)/openal-soft-1.17.2-bin"
|
OPENAL_SDK="$(real_pwd)/openal-soft-1.17.2-bin"
|
||||||
|
|
||||||
add_cmake_opts -DOPENAL_INCLUDE_DIR="${OPENAL_SDK}/include/AL" \
|
add_cmake_opts -DOPENAL_INCLUDE_DIR="${OPENAL_SDK}/include/AL" \
|
||||||
-DOPENAL_LIBRARY="${OPENAL_SDK}/libs/Win${BITS}/OpenAL32.lib"
|
-DOPENAL_LIBRARY="${OPENAL_SDK}/libs/Win${BITS}/OpenAL32.lib"
|
||||||
|
|
||||||
add_runtime_dlls "$(pwd)/openal-soft-1.17.2-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll"
|
add_runtime_dlls "$(pwd)/openal-soft-1.17.2-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll"
|
||||||
|
|
||||||
echo Done.
|
echo Done.
|
||||||
}
|
}
|
||||||
cd $DEPS
|
cd $DEPS
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# OSG
|
# OSG
|
||||||
printf "OSG 3.4.1-scrawl... "
|
printf "OSG 3.4.1-scrawl... "
|
||||||
{
|
{
|
||||||
cd $DEPS_INSTALL
|
cd $DEPS_INSTALL
|
||||||
|
|
||||||
if [ -d OSG ] && \
|
if [ -d OSG ] && \
|
||||||
grep "OPENSCENEGRAPH_MAJOR_VERSION 3" OSG/include/osg/Version > /dev/null && \
|
grep "OPENSCENEGRAPH_MAJOR_VERSION 3" OSG/include/osg/Version > /dev/null && \
|
||||||
grep "OPENSCENEGRAPH_MINOR_VERSION 4" OSG/include/osg/Version > /dev/null && \
|
grep "OPENSCENEGRAPH_MINOR_VERSION 4" OSG/include/osg/Version > /dev/null && \
|
||||||
|
@ -556,21 +571,28 @@ printf "OSG 3.4.1-scrawl... "
|
||||||
eval 7z x -y "${DEPS}/OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
|
eval 7z x -y "${DEPS}/OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
|
||||||
mv "OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}" OSG
|
mv "OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}" OSG
|
||||||
fi
|
fi
|
||||||
|
|
||||||
OSG_SDK="$(real_pwd)/OSG"
|
OSG_SDK="$(real_pwd)/OSG"
|
||||||
|
|
||||||
add_cmake_opts -DOSG_DIR="$OSG_SDK"
|
add_cmake_opts -DOSG_DIR="$OSG_SDK"
|
||||||
|
|
||||||
if [ $CONFIGURATION == "Debug" ]; then
|
if [ $CONFIGURATION == "Debug" ]; then
|
||||||
SUFFIX="d"
|
SUFFIX="d"
|
||||||
else
|
else
|
||||||
SUFFIX=""
|
SUFFIX=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
add_runtime_dlls "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng*}${SUFFIX}.dll \
|
add_runtime_dlls "$(pwd)/OSG/bin/"{OpenThreads,zlib,libpng*}${SUFFIX}.dll \
|
||||||
"$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer}${SUFFIX}.dll
|
"$(pwd)/OSG/bin/osg"{,Animation,DB,FX,GA,Particle,Text,Util,Viewer}${SUFFIX}.dll
|
||||||
|
|
||||||
add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_"{bmp,dds,jpeg,osg,png,tga}${SUFFIX}.dll
|
add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_"{bmp,dds,jpeg,osg,png,tga}${SUFFIX}.dll
|
||||||
add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer}${SUFFIX}.dll
|
add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.1/osgdb_serializers_osg"{,animation,fx,ga,particle,text,util,viewer}${SUFFIX}.dll
|
||||||
|
|
||||||
echo Done.
|
echo Done.
|
||||||
}
|
}
|
||||||
cd $DEPS
|
cd $DEPS
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# Qt
|
# Qt
|
||||||
if [ -z $APPVEYOR ]; then
|
if [ -z $APPVEYOR ]; then
|
||||||
printf "Qt 5.7.0... "
|
printf "Qt 5.7.0... "
|
||||||
|
@ -583,53 +605,71 @@ fi
|
||||||
else
|
else
|
||||||
SUFFIX=""
|
SUFFIX=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z $APPVEYOR ]; then
|
if [ -z $APPVEYOR ]; then
|
||||||
cd $DEPS_INSTALL
|
cd $DEPS_INSTALL
|
||||||
QT_SDK="$(real_pwd)/Qt/5.7/msvc${MSVC_YEAR}${SUFFIX}"
|
QT_SDK="$(real_pwd)/Qt/5.7/msvc${MSVC_YEAR}${SUFFIX}"
|
||||||
|
|
||||||
if [ -d Qt ] && head -n2 Qt/InstallationLog.txt | grep "5.7.0" > /dev/null; then
|
if [ -d Qt ] && head -n2 Qt/InstallationLog.txt | grep "5.7.0" > /dev/null; then
|
||||||
printf "Exists. "
|
printf "Exists. "
|
||||||
elif [ -z $SKIP_EXTRACT ]; then
|
elif [ -z $SKIP_EXTRACT ]; then
|
||||||
rm -rf Qt
|
rm -rf Qt
|
||||||
cp "${DEPS}/qt-5-install.qs" qt-install.qs
|
cp "${DEPS}/qt-5-install.qs" qt-install.qs
|
||||||
|
|
||||||
|
|
||||||
sed -i "s|INSTALL_DIR|$(real_pwd)/Qt|" qt-install.qs
|
sed -i "s|INSTALL_DIR|$(real_pwd)/Qt|" qt-install.qs
|
||||||
sed -i "s/qt.VERSION.winBITS_msvcYEAR/qt.57.win${BITS}_msvc${MSVC_YEAR}${SUFFIX}/" qt-install.qs
|
sed -i "s/qt.VERSION.winBITS_msvcYEAR/qt.57.win${BITS}_msvc${MSVC_YEAR}${SUFFIX}/" qt-install.qs
|
||||||
|
|
||||||
printf -- "(Installation might take a while) "
|
printf -- "(Installation might take a while) "
|
||||||
"${DEPS}/qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" --script qt-install.qs --silent
|
"${DEPS}/qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" --script qt-install.qs --silent
|
||||||
|
|
||||||
mv qt-install.qs Qt/
|
mv qt-install.qs Qt/
|
||||||
|
|
||||||
echo Done.
|
echo Done.
|
||||||
printf " Cleaning up extraneous data... "
|
printf " Cleaning up extraneous data... "
|
||||||
rm -r "$(real_pwd)/Qt/"{dist,Docs,Examples,Tools,vcredist,components.xml,MaintenanceTool.dat,MaintenanceTool.exe,MaintenanceTool.ini,network.xml,qt-install.qs}
|
rm -r "$(real_pwd)/Qt/"{dist,Docs,Examples,Tools,vcredist,components.xml,MaintenanceTool.dat,MaintenanceTool.exe,MaintenanceTool.ini,network.xml,qt-install.qs}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd $QT_SDK
|
cd $QT_SDK
|
||||||
|
|
||||||
add_cmake_opts -DDESIRED_QT_VERSION=5 \
|
add_cmake_opts -DDESIRED_QT_VERSION=5 \
|
||||||
-DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
|
-DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
|
||||||
-DCMAKE_PREFIX_PATH="$QT_SDK"
|
-DCMAKE_PREFIX_PATH="$QT_SDK"
|
||||||
|
|
||||||
if [ $CONFIGURATION == "Debug" ]; then
|
if [ $CONFIGURATION == "Debug" ]; then
|
||||||
SUFFIX="d"
|
SUFFIX="d"
|
||||||
else
|
else
|
||||||
SUFFIX=""
|
SUFFIX=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
add_runtime_dlls "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
|
add_runtime_dlls "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
|
||||||
add_qt_platform_dlls "$(pwd)/plugins/platforms/qwindows${SUFFIX}.dll"
|
add_qt_platform_dlls "$(pwd)/plugins/platforms/qwindows${SUFFIX}.dll"
|
||||||
|
|
||||||
echo Done.
|
echo Done.
|
||||||
else
|
else
|
||||||
QT_SDK="C:/Qt/5.10/msvc${MSVC_DISPLAY_YEAR}${SUFFIX}"
|
QT_SDK="C:/Qt/5.10/msvc${MSVC_DISPLAY_YEAR}${SUFFIX}"
|
||||||
|
|
||||||
add_cmake_opts -DDESIRED_QT_VERSION=5 \
|
add_cmake_opts -DDESIRED_QT_VERSION=5 \
|
||||||
-DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
|
-DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
|
||||||
-DCMAKE_PREFIX_PATH="$QT_SDK"
|
-DCMAKE_PREFIX_PATH="$QT_SDK"
|
||||||
|
|
||||||
if [ $CONFIGURATION == "Debug" ]; then
|
if [ $CONFIGURATION == "Debug" ]; then
|
||||||
SUFFIX="d"
|
SUFFIX="d"
|
||||||
else
|
else
|
||||||
SUFFIX=""
|
SUFFIX=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DIR=$(echo "${QT_SDK}" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,")
|
DIR=$(echo "${QT_SDK}" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,")
|
||||||
|
|
||||||
add_runtime_dlls "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
|
add_runtime_dlls "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
|
||||||
add_qt_platform_dlls "${DIR}/plugins/platforms/qwindows${SUFFIX}.dll"
|
add_qt_platform_dlls "${DIR}/plugins/platforms/qwindows${SUFFIX}.dll"
|
||||||
|
|
||||||
echo Done.
|
echo Done.
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
cd $DEPS
|
cd $DEPS
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# SDL2
|
# SDL2
|
||||||
printf "SDL 2.0.7... "
|
printf "SDL 2.0.7... "
|
||||||
{
|
{
|
||||||
|
@ -639,18 +679,26 @@ printf "SDL 2.0.7... "
|
||||||
rm -rf SDL2-2.0.7
|
rm -rf SDL2-2.0.7
|
||||||
eval 7z x -y SDL2-2.0.7.zip $STRIP
|
eval 7z x -y SDL2-2.0.7.zip $STRIP
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export SDL2DIR="$(real_pwd)/SDL2-2.0.7"
|
export SDL2DIR="$(real_pwd)/SDL2-2.0.7"
|
||||||
|
|
||||||
add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll"
|
add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll"
|
||||||
|
|
||||||
echo Done.
|
echo Done.
|
||||||
}
|
}
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
|
||||||
cd $DEPS_INSTALL/..
|
cd $DEPS_INSTALL/..
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Setting up OpenMW build..."
|
echo "Setting up OpenMW build..."
|
||||||
|
|
||||||
add_cmake_opts -DBUILD_BSATOOL=no \
|
add_cmake_opts -DBUILD_BSATOOL=no \
|
||||||
-DBUILD_ESMTOOL=no \
|
-DBUILD_ESMTOOL=no \
|
||||||
-DBUILD_MYGUI_PLUGIN=no \
|
-DBUILD_MYGUI_PLUGIN=no \
|
||||||
-DOPENMW_MP_BUILD=on
|
-DOPENMW_MP_BUILD=on
|
||||||
|
|
||||||
if [ ! -z $CI ]; then
|
if [ ! -z $CI ]; then
|
||||||
case $STEP in
|
case $STEP in
|
||||||
components )
|
components )
|
||||||
|
@ -662,6 +710,7 @@ if [ ! -z $CI ]; then
|
||||||
-DBUILD_OPENMW=no \
|
-DBUILD_OPENMW=no \
|
||||||
-DBUILD_WIZARD=no
|
-DBUILD_WIZARD=no
|
||||||
;;
|
;;
|
||||||
|
|
||||||
openmw )
|
openmw )
|
||||||
echo " Building subproject: OpenMW."
|
echo " Building subproject: OpenMW."
|
||||||
add_cmake_opts -DBUILD_ESSIMPORTER=no \
|
add_cmake_opts -DBUILD_ESSIMPORTER=no \
|
||||||
|
@ -670,6 +719,7 @@ if [ ! -z $CI ]; then
|
||||||
-DBUILD_OPENCS=no \
|
-DBUILD_OPENCS=no \
|
||||||
-DBUILD_WIZARD=no
|
-DBUILD_WIZARD=no
|
||||||
;;
|
;;
|
||||||
|
|
||||||
opencs )
|
opencs )
|
||||||
echo " Building subproject: OpenCS."
|
echo " Building subproject: OpenCS."
|
||||||
add_cmake_opts -DBUILD_ESSIMPORTER=no \
|
add_cmake_opts -DBUILD_ESSIMPORTER=no \
|
||||||
|
@ -678,6 +728,7 @@ if [ ! -z $CI ]; then
|
||||||
-DBUILD_OPENMW=no \
|
-DBUILD_OPENMW=no \
|
||||||
-DBUILD_WIZARD=no
|
-DBUILD_WIZARD=no
|
||||||
;;
|
;;
|
||||||
|
|
||||||
misc )
|
misc )
|
||||||
echo " Building subprojects: Misc."
|
echo " Building subprojects: Misc."
|
||||||
add_cmake_opts -DBUILD_OPENCS=no \
|
add_cmake_opts -DBUILD_OPENCS=no \
|
||||||
|
@ -685,6 +736,7 @@ if [ ! -z $CI ]; then
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# NOTE: Disable this when/if we want to run test cases
|
# NOTE: Disable this when/if we want to run test cases
|
||||||
#if [ -z $CI ]; then
|
#if [ -z $CI ]; then
|
||||||
echo "- Copying Runtime DLLs..."
|
echo "- Copying Runtime DLLs..."
|
||||||
|
@ -693,13 +745,16 @@ fi
|
||||||
TARGET="$(basename "$DLL")"
|
TARGET="$(basename "$DLL")"
|
||||||
if [[ "$DLL" == *":"* ]]; then
|
if [[ "$DLL" == *":"* ]]; then
|
||||||
IFS=':'; SPLIT=( ${DLL} ); unset IFS
|
IFS=':'; SPLIT=( ${DLL} ); unset IFS
|
||||||
|
|
||||||
DLL=${SPLIT[0]}
|
DLL=${SPLIT[0]}
|
||||||
TARGET=${SPLIT[1]}
|
TARGET=${SPLIT[1]}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo " ${TARGET}."
|
echo " ${TARGET}."
|
||||||
cp "$DLL" "$BUILD_CONFIG/$TARGET"
|
cp "$DLL" "$BUILD_CONFIG/$TARGET"
|
||||||
done
|
done
|
||||||
echo
|
echo
|
||||||
|
|
||||||
echo "- OSG Plugin DLLs..."
|
echo "- OSG Plugin DLLs..."
|
||||||
mkdir -p $BUILD_CONFIG/osgPlugins-3.4.1
|
mkdir -p $BUILD_CONFIG/osgPlugins-3.4.1
|
||||||
for DLL in $OSG_PLUGINS; do
|
for DLL in $OSG_PLUGINS; do
|
||||||
|
@ -707,6 +762,7 @@ fi
|
||||||
cp "$DLL" $BUILD_CONFIG/osgPlugins-3.4.1
|
cp "$DLL" $BUILD_CONFIG/osgPlugins-3.4.1
|
||||||
done
|
done
|
||||||
echo
|
echo
|
||||||
|
|
||||||
echo "- Qt Platform DLLs..."
|
echo "- Qt Platform DLLs..."
|
||||||
mkdir -p ${BUILD_CONFIG}/platforms
|
mkdir -p ${BUILD_CONFIG}/platforms
|
||||||
for DLL in $QT_PLATFORMS; do
|
for DLL in $QT_PLATFORMS; do
|
||||||
|
@ -715,13 +771,16 @@ fi
|
||||||
done
|
done
|
||||||
echo
|
echo
|
||||||
#fi
|
#fi
|
||||||
|
|
||||||
if [ -z $VERBOSE ]; then
|
if [ -z $VERBOSE ]; then
|
||||||
printf -- "- Configuring... "
|
printf -- "- Configuring... "
|
||||||
else
|
else
|
||||||
echo "- cmake .. $CMAKE_OPTS"
|
echo "- cmake .. $CMAKE_OPTS"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
run_cmd cmake .. $CMAKE_OPTS
|
run_cmd cmake .. $CMAKE_OPTS
|
||||||
RET=$?
|
RET=$?
|
||||||
|
|
||||||
if [ -z $VERBOSE ]; then
|
if [ -z $VERBOSE ]; then
|
||||||
if [ $RET -eq 0 ]; then
|
if [ $RET -eq 0 ]; then
|
||||||
echo Done.
|
echo Done.
|
||||||
|
@ -729,4 +788,5 @@ if [ -z $VERBOSE ]; then
|
||||||
echo Failed.
|
echo Failed.
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exit $RET
|
exit $RET
|
||||||
|
|
|
@ -4,14 +4,14 @@ export CXX=clang++
|
||||||
export CC=clang
|
export CC=clang
|
||||||
|
|
||||||
DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps"
|
DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps"
|
||||||
QT_PATH=`brew --prefix qt`
|
QT_PATH=`brew --prefix $macos_qt_formula`
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
|
|
||||||
cmake \
|
cmake \
|
||||||
-D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \
|
-D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \
|
||||||
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.9" \
|
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.9" \
|
||||||
-D CMAKE_OSX_SYSROOT="macosx10.13" \
|
-D CMAKE_OSX_SYSROOT="macosx10.12" \
|
||||||
-D CMAKE_BUILD_TYPE=Release \
|
-D CMAKE_BUILD_TYPE=Release \
|
||||||
-D OPENMW_OSX_DEPLOYMENT=TRUE \
|
-D OPENMW_OSX_DEPLOYMENT=TRUE \
|
||||||
-D DESIRED_QT_VERSION=5 \
|
-D DESIRED_QT_VERSION=5 \
|
||||||
|
|
|
@ -140,6 +140,16 @@ endif()
|
||||||
find_package(RakNet REQUIRED)
|
find_package(RakNet REQUIRED)
|
||||||
include_directories(${RakNet_INCLUDES})
|
include_directories(${RakNet_INCLUDES})
|
||||||
|
|
||||||
|
if (BUILD_OPENMW_MP OR BUILD_MASTER)
|
||||||
|
find_package(LuaJit REQUIRED)
|
||||||
|
find_package(Sol2 REQUIRED)
|
||||||
|
include_directories(${LuaJit_INCLUDE_DIRS} ${Sol2_INCLUDE_DIRS})
|
||||||
|
endif()
|
||||||
|
if (BUILD_MASTER)
|
||||||
|
find_package(OpenSSL REQUIRED)
|
||||||
|
include_directories(${OpenSSL_INCLUDE_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
find_package(OpenGL REQUIRED)
|
find_package(OpenGL REQUIRED)
|
||||||
|
|
||||||
|
@ -209,7 +219,6 @@ endif()
|
||||||
IF(BUILD_OPENMW OR BUILD_OPENCS)
|
IF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||||
|
|
||||||
find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX)
|
find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX)
|
||||||
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
|
|
||||||
|
|
||||||
set(USED_OSG_PLUGINS
|
set(USED_OSG_PLUGINS
|
||||||
osgdb_bmp
|
osgdb_bmp
|
||||||
|
@ -250,10 +259,11 @@ IF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||||
find_package(SDL2 REQUIRED)
|
find_package(SDL2 REQUIRED)
|
||||||
find_package(OpenAL REQUIRED)
|
find_package(OpenAL REQUIRED)
|
||||||
find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath)
|
find_package(Bullet ${REQUIRED_BULLET_VERSION} REQUIRED COMPONENTS BulletCollision LinearMath)
|
||||||
ELSE()
|
|
||||||
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) # HACK: DO NOT MOVE THIS. Used for server only build, kept here to avoid merge conflicts above.
|
|
||||||
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
|
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||||
|
|
||||||
|
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) # HACK: DON'T TOUCH IT!
|
||||||
|
|
||||||
|
|
||||||
set(BOOST_COMPONENTS system filesystem program_options)
|
set(BOOST_COMPONENTS system filesystem program_options)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
@ -666,10 +676,10 @@ if (WIN32)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (BUILD_OPENMW)
|
if (BUILD_OPENMW)
|
||||||
# Release builds don't use the debug console
|
# Release builds use the debug console
|
||||||
set_target_properties(tes3mp PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
|
set_target_properties(tes3mp PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:CONSOLE")
|
||||||
set_target_properties(tes3mp PROPERTIES COMPILE_DEFINITIONS_RELEASE "_WINDOWS")
|
set_target_properties(tes3mp PROPERTIES COMPILE_DEFINITIONS_RELEASE "_CONSOLE")
|
||||||
set_target_properties(tes3mp PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
|
set_target_properties(tes3mp PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:CONSOLE")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Play a bit with the warning levels
|
# Play a bit with the warning levels
|
||||||
|
@ -680,8 +690,7 @@ if (WIN32)
|
||||||
# Warnings that aren't enabled normally and don't need to be enabled
|
# Warnings that aren't enabled normally and don't need to be enabled
|
||||||
# They're unneeded and sometimes completely retarded warnings that /Wall enables
|
# They're unneeded and sometimes completely retarded warnings that /Wall enables
|
||||||
# Not going to bother commenting them as they tend to warn on every standard library file
|
# Not going to bother commenting them as they tend to warn on every standard library file
|
||||||
4061 4263 4264 4266 4350 4371 4435 4514 4548 4571 4610 4619 4623 4625
|
4061 4263 4264 4266 4350 4371 4435 4514 4548 4571 4610 4619 4623 4625 4626 4628 4640 4668 4710 4711 4820 4826 4917 4946
|
||||||
4626 4628 4640 4668 4710 4711 4768 4820 4826 4917 4946 5032 5039 5045
|
|
||||||
|
|
||||||
# Warnings that are thrown on standard libraries and not OpenMW
|
# Warnings that are thrown on standard libraries and not OpenMW
|
||||||
4347 # Non-template function with same name and parameter count as template function
|
4347 # Non-template function with same name and parameter count as template function
|
||||||
|
@ -702,7 +711,6 @@ if (WIN32)
|
||||||
|
|
||||||
# caused by MyGUI
|
# caused by MyGUI
|
||||||
4275 # non dll-interface class 'std::exception' used as base for dll-interface class 'MyGUI::Exception'
|
4275 # non dll-interface class 'std::exception' used as base for dll-interface class 'MyGUI::Exception'
|
||||||
4297 # function assumed not to throw an exception but does
|
|
||||||
|
|
||||||
# OpenMW specific warnings
|
# OpenMW specific warnings
|
||||||
4099 # Type mismatch, declared class or struct is defined with other type
|
4099 # Type mismatch, declared class or struct is defined with other type
|
||||||
|
@ -736,10 +744,7 @@ if (WIN32)
|
||||||
endforeach(d)
|
endforeach(d)
|
||||||
|
|
||||||
set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
|
set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
|
||||||
|
set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
|
||||||
if (BUILD_OPENMW)
|
|
||||||
set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (BUILD_BSATOOL)
|
if (BUILD_BSATOOL)
|
||||||
set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
|
set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}")
|
||||||
|
|
|
@ -3,7 +3,7 @@ How to contribute to OpenMW
|
||||||
|
|
||||||
Not sure what to do with all your free time? Pick out a task from here:
|
Not sure what to do with all your free time? Pick out a task from here:
|
||||||
|
|
||||||
https://gitlab.com/OpenMW/openmw/issues
|
https://bugs.openmw.org/
|
||||||
|
|
||||||
Currently, we are focused on completing the MW game experience and general polishing. Features out of this scope may be approved in some cases, but you should probably start a discussion first.
|
Currently, we are focused on completing the MW game experience and general polishing. Features out of this scope may be approved in some cases, but you should probably start a discussion first.
|
||||||
|
|
||||||
|
|
34
README.md
34
README.md
|
@ -1,49 +1,47 @@
|
||||||
TES3MP
|
TES3MP
|
||||||
======
|
======
|
||||||
|
|
||||||
Copyright (c) 2008-2015, OpenMW Team
|
[](https://travis-ci.org/TES3MP/openmw-tes3mp)
|
||||||
Copyright (c) 2016-2019, Stanislav Zhukov & David Cernat
|
|
||||||
|
|
||||||
[](https://travis-ci.org/TES3MP/openmw-tes3mp)
|
TES3MP is a project aiming to add multiplayer functionality to [OpenMW](https://github.com/OpenMW/openmw), a free and open source engine recreation of the popular Bethesda Softworks game "The Elder Scrolls III: Morrowind".
|
||||||
|
|
||||||
TES3MP is a project adding multiplayer functionality to [OpenMW](https://github.com/OpenMW/openmw), an open-source game engine that supports playing "The Elder Scrolls III: Morrowind" by Bethesda Softworks.
|
* TES3MP version: TBD-rewrite
|
||||||
|
|
||||||
* TES3MP version: 0.7.0-alpha
|
|
||||||
* OpenMW version: 0.44.0
|
* OpenMW version: 0.44.0
|
||||||
* License: GPLv3 (see [LICENSE](https://github.com/TES3MP/openmw-tes3mp/blob/master/LICENSE) for more information)
|
* License: GPLv3 (see [LICENSE](https://github.com/TES3MP/openmw-tes3mp/blob/master/LICENSE) for more information)
|
||||||
|
|
||||||
Font Licenses:
|
Font Licenses:
|
||||||
* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVu Font License.txt](https://github.com/TES3MP/openmw-tes3mp/blob/master/files/mygui/DejaVu%20Font%20License.txt) for more information)
|
* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVu Font License.txt](https://github.com/TES3MP/openmw-tes3mp/blob/master/files/mygui/DejaVu%20Font%20License.txt) for more information)
|
||||||
|
|
||||||
Project status
|
Project Status
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
[Version changelog](https://github.com/TES3MP/openmw-tes3mp/blob/master/tes3mp-changelog.md)
|
[Version changelog](https://github.com/TES3MP/openmw-tes3mp/blob/master/tes3mp-changelog.md)
|
||||||
|
|
||||||
As of version 0.7.0, TES3MP is fully playable, providing very extensive player, NPC, world and quest synchronization, as well as state saving and loading, all of which are highly customizable via [serverside Lua scripts](https://github.com/TES3MP/CoreScripts).
|
As of version 0.6, TES3MP is playable in most respects. Player and NPC movement, animations, combat and spell casting are properly synchronized with small exceptions, as is picking up and dropping items in the world, using doors and levers, and adding and removing items from containers. Journal entries, faction stats and dialogue topics are also synchronized, allowing the majority of quests to work fine.
|
||||||
|
|
||||||
Remaining gameplay problems mostly relate to AI and the synchronization of clientside script variables.
|
[Serverside Lua scripts](https://github.com/TES3MP/CoreScripts) are used to save and load the state of most of the aforementioned.
|
||||||
|
|
||||||
Donations
|
Please note that the master branch is an unfinished rewrite of a large part of the project and is not yet playable. You should use the [latest 0.6 release](https://github.com/TES3MP/openmw-tes3mp/releases/latest) for now.
|
||||||
---------------
|
|
||||||
|
|
||||||
You can benefit the project by donating on Patreon to our two developers, [David Cernat](https://www.patreon.com/davidcernat) and [Koncord](https://www.patreon.com/Koncord), as well as by supporting [OpenMW](https://openmw.org).
|
|
||||||
|
|
||||||
Contributing
|
Contributing
|
||||||
---------------
|
--------------
|
||||||
|
|
||||||
Helping us with documentation, bug hunting and video showcases is always greatly appreciated.
|
Development has been relatively fast, but any contribution regarding [code](https://github.com/TES3MP/openmw-tes3mp/blob/master/CONTRIBUTING.md), documentation, bug hunting or video showcases is greatly appreciated.
|
||||||
|
|
||||||
For code contributions, it's best to start out with modestly sized fixes and features and work your way up. There are so many different possible implementations of more major features – many of which would cause undesirable code or vision conflicts with OpenMW – that those should be talked over in advance with the existing developers before effort is spent on them.
|
Test sessions are often advertised on [our Discord server](https://discord.gg/ECJk293) or in [our Steam group](https://steamcommunity.com/groups/mwmulti).
|
||||||
|
|
||||||
Feel free to contact the [team members](https://github.com/TES3MP/openmw-tes3mp/blob/master/tes3mp-credits.md) for any questions you might have.
|
Feel free to contact the [team members](https://github.com/TES3MP/openmw-tes3mp/blob/master/tes3mp-credits.md) for any questions you might have.
|
||||||
|
|
||||||
Getting started
|
Getting Started
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
* [Quickstart guide](https://github.com/TES3MP/openmw-tes3mp/wiki/Quickstart-guide)
|
* [Quickstart guide](https://github.com/TES3MP/openmw-tes3mp/wiki/Quickstart-guide)
|
||||||
* [Steam group](https://steamcommunity.com/groups/mwmulti) and its [detailed FAQ](https://steamcommunity.com/groups/mwmulti/discussions/1/353916184342480541/)
|
* [Steam group](https://steamcommunity.com/groups/mwmulti) and its [detailed FAQ](https://steamcommunity.com/groups/mwmulti/discussions/1/353916184342480541/)
|
||||||
* [TES3MP section on OpenMW forums](https://forum.openmw.org/viewforum.php?f=45)
|
* [TES3MP section on OpenMW forums](https://forum.openmw.org/viewforum.php?f=45)
|
||||||
* [Discord server](https://discord.gg/ECJk293)
|
|
||||||
* [Subreddit](https://www.reddit.com/r/tes3mp)
|
* [Subreddit](https://www.reddit.com/r/tes3mp)
|
||||||
* [Known issues and bug reports](https://github.com/TES3MP/openmw-tes3mp/issues)
|
* [Known issues and bug reports](https://github.com/TES3MP/openmw-tes3mp/issues)
|
||||||
|
|
||||||
|
Donations
|
||||||
|
---------------
|
||||||
|
|
||||||
|
You can benefit the project by contributing to the Patreon pages of our two developers, [Koncord](https://www.patreon.com/Koncord) and [David Cernat](https://www.patreon.com/davidcernat), as well as by supporting [OpenMW](https://openmw.org).
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
option(OPTION_TES3MP_PRE07 "Temporary. Pre 0.7.0 compatible mode." OFF)
|
||||||
set (CMAKE_CXX_STANDARD 14)
|
|
||||||
|
|
||||||
set(BROWSER_UI
|
set(BROWSER_UI
|
||||||
${CMAKE_SOURCE_DIR}/files/tes3mp/ui/Main.ui
|
${CMAKE_SOURCE_DIR}/files/tes3mp/ui/Main.ui
|
||||||
|
@ -17,6 +16,7 @@ set(BROWSER
|
||||||
PingUpdater.cpp
|
PingUpdater.cpp
|
||||||
PingHelper.cpp
|
PingHelper.cpp
|
||||||
QueryHelper.cpp
|
QueryHelper.cpp
|
||||||
|
Settings.cpp
|
||||||
${CMAKE_SOURCE_DIR}/files/tes3mp/browser.rc
|
${CMAKE_SOURCE_DIR}/files/tes3mp/browser.rc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ set(BROWSER_HEADER
|
||||||
netutils/Utils.hpp
|
netutils/Utils.hpp
|
||||||
netutils/QueryClient.hpp
|
netutils/QueryClient.hpp
|
||||||
Types.hpp
|
Types.hpp
|
||||||
|
Settings.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
source_group(browser FILES ${BROWSER} ${BROWSER_HEADER})
|
source_group(browser FILES ${BROWSER} ${BROWSER_HEADER})
|
||||||
|
@ -75,6 +76,13 @@ add_executable(tes3mp-browser
|
||||||
${UI_HDRS}
|
${UI_HDRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set_property(TARGET tes3mp-browser PROPERTY CXX_STANDARD 14)
|
||||||
|
|
||||||
|
if (OPTION_TES3MP_PRE07)
|
||||||
|
target_compile_definitions(tes3mp-browser PRIVATE TES3MP_PRE07)
|
||||||
|
endif (OPTION_TES3MP_PRE07)
|
||||||
|
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
INSTALL(TARGETS tes3mp-browser RUNTIME DESTINATION ".")
|
INSTALL(TARGETS tes3mp-browser RUNTIME DESTINATION ".")
|
||||||
endif (WIN32)
|
endif (WIN32)
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
#include <apps/browser/netutils/Utils.hpp>
|
||||||
|
#include <QtWidgets/QFileDialog>
|
||||||
|
|
||||||
|
|
||||||
using namespace Process;
|
using namespace Process;
|
||||||
|
@ -31,9 +33,6 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
tblServerBrowser->setModel(proxyModel);
|
tblServerBrowser->setModel(proxyModel);
|
||||||
tblFavorites->setModel(proxyModel);
|
tblFavorites->setModel(proxyModel);
|
||||||
|
|
||||||
// Remove Favorites tab while it remains broken
|
|
||||||
tabWidget->removeTab(1);
|
|
||||||
|
|
||||||
tblServerBrowser->hideColumn(ServerData::ADDR);
|
tblServerBrowser->hideColumn(ServerData::ADDR);
|
||||||
tblFavorites->hideColumn(ServerData::ADDR);
|
tblFavorites->hideColumn(ServerData::ADDR);
|
||||||
|
|
||||||
|
@ -57,12 +56,31 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
connect(cBBoxWOPass, SIGNAL(toggled(bool)), this, SLOT(noPasswordSwitch(bool)));
|
connect(cBBoxWOPass, SIGNAL(toggled(bool)), this, SLOT(noPasswordSwitch(bool)));
|
||||||
connect(comboLatency, SIGNAL(currentIndexChanged(int)), this, SLOT(maxLatencyChanged(int)));
|
connect(comboLatency, SIGNAL(currentIndexChanged(int)), this, SLOT(maxLatencyChanged(int)));
|
||||||
connect(leGamemode, SIGNAL(textChanged(const QString &)), this, SLOT(gamemodeChanged(const QString &)));
|
connect(leGamemode, SIGNAL(textChanged(const QString &)), this, SLOT(gamemodeChanged(const QString &)));
|
||||||
|
connect(pbModulePath, &QPushButton::clicked, [this](bool) {
|
||||||
|
QString str = QFileDialog::getExistingDirectory(this, tr("Module path"),
|
||||||
|
leModulePath->text(), QFileDialog::ShowDirsOnly);
|
||||||
|
if(!str.isEmpty())
|
||||||
|
leModulePath->setText(str);
|
||||||
|
});
|
||||||
loadFavorites();
|
loadFavorites();
|
||||||
queryHelper->refresh();
|
queryHelper->refresh();
|
||||||
|
settingsMgr.loadBrowserSettings(*this);
|
||||||
|
|
||||||
|
#ifdef TES3MP_PRE07
|
||||||
|
gbModules->setTitle(tr("Plugins Path"));
|
||||||
|
chbAutosort->setVisible(false);
|
||||||
|
listModules->setVisible(false);
|
||||||
|
leAddModule->setVisible(false);
|
||||||
|
pbAddModule->setVisible(false);
|
||||||
|
pbUpModule->setVisible(false);
|
||||||
|
pbDownModule->setVisible(false);
|
||||||
|
pbRemModule->setVisible(false);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
{
|
{
|
||||||
|
settingsMgr.saveBrowserSettings(*this);
|
||||||
delete mGameInvoker;
|
delete mGameInvoker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +89,9 @@ void MainWindow::addServerAndUpdate(const QString &addr)
|
||||||
favorites->insertRow(0);
|
favorites->insertRow(0);
|
||||||
QModelIndex mi = favorites->index(0, ServerData::ADDR);
|
QModelIndex mi = favorites->index(0, ServerData::ADDR);
|
||||||
favorites->setData(mi, addr, Qt::EditRole);
|
favorites->setData(mi, addr, Qt::EditRole);
|
||||||
|
/*auto address = addr.split(":");
|
||||||
|
auto data = getExtendedData(address[0].toLatin1(), address[1].toUShort());*/
|
||||||
|
|
||||||
//NetController::get()->updateInfo(favorites, mi);
|
//NetController::get()->updateInfo(favorites, mi);
|
||||||
//QueryClient::Update(RakNet::SystemAddress())
|
//QueryClient::Update(RakNet::SystemAddress())
|
||||||
/*auto data = QueryClient::Get().Query();
|
/*auto data = QueryClient::Get().Query();
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "ui_Main.h"
|
#include "ui_Main.h"
|
||||||
#include "ServerModel.hpp"
|
#include "ServerModel.hpp"
|
||||||
#include "MySortFilterProxyModel.hpp"
|
#include "MySortFilterProxyModel.hpp"
|
||||||
|
#include "Settings.hpp"
|
||||||
#include <components/process/processinvoker.hpp>
|
#include <components/process/processinvoker.hpp>
|
||||||
|
|
||||||
class QueryHelper;
|
class QueryHelper;
|
||||||
|
@ -39,6 +40,7 @@ private:
|
||||||
Process::ProcessInvoker *mGameInvoker;
|
Process::ProcessInvoker *mGameInvoker;
|
||||||
ServerModel *browser, *favorites;
|
ServerModel *browser, *favorites;
|
||||||
MySortFilterProxyModel *proxyModel;
|
MySortFilterProxyModel *proxyModel;
|
||||||
|
SettingsMgr settingsMgr;
|
||||||
void loadFavorites();
|
void loadFavorites();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
198
apps/browser/Settings.cpp
Normal file
198
apps/browser/Settings.cpp
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
//
|
||||||
|
// Created by koncord on 31.03.18.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Settings.hpp"
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include <components/files/configurationmanager.hpp>
|
||||||
|
#include <apps/browser/netutils/QueryClient.hpp>
|
||||||
|
#include "MainWindow.hpp"
|
||||||
|
|
||||||
|
std::string loadSettings (Settings::Manager & settings, const std::string &cfgName)
|
||||||
|
{
|
||||||
|
Files::ConfigurationManager mCfgMgr;
|
||||||
|
// Create the settings manager and load default settings file
|
||||||
|
const std::string localdefault = (mCfgMgr.getLocalPath() / (cfgName + "-default.cfg")).string();
|
||||||
|
const std::string globaldefault = (mCfgMgr.getGlobalPath() / (cfgName + "-default.cfg")).string();
|
||||||
|
|
||||||
|
// prefer local
|
||||||
|
if (boost::filesystem::exists(localdefault))
|
||||||
|
settings.loadDefault(localdefault);
|
||||||
|
else if (boost::filesystem::exists(globaldefault))
|
||||||
|
settings.loadDefault(globaldefault);
|
||||||
|
else
|
||||||
|
throw std::runtime_error ("No default settings file found! Make sure the file \"" + cfgName + "-default.cfg\" was properly installed.");
|
||||||
|
|
||||||
|
// load user settings if they exist
|
||||||
|
const std::string settingspath = (mCfgMgr.getUserConfigPath() / (cfgName + ".cfg")).string();
|
||||||
|
if (boost::filesystem::exists(settingspath))
|
||||||
|
settings.loadUser(settingspath);
|
||||||
|
|
||||||
|
return settingspath;
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsMgr::SettingsMgr()
|
||||||
|
{
|
||||||
|
clientCfg = loadSettings(clientMgr, "tes3mp-client");
|
||||||
|
switchMgr();
|
||||||
|
serverCfg = loadSettings(serverMgr, "tes3mp-server");
|
||||||
|
switchMgr();
|
||||||
|
|
||||||
|
std::string addr = clientMgr.getString("address", "Master");
|
||||||
|
int port = clientMgr.getInt("port", "Master");
|
||||||
|
QueryClient::Get().SetServer(addr, port);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsMgr::loadBrowserSettings(Ui::MainWindow &mw)
|
||||||
|
{
|
||||||
|
mw.comboLatency->setCurrentIndex(clientMgr.getInt("maxLatency", "Browser"));
|
||||||
|
mw.leGamemode->setText(QString::fromStdString(clientMgr.getString("gameMode", "Browser")));
|
||||||
|
mw.cBoxNotFull->setCheckState(clientMgr.getBool("notFull", "Browser") ? Qt::Checked : Qt::Unchecked);
|
||||||
|
mw.cBoxWithPlayers->setCheckState(clientMgr.getBool("withPlayers", "Browser") ? Qt::Checked : Qt::Unchecked);
|
||||||
|
mw.cBBoxWOPass->setCheckState(clientMgr.getBool("noPassword", "Browser") ? Qt::Checked : Qt::Unchecked);
|
||||||
|
|
||||||
|
mw.tblServerBrowser->sortByColumn(clientMgr.getInt("sortByCol", "Browser"),
|
||||||
|
clientMgr.getBool("sortByColAscending", "Browser") ? Qt::AscendingOrder : Qt::DescendingOrder);
|
||||||
|
|
||||||
|
loadClientSettings(mw);
|
||||||
|
switchMgr();
|
||||||
|
loadServerSettings(mw);
|
||||||
|
switchMgr();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsMgr::saveBrowserSettings(Ui::MainWindow &mw)
|
||||||
|
{
|
||||||
|
clientMgr.setInt("maxLatency", "Browser", mw.comboLatency->currentIndex());
|
||||||
|
clientMgr.setString("gameMode", "Browser", mw.leGamemode->text().toStdString());
|
||||||
|
clientMgr.setBool("notFull", "Browser", mw.cBoxNotFull->checkState() == Qt::Checked);
|
||||||
|
clientMgr.setBool("withPlayers", "Browser", mw.cBoxWithPlayers->checkState() == Qt::Checked);
|
||||||
|
clientMgr.setBool("noPassword", "Browser", mw.cBBoxWOPass->checkState() == Qt::Checked);
|
||||||
|
|
||||||
|
clientMgr.setInt("sortByCol", "Browser", mw.tblServerBrowser->horizontalHeader()->sortIndicatorSection());
|
||||||
|
clientMgr.setBool("sortByColAscending", "Browser", mw.tblServerBrowser->horizontalHeader()->sortIndicatorOrder() == Qt::AscendingOrder);
|
||||||
|
|
||||||
|
saveClientSettings(mw);
|
||||||
|
|
||||||
|
clientMgr.saveUser(clientCfg);
|
||||||
|
switchMgr();
|
||||||
|
saveServerSettings(mw);
|
||||||
|
switchMgr();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsMgr::loadClientSettings(Ui::MainWindow &mw)
|
||||||
|
{
|
||||||
|
mw.leClientAddress->setText(QString::fromStdString(clientMgr.getString("destinationAddress", "General")));
|
||||||
|
mw.leClientPort->setText(QString::fromStdString(clientMgr.getString("port", "General")));
|
||||||
|
mw.leClientPassword->setText(QString::fromStdString(clientMgr.getString("password", "General")));
|
||||||
|
mw.combLoglevel->setCurrentIndex(clientMgr.getInt("logLevel", "General"));
|
||||||
|
|
||||||
|
mw.leClientMAddress->setText(QString::fromStdString(clientMgr.getString("address", "Master")));
|
||||||
|
mw.leClientMPort->setText(QString::fromStdString(clientMgr.getString("port", "Master")));
|
||||||
|
|
||||||
|
mw.pbChatKey->setText(QString::fromStdString(clientMgr.getString("keySay", "Chat")));
|
||||||
|
mw.pbModeKey->setText(QString::fromStdString(clientMgr.getString("keyChatMode", "Chat")));
|
||||||
|
|
||||||
|
mw.sbPosX->setValue(clientMgr.getInt("x", "Chat"));
|
||||||
|
mw.sbPosY->setValue(clientMgr.getInt("y", "Chat"));
|
||||||
|
mw.sbPosW->setValue(clientMgr.getInt("w", "Chat"));
|
||||||
|
mw.sbPosH->setValue(clientMgr.getInt("h", "Chat"));
|
||||||
|
|
||||||
|
mw.sbDelay->setValue(clientMgr.getFloat("delay", "Chat"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsMgr::saveClientSettings(Ui::MainWindow &mw)
|
||||||
|
{
|
||||||
|
clientMgr.setString("destinationAddress", "General", mw.leClientAddress->text().toStdString());
|
||||||
|
clientMgr.setString("port", "General", mw.leClientPort->text().toStdString());
|
||||||
|
clientMgr.setString("password", "General", mw.leClientPassword->text().toStdString());
|
||||||
|
clientMgr.setInt("logLevel", "General", mw.combLoglevel->currentIndex());
|
||||||
|
|
||||||
|
clientMgr.setString("address", "Master", mw.leClientMAddress->text().toStdString());
|
||||||
|
clientMgr.setString("port", "Master", mw.leClientMPort->text().toStdString());
|
||||||
|
|
||||||
|
clientMgr.setString("keySay", "Chat", mw.pbChatKey->text().toStdString());
|
||||||
|
clientMgr.setString("keyChatMode", "Chat", mw.pbModeKey->text().toStdString());
|
||||||
|
|
||||||
|
clientMgr.setInt("x", "Chat", mw.sbPosX->value());
|
||||||
|
clientMgr.setInt("y", "Chat", mw.sbPosY->value());
|
||||||
|
clientMgr.setInt("w", "Chat", mw.sbPosW->value());
|
||||||
|
clientMgr.setInt("h", "Chat", mw.sbPosH->value());
|
||||||
|
|
||||||
|
clientMgr.setFloat("delay", "Chat", mw.sbDelay->value());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsMgr::loadServerSettings(Ui::MainWindow &mw)
|
||||||
|
{
|
||||||
|
mw.leServerAddress->setText(QString::fromStdString(serverMgr.getString("localAddress", "General")));
|
||||||
|
mw.leServerPort->setText(QString::fromStdString(serverMgr.getString("port", "General")));
|
||||||
|
mw.sbMaxPlayers->setValue(serverMgr.getInt("maximumPlayers", "General"));
|
||||||
|
mw.leHostname->setText(QString::fromStdString(serverMgr.getString("hostname", "General")));
|
||||||
|
mw.combServerLoglevel->setCurrentIndex(serverMgr.getInt("logLevel", "General"));
|
||||||
|
mw.leServerPassword->setText(QString::fromStdString(serverMgr.getString("password", "General")));
|
||||||
|
|
||||||
|
mw.chbMSenabled->setCheckState(serverMgr.getBool("enabled", "MasterServer") ? Qt::Checked : Qt::Unchecked);
|
||||||
|
mw.leServerMaddress->setText(QString::fromStdString(serverMgr.getString("address", "MasterServer")));
|
||||||
|
mw.leServerMPort->setText(QString::fromStdString(serverMgr.getString("port", "MasterServer")));
|
||||||
|
mw.sbRate->setValue(serverMgr.getInt("rate", "MasterServer"));
|
||||||
|
|
||||||
|
#ifndef TES3MP_PRE07
|
||||||
|
mw.leModulePath->setText(QString::fromStdString(serverMgr.getString("home", "Modules")));
|
||||||
|
mw.chbAutosort->setCheckState(serverMgr.getBool("autoSort", "Modules") ? Qt::Checked : Qt::Unchecked);
|
||||||
|
#else
|
||||||
|
mw.leModulePath->setText(QString::fromStdString(serverMgr.getString("home", "Plugins")));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsMgr::saveServerSettings(Ui::MainWindow &mw)
|
||||||
|
{
|
||||||
|
serverMgr.setString("localAddress", "General", mw.leServerAddress->text().toStdString());
|
||||||
|
serverMgr.setString("port", "General", mw.leServerPort->text().toStdString());
|
||||||
|
serverMgr.setInt("maximumPlayers", "General", mw.sbMaxPlayers->value());
|
||||||
|
serverMgr.setString("hostname", "General", mw.leHostname->text().toStdString());
|
||||||
|
serverMgr.setInt("logLevel", "General", mw.combServerLoglevel->currentIndex());
|
||||||
|
serverMgr.setString("password", "General", mw.leServerPassword->text().toStdString());
|
||||||
|
|
||||||
|
serverMgr.setBool("enabled", "MasterServer", mw.chbMSenabled->checkState() == Qt::Checked);
|
||||||
|
serverMgr.setString("address", "MasterServer", mw.leServerMaddress->text().toStdString());
|
||||||
|
serverMgr.setString("port", "MasterServer", mw.leServerMPort->text().toStdString());
|
||||||
|
serverMgr.setInt("rate", "MasterServer", mw.sbRate->value());
|
||||||
|
|
||||||
|
#ifndef TES3MP_PRE07
|
||||||
|
serverMgr.setString("home", "Modules", mw.leModulePath->text().toStdString());
|
||||||
|
serverMgr.setBool("autoSort", "Modules", mw.chbAutosort->checkState() == Qt::Checked);
|
||||||
|
#else
|
||||||
|
serverMgr.setString("home", "Plugins", mw.leModulePath->text().toStdString());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
serverMgr.saveUser(serverCfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsMgr::switchMgr()
|
||||||
|
{
|
||||||
|
static Settings::CategorySettingValueMap saveUserSettings;
|
||||||
|
static Settings::CategorySettingValueMap saveDefaultSettings;
|
||||||
|
static Settings::CategorySettingVector saveChangedSettings;
|
||||||
|
static bool currentMgrIsClient = true;
|
||||||
|
|
||||||
|
if(!currentMgrIsClient)
|
||||||
|
{
|
||||||
|
saveUserSettings.swap(clientMgr.mUserSettings);
|
||||||
|
saveDefaultSettings.swap(clientMgr.mDefaultSettings);
|
||||||
|
saveChangedSettings.swap(clientMgr.mChangedSettings);
|
||||||
|
|
||||||
|
qDebug() << "Manager switched to Client config";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
saveUserSettings.swap(serverMgr.mUserSettings);
|
||||||
|
saveDefaultSettings.swap(serverMgr.mDefaultSettings);
|
||||||
|
saveChangedSettings.swap(serverMgr.mChangedSettings);
|
||||||
|
|
||||||
|
qDebug() << "Manager switched to Server config";
|
||||||
|
}
|
||||||
|
currentMgrIsClient = !currentMgrIsClient;
|
||||||
|
}
|
32
apps/browser/Settings.hpp
Normal file
32
apps/browser/Settings.hpp
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
//
|
||||||
|
// Created by koncord on 31.03.18.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
|
namespace Ui
|
||||||
|
{
|
||||||
|
class MainWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsMgr
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SettingsMgr();
|
||||||
|
|
||||||
|
void loadBrowserSettings(Ui::MainWindow &mw);
|
||||||
|
void saveBrowserSettings(Ui::MainWindow &mw);
|
||||||
|
|
||||||
|
void loadClientSettings(Ui::MainWindow &mw);
|
||||||
|
void saveClientSettings(Ui::MainWindow &mw);
|
||||||
|
|
||||||
|
void loadServerSettings(Ui::MainWindow &mw);
|
||||||
|
void saveServerSettings(Ui::MainWindow &mw);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Settings::Manager serverMgr, clientMgr;
|
||||||
|
std::string serverCfg, clientCfg;
|
||||||
|
void switchMgr();
|
||||||
|
};
|
|
@ -4,47 +4,8 @@
|
||||||
#include <apps/browser/netutils/QueryClient.hpp>
|
#include <apps/browser/netutils/QueryClient.hpp>
|
||||||
#include "MainWindow.hpp"
|
#include "MainWindow.hpp"
|
||||||
|
|
||||||
std::string loadSettings (Settings::Manager & settings)
|
|
||||||
{
|
|
||||||
Files::ConfigurationManager mCfgMgr;
|
|
||||||
// Create the settings manager and load default settings file
|
|
||||||
const std::string localdefault = (mCfgMgr.getLocalPath() / "tes3mp-client-default.cfg").string();
|
|
||||||
const std::string globaldefault = (mCfgMgr.getGlobalPath() / "tes3mp-client-default.cfg").string();
|
|
||||||
|
|
||||||
// prefer local
|
|
||||||
if (boost::filesystem::exists(localdefault))
|
|
||||||
settings.loadDefault(localdefault);
|
|
||||||
else if (boost::filesystem::exists(globaldefault))
|
|
||||||
settings.loadDefault(globaldefault);
|
|
||||||
else
|
|
||||||
throw std::runtime_error ("No default settings file found! Make sure the file \"tes3mp-client-default.cfg\" was properly installed.");
|
|
||||||
|
|
||||||
// load user settings if they exist
|
|
||||||
const std::string settingspath = (mCfgMgr.getUserConfigPath() / "tes3mp-client.cfg").string();
|
|
||||||
if (boost::filesystem::exists(settingspath))
|
|
||||||
settings.loadUser(settingspath);
|
|
||||||
|
|
||||||
return settingspath;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
Settings::Manager mgr;
|
|
||||||
|
|
||||||
loadSettings(mgr);
|
|
||||||
|
|
||||||
std::string addr = mgr.getString("address", "Master");
|
|
||||||
int port = mgr.getInt("port", "Master");
|
|
||||||
|
|
||||||
// Is this an attempt to connect to the official master server at the old port? If so,
|
|
||||||
// redirect it to the correct port for the currently used fork of RakNet
|
|
||||||
if (Misc::StringUtils::ciEqual(addr, "master.tes3mp.com") && port == 25560)
|
|
||||||
port = 25561;
|
|
||||||
|
|
||||||
// initialize resources, if needed
|
|
||||||
// Q_INIT_RESOURCE(resfile);
|
|
||||||
|
|
||||||
QueryClient::Get().SetServer(addr, port);
|
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
MainWindow d;
|
MainWindow d;
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ namespace ESSImport
|
||||||
item.mId = contItem.mItem.toString();
|
item.mId = contItem.mItem.toString();
|
||||||
item.mCount = contItem.mCount;
|
item.mCount = contItem.mCount;
|
||||||
item.mRelativeEquipmentSlot = -1;
|
item.mRelativeEquipmentSlot = -1;
|
||||||
item.mLockLevel = 0;
|
|
||||||
|
|
||||||
unsigned int itemCount = std::abs(item.mCount);
|
unsigned int itemCount = std::abs(item.mCount);
|
||||||
bool separateStacks = false;
|
bool separateStacks = false;
|
||||||
|
|
|
@ -8,7 +8,6 @@ set(LAUNCHER
|
||||||
settingspage.cpp
|
settingspage.cpp
|
||||||
advancedpage.cpp
|
advancedpage.cpp
|
||||||
|
|
||||||
utils/cellnameloader.cpp
|
|
||||||
utils/profilescombobox.cpp
|
utils/profilescombobox.cpp
|
||||||
utils/textinputdialog.cpp
|
utils/textinputdialog.cpp
|
||||||
utils/lineedit.cpp
|
utils/lineedit.cpp
|
||||||
|
@ -25,7 +24,6 @@ set(LAUNCHER_HEADER
|
||||||
settingspage.hpp
|
settingspage.hpp
|
||||||
advancedpage.hpp
|
advancedpage.hpp
|
||||||
|
|
||||||
utils/cellnameloader.hpp
|
|
||||||
utils/profilescombobox.hpp
|
utils/profilescombobox.hpp
|
||||||
utils/textinputdialog.hpp
|
utils/textinputdialog.hpp
|
||||||
utils/lineedit.hpp
|
utils/lineedit.hpp
|
||||||
|
@ -41,7 +39,6 @@ set(LAUNCHER_HEADER_MOC
|
||||||
settingspage.hpp
|
settingspage.hpp
|
||||||
advancedpage.hpp
|
advancedpage.hpp
|
||||||
|
|
||||||
utils/cellnameloader.hpp
|
|
||||||
utils/textinputdialog.hpp
|
utils/textinputdialog.hpp
|
||||||
utils/profilescombobox.hpp
|
utils/profilescombobox.hpp
|
||||||
utils/lineedit.hpp
|
utils/lineedit.hpp
|
||||||
|
|
|
@ -1,18 +1,10 @@
|
||||||
#include "advancedpage.hpp"
|
#include "advancedpage.hpp"
|
||||||
|
|
||||||
#include <components/config/gamesettings.hpp>
|
#include <components/files/configurationmanager.hpp>
|
||||||
#include <components/config/launchersettings.hpp>
|
|
||||||
#include <QFileDialog>
|
|
||||||
#include <QCompleter>
|
|
||||||
#include <components/contentselector/view/contentselector.hpp>
|
|
||||||
#include <components/contentselector/model/esmfile.hpp>
|
|
||||||
|
|
||||||
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg,
|
Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent)
|
||||||
Config::GameSettings &gameSettings,
|
|
||||||
Settings::Manager &engineSettings, QWidget *parent)
|
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, mCfgMgr(cfg)
|
, mCfgMgr(cfg)
|
||||||
, mGameSettings(gameSettings)
|
|
||||||
, mEngineSettings(engineSettings)
|
, mEngineSettings(engineSettings)
|
||||||
{
|
{
|
||||||
setObjectName ("AdvancedPage");
|
setObjectName ("AdvancedPage");
|
||||||
|
@ -21,61 +13,23 @@ Launcher::AdvancedPage::AdvancedPage(Files::ConfigurationManager &cfg,
|
||||||
loadSettings();
|
loadSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Launcher::AdvancedPage::loadCellsForAutocomplete(QStringList cellNames) {
|
|
||||||
// Set up an auto-completer for the "Start default character at" field
|
|
||||||
auto *completer = new QCompleter(cellNames);
|
|
||||||
completer->setCompletionMode(QCompleter::PopupCompletion);
|
|
||||||
completer->setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
|
|
||||||
startDefaultCharacterAtField->setCompleter(completer);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::AdvancedPage::on_skipMenuCheckBox_stateChanged(int state) {
|
|
||||||
startDefaultCharacterAtLabel->setEnabled(state == Qt::Checked);
|
|
||||||
startDefaultCharacterAtField->setEnabled(state == Qt::Checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked()
|
|
||||||
{
|
|
||||||
QString scriptFile = QFileDialog::getOpenFileName(
|
|
||||||
this,
|
|
||||||
QObject::tr("Select script file"),
|
|
||||||
QDir::currentPath(),
|
|
||||||
QString(tr("Text file (*.txt)")));
|
|
||||||
|
|
||||||
if (scriptFile.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QFileInfo info(scriptFile);
|
|
||||||
|
|
||||||
if (!info.exists() || !info.isReadable())
|
|
||||||
return;
|
|
||||||
|
|
||||||
const QString path(QDir::toNativeSeparators(info.absoluteFilePath()));
|
|
||||||
runScriptAfterStartupField->setText(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Launcher::AdvancedPage::loadSettings()
|
bool Launcher::AdvancedPage::loadSettings()
|
||||||
{
|
{
|
||||||
// Testing
|
|
||||||
bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1;
|
|
||||||
if (skipMenu) {
|
|
||||||
skipMenuCheckBox->setCheckState(Qt::Checked);
|
|
||||||
}
|
|
||||||
startDefaultCharacterAtLabel->setEnabled(skipMenu);
|
|
||||||
startDefaultCharacterAtField->setEnabled(skipMenu);
|
|
||||||
|
|
||||||
startDefaultCharacterAtField->setText(mGameSettings.value("start"));
|
|
||||||
runScriptAfterStartupField->setText(mGameSettings.value("script-run"));
|
|
||||||
|
|
||||||
// Game Settings
|
// Game Settings
|
||||||
loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
|
loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
|
||||||
loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
|
loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
|
||||||
loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
|
loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
|
||||||
|
loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game");
|
||||||
|
loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
|
||||||
|
loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
|
||||||
|
loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
|
||||||
loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
|
loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
|
||||||
loadSettingBool(chargeForEveryFollowerCheckBox, "charge for every follower travelling", "Game");
|
|
||||||
loadSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game");
|
// Expected values are (0, 1, 2, 3)
|
||||||
loadSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game");
|
int showOwnedIndex = mEngineSettings.getInt("show owned", "Game");
|
||||||
|
// Match the index with the option. Will default to 0 if invalid.
|
||||||
|
if (showOwnedIndex >= 0 && showOwnedIndex <= 3)
|
||||||
|
showOwnedComboBox->setCurrentIndex(showOwnedIndex);
|
||||||
|
|
||||||
// Input Settings
|
// Input Settings
|
||||||
loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input");
|
loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input");
|
||||||
|
@ -86,16 +40,6 @@ bool Launcher::AdvancedPage::loadSettings()
|
||||||
loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
|
loadSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
|
||||||
maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves"));
|
maximumQuicksavesComboBox->setValue(mEngineSettings.getInt("max quicksaves", "Saves"));
|
||||||
|
|
||||||
// User Interface Settings
|
|
||||||
loadSettingBool(showEffectDurationCheckBox, "show effect duration", "Game");
|
|
||||||
loadSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
|
|
||||||
loadSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
|
|
||||||
loadSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
|
|
||||||
int showOwnedIndex = mEngineSettings.getInt("show owned", "Game");
|
|
||||||
// Match the index with the option (only 0, 1, 2, or 3 are valid). Will default to 0 if invalid.
|
|
||||||
if (showOwnedIndex >= 0 && showOwnedIndex <= 3)
|
|
||||||
showOwnedComboBox->setCurrentIndex(showOwnedIndex);
|
|
||||||
|
|
||||||
// Other Settings
|
// Other Settings
|
||||||
QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper();
|
QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper();
|
||||||
if (screenshotFormatComboBox->findText(screenshotFormatString) == -1)
|
if (screenshotFormatComboBox->findText(screenshotFormatString) == -1)
|
||||||
|
@ -110,27 +54,19 @@ void Launcher::AdvancedPage::saveSettings()
|
||||||
// Ensure we only set the new settings if they changed. This is to avoid cluttering the
|
// Ensure we only set the new settings if they changed. This is to avoid cluttering the
|
||||||
// user settings file (which by definition should only contain settings the user has touched)
|
// user settings file (which by definition should only contain settings the user has touched)
|
||||||
|
|
||||||
// Testing
|
|
||||||
int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked;
|
|
||||||
if (skipMenu != mGameSettings.value("skip-menu").toInt())
|
|
||||||
mGameSettings.setValue("skip-menu", QString::number(skipMenu));
|
|
||||||
|
|
||||||
QString startCell = startDefaultCharacterAtField->text();
|
|
||||||
if (startCell != mGameSettings.value("start")) {
|
|
||||||
mGameSettings.setValue("start", startCell);
|
|
||||||
}
|
|
||||||
QString scriptRun = runScriptAfterStartupField->text();
|
|
||||||
if (scriptRun != mGameSettings.value("script-run"))
|
|
||||||
mGameSettings.setValue("script-run", scriptRun);
|
|
||||||
|
|
||||||
// Game Settings
|
// Game Settings
|
||||||
saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
|
saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
|
||||||
saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
|
saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
|
||||||
saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
|
saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
|
||||||
|
saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game");
|
||||||
|
saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
|
||||||
|
saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
|
||||||
|
saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
|
||||||
saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
|
saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
|
||||||
saveSettingBool(chargeForEveryFollowerCheckBox, "charge for every follower travelling", "Game");
|
|
||||||
saveSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game");
|
int showOwnedCurrentIndex = showOwnedComboBox->currentIndex();
|
||||||
saveSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game");
|
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))
|
||||||
|
mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex);
|
||||||
|
|
||||||
// Input Settings
|
// Input Settings
|
||||||
saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input");
|
saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input");
|
||||||
|
@ -144,15 +80,6 @@ void Launcher::AdvancedPage::saveSettings()
|
||||||
mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves);
|
mEngineSettings.setInt("max quicksaves", "Saves", maximumQuicksaves);
|
||||||
}
|
}
|
||||||
|
|
||||||
// User Interface Settings
|
|
||||||
saveSettingBool(showEffectDurationCheckBox, "show effect duration", "Game");
|
|
||||||
saveSettingBool(showEnchantChanceCheckBox, "show enchant chance", "Game");
|
|
||||||
saveSettingBool(showMeleeInfoCheckBox, "show melee info", "Game");
|
|
||||||
saveSettingBool(showProjectileDamageCheckBox, "show projectile damage", "Game");
|
|
||||||
int showOwnedCurrentIndex = showOwnedComboBox->currentIndex();
|
|
||||||
if (showOwnedCurrentIndex != mEngineSettings.getInt("show owned", "Game"))
|
|
||||||
mEngineSettings.setInt("show owned", "Game", showOwnedCurrentIndex);
|
|
||||||
|
|
||||||
// Other Settings
|
// Other Settings
|
||||||
std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString();
|
std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString();
|
||||||
if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General"))
|
if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General"))
|
||||||
|
@ -169,8 +96,3 @@ void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::str
|
||||||
if (cValue != mEngineSettings.getBool(setting, group))
|
if (cValue != mEngineSettings.getBool(setting, group))
|
||||||
mEngineSettings.setBool(setting, group, cValue);
|
mEngineSettings.setBool(setting, group, cValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames)
|
|
||||||
{
|
|
||||||
loadCellsForAutocomplete(cellNames);
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
#include <components/settings/settings.hpp>
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
namespace Files { struct ConfigurationManager; }
|
namespace Files { struct ConfigurationManager; }
|
||||||
namespace Config { class GameSettings; }
|
|
||||||
|
|
||||||
namespace Launcher
|
namespace Launcher
|
||||||
{
|
{
|
||||||
|
@ -17,29 +16,15 @@ namespace Launcher
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
|
AdvancedPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = 0);
|
||||||
Settings::Manager &engineSettings, QWidget *parent = 0);
|
|
||||||
|
|
||||||
bool loadSettings();
|
bool loadSettings();
|
||||||
void saveSettings();
|
void saveSettings();
|
||||||
|
|
||||||
public slots:
|
|
||||||
void slotLoadedCellsChanged(QStringList cellNames);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void on_skipMenuCheckBox_stateChanged(int state);
|
|
||||||
void on_runScriptAfterStartupBrowseButton_clicked();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Files::ConfigurationManager &mCfgMgr;
|
Files::ConfigurationManager &mCfgMgr;
|
||||||
Config::GameSettings &mGameSettings;
|
|
||||||
Settings::Manager &mEngineSettings;
|
Settings::Manager &mEngineSettings;
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the cells associated with the given content files for use in autocomplete
|
|
||||||
* @param filePaths the file paths of the content files to be examined
|
|
||||||
*/
|
|
||||||
void loadCellsForAutocomplete(QStringList filePaths);
|
|
||||||
void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
|
void loadSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
|
||||||
void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
|
void saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,10 +7,7 @@
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
#include <thread>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include <apps/launcher/utils/cellnameloader.hpp>
|
|
||||||
#include <components/files/configurationmanager.hpp>
|
#include <components/files/configurationmanager.hpp>
|
||||||
|
|
||||||
#include <components/contentselector/model/esmfile.hpp>
|
#include <components/contentselector/model/esmfile.hpp>
|
||||||
|
@ -19,7 +16,6 @@
|
||||||
|
|
||||||
#include <components/config/gamesettings.hpp>
|
#include <components/config/gamesettings.hpp>
|
||||||
#include <components/config/launchersettings.hpp>
|
#include <components/config/launchersettings.hpp>
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "utils/textinputdialog.hpp"
|
#include "utils/textinputdialog.hpp"
|
||||||
#include "utils/profilescombobox.hpp"
|
#include "utils/profilescombobox.hpp"
|
||||||
|
@ -44,13 +40,6 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config:
|
||||||
|
|
||||||
buildView();
|
buildView();
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
// Connect signal and slot after the settings have been loaded. We only care about the user changing
|
|
||||||
// the addons and don't want to get signals of the system doing it during startup.
|
|
||||||
connect(mSelector, SIGNAL(signalAddonDataChanged(QModelIndex,QModelIndex)),
|
|
||||||
this, SLOT(slotAddonDataChanged()));
|
|
||||||
// Call manually to indicate all changes to addon data during startup.
|
|
||||||
slotAddonDataChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Launcher::DataFilesPage::buildView()
|
void Launcher::DataFilesPage::buildView()
|
||||||
|
@ -153,17 +142,6 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile)
|
||||||
mGameSettings.setContentList(fileNames);
|
mGameSettings.setContentList(fileNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Launcher::DataFilesPage::selectedFilePaths()
|
|
||||||
{
|
|
||||||
//retrieve the files selected for the profile
|
|
||||||
ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
|
|
||||||
QStringList filePaths;
|
|
||||||
foreach(const ContentSelectorModel::EsmFile *item, items) {
|
|
||||||
filePaths.append(item->filePath());
|
|
||||||
}
|
|
||||||
return filePaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::DataFilesPage::removeProfile(const QString &profile)
|
void Launcher::DataFilesPage::removeProfile(const QString &profile)
|
||||||
{
|
{
|
||||||
mLauncherSettings.removeContentList(profile);
|
mLauncherSettings.removeContentList(profile);
|
||||||
|
@ -330,31 +308,3 @@ bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text)
|
||||||
|
|
||||||
return (msgBox.clickedButton() == deleteButton);
|
return (msgBox.clickedButton() == deleteButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Launcher::DataFilesPage::slotAddonDataChanged()
|
|
||||||
{
|
|
||||||
QStringList selectedFiles = selectedFilePaths();
|
|
||||||
if (previousSelectedFiles != selectedFiles) {
|
|
||||||
previousSelectedFiles = selectedFiles;
|
|
||||||
// Loading cells for core Morrowind + Expansions takes about 0.2 seconds, which is enough to cause a
|
|
||||||
// barely perceptible UI lag. Splitting into its own thread to alleviate that.
|
|
||||||
std::thread loadCellsThread(&DataFilesPage::reloadCells, this, selectedFiles);
|
|
||||||
loadCellsThread.detach();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mutex lock to run reloadCells synchronously.
|
|
||||||
std::mutex _reloadCellsMutex;
|
|
||||||
|
|
||||||
void Launcher::DataFilesPage::reloadCells(QStringList selectedFiles)
|
|
||||||
{
|
|
||||||
// Use a mutex lock so that we can prevent two threads from executing the rest of this code at the same time
|
|
||||||
// Based on https://stackoverflow.com/a/5429695/531762
|
|
||||||
std::unique_lock<std::mutex> lock(_reloadCellsMutex);
|
|
||||||
|
|
||||||
// The following code will run only if there is not another thread currently running it
|
|
||||||
CellNameLoader cellNameLoader;
|
|
||||||
QStringList cellNamesList = QStringList::fromSet(cellNameLoader.getCellNames(selectedFiles));
|
|
||||||
std::sort(cellNamesList.begin(), cellNamesList.end());
|
|
||||||
emit signalLoadedCellsChanged(cellNamesList);
|
|
||||||
}
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QStringList>
|
|
||||||
|
|
||||||
class QSortFilterProxyModel;
|
class QSortFilterProxyModel;
|
||||||
class QAbstractItemModel;
|
class QAbstractItemModel;
|
||||||
|
@ -42,15 +41,8 @@ namespace Launcher
|
||||||
void saveSettings(const QString &profile = "");
|
void saveSettings(const QString &profile = "");
|
||||||
bool loadSettings();
|
bool loadSettings();
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the file paths of all selected content files
|
|
||||||
* @return the file paths of all selected content files
|
|
||||||
*/
|
|
||||||
QStringList selectedFilePaths();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void signalProfileChanged (int index);
|
void signalProfileChanged (int index);
|
||||||
void signalLoadedCellsChanged(QStringList selectedFiles);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void slotProfileChanged (int index);
|
void slotProfileChanged (int index);
|
||||||
|
@ -60,7 +52,6 @@ namespace Launcher
|
||||||
void slotProfileChangedByUser(const QString &previous, const QString ¤t);
|
void slotProfileChangedByUser(const QString &previous, const QString ¤t);
|
||||||
void slotProfileRenamed(const QString &previous, const QString ¤t);
|
void slotProfileRenamed(const QString &previous, const QString ¤t);
|
||||||
void slotProfileDeleted(const QString &item);
|
void slotProfileDeleted(const QString &item);
|
||||||
void slotAddonDataChanged ();
|
|
||||||
|
|
||||||
void updateOkButton(const QString &text);
|
void updateOkButton(const QString &text);
|
||||||
|
|
||||||
|
@ -81,7 +72,7 @@ namespace Launcher
|
||||||
Config::LauncherSettings &mLauncherSettings;
|
Config::LauncherSettings &mLauncherSettings;
|
||||||
|
|
||||||
QString mPreviousProfile;
|
QString mPreviousProfile;
|
||||||
QStringList previousSelectedFiles;
|
|
||||||
QString mDataLocal;
|
QString mDataLocal;
|
||||||
|
|
||||||
void setPluginsCheckstates(Qt::CheckState state);
|
void setPluginsCheckstates(Qt::CheckState state);
|
||||||
|
@ -96,7 +87,6 @@ namespace Launcher
|
||||||
void addProfile (const QString &profile, bool setAsCurrent);
|
void addProfile (const QString &profile, bool setAsCurrent);
|
||||||
void checkForDefaultProfile();
|
void checkForDefaultProfile();
|
||||||
void populateFileViews(const QString& contentModelName);
|
void populateFileViews(const QString& contentModelName);
|
||||||
void reloadCells(QStringList selectedFiles);
|
|
||||||
|
|
||||||
class PathIterator
|
class PathIterator
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "graphicspage.hpp"
|
#include "graphicspage.hpp"
|
||||||
|
|
||||||
#include <boost/math/common_factor.hpp>
|
#include <boost/math/common_factor.hpp>
|
||||||
#include <csignal>
|
|
||||||
#include <QDesktopWidget>
|
#include <QDesktopWidget>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
@ -12,7 +11,6 @@
|
||||||
#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
|
#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
|
||||||
#endif // MAC_OS_X_VERSION_MIN_REQUIRED
|
#endif // MAC_OS_X_VERSION_MIN_REQUIRED
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
#include <SDL_video.h>
|
#include <SDL_video.h>
|
||||||
|
|
||||||
#include <components/files/configurationmanager.hpp>
|
#include <components/files/configurationmanager.hpp>
|
||||||
|
@ -48,28 +46,8 @@ Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings:
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Launcher::GraphicsPage::connectToSdl() {
|
|
||||||
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
|
|
||||||
SDL_SetMainReady();
|
|
||||||
// Required for determining screen resolution and such on the Graphics tab
|
|
||||||
if (SDL_Init(SDL_INIT_VIDEO) != 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
signal(SIGINT, SIG_DFL); // We don't want to use the SDL event loop in the launcher,
|
|
||||||
// so reset SIGINT which SDL wants to redirect to an SDL_Quit event.
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Launcher::GraphicsPage::setupSDL()
|
bool Launcher::GraphicsPage::setupSDL()
|
||||||
{
|
{
|
||||||
bool sdlConnectSuccessful = connectToSdl();
|
|
||||||
if (!sdlConnectSuccessful)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int displays = SDL_GetNumVideoDisplays();
|
int displays = SDL_GetNumVideoDisplays();
|
||||||
|
|
||||||
if (displays < 0)
|
if (displays < 0)
|
||||||
|
@ -89,9 +67,6 @@ bool Launcher::GraphicsPage::setupSDL()
|
||||||
screenComboBox->addItem(QString(tr("Screen ")) + QString::number(i + 1));
|
screenComboBox->addItem(QString(tr("Screen ")) + QString::number(i + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect from SDL processes
|
|
||||||
SDL_Quit();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,11 +37,6 @@ namespace Launcher
|
||||||
QStringList getAvailableResolutions(int screen);
|
QStringList getAvailableResolutions(int screen);
|
||||||
QRect getMaximumResolution();
|
QRect getMaximumResolution();
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to the SDL so that we can use it to determine graphics
|
|
||||||
* @return whether or not connecting to SDL is successful
|
|
||||||
*/
|
|
||||||
bool connectToSdl();
|
|
||||||
bool setupSDL();
|
bool setupSDL();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <csignal>
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QTextCodec>
|
#include <QTextCodec>
|
||||||
|
@ -11,12 +12,24 @@
|
||||||
#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
|
#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
|
||||||
#endif // MAC_OS_X_VERSION_MIN_REQUIRED
|
#endif // MAC_OS_X_VERSION_MIN_REQUIRED
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
#include "maindialog.hpp"
|
#include "maindialog.hpp"
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
|
||||||
|
SDL_SetMainReady();
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) != 0)
|
||||||
|
{
|
||||||
|
qDebug() << "SDL_Init failed: " << QString::fromUtf8(SDL_GetError());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
signal(SIGINT, SIG_DFL); // We don't want to use the SDL event loop in the launcher,
|
||||||
|
// so reset SIGINT which SDL wants to redirect to an SDL_Quit event.
|
||||||
|
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
// Now we make sure the current dir is set to application path
|
// Now we make sure the current dir is set to application path
|
||||||
|
@ -33,7 +46,9 @@ int main(int argc, char *argv[])
|
||||||
if (result == Launcher::FirstRunDialogResultContinue)
|
if (result == Launcher::FirstRunDialogResultContinue)
|
||||||
mainWin.show();
|
mainWin.show();
|
||||||
|
|
||||||
return app.exec();
|
int returnValue = app.exec();
|
||||||
|
SDL_Quit();
|
||||||
|
return returnValue;
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
catch (std::exception& e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -90,19 +90,19 @@ void Launcher::MainDialog::createIcons()
|
||||||
dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
||||||
|
|
||||||
QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget);
|
QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget);
|
||||||
graphicsButton->setIcon(QIcon(":/images/preferences-video.png"));
|
graphicsButton->setIcon(QIcon::fromTheme("video-display"));
|
||||||
graphicsButton->setText(tr("Graphics"));
|
graphicsButton->setText(tr("Graphics"));
|
||||||
graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute);
|
graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute);
|
||||||
graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
||||||
|
|
||||||
QListWidgetItem *settingsButton = new QListWidgetItem(iconWidget);
|
QListWidgetItem *settingsButton = new QListWidgetItem(iconWidget);
|
||||||
settingsButton->setIcon(QIcon(":/images/preferences.png"));
|
settingsButton->setIcon(QIcon::fromTheme("preferences-system"));
|
||||||
settingsButton->setText(tr("Settings"));
|
settingsButton->setText(tr("Settings"));
|
||||||
settingsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
|
settingsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
|
||||||
settingsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
settingsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
||||||
|
|
||||||
QListWidgetItem *advancedButton = new QListWidgetItem(iconWidget);
|
QListWidgetItem *advancedButton = new QListWidgetItem(iconWidget);
|
||||||
advancedButton->setIcon(QIcon(":/images/preferences-advanced.png"));
|
advancedButton->setIcon(QIcon::fromTheme("emblem-system"));
|
||||||
advancedButton->setText(tr("Advanced"));
|
advancedButton->setText(tr("Advanced"));
|
||||||
advancedButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
|
advancedButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
|
||||||
advancedButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
advancedButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
||||||
|
@ -119,7 +119,7 @@ void Launcher::MainDialog::createPages()
|
||||||
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
|
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
|
||||||
mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this);
|
mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this);
|
||||||
mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
|
mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
|
||||||
mAdvancedPage = new AdvancedPage(mCfgMgr, mGameSettings, mEngineSettings, this);
|
mAdvancedPage = new AdvancedPage(mCfgMgr, mEngineSettings, this);
|
||||||
|
|
||||||
// Set the combobox of the play page to imitate the combobox on the datafilespage
|
// Set the combobox of the play page to imitate the combobox on the datafilespage
|
||||||
mPlayPage->setProfilesModel(mDataFilesPage->profilesModel());
|
mPlayPage->setProfilesModel(mDataFilesPage->profilesModel());
|
||||||
|
@ -139,8 +139,6 @@ void Launcher::MainDialog::createPages()
|
||||||
|
|
||||||
connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(int)));
|
connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(int)));
|
||||||
connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int)));
|
connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int)));
|
||||||
// Using Qt::QueuedConnection because signal is emitted in a subthread and slot is in the main thread
|
|
||||||
connect(mDataFilesPage, SIGNAL(signalLoadedCellsChanged(QStringList)), mAdvancedPage, SLOT(slotLoadedCellsChanged(QStringList)), Qt::QueuedConnection);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
#include "cellnameloader.hpp"
|
|
||||||
|
|
||||||
#include <components/esm/loadcell.hpp>
|
|
||||||
#include <components/contentselector/view/contentselector.hpp>
|
|
||||||
|
|
||||||
QSet<QString> CellNameLoader::getCellNames(QStringList &contentPaths)
|
|
||||||
{
|
|
||||||
QSet<QString> cellNames;
|
|
||||||
ESM::ESMReader esmReader;
|
|
||||||
|
|
||||||
// Loop through all content files
|
|
||||||
for (auto &contentPath : contentPaths) {
|
|
||||||
esmReader.open(contentPath.toStdString());
|
|
||||||
|
|
||||||
// Loop through all records
|
|
||||||
while(esmReader.hasMoreRecs())
|
|
||||||
{
|
|
||||||
ESM::NAME recordName = esmReader.getRecName();
|
|
||||||
esmReader.getRecHeader();
|
|
||||||
|
|
||||||
if (isCellRecord(recordName)) {
|
|
||||||
QString cellName = getCellName(esmReader);
|
|
||||||
if (!cellName.isEmpty()) {
|
|
||||||
cellNames.insert(cellName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop loading content for this record and continue to the next
|
|
||||||
esmReader.skipRecord();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cellNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CellNameLoader::isCellRecord(ESM::NAME &recordName)
|
|
||||||
{
|
|
||||||
return recordName.intval == ESM::REC_CELL;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString CellNameLoader::getCellName(ESM::ESMReader &esmReader)
|
|
||||||
{
|
|
||||||
ESM::Cell cell;
|
|
||||||
bool isDeleted = false;
|
|
||||||
cell.loadNameAndData(esmReader, isDeleted);
|
|
||||||
|
|
||||||
return QString::fromStdString(cell.mName);
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
#ifndef OPENMW_CELLNAMELOADER_H
|
|
||||||
#define OPENMW_CELLNAMELOADER_H
|
|
||||||
|
|
||||||
#include <QComboBox>
|
|
||||||
#include <QSet>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include <components/esm/esmreader.hpp>
|
|
||||||
|
|
||||||
namespace ESM {class ESMReader; struct Cell;}
|
|
||||||
namespace ContentSelectorView {class ContentSelector;}
|
|
||||||
|
|
||||||
class CellNameLoader {
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the names of all cells contained within the given content files
|
|
||||||
* @param contentPaths the file paths of each content file to be examined
|
|
||||||
* @return the names of all cells
|
|
||||||
*/
|
|
||||||
QSet<QString> getCellNames(QStringList &contentPaths);
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* Returns whether or not the given record is of type "Cell"
|
|
||||||
* @param name The name associated with the record
|
|
||||||
* @return whether or not the given record is of type "Cell"
|
|
||||||
*/
|
|
||||||
bool isCellRecord(ESM::NAME &name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the cell
|
|
||||||
* @param esmReader the reader currently pointed to a loaded cell
|
|
||||||
* @return the name of the cell
|
|
||||||
*/
|
|
||||||
QString getCellName(ESM::ESMReader &esmReader);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //OPENMW_CELLNAMELOADER_H
|
|
69
apps/master/AdminRest.cpp
Normal file
69
apps/master/AdminRest.cpp
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
//
|
||||||
|
// Created by koncord on 04.09.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "AdminRest.hpp"
|
||||||
|
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
|
|
||||||
|
#include "RestUtils.hpp"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace chrono;
|
||||||
|
using namespace boost::property_tree;
|
||||||
|
|
||||||
|
|
||||||
|
AdminRest::AdminRest(const std::string &cert, const std::string &key, const std::string &verifyFile,
|
||||||
|
unsigned short port, std::shared_ptr<MasterServer> master) : httpServer(cert, key, verifyFile), master(master)
|
||||||
|
{
|
||||||
|
httpServer.config.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AdminRest::start()
|
||||||
|
{
|
||||||
|
static const string AdminArea = "^/api/admin?";
|
||||||
|
|
||||||
|
httpServer.resource[AdminArea]["POST"] = [this](auto response, auto request) {
|
||||||
|
cout << request->method << endl;
|
||||||
|
cout << request->path << endl;
|
||||||
|
cout << request->http_version << endl;
|
||||||
|
|
||||||
|
for (auto &header : request->header)
|
||||||
|
cout << header.first << ": " << header.second << endl;
|
||||||
|
|
||||||
|
string resp;
|
||||||
|
master->luaStuff([&request, &response, &resp](sol::state &state) {
|
||||||
|
sol::protected_function func = state["OnAdminRequest"];
|
||||||
|
|
||||||
|
sol::protected_function_result result = func.call(request->remote_endpoint_address, request->content.string());
|
||||||
|
if (result.valid())
|
||||||
|
*response << result.get<string>();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cerr << "Error: " << result.get<string>() << endl;
|
||||||
|
*response << response500;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*httpServer.on_error = [](auto request, const boost::system::error_code& err)
|
||||||
|
{
|
||||||
|
std::cerr << "Error: " << err.message() << " " << err.category().name() << std::endl;
|
||||||
|
};*/
|
||||||
|
|
||||||
|
httpServer.default_resource["GET"] = [](auto response, auto /*request*/) {
|
||||||
|
cout << "Default request" << endl;
|
||||||
|
*response << response400;
|
||||||
|
};
|
||||||
|
|
||||||
|
thr = thread([this](){httpServer.start();});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AdminRest::stop()
|
||||||
|
{
|
||||||
|
httpServer.stop();
|
||||||
|
if(thr.joinable())
|
||||||
|
thr.join();
|
||||||
|
}
|
25
apps/master/AdminRest.hpp
Normal file
25
apps/master/AdminRest.hpp
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
//
|
||||||
|
// Created by koncord on 04.09.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "SimpleWeb/https_server.hpp"
|
||||||
|
#include "MasterServer.hpp"
|
||||||
|
|
||||||
|
typedef SimpleWeb::Server<SimpleWeb::HTTPS> HttpsServer;
|
||||||
|
|
||||||
|
class AdminRest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AdminRest(const std::string &cert, const std::string &key, const std::string &verifyFile, unsigned short port, std::shared_ptr<MasterServer> master);
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
HttpsServer httpServer;
|
||||||
|
std::shared_ptr<MasterServer> master;
|
||||||
|
std::thread thr;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
project(masterserver)
|
project(masterserver)
|
||||||
|
|
||||||
#set(CMAKE_CXX_STANDARD 14)
|
|
||||||
add_definitions(-std=gnu++14)
|
|
||||||
|
|
||||||
include_directories("./")
|
include_directories("./")
|
||||||
|
|
||||||
set(SOURCE_FILES main.cpp MasterServer.cpp MasterServer.hpp RestServer.cpp RestServer.hpp)
|
set(SOURCE_FILES main.cpp MasterServer.cpp MasterServer.hpp RestServer.cpp RestServer.hpp AdminRest.cpp)
|
||||||
|
|
||||||
add_executable(masterserver ${SOURCE_FILES})
|
add_executable(masterserver ${SOURCE_FILES})
|
||||||
target_link_libraries(masterserver ${RakNet_LIBRARY} components)
|
target_link_libraries(masterserver ${RakNet_LIBRARY} ${LuaJit_LIBRARIES} ${OPENSSL_LIBRARIES} components)
|
||||||
|
|
||||||
|
set_property(TARGET masterserver PROPERTY CXX_STANDARD 14)
|
||||||
|
|
||||||
option(BUILD_MASTER_TEST "build master server test program" OFF)
|
option(BUILD_MASTER_TEST "build master server test program" OFF)
|
||||||
|
|
||||||
|
|
|
@ -12,19 +12,54 @@
|
||||||
#include <components/openmw-mp/Master/PacketMasterUpdate.hpp>
|
#include <components/openmw-mp/Master/PacketMasterUpdate.hpp>
|
||||||
#include <components/openmw-mp/Master/PacketMasterAnnounce.hpp>
|
#include <components/openmw-mp/Master/PacketMasterAnnounce.hpp>
|
||||||
#include <components/openmw-mp/Version.hpp>
|
#include <components/openmw-mp/Version.hpp>
|
||||||
|
#include <components/openmw-mp/Utils.hpp>
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
using namespace RakNet;
|
using namespace RakNet;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace mwmp;
|
using namespace mwmp;
|
||||||
using namespace chrono;
|
using namespace chrono;
|
||||||
|
|
||||||
MasterServer::MasterServer(unsigned short maxConnections, unsigned short port)
|
MasterServer::MasterServer(const std::string &luaScript)
|
||||||
{
|
{
|
||||||
peer = RakPeerInterface::GetInstance();
|
state.open_libraries();
|
||||||
sockdescr = SocketDescriptor(port, 0);
|
|
||||||
peer->Startup(maxConnections, &sockdescr, 1, 1000);
|
|
||||||
|
|
||||||
peer->SetMaximumIncomingConnections(maxConnections);
|
boost::filesystem::path absPath = boost::filesystem::absolute(luaScript);
|
||||||
|
|
||||||
|
std::string package_path = state["package"]["path"];
|
||||||
|
state["package"]["path"] = Utils::convertPath(absPath.parent_path().string() + "/?.lua") + ";" + package_path;
|
||||||
|
|
||||||
|
state.set_function("BanAddress", &MasterServer::ban, this);
|
||||||
|
state.set_function("UnbanAddress", &MasterServer::unban, this);
|
||||||
|
|
||||||
|
state.script_file(luaScript);
|
||||||
|
|
||||||
|
sol::table config = state["config"];
|
||||||
|
|
||||||
|
if (config.get_type() != sol::type::table)
|
||||||
|
throw runtime_error("config is not correct");
|
||||||
|
|
||||||
|
sol::object maxConnections = config["maxConnections"];
|
||||||
|
if (maxConnections.get_type() != sol::type::number)
|
||||||
|
throw runtime_error("config.maxConnections is not correct");
|
||||||
|
|
||||||
|
sol::object port = config["port"];
|
||||||
|
if (port.get_type() != sol::type::number)
|
||||||
|
throw runtime_error("config.port is not correct");
|
||||||
|
|
||||||
|
state.new_usertype<MasterServer::SServer>("Server",
|
||||||
|
"name", sol::property(&MasterServer::SServer::GetName),
|
||||||
|
"gamemode", sol::property(&MasterServer::SServer::GetGameMode),
|
||||||
|
"version", sol::property(&MasterServer::SServer::GetVersion)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
peer = RakPeerInterface::GetInstance();
|
||||||
|
sockdescr = SocketDescriptor(port.as<unsigned short>(), nullptr);
|
||||||
|
peer->Startup(maxConnections.as<unsigned short>(), &sockdescr, 1, 1000);
|
||||||
|
peer->SetLimitIPConnectionFrequency(true);
|
||||||
|
|
||||||
|
peer->SetMaximumIncomingConnections(maxConnections.as<unsigned short>());
|
||||||
peer->SetIncomingPassword(TES3MP_MASTERSERVER_PASSW, (int) strlen(TES3MP_MASTERSERVER_PASSW));
|
peer->SetIncomingPassword(TES3MP_MASTERSERVER_PASSW, (int) strlen(TES3MP_MASTERSERVER_PASSW));
|
||||||
run = false;
|
run = false;
|
||||||
}
|
}
|
||||||
|
@ -52,6 +87,13 @@ void MasterServer::Thread()
|
||||||
PacketMasterAnnounce pma(peer);
|
PacketMasterAnnounce pma(peer);
|
||||||
pma.SetSendStream(&send);
|
pma.SetSendStream(&send);
|
||||||
|
|
||||||
|
luaStuff([](sol::state &state) {
|
||||||
|
sol::protected_function func = state["OnInit"];
|
||||||
|
sol::protected_function_result result = func.call();
|
||||||
|
if (!result.valid())
|
||||||
|
cerr << "Error: " << result.get<string>() << endl;
|
||||||
|
});
|
||||||
|
|
||||||
while (run)
|
while (run)
|
||||||
{
|
{
|
||||||
Packet *packet = peer->Receive();
|
Packet *packet = peer->Receive();
|
||||||
|
@ -67,9 +109,9 @@ void MasterServer::Thread()
|
||||||
servers.erase(it++);
|
servers.erase(it++);
|
||||||
else ++it;
|
else ++it;
|
||||||
}
|
}
|
||||||
for(auto id = pendingACKs.begin(); id != pendingACKs.end();)
|
for (auto id = pendingACKs.begin(); id != pendingACKs.end();)
|
||||||
{
|
{
|
||||||
if(now - id->second >= 30s)
|
if (now - id->second >= 30s)
|
||||||
{
|
{
|
||||||
cout << "timeout: " << peer->GetSystemAddressFromGuid(id->first).ToString() << endl;
|
cout << "timeout: " << peer->GetSystemAddressFromGuid(id->first).ToString() << endl;
|
||||||
peer->CloseConnection(id->first, true);
|
peer->CloseConnection(id->first, true);
|
||||||
|
@ -113,7 +155,7 @@ void MasterServer::Thread()
|
||||||
SystemAddress addr;
|
SystemAddress addr;
|
||||||
data.Read(addr); // update 1 server
|
data.Read(addr); // update 1 server
|
||||||
|
|
||||||
ServerIter it = servers.find(addr);
|
auto it = servers.find(addr);
|
||||||
if (it != servers.end())
|
if (it != servers.end())
|
||||||
{
|
{
|
||||||
pair<SystemAddress, QueryData> pairPtr(it->first, static_cast<QueryData>(it->second));
|
pair<SystemAddress, QueryData> pairPtr(it->first, static_cast<QueryData>(it->second));
|
||||||
|
@ -127,7 +169,7 @@ void MasterServer::Thread()
|
||||||
}
|
}
|
||||||
case ID_MASTER_ANNOUNCE:
|
case ID_MASTER_ANNOUNCE:
|
||||||
{
|
{
|
||||||
ServerIter iter = servers.find(packet->systemAddress);
|
auto iter = servers.find(packet->systemAddress);
|
||||||
|
|
||||||
pma.SetReadStream(&data);
|
pma.SetReadStream(&data);
|
||||||
SServer server;
|
SServer server;
|
||||||
|
@ -141,6 +183,26 @@ void MasterServer::Thread()
|
||||||
pendingACKs[packet->guid] = steady_clock::now();
|
pendingACKs[packet->guid] = steady_clock::now();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto isServerValid = [&](const SServer &sserver) {
|
||||||
|
bool ret = false;
|
||||||
|
auto addr = packet->systemAddress.ToString(false);
|
||||||
|
|
||||||
|
lock_guard<mutex> lock(banMutex);
|
||||||
|
|
||||||
|
if (peer->IsBanned(addr)) // check if address is banned
|
||||||
|
return false;
|
||||||
|
|
||||||
|
luaStuff([&ret, &packet, &sserver, &addr](sol::state &state) {
|
||||||
|
sol::protected_function func = state["OnServerAnnounce"];
|
||||||
|
sol::protected_function_result result = func.call(addr, sserver);
|
||||||
|
if (result.valid())
|
||||||
|
ret = result.get<bool>();
|
||||||
|
else
|
||||||
|
cerr << "Error: " << result.get<string>() << endl;
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
if (iter != servers.end())
|
if (iter != servers.end())
|
||||||
{
|
{
|
||||||
if (pma.GetFunc() == PacketMasterAnnounce::FUNCTION_DELETE)
|
if (pma.GetFunc() == PacketMasterAnnounce::FUNCTION_DELETE)
|
||||||
|
@ -152,9 +214,19 @@ void MasterServer::Thread()
|
||||||
}
|
}
|
||||||
else if (pma.GetFunc() == PacketMasterAnnounce::FUNCTION_ANNOUNCE)
|
else if (pma.GetFunc() == PacketMasterAnnounce::FUNCTION_ANNOUNCE)
|
||||||
{
|
{
|
||||||
cout << "Updated";
|
|
||||||
iter->second = server;
|
if (isServerValid(server))
|
||||||
keepAliveFunc();
|
{
|
||||||
|
cout << "Updated";
|
||||||
|
iter->second = server;
|
||||||
|
keepAliveFunc();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cout << "Update rejected";
|
||||||
|
servers.erase(iter);
|
||||||
|
pendingACKs.erase(packet->guid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -164,9 +236,14 @@ void MasterServer::Thread()
|
||||||
}
|
}
|
||||||
else if (pma.GetFunc() == PacketMasterAnnounce::FUNCTION_ANNOUNCE)
|
else if (pma.GetFunc() == PacketMasterAnnounce::FUNCTION_ANNOUNCE)
|
||||||
{
|
{
|
||||||
cout << "Added";
|
if (isServerValid(server))
|
||||||
iter = servers.insert({packet->systemAddress, server}).first;
|
{
|
||||||
keepAliveFunc();
|
cout << "Added";
|
||||||
|
iter = servers.insert({packet->systemAddress, server}).first;
|
||||||
|
keepAliveFunc();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
cout << "Adding rejected";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -186,7 +263,8 @@ void MasterServer::Thread()
|
||||||
peer->CloseConnection(packet->systemAddress, true);
|
peer->CloseConnection(packet->systemAddress, true);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
cout << "Wrong packet. id " << (unsigned) packet->data[0] << " packet length " << packet->length << " from " << packet->systemAddress.ToString() << endl;
|
cout << "Wrong packet. id " << (unsigned) packet->data[0] << " packet length "
|
||||||
|
<< packet->length << " from " << packet->systemAddress.ToString() << endl;
|
||||||
peer->CloseConnection(packet->systemAddress, true);
|
peer->CloseConnection(packet->systemAddress, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,3 +312,22 @@ MasterServer::ServerMap *MasterServer::GetServers()
|
||||||
{
|
{
|
||||||
return &servers;
|
return &servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MasterServer::luaStuff(std::function<void(sol::state &)> f)
|
||||||
|
{
|
||||||
|
lock_guard<mutex> lock(luaMutex);
|
||||||
|
f(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterServer::ban(const std::string &addr)
|
||||||
|
{
|
||||||
|
lock_guard<mutex> lock(banMutex);
|
||||||
|
peer->AddToBanList(addr.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterServer::unban(const std::string &addr)
|
||||||
|
{
|
||||||
|
lock_guard<mutex> lock(banMutex);
|
||||||
|
peer->RemoveFromBanList(addr.c_str());
|
||||||
|
}
|
||||||
|
|
|
@ -9,18 +9,12 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <RakPeerInterface.h>
|
#include <RakPeerInterface.h>
|
||||||
#include <components/openmw-mp/Master/MasterData.hpp>
|
#include <components/openmw-mp/Master/MasterData.hpp>
|
||||||
|
#include <sol.hpp>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
class MasterServer
|
class MasterServer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct Ban
|
|
||||||
{
|
|
||||||
RakNet::SystemAddress sa;
|
|
||||||
bool permanent;
|
|
||||||
struct Date
|
|
||||||
{
|
|
||||||
} date;
|
|
||||||
};
|
|
||||||
struct SServer : QueryData
|
struct SServer : QueryData
|
||||||
{
|
{
|
||||||
std::chrono::steady_clock::time_point lastUpdate;
|
std::chrono::steady_clock::time_point lastUpdate;
|
||||||
|
@ -29,7 +23,7 @@ public:
|
||||||
//typedef ServerMap::const_iterator ServerCIter;
|
//typedef ServerMap::const_iterator ServerCIter;
|
||||||
typedef ServerMap::iterator ServerIter;
|
typedef ServerMap::iterator ServerIter;
|
||||||
|
|
||||||
MasterServer(unsigned short maxConnections, unsigned short port);
|
explicit MasterServer(const std::string &luaScript);
|
||||||
~MasterServer();
|
~MasterServer();
|
||||||
|
|
||||||
void Start();
|
void Start();
|
||||||
|
@ -38,6 +32,10 @@ public:
|
||||||
void Wait();
|
void Wait();
|
||||||
|
|
||||||
ServerMap* GetServers();
|
ServerMap* GetServers();
|
||||||
|
void luaStuff(std::function<void(sol::state &)> f);
|
||||||
|
|
||||||
|
void ban(const std::string &addr);
|
||||||
|
void unban(const std::string &addr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Thread();
|
void Thread();
|
||||||
|
@ -49,6 +47,9 @@ private:
|
||||||
ServerMap servers;
|
ServerMap servers;
|
||||||
bool run;
|
bool run;
|
||||||
std::map<RakNet::RakNetGUID, std::chrono::steady_clock::time_point> pendingACKs;
|
std::map<RakNet::RakNetGUID, std::chrono::steady_clock::time_point> pendingACKs;
|
||||||
|
sol::state state;
|
||||||
|
std::mutex luaMutex;
|
||||||
|
std::mutex banMutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,22 +7,12 @@
|
||||||
#include <boost/property_tree/ptree.hpp>
|
#include <boost/property_tree/ptree.hpp>
|
||||||
#include <boost/property_tree/json_parser.hpp>
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
|
|
||||||
|
#include "RestUtils.hpp"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace chrono;
|
using namespace chrono;
|
||||||
using namespace boost::property_tree;
|
using namespace boost::property_tree;
|
||||||
|
|
||||||
static string response201 = "HTTP/1.1 201 Created\r\nContent-Length: 7\r\n\r\nCreated";
|
|
||||||
static string response202 = "HTTP/1.1 202 Accepted\r\nContent-Length: 8\r\n\r\nAccepted";
|
|
||||||
static string response400 = "HTTP/1.1 400 Bad Request\r\nContent-Length: 11\r\n\r\nbad request";
|
|
||||||
|
|
||||||
inline void ResponseStr(HttpServer::Response &response, string content, string type = "", string code = "200 OK")
|
|
||||||
{
|
|
||||||
response << "HTTP/1.1 " << code << "\r\n";
|
|
||||||
if (!type.empty())
|
|
||||||
response << "Content-Type: " << type <<"\r\n";
|
|
||||||
response << "Content-Length: " << content.length() << "\r\n\r\n" << content;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void ptreeToServer(boost::property_tree::ptree &pt, MasterServer::SServer &server)
|
inline void ptreeToServer(boost::property_tree::ptree &pt, MasterServer::SServer &server)
|
||||||
{
|
{
|
||||||
server.SetName(pt.get<string>("hostname").c_str());
|
server.SetName(pt.get<string>("hostname").c_str());
|
||||||
|
@ -70,7 +60,7 @@ void RestServer::start()
|
||||||
auto port = (unsigned short)stoi(&(addr[addr.find(':')+1]));
|
auto port = (unsigned short)stoi(&(addr[addr.find(':')+1]));
|
||||||
queryToStringStream(ss, "server", serverMap->at(RakNet::SystemAddress(addr.c_str(), port)));
|
queryToStringStream(ss, "server", serverMap->at(RakNet::SystemAddress(addr.c_str(), port)));
|
||||||
ss << "}";
|
ss << "}";
|
||||||
ResponseStr(*response, ss.str(), "application/json");
|
ResponseStr<SimpleWeb::HTTP>(response, ss.str(), "application/json");
|
||||||
}
|
}
|
||||||
catch(out_of_range e)
|
catch(out_of_range e)
|
||||||
{
|
{
|
||||||
|
@ -93,7 +83,7 @@ void RestServer::start()
|
||||||
ss << ", ";
|
ss << ", ";
|
||||||
}
|
}
|
||||||
ss << "}}";
|
ss << "}}";
|
||||||
ResponseStr(*response, ss.str(), "application/json");
|
ResponseStr<SimpleWeb::HTTP>(response, ss.str(), "application/json");
|
||||||
updatedCache = false;
|
updatedCache = false;
|
||||||
}
|
}
|
||||||
*response << str;
|
*response << str;
|
||||||
|
@ -110,7 +100,7 @@ void RestServer::start()
|
||||||
MasterServer::SServer server;
|
MasterServer::SServer server;
|
||||||
ptreeToServer(pt, server);
|
ptreeToServer(pt, server);
|
||||||
|
|
||||||
unsigned short port = pt.get<unsigned short>("port");
|
auto port = pt.get<unsigned short>("port");
|
||||||
server.lastUpdate = steady_clock::now();
|
server.lastUpdate = steady_clock::now();
|
||||||
serverMap->insert({RakNet::SystemAddress(request->remote_endpoint_address.c_str(), port), server});
|
serverMap->insert({RakNet::SystemAddress(request->remote_endpoint_address.c_str(), port), server});
|
||||||
updatedCache = true;
|
updatedCache = true;
|
||||||
|
@ -137,7 +127,6 @@ void RestServer::start()
|
||||||
*response << response400;
|
*response << response400;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request->content.size() != 0)
|
if (request->content.size() != 0)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -171,14 +160,14 @@ void RestServer::start()
|
||||||
ss << ", \"players\": " << players;
|
ss << ", \"players\": " << players;
|
||||||
ss << "}";
|
ss << "}";
|
||||||
|
|
||||||
ResponseStr(*response, ss.str(), "application/json");
|
ResponseStr<SimpleWeb::HTTP>(response, ss.str(), "application/json");
|
||||||
};
|
};
|
||||||
|
|
||||||
httpServer.default_resource["GET"]=[](auto response, auto /*request*/) {
|
httpServer.default_resource["GET"]=[](auto response, auto /*request*/) {
|
||||||
*response << response400;
|
*response << response400;
|
||||||
};
|
};
|
||||||
|
|
||||||
httpServer.start();
|
thr = thread([this](){httpServer.start();});
|
||||||
}
|
}
|
||||||
|
|
||||||
void RestServer::cacheUpdated()
|
void RestServer::cacheUpdated()
|
||||||
|
@ -189,4 +178,6 @@ void RestServer::cacheUpdated()
|
||||||
void RestServer::stop()
|
void RestServer::stop()
|
||||||
{
|
{
|
||||||
httpServer.stop();
|
httpServer.stop();
|
||||||
|
if(thr.joinable())
|
||||||
|
thr.join();
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ private:
|
||||||
HttpServer httpServer;
|
HttpServer httpServer;
|
||||||
MasterServer::ServerMap *serverMap;
|
MasterServer::ServerMap *serverMap;
|
||||||
bool updatedCache = true;
|
bool updatedCache = true;
|
||||||
|
std::thread thr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
25
apps/master/RestUtils.hpp
Normal file
25
apps/master/RestUtils.hpp
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
//
|
||||||
|
// Created by koncord on 04.09.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "SimpleWeb/base_server.hpp"
|
||||||
|
|
||||||
|
static std::string response201 = "HTTP/1.1 201 Created\r\nContent-Length: 7\r\n\r\nCreated";
|
||||||
|
static std::string response202 = "HTTP/1.1 202 Accepted\r\nContent-Length: 8\r\n\r\nAccepted";
|
||||||
|
static std::string response400 = "HTTP/1.1 400 Bad Request\r\nContent-Length: 11\r\n\r\nbad request";
|
||||||
|
|
||||||
|
static std::string response403 = "HTTP/1.1 403 Forbidden\r\nContent-Length: 9\r\n\r\nForbidden";
|
||||||
|
static std::string response500 = "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 21\r\n\r\nInternal Server Error";
|
||||||
|
|
||||||
|
template <class Protocol>
|
||||||
|
inline void ResponseStr(std::shared_ptr<typename SimpleWeb::ServerBase<Protocol>::Response> response,
|
||||||
|
std::string content, std::string type = "", std::string code = "200 OK")
|
||||||
|
{
|
||||||
|
*response << "HTTP/1.1 " << code << "\r\n";
|
||||||
|
if (!type.empty())
|
||||||
|
*response << "Content-Type: " << type <<"\r\n";
|
||||||
|
*response << "Content-Length: " << content.length() << "\r\n\r\n" << content;
|
||||||
|
}
|
21
apps/master/SimpleWeb/LICENSE
Normal file
21
apps/master/SimpleWeb/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014-2016 Ole Christian Eidheim
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -1,72 +1,137 @@
|
||||||
#ifndef BASE_SERVER_HPP
|
#pragma once
|
||||||
#define BASE_SERVER_HPP
|
|
||||||
|
|
||||||
#include <boost/asio.hpp>
|
#include "utility.hpp"
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <condition_variable>
|
||||||
#include <boost/functional/hash.hpp>
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <thread>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_set>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
#ifndef CASE_INSENSITIVE_EQUALS_AND_HASH
|
#ifdef USE_STANDALONE_ASIO
|
||||||
#define CASE_INSENSITIVE_EQUALS_AND_HASH
|
#include <asio.hpp>
|
||||||
|
#include <asio/steady_timer.hpp>
|
||||||
//Based on http://www.boost.org/doc/libs/1_60_0/doc/html/unordered/hash_equality.html
|
namespace SimpleWeb {
|
||||||
struct case_insensitive_equals
|
using error_code = std::error_code;
|
||||||
{
|
using errc = std::errc;
|
||||||
bool operator()(const std::string &key1, const std::string &key2) const
|
namespace make_error_code = std;
|
||||||
{
|
} // namespace SimpleWeb
|
||||||
return boost::algorithm::iequals(key1, key2);
|
#else
|
||||||
}
|
#include <boost/asio.hpp>
|
||||||
};
|
#include <boost/asio/steady_timer.hpp>
|
||||||
|
namespace SimpleWeb {
|
||||||
struct case_insensitive_hash
|
namespace asio = boost::asio;
|
||||||
{
|
using error_code = boost::system::error_code;
|
||||||
size_t operator()(const std::string &key) const
|
namespace errc = boost::system::errc;
|
||||||
{
|
namespace make_error_code = boost::system::errc;
|
||||||
std::size_t seed = 0;
|
} // namespace SimpleWeb
|
||||||
for (auto &c: key)
|
|
||||||
boost::hash_combine(seed, std::tolower(c));
|
|
||||||
return seed;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace SimpleWeb
|
namespace SimpleWeb {
|
||||||
{
|
template <class socket_type>
|
||||||
template<class socket_type>
|
|
||||||
class Server;
|
class Server;
|
||||||
|
|
||||||
template<class socket_type>
|
template <class socket_type>
|
||||||
class ServerBase
|
class ServerBase {
|
||||||
{
|
protected:
|
||||||
|
class Session;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~ServerBase()
|
class Response : public std::enable_shared_from_this<Response>, public std::ostream {
|
||||||
{}
|
|
||||||
|
|
||||||
class Response : public std::ostream
|
|
||||||
{
|
|
||||||
friend class ServerBase<socket_type>;
|
friend class ServerBase<socket_type>;
|
||||||
|
friend class Server<socket_type>;
|
||||||
|
|
||||||
boost::asio::streambuf streambuf;
|
asio::streambuf streambuf;
|
||||||
|
|
||||||
std::shared_ptr<socket_type> socket;
|
std::shared_ptr<Session> session;
|
||||||
|
long timeout_content;
|
||||||
|
|
||||||
Response(const std::shared_ptr<socket_type> &socket) : std::ostream(&streambuf), socket(socket)
|
Response(std::shared_ptr<Session> session, long timeout_content) noexcept : std::ostream(&streambuf), session(std::move(session)), timeout_content(timeout_content) {}
|
||||||
{}
|
|
||||||
|
template <typename size_type>
|
||||||
|
void write_header(const CaseInsensitiveMultimap &header, size_type size) {
|
||||||
|
bool content_length_written = false;
|
||||||
|
bool chunked_transfer_encoding = false;
|
||||||
|
for(auto &field : header) {
|
||||||
|
if(!content_length_written && case_insensitive_equal(field.first, "content-length"))
|
||||||
|
content_length_written = true;
|
||||||
|
else if(!chunked_transfer_encoding && case_insensitive_equal(field.first, "transfer-encoding") && case_insensitive_equal(field.second, "chunked"))
|
||||||
|
chunked_transfer_encoding = true;
|
||||||
|
|
||||||
|
*this << field.first << ": " << field.second << "\r\n";
|
||||||
|
}
|
||||||
|
if(!content_length_written && !chunked_transfer_encoding && !close_connection_after_response)
|
||||||
|
*this << "Content-Length: " << size << "\r\n\r\n";
|
||||||
|
else
|
||||||
|
*this << "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
size_t size()
|
size_t size() noexcept {
|
||||||
{
|
|
||||||
return streambuf.size();
|
return streambuf.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use this function if you need to recursively send parts of a longer message
|
||||||
|
void send(const std::function<void(const error_code &)> &callback = nullptr) noexcept {
|
||||||
|
session->connection->set_timeout(timeout_content);
|
||||||
|
auto self = this->shared_from_this(); // Keep Response instance alive through the following async_write
|
||||||
|
asio::async_write(*session->connection->socket, streambuf, [self, callback](const error_code &ec, size_t /*bytes_transferred*/) {
|
||||||
|
self->session->connection->cancel_timeout();
|
||||||
|
auto lock = self->session->connection->handler_runner->continue_lock();
|
||||||
|
if(!lock)
|
||||||
|
return;
|
||||||
|
if(callback)
|
||||||
|
callback(ec);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write directly to stream buffer using std::ostream::write
|
||||||
|
void write(const char_type *ptr, std::streamsize n) {
|
||||||
|
std::ostream::write(ptr, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function for writing status line, potential header fields, and empty content
|
||||||
|
void write(StatusCode status_code = StatusCode::success_ok, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
|
||||||
|
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
|
||||||
|
write_header(header, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function for writing status line, header fields, and content
|
||||||
|
void write(StatusCode status_code, const std::string &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
|
||||||
|
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
|
||||||
|
write_header(header, content.size());
|
||||||
|
if(!content.empty())
|
||||||
|
*this << content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function for writing status line, header fields, and content
|
||||||
|
void write(StatusCode status_code, std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
|
||||||
|
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
|
||||||
|
content.seekg(0, std::ios::end);
|
||||||
|
auto size = content.tellg();
|
||||||
|
content.seekg(0, std::ios::beg);
|
||||||
|
write_header(header, size);
|
||||||
|
if(size)
|
||||||
|
*this << content.rdbuf();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function for writing success status line, header fields, and content
|
||||||
|
void write(const std::string &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
|
||||||
|
write(StatusCode::success_ok, content, header);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function for writing success status line, header fields, and content
|
||||||
|
void write(std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
|
||||||
|
write(StatusCode::success_ok, content, header);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function for writing success status line, and header fields
|
||||||
|
void write(const CaseInsensitiveMultimap &header) {
|
||||||
|
write(StatusCode::success_ok, std::string(), header);
|
||||||
|
}
|
||||||
|
|
||||||
/// If true, force server to close the connection after the response have been sent.
|
/// If true, force server to close the connection after the response have been sent.
|
||||||
///
|
///
|
||||||
/// This is useful when implementing a HTTP/1.0-server sending content
|
/// This is useful when implementing a HTTP/1.0-server sending content
|
||||||
|
@ -74,438 +139,399 @@ namespace SimpleWeb
|
||||||
bool close_connection_after_response = false;
|
bool close_connection_after_response = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Content : public std::istream
|
class Content : public std::istream {
|
||||||
{
|
|
||||||
friend class ServerBase<socket_type>;
|
friend class ServerBase<socket_type>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
size_t size()
|
size_t size() noexcept {
|
||||||
{
|
|
||||||
return streambuf.size();
|
return streambuf.size();
|
||||||
}
|
}
|
||||||
|
/// Convenience function to return std::string. The stream buffer is consumed.
|
||||||
std::string string()
|
std::string string() noexcept {
|
||||||
{
|
try {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << rdbuf();
|
ss << rdbuf();
|
||||||
return ss.str();
|
return ss.str();
|
||||||
|
}
|
||||||
|
catch(...) {
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
boost::asio::streambuf &streambuf;
|
asio::streambuf &streambuf;
|
||||||
|
Content(asio::streambuf &streambuf) noexcept : std::istream(&streambuf), streambuf(streambuf) {}
|
||||||
Content(boost::asio::streambuf &streambuf) : std::istream(&streambuf), streambuf(streambuf)
|
|
||||||
{}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Request
|
class Request {
|
||||||
{
|
|
||||||
friend class ServerBase<socket_type>;
|
friend class ServerBase<socket_type>;
|
||||||
|
|
||||||
friend class Server<socket_type>;
|
friend class Server<socket_type>;
|
||||||
|
friend class Session;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::string method, path, http_version;
|
std::string method, path, query_string, http_version;
|
||||||
|
|
||||||
Content content;
|
Content content;
|
||||||
|
|
||||||
std::unordered_multimap<std::string, std::string, case_insensitive_hash, case_insensitive_equals> header;
|
CaseInsensitiveMultimap header;
|
||||||
|
|
||||||
std::smatch path_match;
|
std::smatch path_match;
|
||||||
|
|
||||||
std::string remote_endpoint_address;
|
std::string remote_endpoint_address;
|
||||||
unsigned short remote_endpoint_port;
|
unsigned short remote_endpoint_port;
|
||||||
|
|
||||||
private:
|
/// Returns query keys with percent-decoded values.
|
||||||
Request(const socket_type &socket) : content(streambuf)
|
CaseInsensitiveMultimap parse_query_string() noexcept {
|
||||||
{
|
return SimpleWeb::QueryString::parse(query_string);
|
||||||
try
|
|
||||||
{
|
|
||||||
remote_endpoint_address = socket.lowest_layer().remote_endpoint().address().to_string();
|
|
||||||
remote_endpoint_port = socket.lowest_layer().remote_endpoint().port();
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::asio::streambuf streambuf;
|
private:
|
||||||
|
asio::streambuf streambuf;
|
||||||
|
|
||||||
|
Request(const std::string &remote_endpoint_address = std::string(), unsigned short remote_endpoint_port = 0) noexcept
|
||||||
|
: content(streambuf), remote_endpoint_address(remote_endpoint_address), remote_endpoint_port(remote_endpoint_port) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class Config
|
protected:
|
||||||
{
|
class Connection : public std::enable_shared_from_this<Connection> {
|
||||||
|
public:
|
||||||
|
template <typename... Args>
|
||||||
|
Connection(std::shared_ptr<ScopeRunner> handler_runner, Args &&... args) noexcept : handler_runner(std::move(handler_runner)), socket(new socket_type(std::forward<Args>(args)...)) {}
|
||||||
|
|
||||||
|
std::shared_ptr<ScopeRunner> handler_runner;
|
||||||
|
|
||||||
|
std::unique_ptr<socket_type> socket; // Socket must be unique_ptr since asio::ssl::stream<asio::ip::tcp::socket> is not movable
|
||||||
|
std::mutex socket_close_mutex;
|
||||||
|
|
||||||
|
std::unique_ptr<asio::steady_timer> timer;
|
||||||
|
|
||||||
|
void close() noexcept {
|
||||||
|
error_code ec;
|
||||||
|
std::unique_lock<std::mutex> lock(socket_close_mutex); // The following operations seems to be needed to run sequentially
|
||||||
|
socket->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
|
||||||
|
socket->lowest_layer().close(ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_timeout(long seconds) noexcept {
|
||||||
|
if(seconds == 0) {
|
||||||
|
timer = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timer = std::unique_ptr<asio::steady_timer>(new asio::steady_timer(socket->get_io_service()));
|
||||||
|
timer->expires_from_now(std::chrono::seconds(seconds));
|
||||||
|
auto self = this->shared_from_this();
|
||||||
|
timer->async_wait([self](const error_code &ec) {
|
||||||
|
if(!ec)
|
||||||
|
self->close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel_timeout() noexcept {
|
||||||
|
if(timer) {
|
||||||
|
error_code ec;
|
||||||
|
timer->cancel(ec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Session {
|
||||||
|
public:
|
||||||
|
Session(std::shared_ptr<Connection> connection) noexcept : connection(std::move(connection)) {
|
||||||
|
try {
|
||||||
|
auto remote_endpoint = this->connection->socket->lowest_layer().remote_endpoint();
|
||||||
|
request = std::shared_ptr<Request>(new Request(remote_endpoint.address().to_string(), remote_endpoint.port()));
|
||||||
|
}
|
||||||
|
catch(...) {
|
||||||
|
request = std::shared_ptr<Request>(new Request());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Connection> connection;
|
||||||
|
std::shared_ptr<Request> request;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
class Config {
|
||||||
friend class ServerBase<socket_type>;
|
friend class ServerBase<socket_type>;
|
||||||
|
|
||||||
Config(unsigned short port) : port(port)
|
Config(unsigned short port) noexcept : port(port) {}
|
||||||
{}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Port number to use. Defaults to 80 for HTTP and 443 for HTTPS.
|
/// Port number to use. Defaults to 80 for HTTP and 443 for HTTPS.
|
||||||
unsigned short port;
|
unsigned short port;
|
||||||
/// Number of threads that the server will use when start() is called. Defaults to 1 thread.
|
/// If io_service is not set, number of threads that the server will use when start() is called.
|
||||||
|
/// Defaults to 1 thread.
|
||||||
size_t thread_pool_size = 1;
|
size_t thread_pool_size = 1;
|
||||||
/// Timeout on request handling. Defaults to 5 seconds.
|
/// Timeout on request handling. Defaults to 5 seconds.
|
||||||
size_t timeout_request = 5;
|
long timeout_request = 5;
|
||||||
/// Timeout on content handling. Defaults to 300 seconds.
|
/// Timeout on content handling. Defaults to 300 seconds.
|
||||||
size_t timeout_content = 300;
|
long timeout_content = 300;
|
||||||
/// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation.
|
/// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation.
|
||||||
/// If empty, the address will be any address.
|
/// If empty, the address will be any address.
|
||||||
std::string address;
|
std::string address;
|
||||||
/// Set to false to avoid binding the socket to an address that is already in use. Defaults to true.
|
/// Set to false to avoid binding the socket to an address that is already in use. Defaults to true.
|
||||||
bool reuse_address = true;
|
bool reuse_address = true;
|
||||||
};
|
};
|
||||||
|
/// Set before calling start().
|
||||||
///Set before calling start().
|
|
||||||
Config config;
|
Config config;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class regex_orderable : public std::regex
|
class regex_orderable : public std::regex {
|
||||||
{
|
|
||||||
std::string str;
|
std::string str;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
regex_orderable(const char *regex_cstr) : std::regex(regex_cstr), str(regex_cstr)
|
regex_orderable(const char *regex_cstr) : std::regex(regex_cstr), str(regex_cstr) {}
|
||||||
{}
|
regex_orderable(std::string regex_str) : std::regex(regex_str), str(std::move(regex_str)) {}
|
||||||
|
bool operator<(const regex_orderable &rhs) const noexcept {
|
||||||
regex_orderable(const std::string ®ex_str) : std::regex(regex_str), str(regex_str)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool operator<(const regex_orderable &rhs) const
|
|
||||||
{
|
|
||||||
return str < rhs.str;
|
return str < rhs.str;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Warning: do not add or remove resources after start() is called
|
/// Warning: do not add or remove resources after start() is called
|
||||||
std::map<regex_orderable, std::map<std::string,
|
std::map<regex_orderable, std::map<std::string, std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)>>> resource;
|
||||||
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>,
|
|
||||||
std::shared_ptr<typename ServerBase<socket_type>::Request>)>>>
|
|
||||||
resource;
|
|
||||||
|
|
||||||
std::map<std::string,
|
std::map<std::string, std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)>> default_resource;
|
||||||
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>,
|
|
||||||
std::shared_ptr<typename ServerBase<socket_type>::Request>)>> default_resource;
|
|
||||||
|
|
||||||
std::function<
|
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Request>, const error_code &)> on_error;
|
||||||
void(std::shared_ptr<typename ServerBase<socket_type>::Request>,
|
|
||||||
const boost::system::error_code &)>
|
|
||||||
on_error;
|
|
||||||
|
|
||||||
std::function<void(std::shared_ptr<socket_type> socket,
|
std::function<void(std::unique_ptr<socket_type> &, std::shared_ptr<typename ServerBase<socket_type>::Request>)> on_upgrade;
|
||||||
std::shared_ptr<typename ServerBase<socket_type>::Request>)> on_upgrade;
|
|
||||||
|
|
||||||
virtual void start()
|
/// If you have your own asio::io_service, store its pointer here before running start().
|
||||||
{
|
std::shared_ptr<asio::io_service> io_service;
|
||||||
if (!io_service)
|
|
||||||
io_service = std::make_shared<boost::asio::io_service>();
|
|
||||||
|
|
||||||
if (io_service->stopped())
|
virtual void start() {
|
||||||
|
if(!io_service) {
|
||||||
|
io_service = std::make_shared<asio::io_service>();
|
||||||
|
internal_io_service = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(io_service->stopped())
|
||||||
io_service->reset();
|
io_service->reset();
|
||||||
|
|
||||||
boost::asio::ip::tcp::endpoint endpoint;
|
asio::ip::tcp::endpoint endpoint;
|
||||||
if (config.address.size() > 0)
|
if(config.address.size() > 0)
|
||||||
endpoint = boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(config.address),
|
endpoint = asio::ip::tcp::endpoint(asio::ip::address::from_string(config.address), config.port);
|
||||||
config.port);
|
|
||||||
else
|
else
|
||||||
endpoint = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), config.port);
|
endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v4(), config.port);
|
||||||
|
|
||||||
if (!acceptor)
|
if(!acceptor)
|
||||||
acceptor = std::unique_ptr<boost::asio::ip::tcp::acceptor>(
|
acceptor = std::unique_ptr<asio::ip::tcp::acceptor>(new asio::ip::tcp::acceptor(*io_service));
|
||||||
new boost::asio::ip::tcp::acceptor(*io_service));
|
|
||||||
acceptor->open(endpoint.protocol());
|
acceptor->open(endpoint.protocol());
|
||||||
acceptor->set_option(boost::asio::socket_base::reuse_address(config.reuse_address));
|
acceptor->set_option(asio::socket_base::reuse_address(config.reuse_address));
|
||||||
acceptor->bind(endpoint);
|
acceptor->bind(endpoint);
|
||||||
acceptor->listen();
|
acceptor->listen();
|
||||||
|
|
||||||
accept();
|
accept();
|
||||||
|
|
||||||
//If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling
|
if(internal_io_service) {
|
||||||
threads.clear();
|
// If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling
|
||||||
for (size_t c = 1; c < config.thread_pool_size; c++)
|
threads.clear();
|
||||||
{
|
for(size_t c = 1; c < config.thread_pool_size; c++) {
|
||||||
threads.emplace_back([this]()
|
threads.emplace_back([this]() {
|
||||||
{
|
this->io_service->run();
|
||||||
io_service->run();
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//Main thread
|
// Main thread
|
||||||
if (config.thread_pool_size > 0)
|
if(config.thread_pool_size > 0)
|
||||||
io_service->run();
|
io_service->run();
|
||||||
|
|
||||||
//Wait for the rest of the threads, if any, to finish as well
|
// Wait for the rest of the threads, if any, to finish as well
|
||||||
for (auto &t: threads)
|
for(auto &t : threads)
|
||||||
{
|
t.join();
|
||||||
t.join();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop()
|
/// Stop accepting new requests, and close current connections.
|
||||||
{
|
void stop() noexcept {
|
||||||
acceptor->close();
|
if(acceptor) {
|
||||||
if (config.thread_pool_size > 0)
|
error_code ec;
|
||||||
io_service->stop();
|
acceptor->close(ec);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(*connections_mutex);
|
||||||
|
for(auto &connection : *connections)
|
||||||
|
connection->close();
|
||||||
|
connections->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(internal_io_service)
|
||||||
|
io_service->stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///Use this function if you need to recursively send parts of a longer message
|
virtual ~ServerBase() noexcept {
|
||||||
void send(const std::shared_ptr<Response> &response,
|
handler_runner->stop();
|
||||||
const std::function<void(const boost::system::error_code &)> &callback = nullptr) const
|
stop();
|
||||||
{
|
|
||||||
boost::asio::async_write(*response->socket, response->streambuf, [this, response, callback]
|
|
||||||
(const boost::system::error_code &ec, size_t /*bytes_transferred*/)
|
|
||||||
{
|
|
||||||
if (callback)
|
|
||||||
callback(ec);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If you have your own boost::asio::io_service, store its pointer here before running start().
|
|
||||||
/// You might also want to set config.thread_pool_size to 0.
|
|
||||||
std::shared_ptr<boost::asio::io_service> io_service;
|
|
||||||
protected:
|
protected:
|
||||||
std::unique_ptr<boost::asio::ip::tcp::acceptor> acceptor;
|
bool internal_io_service = false;
|
||||||
|
|
||||||
|
std::unique_ptr<asio::ip::tcp::acceptor> acceptor;
|
||||||
std::vector<std::thread> threads;
|
std::vector<std::thread> threads;
|
||||||
|
|
||||||
ServerBase(unsigned short port) : config(port)
|
std::shared_ptr<std::unordered_set<Connection *>> connections;
|
||||||
{}
|
std::shared_ptr<std::mutex> connections_mutex;
|
||||||
|
|
||||||
virtual void accept()=0;
|
std::shared_ptr<ScopeRunner> handler_runner;
|
||||||
|
|
||||||
std::shared_ptr<boost::asio::deadline_timer>
|
ServerBase(unsigned short port) noexcept : config(port), connections(new std::unordered_set<Connection *>()), connections_mutex(new std::mutex()), handler_runner(new ScopeRunner()) {}
|
||||||
get_timeout_timer(const std::shared_ptr<socket_type> &socket, long seconds)
|
|
||||||
{
|
|
||||||
if (seconds == 0)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
auto timer = std::make_shared<boost::asio::deadline_timer>(*io_service);
|
virtual void accept() = 0;
|
||||||
timer->expires_from_now(boost::posix_time::seconds(seconds));
|
|
||||||
timer->async_wait([socket](const boost::system::error_code &ec)
|
template <typename... Args>
|
||||||
{
|
std::shared_ptr<Connection> create_connection(Args &&... args) noexcept {
|
||||||
if (!ec)
|
auto connections = this->connections;
|
||||||
{
|
auto connections_mutex = this->connections_mutex;
|
||||||
boost::system::error_code ec;
|
auto connection = std::shared_ptr<Connection>(new Connection(handler_runner, std::forward<Args>(args)...), [connections, connections_mutex](Connection *connection) {
|
||||||
socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
|
{
|
||||||
socket->lowest_layer().close();
|
std::unique_lock<std::mutex> lock(*connections_mutex);
|
||||||
}
|
auto it = connections->find(connection);
|
||||||
});
|
if(it != connections->end())
|
||||||
return timer;
|
connections->erase(it);
|
||||||
|
}
|
||||||
|
delete connection;
|
||||||
|
});
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(*connections_mutex);
|
||||||
|
connections->emplace(connection.get());
|
||||||
|
}
|
||||||
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
void read_request_and_content(const std::shared_ptr<socket_type> &socket)
|
void read_request_and_content(const std::shared_ptr<Session> &session) {
|
||||||
{
|
session->connection->set_timeout(config.timeout_request);
|
||||||
//Create new streambuf (Request::streambuf) for async_read_until()
|
asio::async_read_until(*session->connection->socket, session->request->streambuf, "\r\n\r\n", [this, session](const error_code &ec, size_t bytes_transferred) {
|
||||||
//shared_ptr is used to pass temporary objects to the asynchronous functions
|
session->connection->cancel_timeout();
|
||||||
std::shared_ptr<Request> request(new Request(*socket));
|
auto lock = session->connection->handler_runner->continue_lock();
|
||||||
|
if(!lock)
|
||||||
|
return;
|
||||||
|
if(!ec) {
|
||||||
|
// request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs:
|
||||||
|
// "After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter"
|
||||||
|
// The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the
|
||||||
|
// streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content).
|
||||||
|
size_t num_additional_bytes = session->request->streambuf.size() - bytes_transferred;
|
||||||
|
|
||||||
//Set timeout on the following boost::asio::async-read or write function
|
if(!RequestMessage::parse(session->request->content, session->request->method, session->request->path,
|
||||||
auto timer = this->get_timeout_timer(socket, config.timeout_request);
|
session->request->query_string, session->request->http_version, session->request->header)) {
|
||||||
|
if(this->on_error)
|
||||||
boost::asio::async_read_until(*socket, request->streambuf, "\r\n\r\n", [this, socket, request, timer]
|
this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error));
|
||||||
(const boost::system::error_code &ec,
|
|
||||||
size_t bytes_transferred)
|
|
||||||
{
|
|
||||||
if (timer)
|
|
||||||
timer->cancel();
|
|
||||||
if (!ec)
|
|
||||||
{
|
|
||||||
//request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs:
|
|
||||||
//"After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter"
|
|
||||||
//The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the
|
|
||||||
//streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content).
|
|
||||||
size_t num_additional_bytes =
|
|
||||||
request->streambuf.size() - bytes_transferred;
|
|
||||||
|
|
||||||
if (!this->parse_request(request))
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//If content, read that as well
|
// If content, read that as well
|
||||||
auto it = request->header.find("Content-Length");
|
auto it = session->request->header.find("Content-Length");
|
||||||
if (it != request->header.end())
|
if(it != session->request->header.end()) {
|
||||||
{
|
unsigned long long content_length = 0;
|
||||||
unsigned long long content_length;
|
try {
|
||||||
try
|
|
||||||
{
|
|
||||||
content_length = stoull(it->second);
|
content_length = stoull(it->second);
|
||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch(const std::exception &e) {
|
||||||
{
|
if(this->on_error)
|
||||||
if (on_error)
|
this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error));
|
||||||
on_error(request, boost::system::error_code(
|
|
||||||
boost::system::errc::protocol_error,
|
|
||||||
boost::system::generic_category()));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (content_length > num_additional_bytes)
|
if(content_length > num_additional_bytes) {
|
||||||
{
|
session->connection->set_timeout(config.timeout_content);
|
||||||
//Set timeout on the following boost::asio::async-read or write function
|
asio::async_read(*session->connection->socket, session->request->streambuf, asio::transfer_exactly(content_length - num_additional_bytes), [this, session](const error_code &ec, size_t /*bytes_transferred*/) {
|
||||||
auto timer = this->get_timeout_timer(socket,
|
session->connection->cancel_timeout();
|
||||||
config.timeout_content);
|
auto lock = session->connection->handler_runner->continue_lock();
|
||||||
boost::asio::async_read(*socket, request->streambuf,
|
if(!lock)
|
||||||
boost::asio::transfer_exactly(
|
return;
|
||||||
content_length -
|
if(!ec)
|
||||||
num_additional_bytes),
|
this->find_resource(session);
|
||||||
[this, socket, request, timer]
|
else if(this->on_error)
|
||||||
(const boost::system::error_code &ec,
|
this->on_error(session->request, ec);
|
||||||
size_t /*bytes_transferred*/)
|
});
|
||||||
{
|
|
||||||
if (timer)
|
|
||||||
timer->cancel();
|
|
||||||
if (!ec)
|
|
||||||
this->find_resource(socket,
|
|
||||||
request);
|
|
||||||
else if (on_error)
|
|
||||||
on_error(request, ec);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
this->find_resource(socket, request);
|
this->find_resource(session);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
this->find_resource(socket, request);
|
this->find_resource(session);
|
||||||
}
|
}
|
||||||
else if (on_error)
|
else if(this->on_error)
|
||||||
on_error(request, ec);
|
this->on_error(session->request, ec);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool parse_request(const std::shared_ptr<Request> &request) const
|
void find_resource(const std::shared_ptr<Session> &session) {
|
||||||
{
|
// Upgrade connection
|
||||||
std::string line;
|
if(on_upgrade) {
|
||||||
getline(request->content, line);
|
auto it = session->request->header.find("Upgrade");
|
||||||
size_t method_end;
|
if(it != session->request->header.end()) {
|
||||||
if ((method_end = line.find(' ')) != std::string::npos)
|
// remove connection from connections
|
||||||
{
|
|
||||||
size_t path_end;
|
|
||||||
if ((path_end = line.find(' ', method_end + 1)) != std::string::npos)
|
|
||||||
{
|
|
||||||
request->method = line.substr(0, method_end);
|
|
||||||
request->path = line.substr(method_end + 1, path_end - method_end - 1);
|
|
||||||
|
|
||||||
size_t protocol_end;
|
|
||||||
if ((protocol_end = line.find('/', path_end + 1)) != std::string::npos)
|
|
||||||
{
|
{
|
||||||
if (line.compare(path_end + 1, protocol_end - path_end - 1, "HTTP") != 0)
|
std::unique_lock<std::mutex> lock(*connections_mutex);
|
||||||
return false;
|
auto it = connections->find(session->connection.get());
|
||||||
request->http_version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
|
if(it != connections->end())
|
||||||
|
connections->erase(it);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
return false;
|
|
||||||
|
|
||||||
getline(request->content, line);
|
on_upgrade(session->connection->socket, session->request);
|
||||||
size_t param_end;
|
|
||||||
while ((param_end = line.find(':')) != std::string::npos)
|
|
||||||
{
|
|
||||||
size_t value_start = param_end + 1;
|
|
||||||
if ((value_start) < line.size())
|
|
||||||
{
|
|
||||||
if (line[value_start] == ' ')
|
|
||||||
value_start++;
|
|
||||||
if (value_start < line.size())
|
|
||||||
request->header.emplace(line.substr(0, param_end),
|
|
||||||
line.substr(value_start, line.size() - value_start - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
getline(request->content, line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void find_resource(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request)
|
|
||||||
{
|
|
||||||
//Upgrade connection
|
|
||||||
if (on_upgrade)
|
|
||||||
{
|
|
||||||
auto it = request->header.find("Upgrade");
|
|
||||||
if (it != request->header.end())
|
|
||||||
{
|
|
||||||
on_upgrade(socket, request);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Find path- and method-match, and call write_response
|
// Find path- and method-match, and call write_response
|
||||||
for (auto ®ex_method: resource)
|
for(auto ®ex_method : resource) {
|
||||||
{
|
auto it = regex_method.second.find(session->request->method);
|
||||||
auto it = regex_method.second.find(request->method);
|
if(it != regex_method.second.end()) {
|
||||||
if (it != regex_method.second.end())
|
|
||||||
{
|
|
||||||
std::smatch sm_res;
|
std::smatch sm_res;
|
||||||
if (std::regex_match(request->path, sm_res, regex_method.first))
|
if(std::regex_match(session->request->path, sm_res, regex_method.first)) {
|
||||||
{
|
session->request->path_match = std::move(sm_res);
|
||||||
request->path_match = std::move(sm_res);
|
write_response(session, it->second);
|
||||||
write_response(socket, request, it->second);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto it = default_resource.find(request->method);
|
auto it = default_resource.find(session->request->method);
|
||||||
if (it != default_resource.end())
|
if(it != default_resource.end())
|
||||||
{
|
write_response(session, it->second);
|
||||||
write_response(socket, request, it->second);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_response(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request,
|
void write_response(const std::shared_ptr<Session> &session,
|
||||||
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>,
|
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)> &resource_function) {
|
||||||
std::shared_ptr<
|
session->connection->set_timeout(config.timeout_content);
|
||||||
typename ServerBase<socket_type>::Request>)> &resource_function)
|
auto response = std::shared_ptr<Response>(new Response(session, config.timeout_content), [this](Response *response_ptr) {
|
||||||
{
|
|
||||||
//Set timeout on the following boost::asio::async-read or write function
|
|
||||||
auto timer = this->get_timeout_timer(socket, config.timeout_content);
|
|
||||||
|
|
||||||
auto response = std::shared_ptr<Response>(new Response(socket), [this, request, timer]
|
|
||||||
(Response *response_ptr)
|
|
||||||
{
|
|
||||||
auto response = std::shared_ptr<Response>(response_ptr);
|
auto response = std::shared_ptr<Response>(response_ptr);
|
||||||
this->send(response, [this, response, request, timer](
|
response->send([this, response](const error_code &ec) {
|
||||||
const boost::system::error_code &ec)
|
if(!ec) {
|
||||||
{
|
if(response->close_connection_after_response)
|
||||||
if (timer)
|
|
||||||
timer->cancel();
|
|
||||||
if (!ec)
|
|
||||||
{
|
|
||||||
if (response->close_connection_after_response)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto range = request->header.equal_range(
|
auto range = response->session->request->header.equal_range("Connection");
|
||||||
"Connection");
|
for(auto it = range.first; it != range.second; it++) {
|
||||||
for (auto it = range.first; it != range.second; it++)
|
if(case_insensitive_equal(it->second, "close"))
|
||||||
{
|
|
||||||
if (boost::iequals(it->second, "close"))
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
else if(case_insensitive_equal(it->second, "keep-alive")) {
|
||||||
else if (boost::iequals(it->second, "keep-alive"))
|
auto new_session = std::make_shared<Session>(response->session->connection);
|
||||||
{
|
this->read_request_and_content(new_session);
|
||||||
this->read_request_and_content(
|
|
||||||
response->socket);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (request->http_version >= "1.1")
|
if(response->session->request->http_version >= "1.1") {
|
||||||
this->read_request_and_content(response->socket);
|
auto new_session = std::make_shared<Session>(response->session->connection);
|
||||||
|
this->read_request_and_content(new_session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (on_error)
|
else if(this->on_error)
|
||||||
on_error(request, ec);
|
this->on_error(response->session->request, ec);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
resource_function(response, session->request);
|
||||||
resource_function(response, request);
|
|
||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch(const std::exception &e) {
|
||||||
{
|
if(on_error)
|
||||||
if (on_error)
|
on_error(session->request, make_error_code::make_error_code(errc::operation_canceled));
|
||||||
on_error(request, boost::system::error_code(boost::system::errc::operation_canceled,
|
|
||||||
boost::system::generic_category()));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif //BASE_SERVER_HPP
|
|
||||||
|
|
|
@ -1,55 +1,42 @@
|
||||||
/*
|
#pragma once
|
||||||
* https://github.com/eidheim/Simple-Web-Server/
|
|
||||||
*
|
|
||||||
* The MIT License (MIT)
|
|
||||||
* Copyright (c) 2014-2016 Ole Christian Eidheim
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef SERVER_HTTP_HPP
|
|
||||||
#define SERVER_HTTP_HPP
|
|
||||||
|
|
||||||
#include "base_server.hpp"
|
#include "base_server.hpp"
|
||||||
|
|
||||||
namespace SimpleWeb
|
namespace SimpleWeb {
|
||||||
{
|
|
||||||
|
|
||||||
template<class socket_type>
|
template <class socket_type>
|
||||||
class Server : public ServerBase<socket_type> {};
|
class Server : public ServerBase<socket_type> {};
|
||||||
|
|
||||||
typedef boost::asio::ip::tcp::socket HTTP;
|
using HTTP = asio::ip::tcp::socket;
|
||||||
|
|
||||||
template<>
|
template <>
|
||||||
class Server<HTTP> : public ServerBase<HTTP>
|
class Server<HTTP> : public ServerBase<HTTP> {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
Server() : ServerBase<HTTP>::ServerBase(80)
|
Server() noexcept : ServerBase<HTTP>::ServerBase(80) {}
|
||||||
{}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void accept()
|
void accept() override {
|
||||||
{
|
auto session = std::make_shared<Session>(create_connection(*io_service));
|
||||||
//Create new socket for this connection
|
|
||||||
//Shared_ptr is used to pass temporary objects to the asynchronous functions
|
|
||||||
auto socket = std::make_shared<HTTP>(*io_service);
|
|
||||||
|
|
||||||
acceptor->async_accept(*socket, [this, socket](const boost::system::error_code &ec)
|
acceptor->async_accept(*session->connection->socket, [this, session](const error_code &ec) {
|
||||||
{
|
auto lock = session->connection->handler_runner->continue_lock();
|
||||||
//Immediately start accepting a new connection (if io_service hasn't been stopped)
|
if(!lock)
|
||||||
if (ec != boost::asio::error::operation_aborted)
|
return;
|
||||||
accept();
|
|
||||||
|
|
||||||
if (!ec)
|
// Immediately start accepting a new connection (unless io_service has been stopped)
|
||||||
{
|
if(ec != asio::error::operation_aborted)
|
||||||
boost::asio::ip::tcp::no_delay option(true);
|
this->accept();
|
||||||
socket->set_option(option);
|
|
||||||
|
|
||||||
this->read_request_and_content(socket);
|
if(!ec) {
|
||||||
|
asio::ip::tcp::no_delay option(true);
|
||||||
|
error_code ec;
|
||||||
|
session->connection->socket->set_option(option, ec);
|
||||||
|
|
||||||
|
this->read_request_and_content(session);
|
||||||
}
|
}
|
||||||
else if (on_error)
|
else if(this->on_error)
|
||||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
this->on_error(session->request, ec);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
} // namespace SimpleWeb
|
||||||
|
|
||||||
#endif //SERVER_HTTP_HPP
|
|
||||||
|
|
|
@ -1,91 +1,82 @@
|
||||||
#ifndef HTTPS_SERVER_HPP
|
#pragma once
|
||||||
#define HTTPS_SERVER_HPP
|
|
||||||
|
|
||||||
#include "base_server.hpp"
|
#include "base_server.hpp"
|
||||||
|
|
||||||
|
#ifdef USE_STANDALONE_ASIO
|
||||||
|
#include <asio/ssl.hpp>
|
||||||
|
#else
|
||||||
#include <boost/asio/ssl.hpp>
|
#include <boost/asio/ssl.hpp>
|
||||||
#include <openssl/ssl.h>
|
#endif
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
namespace SimpleWeb
|
namespace SimpleWeb {
|
||||||
{
|
using HTTPS = asio::ssl::stream<asio::ip::tcp::socket>;
|
||||||
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> HTTPS;
|
|
||||||
|
|
||||||
template<>
|
template <>
|
||||||
class Server<HTTPS> : public ServerBase<HTTPS>
|
class Server<HTTPS> : public ServerBase<HTTPS> {
|
||||||
{
|
|
||||||
std::string session_id_context;
|
std::string session_id_context;
|
||||||
bool set_session_id_context = false;
|
bool set_session_id_context = false;
|
||||||
public:
|
|
||||||
Server(const std::string &cert_file, const std::string &private_key_file,
|
|
||||||
const std::string &verify_file = std::string()) : ServerBase<HTTPS>::ServerBase(443),
|
|
||||||
context(boost::asio::ssl::context::tlsv12)
|
|
||||||
{
|
|
||||||
context.use_certificate_chain_file(cert_file);
|
|
||||||
context.use_private_key_file(private_key_file, boost::asio::ssl::context::pem);
|
|
||||||
|
|
||||||
if (verify_file.size() > 0)
|
public:
|
||||||
{
|
Server(const std::string &cert_file, const std::string &private_key_file, const std::string &verify_file = std::string())
|
||||||
|
: ServerBase<HTTPS>::ServerBase(443), context(asio::ssl::context::tlsv12) {
|
||||||
|
context.use_certificate_chain_file(cert_file);
|
||||||
|
context.use_private_key_file(private_key_file, asio::ssl::context::pem);
|
||||||
|
|
||||||
|
if(verify_file.size() > 0) {
|
||||||
context.load_verify_file(verify_file);
|
context.load_verify_file(verify_file);
|
||||||
context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert |
|
context.set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert | asio::ssl::verify_client_once);
|
||||||
boost::asio::ssl::verify_client_once);
|
|
||||||
set_session_id_context = true;
|
set_session_id_context = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void start()
|
void start() override {
|
||||||
{
|
if(set_session_id_context) {
|
||||||
if (set_session_id_context)
|
|
||||||
{
|
|
||||||
// Creating session_id_context from address:port but reversed due to small SSL_MAX_SSL_SESSION_ID_LENGTH
|
// Creating session_id_context from address:port but reversed due to small SSL_MAX_SSL_SESSION_ID_LENGTH
|
||||||
session_id_context = std::to_string(config.port) + ':';
|
session_id_context = std::to_string(config.port) + ':';
|
||||||
session_id_context.append(config.address.rbegin(), config.address.rend());
|
session_id_context.append(config.address.rbegin(), config.address.rend());
|
||||||
SSL_CTX_set_session_id_context(context.native_handle(),
|
SSL_CTX_set_session_id_context(context.native_handle(), reinterpret_cast<const unsigned char *>(session_id_context.data()),
|
||||||
reinterpret_cast<const unsigned char *>(session_id_context.data()),
|
std::min<size_t>(session_id_context.size(), SSL_MAX_SSL_SESSION_ID_LENGTH));
|
||||||
std::min<size_t>(session_id_context.size(),
|
|
||||||
SSL_MAX_SSL_SESSION_ID_LENGTH));
|
|
||||||
}
|
}
|
||||||
ServerBase::start();
|
ServerBase::start();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
boost::asio::ssl::context context;
|
asio::ssl::context context;
|
||||||
|
|
||||||
virtual void accept()
|
void accept() override {
|
||||||
{
|
auto session = std::make_shared<Session>(create_connection(*io_service, context));
|
||||||
//Create new socket for this connection
|
|
||||||
//Shared_ptr is used to pass temporary objects to the asynchronous functions
|
|
||||||
auto socket = std::make_shared<HTTPS>(*io_service, context);
|
|
||||||
|
|
||||||
acceptor->async_accept((*socket).lowest_layer(), [this, socket](const boost::system::error_code &ec)
|
acceptor->async_accept(session->connection->socket->lowest_layer(), [this, session](const error_code &ec) {
|
||||||
{
|
auto lock = session->connection->handler_runner->continue_lock();
|
||||||
//Immediately start accepting a new connection (if io_service hasn't been stopped)
|
if(!lock)
|
||||||
if (ec != boost::asio::error::operation_aborted)
|
return;
|
||||||
accept();
|
|
||||||
|
|
||||||
|
if(ec != asio::error::operation_aborted)
|
||||||
|
this->accept();
|
||||||
|
|
||||||
if (!ec)
|
if(!ec) {
|
||||||
{
|
asio::ip::tcp::no_delay option(true);
|
||||||
boost::asio::ip::tcp::no_delay option(true);
|
error_code ec;
|
||||||
socket->lowest_layer().set_option(option);
|
session->connection->socket->lowest_layer().set_option(option, ec);
|
||||||
|
|
||||||
//Set timeout on the following boost::asio::ssl::stream::async_handshake
|
session->connection->set_timeout(config.timeout_request);
|
||||||
auto timer = get_timeout_timer(socket, config.timeout_request);
|
session->connection->socket->async_handshake(asio::ssl::stream_base::server, [this, session](const error_code &ec) {
|
||||||
socket->async_handshake(boost::asio::ssl::stream_base::server, [this, socket, timer]
|
session->connection->cancel_timeout();
|
||||||
(const boost::system::error_code &ec)
|
auto lock = session->connection->handler_runner->continue_lock();
|
||||||
{
|
if(!lock)
|
||||||
if (timer)
|
return;
|
||||||
timer->cancel();
|
if(!ec)
|
||||||
if (!ec)
|
this->read_request_and_content(session);
|
||||||
read_request_and_content(socket);
|
else if(this->on_error)
|
||||||
else if (on_error)
|
this->on_error(session->request, ec);
|
||||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (on_error)
|
else if(this->on_error)
|
||||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
this->on_error(session->request, ec);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
} // namespace SimpleWeb
|
||||||
|
|
||||||
#endif //HTTPS_SERVER_HPP
|
|
||||||
|
|
154
apps/master/SimpleWeb/status_code.hpp
Normal file
154
apps/master/SimpleWeb/status_code.hpp
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace SimpleWeb {
|
||||||
|
enum class StatusCode {
|
||||||
|
unknown = 0,
|
||||||
|
information_continue = 100,
|
||||||
|
information_switching_protocols,
|
||||||
|
information_processing,
|
||||||
|
success_ok = 200,
|
||||||
|
success_created,
|
||||||
|
success_accepted,
|
||||||
|
success_non_authoritative_information,
|
||||||
|
success_no_content,
|
||||||
|
success_reset_content,
|
||||||
|
success_partial_content,
|
||||||
|
success_multi_status,
|
||||||
|
success_already_reported,
|
||||||
|
success_im_used = 226,
|
||||||
|
redirection_multiple_choices = 300,
|
||||||
|
redirection_moved_permanently,
|
||||||
|
redirection_found,
|
||||||
|
redirection_see_other,
|
||||||
|
redirection_not_modified,
|
||||||
|
redirection_use_proxy,
|
||||||
|
redirection_switch_proxy,
|
||||||
|
redirection_temporary_redirect,
|
||||||
|
redirection_permanent_redirect,
|
||||||
|
client_error_bad_request = 400,
|
||||||
|
client_error_unauthorized,
|
||||||
|
client_error_payment_required,
|
||||||
|
client_error_forbidden,
|
||||||
|
client_error_not_found,
|
||||||
|
client_error_method_not_allowed,
|
||||||
|
client_error_not_acceptable,
|
||||||
|
client_error_proxy_authentication_required,
|
||||||
|
client_error_request_timeout,
|
||||||
|
client_error_conflict,
|
||||||
|
client_error_gone,
|
||||||
|
client_error_length_required,
|
||||||
|
client_error_precondition_failed,
|
||||||
|
client_error_payload_too_large,
|
||||||
|
client_error_uri_too_long,
|
||||||
|
client_error_unsupported_media_type,
|
||||||
|
client_error_range_not_satisfiable,
|
||||||
|
client_error_expectation_failed,
|
||||||
|
client_error_im_a_teapot,
|
||||||
|
client_error_misdirection_required = 421,
|
||||||
|
client_error_unprocessable_entity,
|
||||||
|
client_error_locked,
|
||||||
|
client_error_failed_dependency,
|
||||||
|
client_error_upgrade_required = 426,
|
||||||
|
client_error_precondition_required = 428,
|
||||||
|
client_error_too_many_requests,
|
||||||
|
client_error_request_header_fields_too_large = 431,
|
||||||
|
client_error_unavailable_for_legal_reasons = 451,
|
||||||
|
server_error_internal_server_error = 500,
|
||||||
|
server_error_not_implemented,
|
||||||
|
server_error_bad_gateway,
|
||||||
|
server_error_service_unavailable,
|
||||||
|
server_error_gateway_timeout,
|
||||||
|
server_error_http_version_not_supported,
|
||||||
|
server_error_variant_also_negotiates,
|
||||||
|
server_error_insufficient_storage,
|
||||||
|
server_error_loop_detected,
|
||||||
|
server_error_not_extended = 510,
|
||||||
|
server_error_network_authentication_required
|
||||||
|
};
|
||||||
|
|
||||||
|
const static std::vector<std::pair<StatusCode, std::string>> &status_codes() noexcept {
|
||||||
|
const static std::vector<std::pair<StatusCode, std::string>> status_codes = {
|
||||||
|
{StatusCode::unknown, ""},
|
||||||
|
{StatusCode::information_continue, "100 Continue"},
|
||||||
|
{StatusCode::information_switching_protocols, "101 Switching Protocols"},
|
||||||
|
{StatusCode::information_processing, "102 Processing"},
|
||||||
|
{StatusCode::success_ok, "200 OK"},
|
||||||
|
{StatusCode::success_created, "201 Created"},
|
||||||
|
{StatusCode::success_accepted, "202 Accepted"},
|
||||||
|
{StatusCode::success_non_authoritative_information, "203 Non-Authoritative Information"},
|
||||||
|
{StatusCode::success_no_content, "204 No Content"},
|
||||||
|
{StatusCode::success_reset_content, "205 Reset Content"},
|
||||||
|
{StatusCode::success_partial_content, "206 Partial Content"},
|
||||||
|
{StatusCode::success_multi_status, "207 Multi-Status"},
|
||||||
|
{StatusCode::success_already_reported, "208 Already Reported"},
|
||||||
|
{StatusCode::success_im_used, "226 IM Used"},
|
||||||
|
{StatusCode::redirection_multiple_choices, "300 Multiple Choices"},
|
||||||
|
{StatusCode::redirection_moved_permanently, "301 Moved Permanently"},
|
||||||
|
{StatusCode::redirection_found, "302 Found"},
|
||||||
|
{StatusCode::redirection_see_other, "303 See Other"},
|
||||||
|
{StatusCode::redirection_not_modified, "304 Not Modified"},
|
||||||
|
{StatusCode::redirection_use_proxy, "305 Use Proxy"},
|
||||||
|
{StatusCode::redirection_switch_proxy, "306 Switch Proxy"},
|
||||||
|
{StatusCode::redirection_temporary_redirect, "307 Temporary Redirect"},
|
||||||
|
{StatusCode::redirection_permanent_redirect, "308 Permanent Redirect"},
|
||||||
|
{StatusCode::client_error_bad_request, "400 Bad Request"},
|
||||||
|
{StatusCode::client_error_unauthorized, "401 Unauthorized"},
|
||||||
|
{StatusCode::client_error_payment_required, "402 Payment Required"},
|
||||||
|
{StatusCode::client_error_forbidden, "403 Forbidden"},
|
||||||
|
{StatusCode::client_error_not_found, "404 Not Found"},
|
||||||
|
{StatusCode::client_error_method_not_allowed, "405 Method Not Allowed"},
|
||||||
|
{StatusCode::client_error_not_acceptable, "406 Not Acceptable"},
|
||||||
|
{StatusCode::client_error_proxy_authentication_required, "407 Proxy Authentication Required"},
|
||||||
|
{StatusCode::client_error_request_timeout, "408 Request Timeout"},
|
||||||
|
{StatusCode::client_error_conflict, "409 Conflict"},
|
||||||
|
{StatusCode::client_error_gone, "410 Gone"},
|
||||||
|
{StatusCode::client_error_length_required, "411 Length Required"},
|
||||||
|
{StatusCode::client_error_precondition_failed, "412 Precondition Failed"},
|
||||||
|
{StatusCode::client_error_payload_too_large, "413 Payload Too Large"},
|
||||||
|
{StatusCode::client_error_uri_too_long, "414 URI Too Long"},
|
||||||
|
{StatusCode::client_error_unsupported_media_type, "415 Unsupported Media Type"},
|
||||||
|
{StatusCode::client_error_range_not_satisfiable, "416 Range Not Satisfiable"},
|
||||||
|
{StatusCode::client_error_expectation_failed, "417 Expectation Failed"},
|
||||||
|
{StatusCode::client_error_im_a_teapot, "418 I'm a teapot"},
|
||||||
|
{StatusCode::client_error_misdirection_required, "421 Misdirected Request"},
|
||||||
|
{StatusCode::client_error_unprocessable_entity, "422 Unprocessable Entity"},
|
||||||
|
{StatusCode::client_error_locked, "423 Locked"},
|
||||||
|
{StatusCode::client_error_failed_dependency, "424 Failed Dependency"},
|
||||||
|
{StatusCode::client_error_upgrade_required, "426 Upgrade Required"},
|
||||||
|
{StatusCode::client_error_precondition_required, "428 Precondition Required"},
|
||||||
|
{StatusCode::client_error_too_many_requests, "429 Too Many Requests"},
|
||||||
|
{StatusCode::client_error_request_header_fields_too_large, "431 Request Header Fields Too Large"},
|
||||||
|
{StatusCode::client_error_unavailable_for_legal_reasons, "451 Unavailable For Legal Reasons"},
|
||||||
|
{StatusCode::server_error_internal_server_error, "500 Internal Server Error"},
|
||||||
|
{StatusCode::server_error_not_implemented, "501 Not Implemented"},
|
||||||
|
{StatusCode::server_error_bad_gateway, "502 Bad Gateway"},
|
||||||
|
{StatusCode::server_error_service_unavailable, "503 Service Unavailable"},
|
||||||
|
{StatusCode::server_error_gateway_timeout, "504 Gateway Timeout"},
|
||||||
|
{StatusCode::server_error_http_version_not_supported, "505 HTTP Version Not Supported"},
|
||||||
|
{StatusCode::server_error_variant_also_negotiates, "506 Variant Also Negotiates"},
|
||||||
|
{StatusCode::server_error_insufficient_storage, "507 Insufficient Storage"},
|
||||||
|
{StatusCode::server_error_loop_detected, "508 Loop Detected"},
|
||||||
|
{StatusCode::server_error_not_extended, "510 Not Extended"},
|
||||||
|
{StatusCode::server_error_network_authentication_required, "511 Network Authentication Required"}};
|
||||||
|
return status_codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline StatusCode status_code(const std::string &status_code_str) noexcept {
|
||||||
|
for(auto &status_code : status_codes()) {
|
||||||
|
if(status_code.second == status_code_str)
|
||||||
|
return status_code.first;
|
||||||
|
}
|
||||||
|
return StatusCode::unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const std::string &status_code(StatusCode status_code_enum) noexcept {
|
||||||
|
for(auto &status_code : status_codes()) {
|
||||||
|
if(status_code.first == status_code_enum)
|
||||||
|
return status_code.second;
|
||||||
|
}
|
||||||
|
return status_codes()[0].second;
|
||||||
|
}
|
||||||
|
} // namespace SimpleWeb
|
340
apps/master/SimpleWeb/utility.hpp
Normal file
340
apps/master/SimpleWeb/utility.hpp
Normal file
|
@ -0,0 +1,340 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "status_code.hpp"
|
||||||
|
#include <atomic>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace SimpleWeb {
|
||||||
|
inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) noexcept {
|
||||||
|
return str1.size() == str2.size() &&
|
||||||
|
std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) {
|
||||||
|
return tolower(a) == tolower(b);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
class CaseInsensitiveEqual {
|
||||||
|
public:
|
||||||
|
bool operator()(const std::string &str1, const std::string &str2) const noexcept {
|
||||||
|
return case_insensitive_equal(str1, str2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226
|
||||||
|
class CaseInsensitiveHash {
|
||||||
|
public:
|
||||||
|
size_t operator()(const std::string &str) const noexcept {
|
||||||
|
size_t h = 0;
|
||||||
|
std::hash<int> hash;
|
||||||
|
for(auto c : str)
|
||||||
|
h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using CaseInsensitiveMultimap = std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual>;
|
||||||
|
|
||||||
|
/// Percent encoding and decoding
|
||||||
|
class Percent {
|
||||||
|
public:
|
||||||
|
/// Returns percent-encoded string
|
||||||
|
static std::string encode(const std::string &value) noexcept {
|
||||||
|
static auto hex_chars = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
result.reserve(value.size()); // Minimum size of result
|
||||||
|
|
||||||
|
for(auto &chr : value) {
|
||||||
|
if(chr == ' ')
|
||||||
|
result += '+';
|
||||||
|
else if(chr == '!' || chr == '#' || chr == '$' || (chr >= '&' && chr <= ',') || (chr >= '/' && chr <= ';') || chr == '=' || chr == '?' || chr == '@' || chr == '[' || chr == ']')
|
||||||
|
result += std::string("%") + hex_chars[chr >> 4] + hex_chars[chr & 15];
|
||||||
|
else
|
||||||
|
result += chr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns percent-decoded string
|
||||||
|
static std::string decode(const std::string &value) noexcept {
|
||||||
|
std::string result;
|
||||||
|
result.reserve(value.size() / 3 + (value.size() % 3)); // Minimum size of result
|
||||||
|
|
||||||
|
for(size_t i = 0; i < value.size(); ++i) {
|
||||||
|
auto &chr = value[i];
|
||||||
|
if(chr == '%' && i + 2 < value.size()) {
|
||||||
|
auto hex = value.substr(i + 1, 2);
|
||||||
|
auto decoded_chr = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
|
||||||
|
result += decoded_chr;
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
else if(chr == '+')
|
||||||
|
result += ' ';
|
||||||
|
else
|
||||||
|
result += chr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Query string creation and parsing
|
||||||
|
class QueryString {
|
||||||
|
public:
|
||||||
|
/// Returns query string created from given field names and values
|
||||||
|
static std::string create(const CaseInsensitiveMultimap &fields) noexcept {
|
||||||
|
std::string result;
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
for(auto &field : fields) {
|
||||||
|
result += (!first ? "&" : "") + field.first + '=' + Percent::encode(field.second);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns query keys with percent-decoded values.
|
||||||
|
static CaseInsensitiveMultimap parse(const std::string &query_string) noexcept {
|
||||||
|
CaseInsensitiveMultimap result;
|
||||||
|
|
||||||
|
if(query_string.empty())
|
||||||
|
return result;
|
||||||
|
|
||||||
|
size_t name_pos = 0;
|
||||||
|
auto name_end_pos = std::string::npos;
|
||||||
|
auto value_pos = std::string::npos;
|
||||||
|
for(size_t c = 0; c < query_string.size(); ++c) {
|
||||||
|
if(query_string[c] == '&') {
|
||||||
|
auto name = query_string.substr(name_pos, (name_end_pos == std::string::npos ? c : name_end_pos) - name_pos);
|
||||||
|
if(!name.empty()) {
|
||||||
|
auto value = value_pos == std::string::npos ? std::string() : query_string.substr(value_pos, c - value_pos);
|
||||||
|
result.emplace(std::move(name), Percent::decode(value));
|
||||||
|
}
|
||||||
|
name_pos = c + 1;
|
||||||
|
name_end_pos = std::string::npos;
|
||||||
|
value_pos = std::string::npos;
|
||||||
|
}
|
||||||
|
else if(query_string[c] == '=') {
|
||||||
|
name_end_pos = c;
|
||||||
|
value_pos = c + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(name_pos < query_string.size()) {
|
||||||
|
auto name = query_string.substr(name_pos, name_end_pos - name_pos);
|
||||||
|
if(!name.empty()) {
|
||||||
|
auto value = value_pos >= query_string.size() ? std::string() : query_string.substr(value_pos);
|
||||||
|
result.emplace(std::move(name), Percent::decode(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class HttpHeader {
|
||||||
|
public:
|
||||||
|
/// Parse header fields
|
||||||
|
static CaseInsensitiveMultimap parse(std::istream &stream) noexcept {
|
||||||
|
CaseInsensitiveMultimap result;
|
||||||
|
std::string line;
|
||||||
|
getline(stream, line);
|
||||||
|
size_t param_end;
|
||||||
|
while((param_end = line.find(':')) != std::string::npos) {
|
||||||
|
size_t value_start = param_end + 1;
|
||||||
|
if(value_start < line.size()) {
|
||||||
|
if(line[value_start] == ' ')
|
||||||
|
value_start++;
|
||||||
|
if(value_start < line.size())
|
||||||
|
result.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
getline(stream, line);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class RequestMessage {
|
||||||
|
public:
|
||||||
|
/// Parse request line and header fields
|
||||||
|
static bool parse(std::istream &stream, std::string &method, std::string &path, std::string &query_string, std::string &version, CaseInsensitiveMultimap &header) noexcept {
|
||||||
|
header.clear();
|
||||||
|
std::string line;
|
||||||
|
getline(stream, line);
|
||||||
|
size_t method_end;
|
||||||
|
if((method_end = line.find(' ')) != std::string::npos) {
|
||||||
|
method = line.substr(0, method_end);
|
||||||
|
|
||||||
|
size_t query_start = std::string::npos;
|
||||||
|
size_t path_and_query_string_end = std::string::npos;
|
||||||
|
for(size_t i = method_end + 1; i < line.size(); ++i) {
|
||||||
|
if(line[i] == '?' && (i + 1) < line.size())
|
||||||
|
query_start = i + 1;
|
||||||
|
else if(line[i] == ' ') {
|
||||||
|
path_and_query_string_end = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(path_and_query_string_end != std::string::npos) {
|
||||||
|
if(query_start != std::string::npos) {
|
||||||
|
path = line.substr(method_end + 1, query_start - method_end - 2);
|
||||||
|
query_string = line.substr(query_start, path_and_query_string_end - query_start);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
path = line.substr(method_end + 1, path_and_query_string_end - method_end - 1);
|
||||||
|
|
||||||
|
size_t protocol_end;
|
||||||
|
if((protocol_end = line.find('/', path_and_query_string_end + 1)) != std::string::npos) {
|
||||||
|
if(line.compare(path_and_query_string_end + 1, protocol_end - path_and_query_string_end - 1, "HTTP") != 0)
|
||||||
|
return false;
|
||||||
|
version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
|
||||||
|
header = HttpHeader::parse(stream);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ResponseMessage {
|
||||||
|
public:
|
||||||
|
/// Parse status line and header fields
|
||||||
|
static bool parse(std::istream &stream, std::string &version, std::string &status_code, CaseInsensitiveMultimap &header) noexcept {
|
||||||
|
header.clear();
|
||||||
|
std::string line;
|
||||||
|
getline(stream, line);
|
||||||
|
size_t version_end = line.find(' ');
|
||||||
|
if(version_end != std::string::npos) {
|
||||||
|
if(5 < line.size())
|
||||||
|
version = line.substr(5, version_end - 5);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
if((version_end + 1) < line.size())
|
||||||
|
status_code = line.substr(version_end + 1, line.size() - (version_end + 1) - 1);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
|
||||||
|
header = HttpHeader::parse(stream);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ContentDisposition {
|
||||||
|
public:
|
||||||
|
/// Can be used to parse the Content-Disposition header field value when
|
||||||
|
/// clients are posting requests with enctype="multipart/form-data"
|
||||||
|
static CaseInsensitiveMultimap parse(const std::string &line) {
|
||||||
|
CaseInsensitiveMultimap result;
|
||||||
|
|
||||||
|
size_t para_start_pos = 0;
|
||||||
|
size_t para_end_pos = std::string::npos;
|
||||||
|
size_t value_start_pos = std::string::npos;
|
||||||
|
for(size_t c = 0; c < line.size(); ++c) {
|
||||||
|
if(para_start_pos != std::string::npos) {
|
||||||
|
if(para_end_pos == std::string::npos) {
|
||||||
|
if(line[c] == ';') {
|
||||||
|
result.emplace(line.substr(para_start_pos, c - para_start_pos), std::string());
|
||||||
|
para_start_pos = std::string::npos;
|
||||||
|
}
|
||||||
|
else if(line[c] == '=')
|
||||||
|
para_end_pos = c;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(value_start_pos == std::string::npos) {
|
||||||
|
if(line[c] == '"' && c + 1 < line.size())
|
||||||
|
value_start_pos = c + 1;
|
||||||
|
}
|
||||||
|
else if(line[c] == '"') {
|
||||||
|
result.emplace(line.substr(para_start_pos, para_end_pos - para_start_pos), line.substr(value_start_pos, c - value_start_pos));
|
||||||
|
para_start_pos = std::string::npos;
|
||||||
|
para_end_pos = std::string::npos;
|
||||||
|
value_start_pos = std::string::npos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(line[c] != ' ' && line[c] != ';')
|
||||||
|
para_start_pos = c;
|
||||||
|
}
|
||||||
|
if(para_start_pos != std::string::npos && para_end_pos == std::string::npos)
|
||||||
|
result.emplace(line.substr(para_start_pos), std::string());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace SimpleWeb
|
||||||
|
|
||||||
|
#ifdef __SSE2__
|
||||||
|
#include <emmintrin.h>
|
||||||
|
namespace SimpleWeb {
|
||||||
|
inline void spin_loop_pause() noexcept { _mm_pause(); }
|
||||||
|
} // namespace SimpleWeb
|
||||||
|
// TODO: need verification that the following checks are correct:
|
||||||
|
#elif defined(_MSC_VER) && _MSC_VER >= 1800 && (defined(_M_X64) || defined(_M_IX86))
|
||||||
|
#include <intrin.h>
|
||||||
|
namespace SimpleWeb {
|
||||||
|
inline void spin_loop_pause() noexcept { _mm_pause(); }
|
||||||
|
} // namespace SimpleWeb
|
||||||
|
#else
|
||||||
|
namespace SimpleWeb {
|
||||||
|
inline void spin_loop_pause() noexcept {}
|
||||||
|
} // namespace SimpleWeb
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace SimpleWeb {
|
||||||
|
/// Makes it possible to for instance cancel Asio handlers without stopping asio::io_service
|
||||||
|
class ScopeRunner {
|
||||||
|
/// Scope count that is set to -1 if scopes are to be canceled
|
||||||
|
std::atomic<long> count;
|
||||||
|
|
||||||
|
public:
|
||||||
|
class SharedLock {
|
||||||
|
friend class ScopeRunner;
|
||||||
|
std::atomic<long> &count;
|
||||||
|
SharedLock(std::atomic<long> &count) noexcept : count(count) {}
|
||||||
|
SharedLock &operator=(const SharedLock &) = delete;
|
||||||
|
SharedLock(const SharedLock &) = delete;
|
||||||
|
|
||||||
|
public:
|
||||||
|
~SharedLock() noexcept {
|
||||||
|
count.fetch_sub(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ScopeRunner() noexcept : count(0) {}
|
||||||
|
|
||||||
|
/// Returns nullptr if scope should be exited, or a shared lock otherwise
|
||||||
|
std::unique_ptr<SharedLock> continue_lock() noexcept {
|
||||||
|
long expected = count;
|
||||||
|
while(expected >= 0 && !count.compare_exchange_weak(expected, expected + 1))
|
||||||
|
spin_loop_pause();
|
||||||
|
|
||||||
|
if(expected < 0)
|
||||||
|
return nullptr;
|
||||||
|
else
|
||||||
|
return std::unique_ptr<SharedLock>(new SharedLock(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blocks until all shared locks are released, then prevents future shared locks
|
||||||
|
void stop() noexcept {
|
||||||
|
long expected = 0;
|
||||||
|
while(!count.compare_exchange_weak(expected, -1)) {
|
||||||
|
if(expected < 0)
|
||||||
|
return;
|
||||||
|
expected = 0;
|
||||||
|
spin_loop_pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace SimpleWeb
|
|
@ -1,36 +1,74 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <Kbhit.h>
|
#include <Kbhit.h>
|
||||||
#include <RakSleep.h>
|
#include <RakSleep.h>
|
||||||
|
#include <sol.hpp>
|
||||||
#include "MasterServer.hpp"
|
#include "MasterServer.hpp"
|
||||||
#include "RestServer.hpp"
|
#include "RestServer.hpp"
|
||||||
|
#include "AdminRest.hpp"
|
||||||
|
|
||||||
using namespace RakNet;
|
using namespace RakNet;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
unique_ptr<RestServer> restServer;
|
unique_ptr<RestServer> restServer;
|
||||||
unique_ptr<MasterServer> masterServer;
|
shared_ptr<MasterServer> masterServer;
|
||||||
bool run = true;
|
unique_ptr<AdminRest> restAdminServer;
|
||||||
|
|
||||||
int main()
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
masterServer.reset(new MasterServer(2000, 25560));
|
if (argc != 2)
|
||||||
restServer.reset(new RestServer(8080, masterServer->GetServers()));
|
return 1;
|
||||||
|
|
||||||
|
string luaScript(argv[1]);
|
||||||
|
|
||||||
|
masterServer = make_shared<MasterServer>(luaScript);
|
||||||
|
masterServer->luaStuff([](sol::state &state)
|
||||||
|
{
|
||||||
|
sol::table config = state["config"];
|
||||||
|
sol::object restPort = config["restPort"];
|
||||||
|
if (restPort.get_type() != sol::type::number)
|
||||||
|
throw runtime_error("config.restPort is not correct");
|
||||||
|
|
||||||
|
|
||||||
|
restServer = make_unique<RestServer>(restPort.as<unsigned short>(), masterServer->GetServers());
|
||||||
|
|
||||||
|
sol::object restAdminCert = config["restAdminCert"];
|
||||||
|
if (restAdminCert.get_type() != sol::type::string)
|
||||||
|
throw runtime_error("config.restAdminCert is not correct");
|
||||||
|
|
||||||
|
sol::object restAdminKey = config["restAdminKey"];
|
||||||
|
if (restAdminKey.get_type() != sol::type::string)
|
||||||
|
throw runtime_error("config.restAdminKey is not correct");
|
||||||
|
|
||||||
|
sol::object restAdminVerifyFile = config["restAdminVerifyFile"];
|
||||||
|
if (restAdminVerifyFile.get_type() != sol::type::string)
|
||||||
|
throw runtime_error("config.restAdminVerifyFile is not correct");
|
||||||
|
|
||||||
|
sol::object restAdminPort = config["restAdminPort"];
|
||||||
|
if (restAdminPort.get_type() != sol::type::number)
|
||||||
|
throw runtime_error("config.restAdminPort is not correct");
|
||||||
|
|
||||||
|
restAdminServer = make_unique<AdminRest>(restAdminCert.as<string>(), restAdminKey.as<string>(),
|
||||||
|
restAdminVerifyFile.as<string>(), restAdminPort.as<unsigned short>(), masterServer);
|
||||||
|
});
|
||||||
|
|
||||||
auto onExit = [](int /*sig*/){
|
auto onExit = [](int /*sig*/){
|
||||||
restServer->stop();
|
restServer->stop();
|
||||||
|
restAdminServer->stop();
|
||||||
|
masterServer->luaStuff([](sol::state &state) {
|
||||||
|
sol::protected_function func = state["OnExit"];
|
||||||
|
if (func.valid())
|
||||||
|
func.call();
|
||||||
|
});
|
||||||
masterServer->Stop(false);
|
masterServer->Stop(false);
|
||||||
masterServer->Wait();
|
masterServer->Wait();
|
||||||
run = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
signal(SIGINT, onExit);
|
signal(SIGINT, onExit);
|
||||||
signal(SIGTERM, onExit);
|
signal(SIGTERM, onExit);
|
||||||
|
|
||||||
masterServer->Start();
|
masterServer->Start();
|
||||||
|
restServer->start();
|
||||||
thread server_thread([]() { restServer->start(); });
|
restAdminServer->start();
|
||||||
|
|
||||||
server_thread.join();
|
|
||||||
masterServer->Wait();
|
masterServer->Wait();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -874,6 +874,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co
|
||||||
{
|
{
|
||||||
std::vector<std::pair<std::time_t, boost::filesystem::path>> contentFiles;
|
std::vector<std::pair<std::time_t, boost::filesystem::path>> contentFiles;
|
||||||
std::string baseGameFile("Game Files:GameFile");
|
std::string baseGameFile("Game Files:GameFile");
|
||||||
|
std::string gameFile("");
|
||||||
std::time_t defaultTime = 0;
|
std::time_t defaultTime = 0;
|
||||||
ToUTF8::Utf8Encoder encoder(mEncoding);
|
ToUTF8::Utf8Encoder encoder(mEncoding);
|
||||||
|
|
||||||
|
@ -889,7 +890,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co
|
||||||
multistrmap::const_iterator it = ini.begin();
|
multistrmap::const_iterator it = ini.begin();
|
||||||
for (int i=0; it != ini.end(); i++)
|
for (int i=0; it != ini.end(); i++)
|
||||||
{
|
{
|
||||||
std::string gameFile = baseGameFile;
|
gameFile = baseGameFile;
|
||||||
gameFile.append(std::to_string(i));
|
gameFile.append(std::to_string(i));
|
||||||
|
|
||||||
it = ini.find(gameFile);
|
it = ini.find(gameFile);
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
|
||||||
#include <components/crashcatcher/crashcatcher.hpp>
|
|
||||||
|
|
||||||
#include <components/fallback/validate.hpp>
|
#include <components/fallback/validate.hpp>
|
||||||
|
|
||||||
#include <components/nifosg/nifloader.hpp>
|
#include <components/nifosg/nifloader.hpp>
|
||||||
|
@ -21,16 +18,12 @@
|
||||||
|
|
||||||
using namespace Fallback;
|
using namespace Fallback;
|
||||||
|
|
||||||
CS::Editor::Editor (int argc, char **argv)
|
CS::Editor::Editor ()
|
||||||
: mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr),
|
: mSettingsState (mCfgMgr), mDocumentManager (mCfgMgr),
|
||||||
mViewManager (mDocumentManager), mPid(""),
|
mViewManager (mDocumentManager), mPid(""),
|
||||||
mLock(), mMerge (mDocumentManager),
|
mLock(), mMerge (mDocumentManager),
|
||||||
mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL)
|
mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL)
|
||||||
{
|
{
|
||||||
// install the crash handler as soon as possible. note that the log path
|
|
||||||
// does not depend on config being read.
|
|
||||||
crashCatcherInstall(argc, argv, (mCfgMgr.getLogPath() / "openmw-cs-crash.log").string());
|
|
||||||
|
|
||||||
std::pair<Files::PathContainer, std::vector<std::string> > config = readConfig();
|
std::pair<Files::PathContainer, std::vector<std::string> > config = readConfig();
|
||||||
|
|
||||||
setupDataFiles (config.first);
|
setupDataFiles (config.first);
|
||||||
|
|
|
@ -66,7 +66,7 @@ namespace CS
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Editor (int argc, char **argv);
|
Editor ();
|
||||||
~Editor ();
|
~Editor ();
|
||||||
|
|
||||||
bool makeIPCServer();
|
bool makeIPCServer();
|
||||||
|
|
|
@ -8,9 +8,8 @@
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
|
|
||||||
#include <components/misc/debugging.hpp>
|
|
||||||
|
|
||||||
#include "model/doc/messages.hpp"
|
#include "model/doc/messages.hpp"
|
||||||
|
|
||||||
#include "model/world/universalid.hpp"
|
#include "model/world/universalid.hpp"
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
|
@ -42,43 +41,45 @@ class Application : public QApplication
|
||||||
Application (int& argc, char *argv[]) : QApplication (argc, argv) {}
|
Application (int& argc, char *argv[]) : QApplication (argc, argv) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
int runApplication(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0);
|
setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// To allow background thread drawing in OSG
|
try
|
||||||
QApplication::setAttribute(Qt::AA_X11InitThreads, true);
|
|
||||||
|
|
||||||
Q_INIT_RESOURCE (resources);
|
|
||||||
|
|
||||||
qRegisterMetaType<std::string> ("std::string");
|
|
||||||
qRegisterMetaType<CSMWorld::UniversalId> ("CSMWorld::UniversalId");
|
|
||||||
qRegisterMetaType<CSMDoc::Message> ("CSMDoc::Message");
|
|
||||||
|
|
||||||
Application application (argc, argv);
|
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
QDir dir(QCoreApplication::applicationDirPath());
|
|
||||||
QDir::setCurrent(dir.absolutePath());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
application.setWindowIcon (QIcon (":./openmw-cs.png"));
|
|
||||||
|
|
||||||
CS::Editor editor(argc, argv);
|
|
||||||
|
|
||||||
if(!editor.makeIPCServer())
|
|
||||||
{
|
{
|
||||||
editor.connectToIPCServer();
|
// To allow background thread drawing in OSG
|
||||||
|
QApplication::setAttribute(Qt::AA_X11InitThreads, true);
|
||||||
|
|
||||||
|
Q_INIT_RESOURCE (resources);
|
||||||
|
|
||||||
|
qRegisterMetaType<std::string> ("std::string");
|
||||||
|
qRegisterMetaType<CSMWorld::UniversalId> ("CSMWorld::UniversalId");
|
||||||
|
qRegisterMetaType<CSMDoc::Message> ("CSMDoc::Message");
|
||||||
|
|
||||||
|
Application application (argc, argv);
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
QDir dir(QCoreApplication::applicationDirPath());
|
||||||
|
QDir::setCurrent(dir.absolutePath());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
application.setWindowIcon (QIcon (":./openmw-cs.png"));
|
||||||
|
|
||||||
|
CS::Editor editor;
|
||||||
|
|
||||||
|
if(!editor.makeIPCServer())
|
||||||
|
{
|
||||||
|
editor.connectToIPCServer();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return editor.run();
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
std::cerr << "ERROR: " << e.what() << std::endl;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return editor.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
return wrapApplication(&runApplication, argc, argv, "/openmw-cs.log");
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -320,13 +320,12 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration,
|
||||||
connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool)));
|
connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool)));
|
||||||
|
|
||||||
connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int)));
|
connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int)));
|
||||||
connect (&mTools, SIGNAL (done (int, bool)), this, SIGNAL (operationDone (int, bool)));
|
connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool)));
|
||||||
connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone2 (int, bool)));
|
|
||||||
connect (&mTools, SIGNAL (mergeDone (CSMDoc::Document*)),
|
connect (&mTools, SIGNAL (mergeDone (CSMDoc::Document*)),
|
||||||
this, SIGNAL (mergeDone (CSMDoc::Document*)));
|
this, SIGNAL (mergeDone (CSMDoc::Document*)));
|
||||||
|
|
||||||
connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int)));
|
connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int)));
|
||||||
connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone2 (int, bool)));
|
connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool)));
|
||||||
|
|
||||||
connect (
|
connect (
|
||||||
&mSaving, SIGNAL (reportMessage (const CSMDoc::Message&, int)),
|
&mSaving, SIGNAL (reportMessage (const CSMDoc::Message&, int)),
|
||||||
|
@ -438,7 +437,7 @@ void CSMDoc::Document::reportMessage (const CSMDoc::Message& message, int type)
|
||||||
std::cout << message.mMessage << std::endl;
|
std::cout << message.mMessage << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMDoc::Document::operationDone2 (int type, bool failed)
|
void CSMDoc::Document::operationDone (int type, bool failed)
|
||||||
{
|
{
|
||||||
if (type==CSMDoc::State_Saving && !failed)
|
if (type==CSMDoc::State_Saving && !failed)
|
||||||
mDirty = false;
|
mDirty = false;
|
||||||
|
|
|
@ -168,15 +168,13 @@ namespace CSMDoc
|
||||||
/// document. This signal must be handled to avoid a leak.
|
/// document. This signal must be handled to avoid a leak.
|
||||||
void mergeDone (CSMDoc::Document *document);
|
void mergeDone (CSMDoc::Document *document);
|
||||||
|
|
||||||
void operationDone (int type, bool failed);
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
||||||
void modificationStateChanged (bool clean);
|
void modificationStateChanged (bool clean);
|
||||||
|
|
||||||
void reportMessage (const CSMDoc::Message& message, int type);
|
void reportMessage (const CSMDoc::Message& message, int type);
|
||||||
|
|
||||||
void operationDone2 (int type, bool failed);
|
void operationDone (int type, bool failed);
|
||||||
|
|
||||||
void runStateChanged();
|
void runStateChanged();
|
||||||
|
|
||||||
|
|
|
@ -123,7 +123,6 @@ void CSMPrefs::State::declare()
|
||||||
declareEnum ("double-s", "Shift Double Click", actionRemove).addValues (reportValues);
|
declareEnum ("double-s", "Shift Double Click", actionRemove).addValues (reportValues);
|
||||||
declareEnum ("double-c", "Control Double Click", actionEditAndRemove).addValues (reportValues);
|
declareEnum ("double-c", "Control Double Click", actionEditAndRemove).addValues (reportValues);
|
||||||
declareEnum ("double-sc", "Shift Control Double Click", actionNone).addValues (reportValues);
|
declareEnum ("double-sc", "Shift Control Double Click", actionNone).addValues (reportValues);
|
||||||
declareBool("ignore-base-records", "Ignore base records in verifier", false);
|
|
||||||
|
|
||||||
declareCategory ("Search & Replace");
|
declareCategory ("Search & Replace");
|
||||||
declareInt ("char-before", "Characters before search string", 10).
|
declareInt ("char-before", "Characters before search string", 10).
|
||||||
|
@ -201,9 +200,6 @@ void CSMPrefs::State::declare()
|
||||||
declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1);
|
declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1);
|
||||||
|
|
||||||
declareCategory ("Rendering");
|
declareCategory ("Rendering");
|
||||||
declareInt ("framerate-limit", "FPS limit", 60).
|
|
||||||
setTooltip("Framerate limit in 3D preview windows. Zero value means \"unlimited\".").
|
|
||||||
setRange(0, 10000);
|
|
||||||
declareInt ("camera-fov", "Camera FOV", 90).setRange(10, 170);
|
declareInt ("camera-fov", "Camera FOV", 90).setRange(10, 170);
|
||||||
declareBool ("camera-ortho", "Orthographic projection for camera", false);
|
declareBool ("camera-ortho", "Orthographic projection for camera", false);
|
||||||
declareInt ("camera-ortho-size", "Orthographic projection size parameter", 100).
|
declareInt ("camera-ortho-size", "Orthographic projection size parameter", 100).
|
||||||
|
|
|
@ -5,20 +5,14 @@
|
||||||
|
|
||||||
#include <components/esm/loadbsgn.hpp>
|
#include <components/esm/loadbsgn.hpp>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include "../world/universalid.hpp"
|
#include "../world/universalid.hpp"
|
||||||
|
|
||||||
CSMTools::BirthsignCheckStage::BirthsignCheckStage (const CSMWorld::IdCollection<ESM::BirthSign>& birthsigns)
|
CSMTools::BirthsignCheckStage::BirthsignCheckStage (const CSMWorld::IdCollection<ESM::BirthSign>& birthsigns)
|
||||||
: mBirthsigns (birthsigns)
|
: mBirthsigns (birthsigns)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::BirthsignCheckStage::setup()
|
int CSMTools::BirthsignCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mBirthsigns.getSize();
|
return mBirthsigns.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,8 +20,7 @@ void CSMTools::BirthsignCheckStage::perform (int stage, CSMDoc::Messages& messag
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<ESM::BirthSign>& record = mBirthsigns.getRecord (stage);
|
const CSMWorld::Record<ESM::BirthSign>& record = mBirthsigns.getRecord (stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (record.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::BirthSign& birthsign = record.get();
|
const ESM::BirthSign& birthsign = record.get();
|
||||||
|
|
|
@ -13,7 +13,6 @@ namespace CSMTools
|
||||||
class BirthsignCheckStage : public CSMDoc::Stage
|
class BirthsignCheckStage : public CSMDoc::Stage
|
||||||
{
|
{
|
||||||
const CSMWorld::IdCollection<ESM::BirthSign>& mBirthsigns;
|
const CSMWorld::IdCollection<ESM::BirthSign>& mBirthsigns;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
#include "bodypartcheck.hpp"
|
#include "bodypartcheck.hpp"
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
CSMTools::BodyPartCheckStage::BodyPartCheckStage(
|
CSMTools::BodyPartCheckStage::BodyPartCheckStage(
|
||||||
const CSMWorld::IdCollection<ESM::BodyPart> &bodyParts,
|
const CSMWorld::IdCollection<ESM::BodyPart> &bodyParts,
|
||||||
const CSMWorld::Resources &meshes,
|
const CSMWorld::Resources &meshes,
|
||||||
|
@ -9,14 +7,10 @@ CSMTools::BodyPartCheckStage::BodyPartCheckStage(
|
||||||
mBodyParts(bodyParts),
|
mBodyParts(bodyParts),
|
||||||
mMeshes(meshes),
|
mMeshes(meshes),
|
||||||
mRaces(races)
|
mRaces(races)
|
||||||
{
|
{ }
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::BodyPartCheckStage::setup()
|
int CSMTools::BodyPartCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mBodyParts.getSize();
|
return mBodyParts.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,8 +18,7 @@ void CSMTools::BodyPartCheckStage::perform (int stage, CSMDoc::Messages &message
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<ESM::BodyPart> &record = mBodyParts.getRecord(stage);
|
const CSMWorld::Record<ESM::BodyPart> &record = mBodyParts.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if ( record.isDeleted() )
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::BodyPart &bodyPart = record.get();
|
const ESM::BodyPart &bodyPart = record.get();
|
||||||
|
|
|
@ -17,7 +17,6 @@ namespace CSMTools
|
||||||
const CSMWorld::IdCollection<ESM::BodyPart> &mBodyParts;
|
const CSMWorld::IdCollection<ESM::BodyPart> &mBodyParts;
|
||||||
const CSMWorld::Resources &mMeshes;
|
const CSMWorld::Resources &mMeshes;
|
||||||
const CSMWorld::IdCollection<ESM::Race> &mRaces;
|
const CSMWorld::IdCollection<ESM::Race> &mRaces;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BodyPartCheckStage(
|
BodyPartCheckStage(
|
||||||
|
|
|
@ -6,20 +6,14 @@
|
||||||
#include <components/esm/loadclas.hpp>
|
#include <components/esm/loadclas.hpp>
|
||||||
#include <components/esm/loadskil.hpp>
|
#include <components/esm/loadskil.hpp>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include "../world/universalid.hpp"
|
#include "../world/universalid.hpp"
|
||||||
|
|
||||||
CSMTools::ClassCheckStage::ClassCheckStage (const CSMWorld::IdCollection<ESM::Class>& classes)
|
CSMTools::ClassCheckStage::ClassCheckStage (const CSMWorld::IdCollection<ESM::Class>& classes)
|
||||||
: mClasses (classes)
|
: mClasses (classes)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::ClassCheckStage::setup()
|
int CSMTools::ClassCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mClasses.getSize();
|
return mClasses.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +21,7 @@ void CSMTools::ClassCheckStage::perform (int stage, CSMDoc::Messages& messages)
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<ESM::Class>& record = mClasses.getRecord (stage);
|
const CSMWorld::Record<ESM::Class>& record = mClasses.getRecord (stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (record.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Class& class_ = record.get();
|
const ESM::Class& class_ = record.get();
|
||||||
|
|
|
@ -13,7 +13,6 @@ namespace CSMTools
|
||||||
class ClassCheckStage : public CSMDoc::Stage
|
class ClassCheckStage : public CSMDoc::Stage
|
||||||
{
|
{
|
||||||
const CSMWorld::IdCollection<ESM::Class>& mClasses;
|
const CSMWorld::IdCollection<ESM::Class>& mClasses;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
|
@ -6,20 +6,14 @@
|
||||||
#include <components/esm/loadfact.hpp>
|
#include <components/esm/loadfact.hpp>
|
||||||
#include <components/esm/loadskil.hpp>
|
#include <components/esm/loadskil.hpp>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include "../world/universalid.hpp"
|
#include "../world/universalid.hpp"
|
||||||
|
|
||||||
CSMTools::FactionCheckStage::FactionCheckStage (const CSMWorld::IdCollection<ESM::Faction>& factions)
|
CSMTools::FactionCheckStage::FactionCheckStage (const CSMWorld::IdCollection<ESM::Faction>& factions)
|
||||||
: mFactions (factions)
|
: mFactions (factions)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::FactionCheckStage::setup()
|
int CSMTools::FactionCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mFactions.getSize();
|
return mFactions.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +21,7 @@ void CSMTools::FactionCheckStage::perform (int stage, CSMDoc::Messages& messages
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<ESM::Faction>& record = mFactions.getRecord (stage);
|
const CSMWorld::Record<ESM::Faction>& record = mFactions.getRecord (stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (record.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Faction& faction = record.get();
|
const ESM::Faction& faction = record.get();
|
||||||
|
|
|
@ -13,7 +13,6 @@ namespace CSMTools
|
||||||
class FactionCheckStage : public CSMDoc::Stage
|
class FactionCheckStage : public CSMDoc::Stage
|
||||||
{
|
{
|
||||||
const CSMWorld::IdCollection<ESM::Faction>& mFactions;
|
const CSMWorld::IdCollection<ESM::Faction>& mFactions;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
|
@ -2,20 +2,14 @@
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include "../world/defaultgmsts.hpp"
|
#include "../world/defaultgmsts.hpp"
|
||||||
|
|
||||||
CSMTools::GmstCheckStage::GmstCheckStage(const CSMWorld::IdCollection<ESM::GameSetting>& gameSettings)
|
CSMTools::GmstCheckStage::GmstCheckStage(const CSMWorld::IdCollection<ESM::GameSetting>& gameSettings)
|
||||||
: mGameSettings(gameSettings)
|
: mGameSettings(gameSettings)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::GmstCheckStage::setup()
|
int CSMTools::GmstCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mGameSettings.getSize();
|
return mGameSettings.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +17,7 @@ void CSMTools::GmstCheckStage::perform(int stage, CSMDoc::Messages& messages)
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<ESM::GameSetting>& record = mGameSettings.getRecord (stage);
|
const CSMWorld::Record<ESM::GameSetting>& record = mGameSettings.getRecord (stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (record.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::GameSetting& gmst = record.get();
|
const ESM::GameSetting& gmst = record.get();
|
||||||
|
|
|
@ -25,7 +25,6 @@ namespace CSMTools
|
||||||
private:
|
private:
|
||||||
|
|
||||||
const CSMWorld::IdCollection<ESM::GameSetting>& mGameSettings;
|
const CSMWorld::IdCollection<ESM::GameSetting>& mGameSettings;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
std::string varTypeToString(ESM::VarType);
|
std::string varTypeToString(ESM::VarType);
|
||||||
|
|
||||||
|
|
|
@ -3,19 +3,13 @@
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
CSMTools::JournalCheckStage::JournalCheckStage(const CSMWorld::IdCollection<ESM::Dialogue> &journals,
|
CSMTools::JournalCheckStage::JournalCheckStage(const CSMWorld::IdCollection<ESM::Dialogue> &journals,
|
||||||
const CSMWorld::InfoCollection& journalInfos)
|
const CSMWorld::InfoCollection& journalInfos)
|
||||||
: mJournals(journals), mJournalInfos(journalInfos)
|
: mJournals(journals), mJournalInfos(journalInfos)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::JournalCheckStage::setup()
|
int CSMTools::JournalCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mJournals.getSize();
|
return mJournals.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +17,7 @@ void CSMTools::JournalCheckStage::perform(int stage, CSMDoc::Messages& messages)
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<ESM::Dialogue> &journalRecord = mJournals.getRecord(stage);
|
const CSMWorld::Record<ESM::Dialogue> &journalRecord = mJournals.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (journalRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && journalRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || journalRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Dialogue &journal = journalRecord.get();
|
const ESM::Dialogue &journal = journalRecord.get();
|
||||||
|
@ -50,10 +43,6 @@ void CSMTools::JournalCheckStage::perform(int stage, CSMDoc::Messages& messages)
|
||||||
statusNamedCount += 1;
|
statusNamedCount += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip "Base" records (setting!)
|
|
||||||
if (mIgnoreBaseRecords && infoRecord.mState == CSMWorld::RecordBase::State_BaseOnly)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (journalInfo.mResponse.empty())
|
if (journalInfo.mResponse.empty())
|
||||||
{
|
{
|
||||||
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId);
|
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_JournalInfo, journalInfo.mId);
|
||||||
|
|
|
@ -28,7 +28,6 @@ namespace CSMTools
|
||||||
|
|
||||||
const CSMWorld::IdCollection<ESM::Dialogue>& mJournals;
|
const CSMWorld::IdCollection<ESM::Dialogue>& mJournals;
|
||||||
const CSMWorld::InfoCollection& mJournalInfos;
|
const CSMWorld::InfoCollection& mJournalInfos;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
#include <components/misc/resourcehelpers.hpp>
|
#include <components/misc/resourcehelpers.hpp>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include "../world/resources.hpp"
|
#include "../world/resources.hpp"
|
||||||
#include "../world/data.hpp"
|
#include "../world/data.hpp"
|
||||||
|
|
||||||
|
@ -79,26 +77,16 @@ CSMTools::MagicEffectCheckStage::MagicEffectCheckStage(const CSMWorld::IdCollect
|
||||||
mReferenceables(referenceables),
|
mReferenceables(referenceables),
|
||||||
mIcons(icons),
|
mIcons(icons),
|
||||||
mTextures(textures)
|
mTextures(textures)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::MagicEffectCheckStage::setup()
|
int CSMTools::MagicEffectCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mMagicEffects.getSize();
|
return mMagicEffects.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages &messages)
|
void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages &messages)
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<ESM::MagicEffect> &record = mMagicEffects.getRecord(stage);
|
ESM::MagicEffect effect = mMagicEffects.getRecord(stage).get();
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
|
||||||
|
|
||||||
ESM::MagicEffect effect = record.get();
|
|
||||||
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, effect.mId);
|
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, effect.mId);
|
||||||
|
|
||||||
if (effect.mData.mBaseCost < 0.0f)
|
if (effect.mData.mBaseCost < 0.0f)
|
||||||
|
|
|
@ -24,7 +24,6 @@ namespace CSMTools
|
||||||
const CSMWorld::RefIdCollection &mReferenceables;
|
const CSMWorld::RefIdCollection &mReferenceables;
|
||||||
const CSMWorld::Resources &mIcons;
|
const CSMWorld::Resources &mIcons;
|
||||||
const CSMWorld::Resources &mTextures;
|
const CSMWorld::Resources &mTextures;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isTextureExists(const std::string &texture, bool isIcon) const;
|
bool isTextureExists(const std::string &texture, bool isIcon) const;
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include "../world/universalid.hpp"
|
#include "../world/universalid.hpp"
|
||||||
#include "../world/idcollection.hpp"
|
#include "../world/idcollection.hpp"
|
||||||
#include "../world/subcellcollection.hpp"
|
#include "../world/subcellcollection.hpp"
|
||||||
|
@ -12,14 +10,10 @@
|
||||||
|
|
||||||
CSMTools::PathgridCheckStage::PathgridCheckStage (const CSMWorld::SubCellCollection<CSMWorld::Pathgrid>& pathgrids)
|
CSMTools::PathgridCheckStage::PathgridCheckStage (const CSMWorld::SubCellCollection<CSMWorld::Pathgrid>& pathgrids)
|
||||||
: mPathgrids (pathgrids)
|
: mPathgrids (pathgrids)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::PathgridCheckStage::setup()
|
int CSMTools::PathgridCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mPathgrids.getSize();
|
return mPathgrids.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +21,7 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<CSMWorld::Pathgrid>& record = mPathgrids.getRecord (stage);
|
const CSMWorld::Record<CSMWorld::Pathgrid>& record = mPathgrids.getRecord (stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (record.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const CSMWorld::Pathgrid& pathgrid = record.get();
|
const CSMWorld::Pathgrid& pathgrid = record.get();
|
||||||
|
|
|
@ -25,7 +25,6 @@ namespace CSMTools
|
||||||
{
|
{
|
||||||
const CSMWorld::SubCellCollection<CSMWorld::Pathgrid,
|
const CSMWorld::SubCellCollection<CSMWorld::Pathgrid,
|
||||||
CSMWorld::IdAccessor<CSMWorld::Pathgrid> >& mPathgrids;
|
CSMWorld::IdAccessor<CSMWorld::Pathgrid> >& mPathgrids;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
#include <components/esm/loadrace.hpp>
|
#include <components/esm/loadrace.hpp>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include "../world/universalid.hpp"
|
#include "../world/universalid.hpp"
|
||||||
|
|
||||||
void CSMTools::RaceCheckStage::performPerRecord (int stage, CSMDoc::Messages& messages)
|
void CSMTools::RaceCheckStage::performPerRecord (int stage, CSMDoc::Messages& messages)
|
||||||
|
@ -17,14 +15,6 @@ void CSMTools::RaceCheckStage::performPerRecord (int stage, CSMDoc::Messages& me
|
||||||
|
|
||||||
const ESM::Race& race = record.get();
|
const ESM::Race& race = record.get();
|
||||||
|
|
||||||
// Consider mPlayable flag even when "Base" records are ignored
|
|
||||||
if (race.mData.mFlags & 0x1)
|
|
||||||
mPlayable = true;
|
|
||||||
|
|
||||||
// Skip "Base" records (setting!)
|
|
||||||
if (mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Race, race.mId);
|
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Race, race.mId);
|
||||||
|
|
||||||
// test for empty name and description
|
// test for empty name and description
|
||||||
|
@ -48,6 +38,10 @@ void CSMTools::RaceCheckStage::performPerRecord (int stage, CSMDoc::Messages& me
|
||||||
if (race.mData.mWeight.mFemale<0)
|
if (race.mData.mWeight.mFemale<0)
|
||||||
messages.push_back (std::make_pair (id, "female " + race.mId + " has negative weight"));
|
messages.push_back (std::make_pair (id, "female " + race.mId + " has negative weight"));
|
||||||
|
|
||||||
|
// remember playable flag
|
||||||
|
if (race.mData.mFlags & 0x1)
|
||||||
|
mPlayable = true;
|
||||||
|
|
||||||
/// \todo check data members that can't be edited in the table view
|
/// \todo check data members that can't be edited in the table view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,15 +55,11 @@ void CSMTools::RaceCheckStage::performFinal (CSMDoc::Messages& messages)
|
||||||
|
|
||||||
CSMTools::RaceCheckStage::RaceCheckStage (const CSMWorld::IdCollection<ESM::Race>& races)
|
CSMTools::RaceCheckStage::RaceCheckStage (const CSMWorld::IdCollection<ESM::Race>& races)
|
||||||
: mRaces (races), mPlayable (false)
|
: mRaces (races), mPlayable (false)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::RaceCheckStage::setup()
|
int CSMTools::RaceCheckStage::setup()
|
||||||
{
|
{
|
||||||
mPlayable = false;
|
mPlayable = false;
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mRaces.getSize()+1;
|
return mRaces.getSize()+1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ namespace CSMTools
|
||||||
{
|
{
|
||||||
const CSMWorld::IdCollection<ESM::Race>& mRaces;
|
const CSMWorld::IdCollection<ESM::Race>& mRaces;
|
||||||
bool mPlayable;
|
bool mPlayable;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
void performPerRecord (int stage, CSMDoc::Messages& messages);
|
void performPerRecord (int stage, CSMDoc::Messages& messages);
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
#include <components/misc/stringops.hpp>
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include "../world/record.hpp"
|
#include "../world/record.hpp"
|
||||||
#include "../world/universalid.hpp"
|
#include "../world/universalid.hpp"
|
||||||
|
|
||||||
|
@ -20,7 +18,6 @@ CSMTools::ReferenceableCheckStage::ReferenceableCheckStage(
|
||||||
mScripts(scripts),
|
mScripts(scripts),
|
||||||
mPlayerPresent(false)
|
mPlayerPresent(false)
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMTools::ReferenceableCheckStage::perform (int stage, CSMDoc::Messages& messages)
|
void CSMTools::ReferenceableCheckStage::perform (int stage, CSMDoc::Messages& messages)
|
||||||
|
@ -231,8 +228,6 @@ void CSMTools::ReferenceableCheckStage::perform (int stage, CSMDoc::Messages& me
|
||||||
int CSMTools::ReferenceableCheckStage::setup()
|
int CSMTools::ReferenceableCheckStage::setup()
|
||||||
{
|
{
|
||||||
mPlayerPresent = false;
|
mPlayerPresent = false;
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mReferencables.getSize() + 1;
|
return mReferencables.getSize() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,8 +238,7 @@ void CSMTools::ReferenceableCheckStage::bookCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Book& book = (dynamic_cast<const CSMWorld::Record<ESM::Book>& >(baseRecord)).get();
|
const ESM::Book& book = (dynamic_cast<const CSMWorld::Record<ESM::Book>& >(baseRecord)).get();
|
||||||
|
@ -263,8 +257,7 @@ void CSMTools::ReferenceableCheckStage::activatorCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Activator& activator = (dynamic_cast<const CSMWorld::Record<ESM::Activator>& >(baseRecord)).get();
|
const ESM::Activator& activator = (dynamic_cast<const CSMWorld::Record<ESM::Activator>& >(baseRecord)).get();
|
||||||
|
@ -285,8 +278,7 @@ void CSMTools::ReferenceableCheckStage::potionCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Potion& potion = (dynamic_cast<const CSMWorld::Record<ESM::Potion>& >(baseRecord)).get();
|
const ESM::Potion& potion = (dynamic_cast<const CSMWorld::Record<ESM::Potion>& >(baseRecord)).get();
|
||||||
|
@ -307,8 +299,7 @@ void CSMTools::ReferenceableCheckStage::apparatusCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Apparatus& apparatus = (dynamic_cast<const CSMWorld::Record<ESM::Apparatus>& >(baseRecord)).get();
|
const ESM::Apparatus& apparatus = (dynamic_cast<const CSMWorld::Record<ESM::Apparatus>& >(baseRecord)).get();
|
||||||
|
@ -329,8 +320,7 @@ void CSMTools::ReferenceableCheckStage::armorCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Armor& armor = (dynamic_cast<const CSMWorld::Record<ESM::Armor>& >(baseRecord)).get();
|
const ESM::Armor& armor = (dynamic_cast<const CSMWorld::Record<ESM::Armor>& >(baseRecord)).get();
|
||||||
|
@ -357,8 +347,7 @@ void CSMTools::ReferenceableCheckStage::clothingCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Clothing& clothing = (dynamic_cast<const CSMWorld::Record<ESM::Clothing>& >(baseRecord)).get();
|
const ESM::Clothing& clothing = (dynamic_cast<const CSMWorld::Record<ESM::Clothing>& >(baseRecord)).get();
|
||||||
|
@ -376,8 +365,7 @@ void CSMTools::ReferenceableCheckStage::containerCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Container& container = (dynamic_cast<const CSMWorld::Record<ESM::Container>& >(baseRecord)).get();
|
const ESM::Container& container = (dynamic_cast<const CSMWorld::Record<ESM::Container>& >(baseRecord)).get();
|
||||||
|
@ -409,8 +397,7 @@ void CSMTools::ReferenceableCheckStage::creatureCheck (
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Creature& creature = (dynamic_cast<const CSMWorld::Record<ESM::Creature>&>(baseRecord)).get();
|
const ESM::Creature& creature = (dynamic_cast<const CSMWorld::Record<ESM::Creature>&>(baseRecord)).get();
|
||||||
|
@ -486,8 +473,7 @@ void CSMTools::ReferenceableCheckStage::doorCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Door& door = (dynamic_cast<const CSMWorld::Record<ESM::Door>&>(baseRecord)).get();
|
const ESM::Door& door = (dynamic_cast<const CSMWorld::Record<ESM::Door>&>(baseRecord)).get();
|
||||||
|
@ -511,8 +497,7 @@ void CSMTools::ReferenceableCheckStage::ingredientCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Ingredient& ingredient = (dynamic_cast<const CSMWorld::Record<ESM::Ingredient>& >(baseRecord)).get();
|
const ESM::Ingredient& ingredient = (dynamic_cast<const CSMWorld::Record<ESM::Ingredient>& >(baseRecord)).get();
|
||||||
|
@ -531,9 +516,10 @@ void CSMTools::ReferenceableCheckStage::creaturesLevListCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const ESM::CreatureLevList& CreatureLevList = (dynamic_cast<const CSMWorld::Record<ESM::CreatureLevList>& >(baseRecord)).get();
|
const ESM::CreatureLevList& CreatureLevList = (dynamic_cast<const CSMWorld::Record<ESM::CreatureLevList>& >(baseRecord)).get();
|
||||||
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_CreatureLevelledList, CreatureLevList.mId); //CreatureLevList but Type_CreatureLevelledList :/
|
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_CreatureLevelledList, CreatureLevList.mId); //CreatureLevList but Type_CreatureLevelledList :/
|
||||||
|
@ -548,9 +534,10 @@ void CSMTools::ReferenceableCheckStage::itemLevelledListCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const ESM::ItemLevList& ItemLevList = (dynamic_cast<const CSMWorld::Record<ESM::ItemLevList>& >(baseRecord)).get();
|
const ESM::ItemLevList& ItemLevList = (dynamic_cast<const CSMWorld::Record<ESM::ItemLevList>& >(baseRecord)).get();
|
||||||
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_ItemLevelledList, ItemLevList.mId);
|
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_ItemLevelledList, ItemLevList.mId);
|
||||||
|
@ -564,8 +551,7 @@ void CSMTools::ReferenceableCheckStage::lightCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Light& light = (dynamic_cast<const CSMWorld::Record<ESM::Light>& >(baseRecord)).get();
|
const ESM::Light& light = (dynamic_cast<const CSMWorld::Record<ESM::Light>& >(baseRecord)).get();
|
||||||
|
@ -588,8 +574,7 @@ void CSMTools::ReferenceableCheckStage::lockpickCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Lockpick& lockpick = (dynamic_cast<const CSMWorld::Record<ESM::Lockpick>& >(baseRecord)).get();
|
const ESM::Lockpick& lockpick = (dynamic_cast<const CSMWorld::Record<ESM::Lockpick>& >(baseRecord)).get();
|
||||||
|
@ -610,8 +595,7 @@ void CSMTools::ReferenceableCheckStage::miscCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Miscellaneous& miscellaneous = (dynamic_cast<const CSMWorld::Record<ESM::Miscellaneous>& >(baseRecord)).get();
|
const ESM::Miscellaneous& miscellaneous = (dynamic_cast<const CSMWorld::Record<ESM::Miscellaneous>& >(baseRecord)).get();
|
||||||
|
@ -635,14 +619,6 @@ void CSMTools::ReferenceableCheckStage::npcCheck (
|
||||||
const ESM::NPC& npc = (dynamic_cast<const CSMWorld::Record<ESM::NPC>& >(baseRecord)).get();
|
const ESM::NPC& npc = (dynamic_cast<const CSMWorld::Record<ESM::NPC>& >(baseRecord)).get();
|
||||||
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Npc, npc.mId);
|
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Npc, npc.mId);
|
||||||
|
|
||||||
//Detect if player is present
|
|
||||||
if (Misc::StringUtils::ciEqual(npc.mId, "player")) //Happy now, scrawl?
|
|
||||||
mPlayerPresent = true;
|
|
||||||
|
|
||||||
// Skip "Base" records (setting!)
|
|
||||||
if (mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly)
|
|
||||||
return;
|
|
||||||
|
|
||||||
short level(npc.mNpdt.mLevel);
|
short level(npc.mNpdt.mLevel);
|
||||||
char disposition(npc.mNpdt.mDisposition);
|
char disposition(npc.mNpdt.mDisposition);
|
||||||
char reputation(npc.mNpdt.mReputation);
|
char reputation(npc.mNpdt.mReputation);
|
||||||
|
@ -650,6 +626,10 @@ void CSMTools::ReferenceableCheckStage::npcCheck (
|
||||||
//Don't know what unknown is for
|
//Don't know what unknown is for
|
||||||
int gold(npc.mNpdt.mGold);
|
int gold(npc.mNpdt.mGold);
|
||||||
|
|
||||||
|
//Detect if player is present
|
||||||
|
if (Misc::StringUtils::ciEqual(npc.mId, "player")) //Happy now, scrawl?
|
||||||
|
mPlayerPresent = true;
|
||||||
|
|
||||||
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) //12 = autocalculated
|
if (npc.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) //12 = autocalculated
|
||||||
{
|
{
|
||||||
if ((npc.mFlags & ESM::NPC::Autocalc) == 0) //0x0010 = autocalculated flag
|
if ((npc.mFlags & ESM::NPC::Autocalc) == 0) //0x0010 = autocalculated flag
|
||||||
|
@ -748,8 +728,7 @@ void CSMTools::ReferenceableCheckStage::weaponCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord (stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord (stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Weapon& weapon = (dynamic_cast<const CSMWorld::Record<ESM::Weapon>& >(baseRecord)).get();
|
const ESM::Weapon& weapon = (dynamic_cast<const CSMWorld::Record<ESM::Weapon>& >(baseRecord)).get();
|
||||||
|
@ -829,8 +808,7 @@ void CSMTools::ReferenceableCheckStage::probeCheck(
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Probe& probe = (dynamic_cast<const CSMWorld::Record<ESM::Probe>& >(baseRecord)).get();
|
const ESM::Probe& probe = (dynamic_cast<const CSMWorld::Record<ESM::Probe>& >(baseRecord)).get();
|
||||||
|
@ -849,8 +827,7 @@ void CSMTools::ReferenceableCheckStage::repairCheck (
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord (stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord (stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Repair& repair = (dynamic_cast<const CSMWorld::Record<ESM::Repair>& >(baseRecord)).get();
|
const ESM::Repair& repair = (dynamic_cast<const CSMWorld::Record<ESM::Repair>& >(baseRecord)).get();
|
||||||
|
@ -869,8 +846,7 @@ void CSMTools::ReferenceableCheckStage::staticCheck (
|
||||||
{
|
{
|
||||||
const CSMWorld::RecordBase& baseRecord = records.getRecord (stage);
|
const CSMWorld::RecordBase& baseRecord = records.getRecord (stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (baseRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && baseRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || baseRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Static& staticElement = (dynamic_cast<const CSMWorld::Record<ESM::Static>& >(baseRecord)).get();
|
const ESM::Static& staticElement = (dynamic_cast<const CSMWorld::Record<ESM::Static>& >(baseRecord)).get();
|
||||||
|
|
|
@ -82,7 +82,6 @@ namespace CSMTools
|
||||||
const CSMWorld::IdCollection<ESM::Faction>& mFactions;
|
const CSMWorld::IdCollection<ESM::Faction>& mFactions;
|
||||||
const CSMWorld::IdCollection<ESM::Script>& mScripts;
|
const CSMWorld::IdCollection<ESM::Script>& mScripts;
|
||||||
bool mPlayerPresent;
|
bool mPlayerPresent;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif // REFERENCEABLECHECKSTAGE_H
|
#endif // REFERENCEABLECHECKSTAGE_H
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
#include "referencecheck.hpp"
|
#include "referencecheck.hpp"
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
CSMTools::ReferenceCheckStage::ReferenceCheckStage(
|
CSMTools::ReferenceCheckStage::ReferenceCheckStage(
|
||||||
const CSMWorld::RefCollection& references,
|
const CSMWorld::RefCollection& references,
|
||||||
const CSMWorld::RefIdCollection& referencables,
|
const CSMWorld::RefIdCollection& referencables,
|
||||||
|
@ -14,15 +12,13 @@ CSMTools::ReferenceCheckStage::ReferenceCheckStage(
|
||||||
mCells(cells),
|
mCells(cells),
|
||||||
mFactions(factions)
|
mFactions(factions)
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages &messages)
|
void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages &messages)
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<CSMWorld::CellRef>& record = mReferences.getRecord(stage);
|
const CSMWorld::Record<CSMWorld::CellRef>& record = mReferences.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (record.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const CSMWorld::CellRef& cellRef = record.get();
|
const CSMWorld::CellRef& cellRef = record.get();
|
||||||
|
@ -104,7 +100,5 @@ void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages &message
|
||||||
|
|
||||||
int CSMTools::ReferenceCheckStage::setup()
|
int CSMTools::ReferenceCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mReferences.getSize();
|
return mReferences.getSize();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ namespace CSMTools
|
||||||
const CSMWorld::RefIdData& mDataSet;
|
const CSMWorld::RefIdData& mDataSet;
|
||||||
const CSMWorld::IdCollection<CSMWorld::Cell>& mCells;
|
const CSMWorld::IdCollection<CSMWorld::Cell>& mCells;
|
||||||
const CSMWorld::IdCollection<ESM::Faction>& mFactions;
|
const CSMWorld::IdCollection<ESM::Faction>& mFactions;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,20 +5,14 @@
|
||||||
|
|
||||||
#include <components/esm/loadregn.hpp>
|
#include <components/esm/loadregn.hpp>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include "../world/universalid.hpp"
|
#include "../world/universalid.hpp"
|
||||||
|
|
||||||
CSMTools::RegionCheckStage::RegionCheckStage (const CSMWorld::IdCollection<ESM::Region>& regions)
|
CSMTools::RegionCheckStage::RegionCheckStage (const CSMWorld::IdCollection<ESM::Region>& regions)
|
||||||
: mRegions (regions)
|
: mRegions (regions)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::RegionCheckStage::setup()
|
int CSMTools::RegionCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mRegions.getSize();
|
return mRegions.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,8 +20,7 @@ void CSMTools::RegionCheckStage::perform (int stage, CSMDoc::Messages& messages)
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<ESM::Region>& record = mRegions.getRecord (stage);
|
const CSMWorld::Record<ESM::Region>& record = mRegions.getRecord (stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (record.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Region& region = record.get();
|
const ESM::Region& region = record.get();
|
||||||
|
|
|
@ -13,7 +13,6 @@ namespace CSMTools
|
||||||
class RegionCheckStage : public CSMDoc::Stage
|
class RegionCheckStage : public CSMDoc::Stage
|
||||||
{
|
{
|
||||||
const CSMWorld::IdCollection<ESM::Region>& mRegions;
|
const CSMWorld::IdCollection<ESM::Region>& mRegions;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
|
@ -60,8 +60,6 @@ CSMTools::ScriptCheckStage::ScriptCheckStage (const CSMDoc::Document& document)
|
||||||
|
|
||||||
Compiler::registerExtensions (mExtensions);
|
Compiler::registerExtensions (mExtensions);
|
||||||
mContext.setExtensions (&mExtensions);
|
mContext.setExtensions (&mExtensions);
|
||||||
|
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int CSMTools::ScriptCheckStage::setup()
|
int CSMTools::ScriptCheckStage::setup()
|
||||||
|
@ -80,25 +78,17 @@ int CSMTools::ScriptCheckStage::setup()
|
||||||
mId.clear();
|
mId.clear();
|
||||||
Compiler::ErrorHandler::reset();
|
Compiler::ErrorHandler::reset();
|
||||||
|
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mDocument.getData().getScripts().getSize();
|
return mDocument.getData().getScripts().getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages)
|
void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages)
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<ESM::Script> &record = mDocument.getData().getScripts().getRecord(stage);
|
|
||||||
|
|
||||||
mId = mDocument.getData().getScripts().getId (stage);
|
mId = mDocument.getData().getScripts().getId (stage);
|
||||||
|
|
||||||
if (mDocument.isBlacklisted (
|
if (mDocument.isBlacklisted (
|
||||||
CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, mId)))
|
CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Script, mId)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
|
||||||
|
|
||||||
mMessages = &messages;
|
mMessages = &messages;
|
||||||
|
|
||||||
switch (mWarningMode)
|
switch (mWarningMode)
|
||||||
|
@ -110,8 +100,10 @@ void CSMTools::ScriptCheckStage::perform (int stage, CSMDoc::Messages& messages)
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
mFile = record.get().mId;
|
const CSMWorld::Data& data = mDocument.getData();
|
||||||
std::istringstream input (record.get().mScriptText);
|
|
||||||
|
mFile = data.getScripts().getRecord (stage).get().mId;
|
||||||
|
std::istringstream input (data.getScripts().getRecord (stage).get().mScriptText);
|
||||||
|
|
||||||
Compiler::Scanner scanner (*this, input, mContext.getExtensions());
|
Compiler::Scanner scanner (*this, input, mContext.getExtensions());
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,6 @@ namespace CSMTools
|
||||||
std::string mFile;
|
std::string mFile;
|
||||||
CSMDoc::Messages *mMessages;
|
CSMDoc::Messages *mMessages;
|
||||||
WarningMode mWarningMode;
|
WarningMode mWarningMode;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
CSMDoc::Message::Severity getSeverity (Type type);
|
CSMDoc::Message::Severity getSeverity (Type type);
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,7 @@ void CSMTools::Search::searchTextCell (const CSMWorld::IdTableBase *model,
|
||||||
|
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
|
|
||||||
Qt::CaseSensitivity caseSensitivity = mCase ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
while ((pos = text.indexOf (search, pos, Qt::CaseInsensitive))!=-1)
|
||||||
while ((pos = text.indexOf (search, pos, caseSensitivity))!=-1)
|
|
||||||
{
|
{
|
||||||
std::ostringstream hint;
|
std::ostringstream hint;
|
||||||
hint
|
hint
|
||||||
|
@ -121,26 +120,25 @@ QString CSMTools::Search::flatten (const QString& text) const
|
||||||
return flat;
|
return flat;
|
||||||
}
|
}
|
||||||
|
|
||||||
CSMTools::Search::Search() : mType (Type_None), mValue (0), mCase (false), mIdColumn (0), mTypeColumn (0),
|
CSMTools::Search::Search() : mType (Type_None), mValue (0), mIdColumn (0), mTypeColumn (0),
|
||||||
mPaddingBefore (10), mPaddingAfter (10) {}
|
mPaddingBefore (10), mPaddingAfter (10) {}
|
||||||
|
|
||||||
CSMTools::Search::Search (Type type, bool caseSensitive, const std::string& value)
|
CSMTools::Search::Search (Type type, const std::string& value)
|
||||||
: mType (type), mText (value), mValue (0), mCase (caseSensitive), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10)
|
: mType (type), mText (value), mValue (0), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10)
|
||||||
{
|
{
|
||||||
if (type!=Type_Text && type!=Type_Id)
|
if (type!=Type_Text && type!=Type_Id)
|
||||||
throw std::logic_error ("Invalid search parameter (string)");
|
throw std::logic_error ("Invalid search parameter (string)");
|
||||||
}
|
}
|
||||||
|
|
||||||
CSMTools::Search::Search (Type type, bool caseSensitive, const QRegExp& value)
|
CSMTools::Search::Search (Type type, const QRegExp& value)
|
||||||
: mType (type), mRegExp (value), mValue (0), mCase (caseSensitive), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10)
|
: mType (type), mRegExp (value), mValue (0), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10)
|
||||||
{
|
{
|
||||||
mRegExp.setCaseSensitivity(mCase ? Qt::CaseSensitive : Qt::CaseInsensitive);
|
|
||||||
if (type!=Type_TextRegEx && type!=Type_IdRegEx)
|
if (type!=Type_TextRegEx && type!=Type_IdRegEx)
|
||||||
throw std::logic_error ("Invalid search parameter (RegExp)");
|
throw std::logic_error ("Invalid search parameter (RegExp)");
|
||||||
}
|
}
|
||||||
|
|
||||||
CSMTools::Search::Search (Type type, bool caseSensitive, int value)
|
CSMTools::Search::Search (Type type, int value)
|
||||||
: mType (type), mValue (value), mCase (caseSensitive), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10)
|
: mType (type), mValue (value), mIdColumn (0), mTypeColumn (0), mPaddingBefore (10), mPaddingAfter (10)
|
||||||
{
|
{
|
||||||
if (type!=Type_RecordState)
|
if (type!=Type_RecordState)
|
||||||
throw std::logic_error ("invalid search parameter (int)");
|
throw std::logic_error ("invalid search parameter (int)");
|
||||||
|
|
|
@ -43,7 +43,6 @@ namespace CSMTools
|
||||||
std::string mText;
|
std::string mText;
|
||||||
QRegExp mRegExp;
|
QRegExp mRegExp;
|
||||||
int mValue;
|
int mValue;
|
||||||
bool mCase;
|
|
||||||
std::set<int> mColumns;
|
std::set<int> mColumns;
|
||||||
int mIdColumn;
|
int mIdColumn;
|
||||||
int mTypeColumn;
|
int mTypeColumn;
|
||||||
|
@ -68,11 +67,11 @@ namespace CSMTools
|
||||||
|
|
||||||
Search();
|
Search();
|
||||||
|
|
||||||
Search (Type type, bool caseSensitive, const std::string& value);
|
Search (Type type, const std::string& value);
|
||||||
|
|
||||||
Search (Type type, bool caseSensitive, const QRegExp& value);
|
Search (Type type, const QRegExp& value);
|
||||||
|
|
||||||
Search (Type type, bool caseSensitive, int value);
|
Search (Type type, int value);
|
||||||
|
|
||||||
// Configure search for the specified model.
|
// Configure search for the specified model.
|
||||||
void configure (const CSMWorld::IdTableBase *model);
|
void configure (const CSMWorld::IdTableBase *model);
|
||||||
|
|
|
@ -4,20 +4,14 @@
|
||||||
|
|
||||||
#include <components/esm/loadskil.hpp>
|
#include <components/esm/loadskil.hpp>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include "../world/universalid.hpp"
|
#include "../world/universalid.hpp"
|
||||||
|
|
||||||
CSMTools::SkillCheckStage::SkillCheckStage (const CSMWorld::IdCollection<ESM::Skill>& skills)
|
CSMTools::SkillCheckStage::SkillCheckStage (const CSMWorld::IdCollection<ESM::Skill>& skills)
|
||||||
: mSkills (skills)
|
: mSkills (skills)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::SkillCheckStage::setup()
|
int CSMTools::SkillCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mSkills.getSize();
|
return mSkills.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,8 +19,7 @@ void CSMTools::SkillCheckStage::perform (int stage, CSMDoc::Messages& messages)
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<ESM::Skill>& record = mSkills.getRecord (stage);
|
const CSMWorld::Record<ESM::Skill>& record = mSkills.getRecord (stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (record.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Skill& skill = record.get();
|
const ESM::Skill& skill = record.get();
|
||||||
|
|
|
@ -13,7 +13,6 @@ namespace CSMTools
|
||||||
class SkillCheckStage : public CSMDoc::Stage
|
class SkillCheckStage : public CSMDoc::Stage
|
||||||
{
|
{
|
||||||
const CSMWorld::IdCollection<ESM::Skill>& mSkills;
|
const CSMWorld::IdCollection<ESM::Skill>& mSkills;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
|
@ -4,20 +4,14 @@
|
||||||
|
|
||||||
#include <components/esm/loadskil.hpp>
|
#include <components/esm/loadskil.hpp>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include "../world/universalid.hpp"
|
#include "../world/universalid.hpp"
|
||||||
|
|
||||||
CSMTools::SoundCheckStage::SoundCheckStage (const CSMWorld::IdCollection<ESM::Sound>& sounds)
|
CSMTools::SoundCheckStage::SoundCheckStage (const CSMWorld::IdCollection<ESM::Sound>& sounds)
|
||||||
: mSounds (sounds)
|
: mSounds (sounds)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::SoundCheckStage::setup()
|
int CSMTools::SoundCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mSounds.getSize();
|
return mSounds.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,8 +19,7 @@ void CSMTools::SoundCheckStage::perform (int stage, CSMDoc::Messages& messages)
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<ESM::Sound>& record = mSounds.getRecord (stage);
|
const CSMWorld::Record<ESM::Sound>& record = mSounds.getRecord (stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (record.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Sound& sound = record.get();
|
const ESM::Sound& sound = record.get();
|
||||||
|
|
|
@ -13,7 +13,6 @@ namespace CSMTools
|
||||||
class SoundCheckStage : public CSMDoc::Stage
|
class SoundCheckStage : public CSMDoc::Stage
|
||||||
{
|
{
|
||||||
const CSMWorld::IdCollection<ESM::Sound>& mSounds;
|
const CSMWorld::IdCollection<ESM::Sound>& mSounds;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include "../world/refiddata.hpp"
|
#include "../world/refiddata.hpp"
|
||||||
#include "../world/universalid.hpp"
|
#include "../world/universalid.hpp"
|
||||||
|
|
||||||
|
@ -13,24 +11,20 @@ CSMTools::SoundGenCheckStage::SoundGenCheckStage(const CSMWorld::IdCollection<ES
|
||||||
: mSoundGens(soundGens),
|
: mSoundGens(soundGens),
|
||||||
mSounds(sounds),
|
mSounds(sounds),
|
||||||
mReferenceables(referenceables)
|
mReferenceables(referenceables)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::SoundGenCheckStage::setup()
|
int CSMTools::SoundGenCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mSoundGens.getSize();
|
return mSoundGens.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMTools::SoundGenCheckStage::perform(int stage, CSMDoc::Messages &messages)
|
void CSMTools::SoundGenCheckStage::perform(int stage, CSMDoc::Messages &messages)
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<ESM::SoundGenerator> &record = mSoundGens.getRecord(stage);
|
const CSMWorld::Record<ESM::SoundGenerator> &record = mSoundGens.getRecord(stage);
|
||||||
|
if (record.isDeleted())
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
{
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const ESM::SoundGenerator& soundGen = record.get();
|
const ESM::SoundGenerator& soundGen = record.get();
|
||||||
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_SoundGen, soundGen.mId);
|
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_SoundGen, soundGen.mId);
|
||||||
|
|
|
@ -13,7 +13,6 @@ namespace CSMTools
|
||||||
const CSMWorld::IdCollection<ESM::SoundGenerator> &mSoundGens;
|
const CSMWorld::IdCollection<ESM::SoundGenerator> &mSoundGens;
|
||||||
const CSMWorld::IdCollection<ESM::Sound> &mSounds;
|
const CSMWorld::IdCollection<ESM::Sound> &mSounds;
|
||||||
const CSMWorld::RefIdCollection &mReferenceables;
|
const CSMWorld::RefIdCollection &mReferenceables;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SoundGenCheckStage(const CSMWorld::IdCollection<ESM::SoundGenerator> &soundGens,
|
SoundGenCheckStage(const CSMWorld::IdCollection<ESM::SoundGenerator> &soundGens,
|
||||||
|
|
|
@ -5,20 +5,14 @@
|
||||||
|
|
||||||
#include <components/esm/loadspel.hpp>
|
#include <components/esm/loadspel.hpp>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include "../world/universalid.hpp"
|
#include "../world/universalid.hpp"
|
||||||
|
|
||||||
CSMTools::SpellCheckStage::SpellCheckStage (const CSMWorld::IdCollection<ESM::Spell>& spells)
|
CSMTools::SpellCheckStage::SpellCheckStage (const CSMWorld::IdCollection<ESM::Spell>& spells)
|
||||||
: mSpells (spells)
|
: mSpells (spells)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::SpellCheckStage::setup()
|
int CSMTools::SpellCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mSpells.getSize();
|
return mSpells.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,8 +20,7 @@ void CSMTools::SpellCheckStage::perform (int stage, CSMDoc::Messages& messages)
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<ESM::Spell>& record = mSpells.getRecord (stage);
|
const CSMWorld::Record<ESM::Spell>& record = mSpells.getRecord (stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (record.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Spell& spell = record.get();
|
const ESM::Spell& spell = record.get();
|
||||||
|
|
|
@ -13,7 +13,6 @@ namespace CSMTools
|
||||||
class SpellCheckStage : public CSMDoc::Stage
|
class SpellCheckStage : public CSMDoc::Stage
|
||||||
{
|
{
|
||||||
const CSMWorld::IdCollection<ESM::Spell>& mSpells;
|
const CSMWorld::IdCollection<ESM::Spell>& mSpells;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,18 @@
|
||||||
#include "startscriptcheck.hpp"
|
#include "startscriptcheck.hpp"
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include <components/misc/stringops.hpp>
|
#include <components/misc/stringops.hpp>
|
||||||
|
|
||||||
CSMTools::StartScriptCheckStage::StartScriptCheckStage (
|
CSMTools::StartScriptCheckStage::StartScriptCheckStage (
|
||||||
const CSMWorld::IdCollection<ESM::StartScript>& startScripts,
|
const CSMWorld::IdCollection<ESM::StartScript>& startScripts,
|
||||||
const CSMWorld::IdCollection<ESM::Script>& scripts)
|
const CSMWorld::IdCollection<ESM::Script>& scripts)
|
||||||
: mStartScripts (startScripts), mScripts (scripts)
|
: mStartScripts (startScripts), mScripts (scripts)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSMTools::StartScriptCheckStage::perform(int stage, CSMDoc::Messages& messages)
|
void CSMTools::StartScriptCheckStage::perform(int stage, CSMDoc::Messages& messages)
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<ESM::StartScript>& record = mStartScripts.getRecord (stage);
|
const CSMWorld::Record<ESM::StartScript>& record = mStartScripts.getRecord (stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (record.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && record.mState == CSMWorld::RecordBase::State_BaseOnly) || record.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::string scriptId = record.get().mId;
|
std::string scriptId = record.get().mId;
|
||||||
|
@ -31,7 +26,5 @@ void CSMTools::StartScriptCheckStage::perform(int stage, CSMDoc::Messages& messa
|
||||||
|
|
||||||
int CSMTools::StartScriptCheckStage::setup()
|
int CSMTools::StartScriptCheckStage::setup()
|
||||||
{
|
{
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mStartScripts.getSize();
|
return mStartScripts.getSize();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ namespace CSMTools
|
||||||
{
|
{
|
||||||
const CSMWorld::IdCollection<ESM::StartScript>& mStartScripts;
|
const CSMWorld::IdCollection<ESM::StartScript>& mStartScripts;
|
||||||
const CSMWorld::IdCollection<ESM::Script>& mScripts;
|
const CSMWorld::IdCollection<ESM::Script>& mScripts;
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include "../prefs/state.hpp"
|
|
||||||
|
|
||||||
#include "../world/infoselectwrapper.hpp"
|
#include "../world/infoselectwrapper.hpp"
|
||||||
|
|
||||||
CSMTools::TopicInfoCheckStage::TopicInfoCheckStage(
|
CSMTools::TopicInfoCheckStage::TopicInfoCheckStage(
|
||||||
|
@ -31,9 +29,7 @@ CSMTools::TopicInfoCheckStage::TopicInfoCheckStage(
|
||||||
mTopics(topics),
|
mTopics(topics),
|
||||||
mReferencables(referencables),
|
mReferencables(referencables),
|
||||||
mSoundFiles(soundFiles)
|
mSoundFiles(soundFiles)
|
||||||
{
|
{}
|
||||||
mIgnoreBaseRecords = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CSMTools::TopicInfoCheckStage::setup()
|
int CSMTools::TopicInfoCheckStage::setup()
|
||||||
{
|
{
|
||||||
|
@ -71,8 +67,6 @@ int CSMTools::TopicInfoCheckStage::setup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mIgnoreBaseRecords = CSMPrefs::get()["Reports"]["ignore-base-records"].isTrue();
|
|
||||||
|
|
||||||
return mTopicInfos.getSize();
|
return mTopicInfos.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,8 +74,7 @@ void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& message
|
||||||
{
|
{
|
||||||
const CSMWorld::Record<CSMWorld::Info>& infoRecord = mTopicInfos.getRecord(stage);
|
const CSMWorld::Record<CSMWorld::Info>& infoRecord = mTopicInfos.getRecord(stage);
|
||||||
|
|
||||||
// Skip "Base" records (setting!) and "Deleted" records
|
if (infoRecord.isDeleted())
|
||||||
if ((mIgnoreBaseRecords && infoRecord.mState == CSMWorld::RecordBase::State_BaseOnly) || infoRecord.isDeleted())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const CSMWorld::Info& topicInfo = infoRecord.get();
|
const CSMWorld::Info& topicInfo = infoRecord.get();
|
||||||
|
|
|
@ -65,8 +65,6 @@ namespace CSMTools
|
||||||
|
|
||||||
std::set<std::string> mCellNames;
|
std::set<std::string> mCellNames;
|
||||||
|
|
||||||
bool mIgnoreBaseRecords;
|
|
||||||
|
|
||||||
// These return false when not successful and write an error
|
// These return false when not successful and write an error
|
||||||
bool verifyActor(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages);
|
bool verifyActor(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages);
|
||||||
bool verifyCell(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages);
|
bool verifyCell(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages);
|
||||||
|
|
|
@ -88,7 +88,6 @@ namespace CSMWorld
|
||||||
Display_UnsignedInteger8,
|
Display_UnsignedInteger8,
|
||||||
Display_Integer,
|
Display_Integer,
|
||||||
Display_Float,
|
Display_Float,
|
||||||
Display_Double,
|
|
||||||
Display_Var,
|
Display_Var,
|
||||||
Display_GmstVarType,
|
Display_GmstVarType,
|
||||||
Display_GlobalVarType,
|
Display_GlobalVarType,
|
||||||
|
|
|
@ -136,7 +136,7 @@ namespace CSMWorld
|
||||||
struct VarTypeColumn : public Column<ESXRecordT>
|
struct VarTypeColumn : public Column<ESXRecordT>
|
||||||
{
|
{
|
||||||
VarTypeColumn (ColumnBase::Display display)
|
VarTypeColumn (ColumnBase::Display display)
|
||||||
: Column<ESXRecordT> (Columns::ColumnId_ValueType, display, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)
|
: Column<ESXRecordT> (Columns::ColumnId_ValueType, display)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
virtual QVariant get (const Record<ESXRecordT>& record) const
|
virtual QVariant get (const Record<ESXRecordT>& record) const
|
||||||
|
@ -161,7 +161,7 @@ namespace CSMWorld
|
||||||
template<typename ESXRecordT>
|
template<typename ESXRecordT>
|
||||||
struct VarValueColumn : public Column<ESXRecordT>
|
struct VarValueColumn : public Column<ESXRecordT>
|
||||||
{
|
{
|
||||||
VarValueColumn() : Column<ESXRecordT> (Columns::ColumnId_Value, ColumnBase::Display_Var, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh) {}
|
VarValueColumn() : Column<ESXRecordT> (Columns::ColumnId_Value, ColumnBase::Display_Var) {}
|
||||||
|
|
||||||
virtual QVariant get (const Record<ESXRecordT>& record) const
|
virtual QVariant get (const Record<ESXRecordT>& record) const
|
||||||
{
|
{
|
||||||
|
@ -1371,7 +1371,7 @@ namespace CSMWorld
|
||||||
RotColumn (ESM::Position ESXRecordT::* position, int index, bool door)
|
RotColumn (ESM::Position ESXRecordT::* position, int index, bool door)
|
||||||
: Column<ESXRecordT> (
|
: Column<ESXRecordT> (
|
||||||
(door ? Columns::ColumnId_DoorPositionXRot : Columns::ColumnId_PositionXRot)+index,
|
(door ? Columns::ColumnId_DoorPositionXRot : Columns::ColumnId_PositionXRot)+index,
|
||||||
ColumnBase::Display_Double), mPosition (position), mIndex (index) {}
|
ColumnBase::Display_Float), mPosition (position), mIndex (index) {}
|
||||||
|
|
||||||
virtual QVariant get (const Record<ESXRecordT>& record) const
|
virtual QVariant get (const Record<ESXRecordT>& record) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -84,28 +84,15 @@ bool CSMWorld::IdTable::setData (const QModelIndex &index, const QVariant &value
|
||||||
if (mIdCollection->getColumn (index.column()).isEditable() && role==Qt::EditRole)
|
if (mIdCollection->getColumn (index.column()).isEditable() && role==Qt::EditRole)
|
||||||
{
|
{
|
||||||
mIdCollection->setData (index.row(), index.column(), value);
|
mIdCollection->setData (index.row(), index.column(), value);
|
||||||
|
emit dataChanged(index, index);
|
||||||
|
|
||||||
|
// Modifying a value can also change the Modified status of a record.
|
||||||
int stateColumn = searchColumnIndex(Columns::ColumnId_Modification);
|
int stateColumn = searchColumnIndex(Columns::ColumnId_Modification);
|
||||||
if (stateColumn != -1)
|
if (stateColumn != -1)
|
||||||
{
|
{
|
||||||
if (index.column() == stateColumn)
|
QModelIndex stateIndex = this->index(index.row(), stateColumn);
|
||||||
{
|
emit dataChanged(stateIndex, stateIndex);
|
||||||
// modifying the state column can modify other values. we need to tell
|
}
|
||||||
// views that the whole row has changed.
|
|
||||||
|
|
||||||
emit dataChanged(this->index(index.row(), 0),
|
|
||||||
this->index(index.row(), columnCount(index.parent())));
|
|
||||||
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
emit dataChanged(index, index);
|
|
||||||
|
|
||||||
// Modifying a value can also change the Modified status of a record.
|
|
||||||
QModelIndex stateIndex = this->index(index.row(), stateColumn);
|
|
||||||
emit dataChanged(stateIndex, stateIndex);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
emit dataChanged(index, index);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,11 +184,11 @@ CSMWorld::RefIdCollection::RefIdCollection()
|
||||||
mColumns.back().addColumn(
|
mColumns.back().addColumn(
|
||||||
new RefIdColumn (Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float));
|
new RefIdColumn (Columns::ColumnId_PosZ, CSMWorld::ColumnBase::Display_Float));
|
||||||
mColumns.back().addColumn(
|
mColumns.back().addColumn(
|
||||||
new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Double));
|
new RefIdColumn (Columns::ColumnId_RotX, CSMWorld::ColumnBase::Display_Float));
|
||||||
mColumns.back().addColumn(
|
mColumns.back().addColumn(
|
||||||
new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Double));
|
new RefIdColumn (Columns::ColumnId_RotY, CSMWorld::ColumnBase::Display_Float));
|
||||||
mColumns.back().addColumn(
|
mColumns.back().addColumn(
|
||||||
new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Double));
|
new RefIdColumn (Columns::ColumnId_RotZ, CSMWorld::ColumnBase::Display_Float));
|
||||||
|
|
||||||
// Nested table
|
// Nested table
|
||||||
mColumns.push_back(RefIdColumn (Columns::ColumnId_AiPackageList,
|
mColumns.push_back(RefIdColumn (Columns::ColumnId_AiPackageList,
|
||||||
|
|
|
@ -101,39 +101,15 @@ void CSVDoc::View::setupFileMenu()
|
||||||
file->addAction(exit);
|
file->addAction(exit);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
|
|
||||||
void updateUndoRedoAction(QAction *action, const std::string &settingsKey)
|
|
||||||
{
|
|
||||||
QKeySequence seq;
|
|
||||||
CSMPrefs::State::get().getShortcutManager().getSequence(settingsKey, seq);
|
|
||||||
action->setShortcut(seq);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSVDoc::View::undoActionChanged()
|
|
||||||
{
|
|
||||||
updateUndoRedoAction(mUndo, "document-edit-undo");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSVDoc::View::redoActionChanged()
|
|
||||||
{
|
|
||||||
updateUndoRedoAction(mRedo, "document-edit-redo");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CSVDoc::View::setupEditMenu()
|
void CSVDoc::View::setupEditMenu()
|
||||||
{
|
{
|
||||||
QMenu *edit = menuBar()->addMenu (tr ("Edit"));
|
QMenu *edit = menuBar()->addMenu (tr ("Edit"));
|
||||||
|
|
||||||
mUndo = mDocument->getUndoStack().createUndoAction (this, tr("Undo"));
|
mUndo = mDocument->getUndoStack().createUndoAction (this, tr("Undo"));
|
||||||
setupShortcut("document-edit-undo", mUndo);
|
setupShortcut("document-edit-undo", mUndo);
|
||||||
connect(mUndo, SIGNAL (changed ()), this, SLOT (undoActionChanged ()));
|
|
||||||
edit->addAction (mUndo);
|
edit->addAction (mUndo);
|
||||||
|
|
||||||
mRedo = mDocument->getUndoStack().createRedoAction (this, tr("Redo"));
|
mRedo= mDocument->getUndoStack().createRedoAction (this, tr("Redo"));
|
||||||
connect(mRedo, SIGNAL (changed ()), this, SLOT (redoActionChanged ()));
|
|
||||||
setupShortcut("document-edit-redo", mRedo);
|
setupShortcut("document-edit-redo", mRedo);
|
||||||
edit->addAction (mRedo);
|
edit->addAction (mRedo);
|
||||||
|
|
||||||
|
@ -646,7 +622,6 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin
|
||||||
}
|
}
|
||||||
assert(view);
|
assert(view);
|
||||||
view->setParent(this);
|
view->setParent(this);
|
||||||
view->setEditLock (mDocument->getState() & CSMDoc::State_Locked);
|
|
||||||
mSubViews.append(view); // only after assert
|
mSubViews.append(view); // only after assert
|
||||||
|
|
||||||
int minWidth = windows["minimum-width"].toInt();
|
int minWidth = windows["minimum-width"].toInt();
|
||||||
|
|
|
@ -152,10 +152,6 @@ namespace CSVDoc
|
||||||
|
|
||||||
void settingChanged (const CSMPrefs::Setting *setting);
|
void settingChanged (const CSMPrefs::Setting *setting);
|
||||||
|
|
||||||
void undoActionChanged();
|
|
||||||
|
|
||||||
void redoActionChanged();
|
|
||||||
|
|
||||||
void newView();
|
void newView();
|
||||||
|
|
||||||
void save();
|
void save();
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue