forked from mirror/openmw-tes3mp
Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
|
dd2e0c6eaf |
2609 changed files with 77674 additions and 185230 deletions
|
@ -1,16 +0,0 @@
|
||||||
root = true
|
|
||||||
|
|
||||||
[*.cpp]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.hpp]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.glsl]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
insert_final_newline = false
|
|
38
.gitignore
vendored
38
.gitignore
vendored
|
@ -5,13 +5,9 @@ CMakeCache.txt
|
||||||
cmake_install.cmake
|
cmake_install.cmake
|
||||||
Makefile
|
Makefile
|
||||||
makefile
|
makefile
|
||||||
build*
|
build
|
||||||
prebuilt
|
prebuilt
|
||||||
|
|
||||||
##windows build process
|
|
||||||
/deps
|
|
||||||
/MSVC*
|
|
||||||
|
|
||||||
## doxygen
|
## doxygen
|
||||||
Doxygen
|
Doxygen
|
||||||
|
|
||||||
|
@ -25,10 +21,6 @@ Doxygen
|
||||||
.project
|
.project
|
||||||
.settings
|
.settings
|
||||||
.directory
|
.directory
|
||||||
.idea
|
|
||||||
cmake-build-*
|
|
||||||
files/windows/*.aps
|
|
||||||
cmake-build-*
|
|
||||||
## qt-creator
|
## qt-creator
|
||||||
CMakeLists.txt.user*
|
CMakeLists.txt.user*
|
||||||
|
|
||||||
|
@ -41,36 +33,16 @@ resources
|
||||||
|
|
||||||
## binaries
|
## binaries
|
||||||
/esmtool
|
/esmtool
|
||||||
|
/mwiniimport
|
||||||
|
/omwlauncher
|
||||||
/openmw
|
/openmw
|
||||||
/opencs
|
/opencs
|
||||||
/niftest
|
/niftest
|
||||||
/bsatool
|
|
||||||
/openmw-cs
|
|
||||||
/openmw-essimporter
|
|
||||||
/openmw-iniimporter
|
|
||||||
/openmw-launcher
|
|
||||||
/openmw-wizard
|
|
||||||
|
|
||||||
## generated objects
|
## generated objects
|
||||||
apps/openmw/config.hpp
|
apps/openmw/config.hpp
|
||||||
apps/launcher/ui_contentselector.h
|
components/version/version.hpp
|
||||||
apps/launcher/ui_settingspage.h
|
|
||||||
apps/opencs/ui_contentselector.h
|
|
||||||
apps/opencs/ui_filedialog.h
|
|
||||||
apps/wizard/qrc_wizard.cxx
|
|
||||||
apps/wizard/ui_componentselectionpage.h
|
|
||||||
apps/wizard/ui_conclusionpage.h
|
|
||||||
apps/wizard/ui_existinginstallationpage.h
|
|
||||||
apps/wizard/ui_importpage.h
|
|
||||||
apps/wizard/ui_installationpage.h
|
|
||||||
apps/wizard/ui_installationtargetpage.h
|
|
||||||
apps/wizard/ui_intropage.h
|
|
||||||
apps/wizard/ui_languageselectionpage.h
|
|
||||||
apps/wizard/ui_methodselectionpage.h
|
|
||||||
components/ui_contentselector.h
|
|
||||||
docs/mainpage.hpp
|
docs/mainpage.hpp
|
||||||
docs/Doxyfile
|
|
||||||
docs/DoxyfilePages
|
|
||||||
moc_*.cxx
|
moc_*.cxx
|
||||||
*.cxx_parameters
|
*.cxx_parameters
|
||||||
*qrc_launcher.cxx
|
*qrc_launcher.cxx
|
||||||
|
@ -82,5 +54,3 @@ moc_*.cxx
|
||||||
*ui_playpage.h
|
*ui_playpage.h
|
||||||
*.[ao]
|
*.[ao]
|
||||||
*.so
|
*.so
|
||||||
openmw.appdata.xml
|
|
||||||
venv/
|
|
||||||
|
|
|
@ -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 +0,0 @@
|
||||||
[submodule "extern/breakpad"]
|
|
||||||
path = extern/breakpad
|
|
||||||
url = https://chromium.googlesource.com/breakpad/breakpad
|
|
90
.travis.yml
90
.travis.yml
|
@ -1,92 +1,56 @@
|
||||||
os:
|
os:
|
||||||
- linux
|
- linux
|
||||||
# - osx
|
- osx
|
||||||
osx_image: xcode9.4
|
|
||||||
language: cpp
|
language: cpp
|
||||||
sudo: required
|
|
||||||
dist: xenial
|
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
- coverity_scan
|
- coverity_scan
|
||||||
- /openmw-.*$/
|
- /openmw-.*$/
|
||||||
- /^[0-9]+\.[0-9]+\.[0-9]+.*$/
|
|
||||||
env:
|
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: "jybGzAdUbqt9vWR/GEnRd96BgAi/7Zd1+2HK68j/i/8+/1YH2XxLOy4Jv/DUBhBlJIkxs/Xv8dRcUlFOclZDHX1d/9Qnsqd3oUVkD7k1y7cTOWy9TBQaE/v/kZo3LpzA3xPwwthrb0BvqIbOfIELi5fS5s8ba85WFRg3AX70wWE="
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
|
||||||
sources:
|
|
||||||
- sourceline: 'ppa:openmw/openmw'
|
|
||||||
- sourceline: 'ppa:rakhimov/boost'
|
|
||||||
- ubuntu-toolchain-r-test
|
|
||||||
packages: [
|
|
||||||
# Dev
|
|
||||||
cmake, clang-6.0, libunshield-dev, libtinyxml-dev,
|
|
||||||
g++-8,
|
|
||||||
# Tests
|
|
||||||
libgtest-dev, google-mock,
|
|
||||||
# Boost
|
|
||||||
libboost-filesystem1.61-dev, libboost-program-options1.61-dev, libboost-system1.61-dev,
|
|
||||||
# FFmpeg
|
|
||||||
libavcodec-dev, libavformat-dev, libavutil-dev, libswscale-dev,
|
|
||||||
# Audio & Video
|
|
||||||
libsdl2-dev, qtbase5-dev, libopenal-dev,
|
|
||||||
# The other ones from OpenMW ppa
|
|
||||||
libbullet-dev, libswresample-dev, libopenscenegraph-3.4-dev, libmygui-dev,
|
|
||||||
# tes3mp stuff
|
|
||||||
libboost1.61-dev, libqt5opengl5-dev, libluajit-5.1-dev
|
|
||||||
]
|
|
||||||
|
|
||||||
coverity_scan:
|
coverity_scan:
|
||||||
project:
|
project:
|
||||||
name: "TES3MP/openmw-tes3mp"
|
name: "OpenMW/openmw"
|
||||||
description: "<Your project description here>"
|
description: "<Your project description here>"
|
||||||
notification_email: koncord@tes3mp.com
|
notification_email: scrawl@baseoftrash.de
|
||||||
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 ."
|
||||||
build_command: "make -j3"
|
build_command: "make -j3"
|
||||||
branch_pattern: coverity_scan
|
branch_pattern: coverity_scan
|
||||||
matrix:
|
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.6 --use-cc clang-3.6 --use-c++ clang++-3.6 "
|
||||||
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
|
|
||||||
compiler: clang
|
compiler: clang
|
||||||
- os: linux
|
|
||||||
env:
|
|
||||||
- MATRIX_CC="CC=gcc-8 && CXX=g++-8"
|
|
||||||
- os: linux
|
|
||||||
env:
|
|
||||||
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
|
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- env:
|
- env: ANALYZE="scan-build-3.6 --use-cc clang-3.6 --use-c++ clang++-3.6 "
|
||||||
- MATRIX_CC="CC=clang-6.0 && CXX=clang++-6.0"
|
|
||||||
- env:
|
|
||||||
- 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
|
- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./CI/before_install.linux.sh; fi
|
||||||
before_script: ./CI/before_script.${TRAVIS_OS_NAME}.sh
|
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ./CI/before_install.osx.sh; fi
|
||||||
|
before_script:
|
||||||
|
- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./CI/before_script.linux.sh; fi
|
||||||
|
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ./CI/before_script.osx.sh; fi
|
||||||
script:
|
script:
|
||||||
- cd ./build
|
- cd ./build
|
||||||
- if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j3; fi
|
- if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then ${ANALYZE}make -j4; fi
|
||||||
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
|
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
|
||||||
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi
|
after_script:
|
||||||
- if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
|
- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi
|
||||||
#notifications:
|
notifications:
|
||||||
# email:
|
recipients:
|
||||||
# recipients:
|
- corrmage+travis-ci@gmail.com
|
||||||
# - corrmage+travis-ci@gmail.com
|
email:
|
||||||
# on_success: change
|
on_success: change
|
||||||
# on_failure: always
|
on_failure: always
|
||||||
# irc:
|
irc:
|
||||||
# channels:
|
channels:
|
||||||
# - "chat.freenode.net#openmw"
|
- "chat.freenode.net#openmw"
|
||||||
# on_success: change
|
on_success: change
|
||||||
# on_failure: always
|
on_failure: always
|
||||||
# use_notice: true
|
use_notice: true
|
||||||
|
|
92
AUTHORS.md
92
AUTHORS.md
|
@ -13,42 +13,22 @@ Programmers
|
||||||
Marc Zinnschlag (Zini) - Lead Programmer/Project Manager
|
Marc Zinnschlag (Zini) - Lead Programmer/Project Manager
|
||||||
|
|
||||||
Adam Hogan (aurix)
|
Adam Hogan (aurix)
|
||||||
Aesylwinn
|
|
||||||
aegis
|
|
||||||
AHSauge
|
|
||||||
Aleksandar Jovanov
|
Aleksandar Jovanov
|
||||||
Alex Haddad (rainChu)
|
Alex Haddad (rainChu)
|
||||||
Alex McKibben
|
Alex McKibben (WeirdSexy)
|
||||||
alexanderkjall
|
|
||||||
Alexander Nadeau (wareya)
|
Alexander Nadeau (wareya)
|
||||||
Alexander Olofsson (Ace)
|
Alexander Olofsson (Ace)
|
||||||
Alex S (docwest)
|
|
||||||
Allofich
|
|
||||||
Andrei Kortunov (akortunov)
|
|
||||||
AnyOldName3
|
|
||||||
Aussiemon
|
|
||||||
Austin Salgat (Salgat)
|
|
||||||
Artem Kotsynyak (greye)
|
Artem Kotsynyak (greye)
|
||||||
artemutin
|
|
||||||
Arthur Moore (EmperorArthur)
|
Arthur Moore (EmperorArthur)
|
||||||
Assumeru
|
|
||||||
athile
|
athile
|
||||||
Ben Shealy (bentsherman)
|
|
||||||
Bret Curtis (psi29a)
|
Bret Curtis (psi29a)
|
||||||
Britt Mathis (galdor557)
|
Britt Mathis (galdor557)
|
||||||
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)
|
||||||
Cris Mihalache (Mirceam)
|
Cris Mihalache (Mirceam)
|
||||||
crussell187
|
|
||||||
DanielVukelich
|
|
||||||
darkf
|
darkf
|
||||||
David Cernat (davidcernat)
|
|
||||||
devnexen
|
|
||||||
Dieho
|
|
||||||
Dmitry Shkurskiy (endorph)
|
Dmitry Shkurskiy (endorph)
|
||||||
Douglas Diniz (Dgdiniz)
|
Douglas Diniz (Dgdiniz)
|
||||||
Douglas Mencken (dougmencken)
|
Douglas Mencken (dougmencken)
|
||||||
|
@ -57,47 +37,31 @@ Programmers
|
||||||
Edmondo Tommasina (edmondo)
|
Edmondo Tommasina (edmondo)
|
||||||
Eduard Cot (trombonecot)
|
Eduard Cot (trombonecot)
|
||||||
Eli2
|
Eli2
|
||||||
elsid
|
|
||||||
Emanuel Guével (potatoesmaster)
|
Emanuel Guével (potatoesmaster)
|
||||||
eroen
|
eroen
|
||||||
escondida
|
escondida
|
||||||
Evgeniy Mineev (sandstranger)
|
Evgeniy Mineev (sandstranger)
|
||||||
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
|
||||||
Haoda Wang (h313)
|
|
||||||
hristoast
|
|
||||||
Internecine
|
Internecine
|
||||||
Jacob Essex (Yacoby)
|
Jacob Essex (Yacoby)
|
||||||
Jake Westrip (16bitint)
|
Jannik Heller (scrawl)
|
||||||
Jason Hooks (jhooks)
|
Jason Hooks (jhooks)
|
||||||
jeaye
|
jeaye
|
||||||
Jeffrey Haines (Jyby)
|
Jeffrey Haines (Jyby)
|
||||||
Jengerer
|
Jengerer
|
||||||
Jiří Kuneš (kunesj)
|
|
||||||
Joe Wilkerson (neuralroberts)
|
|
||||||
Joel Graff (graffy)
|
Joel Graff (graffy)
|
||||||
John Blomberg (fstp)
|
John Blomberg (fstp)
|
||||||
Jordan Ayers
|
Jordan Ayers
|
||||||
Jordan Milne
|
Jordan Milne
|
||||||
Jules Blok (Armada651)
|
|
||||||
julianko
|
|
||||||
Julien Voisin (jvoisin/ap0)
|
Julien Voisin (jvoisin/ap0)
|
||||||
Karl-Felix Glatzer (k1ll)
|
Karl-Felix Glatzer (k1ll)
|
||||||
Kevin Poitra (PuppyKevin)
|
Kevin Poitra (PuppyKevin)
|
||||||
Koncord
|
|
||||||
Kurnevsky Evgeny (kurnevsky)
|
|
||||||
Lars Söderberg (Lazaroth)
|
Lars Söderberg (Lazaroth)
|
||||||
lazydev
|
lazydev
|
||||||
Leon Krieg (lkrieg)
|
|
||||||
Leon Saunders (emoose)
|
Leon Saunders (emoose)
|
||||||
Łukasz Gołębiewski (lukago)
|
|
||||||
logzero
|
|
||||||
lohikaarme
|
|
||||||
Lukasz Gromanowski (lgro)
|
Lukasz Gromanowski (lgro)
|
||||||
Manuel Edelmann (vorenon)
|
Manuel Edelmann (vorenon)
|
||||||
Marc Bouvier (CramitDeFrog)
|
Marc Bouvier (CramitDeFrog)
|
||||||
|
@ -105,90 +69,51 @@ Programmers
|
||||||
Mark Siewert (mark76)
|
Mark Siewert (mark76)
|
||||||
Marco Melletti (mellotanica)
|
Marco Melletti (mellotanica)
|
||||||
Marco Schulze
|
Marco Schulze
|
||||||
Martin Otto (MAtahualpa)
|
|
||||||
Mateusz Kołaczek (PL_kolek)
|
Mateusz Kołaczek (PL_kolek)
|
||||||
Mateusz Malisz (malice)
|
|
||||||
megaton
|
megaton
|
||||||
Michael Hogan (Xethik)
|
Michael Hogan (Xethik)
|
||||||
Michael Mc Donnell
|
Michael Mc Donnell
|
||||||
Michael Papageorgiou (werdanith)
|
Michael Papageorgiou (werdanith)
|
||||||
Michał Bień (Glorf)
|
Michał Bień (Glorf)
|
||||||
Michał Moroz (dragonee)
|
|
||||||
Miloslav Číž (drummyfish)
|
|
||||||
Miroslav Puda (pakanek)
|
Miroslav Puda (pakanek)
|
||||||
MiroslavR
|
MiroslavR
|
||||||
Mitchell Schwitzer (schwitzerm)
|
|
||||||
naclander
|
naclander
|
||||||
Narmo
|
Narmo
|
||||||
Nathan Jeffords (blunted2night)
|
Nathan Jeffords (blunted2night)
|
||||||
NeveHanter
|
NeveHanter
|
||||||
Nialsy
|
|
||||||
Nikolay Kasyanov (corristo)
|
Nikolay Kasyanov (corristo)
|
||||||
nobrakal
|
nobrakal
|
||||||
Nolan Poe (nopoe)
|
Nolan Poe (nopoe)
|
||||||
Oleg Chkan (mrcheko)
|
|
||||||
Paul Cercueil (pcercuei)
|
|
||||||
Paul McElroy (Greendogo)
|
Paul McElroy (Greendogo)
|
||||||
Pi03k
|
|
||||||
Pieter van der Kloet (pvdk)
|
Pieter van der Kloet (pvdk)
|
||||||
pkubik
|
|
||||||
PlutonicOverkill
|
|
||||||
Radu-Marius Popovici (rpopovici)
|
Radu-Marius Popovici (rpopovici)
|
||||||
Rafael Moura (dhustkoder)
|
|
||||||
rdimesio
|
rdimesio
|
||||||
rexelion
|
|
||||||
riothamus
|
riothamus
|
||||||
Rob Cutmore (rcutmore)
|
|
||||||
Robert MacGregor (Ragora)
|
Robert MacGregor (Ragora)
|
||||||
Rohit Nirmal
|
Rohit Nirmal
|
||||||
Roman Melnik (Kromgart)
|
Roman Melnik (Kromgart)
|
||||||
Roman Proskuryakov (kpp)
|
Roman Proskuryakov (humbug)
|
||||||
Roman Siromakha (elsid)
|
|
||||||
Sandy Carter (bwrsandman)
|
Sandy Carter (bwrsandman)
|
||||||
Scott Howard
|
Scott Howard
|
||||||
scrawl
|
|
||||||
Sebastian Wick (swick)
|
Sebastian Wick (swick)
|
||||||
Sergey Shambir
|
Sergey Shambir
|
||||||
ShadowRadiance
|
|
||||||
Siimacore
|
|
||||||
sir_herrbatka
|
sir_herrbatka
|
||||||
smbas
|
|
||||||
spycrab
|
|
||||||
Stefan Galowicz (bogglez)
|
Stefan Galowicz (bogglez)
|
||||||
Stanislav Bobrov (Jiub)
|
Stanislav Bobrov (Jiub)
|
||||||
stil-t
|
|
||||||
svaante
|
|
||||||
Sylvain Thesnieres (Garvek)
|
Sylvain Thesnieres (Garvek)
|
||||||
t6
|
|
||||||
terrorfisch
|
terrorfisch
|
||||||
thegriglat
|
|
||||||
Thomas Luppi (Digmaster)
|
Thomas Luppi (Digmaster)
|
||||||
tri4ng1e
|
|
||||||
unelsson
|
|
||||||
Will Herrmann (Thunderforge)
|
|
||||||
Tom Mason (wheybags)
|
Tom Mason (wheybags)
|
||||||
Torben Leif Carrington (TorbenC)
|
Torben Leif Carrington (TorbenC)
|
||||||
viadanna
|
viadanna
|
||||||
Vincent Heuken
|
Vincent Heuken
|
||||||
vocollapse
|
vocollapse
|
||||||
zelurker
|
|
||||||
|
|
||||||
Documentation
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Adam Bowen (adamnbowen)
|
|
||||||
Alejandro Sanchez (HiPhish)
|
|
||||||
Bodillium
|
|
||||||
Bret Curtis (psi29a)
|
|
||||||
Cramal
|
|
||||||
Ryan Tucker (Ravenwing)
|
|
||||||
sir_herrbatka
|
|
||||||
|
|
||||||
Packagers
|
Packagers
|
||||||
---------
|
---------
|
||||||
|
|
||||||
Alexander Olofsson (Ace) - Windows
|
Alexander Olofsson (Ace) - Windows
|
||||||
Bret Curtis (psi29a) - Debian and Ubuntu Linux
|
Bret Curtis (psi29a) - Ubuntu Linux
|
||||||
Edmondo Tommasina (edmondo) - Gentoo Linux
|
Edmondo Tommasina (edmondo) - Gentoo Linux
|
||||||
Julian Ospald (hasufell) - Gentoo Linux
|
Julian Ospald (hasufell) - Gentoo Linux
|
||||||
Karl-Felix Glatzer (k1ll) - Linux Binaries
|
Karl-Felix Glatzer (k1ll) - Linux Binaries
|
||||||
|
@ -199,16 +124,16 @@ Packagers
|
||||||
Public Relations and Translations
|
Public Relations and Translations
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
Alex McKibben (WeirdSexy) - Podcaster
|
||||||
Artem Kotsynyak (greye) - Russian News Writer
|
Artem Kotsynyak (greye) - Russian News Writer
|
||||||
Dawid Lakomy (Vedyimyn) - Polish News Writer
|
|
||||||
Jim Clauwaert (Zedd) - Public Outreach
|
Jim Clauwaert (Zedd) - Public Outreach
|
||||||
Julien Voisin (jvoisin/ap0) - French News Writer
|
Julien Voisin (jvoisin/ap0) - French News Writer
|
||||||
|
Tom Koenderink (Okulo) - English News Writer
|
||||||
Lukasz Gromanowski (lgro) - English News Writer
|
Lukasz Gromanowski (lgro) - English News Writer
|
||||||
Martin Otto (Atahualpa) - Podcaster, Public Outreach, German Translator
|
|
||||||
Mickey Lyle (raevol) - Release Manager
|
Mickey Lyle (raevol) - Release Manager
|
||||||
Pithorn - Chinese News Writer
|
Pithorn - Chinese News Writer
|
||||||
sir_herrbatka - Polish News Writer
|
sir_herrbatka - Polish News Writer
|
||||||
Tom Koenderink (Okulo) - English News Writer
|
Dawid Lakomy (Vedyimyn) - Polish News Writer
|
||||||
|
|
||||||
Website
|
Website
|
||||||
-------
|
-------
|
||||||
|
@ -235,7 +160,7 @@ Artwork
|
||||||
|
|
||||||
Necrod - OpenMW Logo
|
Necrod - OpenMW Logo
|
||||||
Mickey Lyle (raevol) - Wordpress Theme
|
Mickey Lyle (raevol) - Wordpress Theme
|
||||||
Tom Koenderink (Okulo), SirHerrbatka, crysthala, Shnatsel, Lamoot - OpenMW Editor Icons
|
Tom Koenderink (Okulo), SirHerrbatka, crysthala, Shnatsel - OpenMW Editor Icons
|
||||||
|
|
||||||
Inactive Contributors
|
Inactive Contributors
|
||||||
---------------------
|
---------------------
|
||||||
|
@ -259,6 +184,7 @@ Inactive Contributors
|
||||||
Nekochan
|
Nekochan
|
||||||
pchan3
|
pchan3
|
||||||
penguinroad
|
penguinroad
|
||||||
|
psi29a
|
||||||
sergoz
|
sergoz
|
||||||
spyboot
|
spyboot
|
||||||
Star-Demon
|
Star-Demon
|
||||||
|
|
956
CHANGELOG.md
956
CHANGELOG.md
|
@ -1,959 +1,3 @@
|
||||||
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 #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 #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 #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
|
|
||||||
------
|
|
||||||
|
|
||||||
Bug #1428: Daedra summoning scripts aren't executed when the item is taken through the inventory
|
|
||||||
Bug #1987: Some glyphs are not supported
|
|
||||||
Bug #2254: Magic related visual effects are not rendered when loading a saved game
|
|
||||||
Bug #2485: Journal alphabetical index doesn't match "Morrowind content language" setting
|
|
||||||
Bug #2703: OnPCHitMe is not handled correctly
|
|
||||||
Bug #2829: Incorrect order for content list consisting of a game file and an esp without dependencies
|
|
||||||
Bug #2841: "Total eclipse" happens if weather settings are not defined.
|
|
||||||
Bug #2897: Editor: Rename "Original creature" field
|
|
||||||
Bug #3278: Editor: Unchecking "Auto Calc" flag changes certain values
|
|
||||||
Bug #3343: Editor: ID sorting is case-sensitive in certain tables
|
|
||||||
Bug #3557: Resource priority confusion when using the local data path as installation root
|
|
||||||
Bug #3587: Pathgrid and Flying Creatures wrong behaviour – abotWhereAreAllBirdsGoing
|
|
||||||
Bug #3603: SetPos should not skip weather transitions
|
|
||||||
Bug #3618: Myar Aranath total conversion can't be started due to capital-case extension of the master file
|
|
||||||
Bug #3638: Fast forwarding can move NPC inside objects
|
|
||||||
Bug #3664: Combat music does not start in dialogue
|
|
||||||
Bug #3696: Newlines are accompanied by empty rectangle glyph in dialogs
|
|
||||||
Bug #3708: Controllers broken on macOS
|
|
||||||
Bug #3726: Items with suppressed activation can be picked up via the inventory menu
|
|
||||||
Bug #3783: [Mod] Abot's Silt Striders 1.16 - silt strider "falls" to ground and glides on floor during travel
|
|
||||||
Bug #3863: Can be forced to not resist arrest if you cast Calm Humanoid on aggroed death warrant guards
|
|
||||||
Bug #3884: Incorrect enemy behavior when exhausted
|
|
||||||
Bug #3926: Installation Wizard places Morrowind.esm after Tribunal/Bloodmoon if it has a later file creation date
|
|
||||||
Bug #4061: Scripts error on special token included in name
|
|
||||||
Bug #4111: Crash when mouse over soulgem with a now-missing soul
|
|
||||||
Bug #4122: Swim animation should not be interrupted during underwater attack
|
|
||||||
Bug #4134: Battle music behaves different than vanilla
|
|
||||||
Bug #4135: Reflecting an absorb spell different from vanilla
|
|
||||||
Bug #4136: Enchanted weapons without "ignore normal weapons" flag don't bypass creature "ignore normal weapons" effect
|
|
||||||
Bug #4143: Antialiasing produces graphical artifacts when used with shader lighting
|
|
||||||
Bug #4159: NPCs' base skeleton files should not be optimized
|
|
||||||
Bug #4177: Jumping/landing animation interference/flickering
|
|
||||||
Bug #4179: NPCs do not face target
|
|
||||||
Bug #4180: Weapon switch sound playing even though no weapon is switched
|
|
||||||
Bug #4184: Guards can initiate dialogue even though you are far above them
|
|
||||||
Bug #4190: Enchanted clothes changes visibility with Chameleon on equip/unequip
|
|
||||||
Bug #4191: "screenshot saved" message also appears in the screenshot image
|
|
||||||
Bug #4192: Archers in OpenMW have shorter attack range than archers in Morrowind
|
|
||||||
Bug #4210: Some dialogue topics are not highlighted on first encounter
|
|
||||||
Bug #4211: FPS drops after minimizing the game during rainy weather
|
|
||||||
Bug #4216: Thrown weapon projectile doesn't rotate
|
|
||||||
Bug #4223: Displayed spell casting chance must be 0 if player doesn't have enough magicka to cast it
|
|
||||||
Bug #4225: Double "Activate" key presses with Mouse and Gamepad.
|
|
||||||
Bug #4226: The current player's class should be default value in the class select menu
|
|
||||||
Bug #4229: Tribunal/Bloodmoon summoned creatures fight other summons
|
|
||||||
Bug #4233: W and A keys override S and D Keys
|
|
||||||
Bug #4235: Wireframe mode affects local map
|
|
||||||
Bug #4239: Quick load from container screen causes crash
|
|
||||||
Bug #4242: Crime greetings display in Journal
|
|
||||||
Bug #4245: Merchant NPCs sell ingredients growing on potted plants they own
|
|
||||||
Bug #4246: Take armor condition into account when calcuting armor rating
|
|
||||||
Bug #4250: Jumping is not as fluid as it was pre-0.43.0
|
|
||||||
Bug #4252: "Error in frame: FFmpeg exception: Failed to allocate input stream" message spam if OpenMW encounter non-music file in the Music folder
|
|
||||||
Bug #4261: Magic effects from eaten ingredients always have 1 sec duration
|
|
||||||
Bug #4263: Arrow position is incorrect in 3rd person view during attack for beast races
|
|
||||||
Bug #4264: Player in god mode can be affected by some negative spell effects
|
|
||||||
Bug #4269: Crash when hovering the faction section and the 'sAnd' GMST is missing (as in MW 1.0)
|
|
||||||
Bug #4272: Root note transformations are discarded again
|
|
||||||
Bug #4279: Sometimes cells are not marked as explored on the map
|
|
||||||
Bug #4298: Problem with MessageBox and chargen menu interaction order
|
|
||||||
Bug #4301: Optimizer breaks LOD nodes
|
|
||||||
Bug #4308: PlaceAtMe doesn't inherit scale of calling object
|
|
||||||
Bug #4309: Only harmful effects with resistance effect set are resistable
|
|
||||||
Bug #4313: Non-humanoid creatures are capable of opening doors
|
|
||||||
Bug #4314: Rainy weather slows down the game when changing from indoors/outdoors
|
|
||||||
Bug #4319: Collisions for certain meshes are incorrectly ignored
|
|
||||||
Bug #4320: Using mouse 1 to move forward causes selection dialogues to jump selections forward.
|
|
||||||
Bug #4322: NPC disposition: negative faction reaction modifier doesn't take PC rank into account
|
|
||||||
Bug #4328: Ownership by dead actors is not cleared from picked items
|
|
||||||
Bug #4334: Torch and shield usage inconsistent with original game
|
|
||||||
Bug #4336: Wizard: Incorrect Morrowind assets path autodetection
|
|
||||||
Bug #4343: Error message for coc and starting cell shouldn't imply that it only works for interior cells
|
|
||||||
Bug #4346: Count formatting does not work well with very high numbers
|
|
||||||
Bug #4351: Using AddSoulgem fills all soul gems of the specified type
|
|
||||||
Bug #4391: No visual indication is provided when an unavailable spell fails to be chosen via a quick key
|
|
||||||
Bug #4392: Inventory filter breaks after loading a game
|
|
||||||
Bug #4405: No default terrain in empty cells when distant terrain is enabled
|
|
||||||
Bug #4410: [Mod] Arktwend: OpenMW does not use default marker definitions
|
|
||||||
Bug #4412: openmw-iniimporter ignores data paths from config
|
|
||||||
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 #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 #4441: Adding items to currently disabled weapon-wielding creatures crashes the game
|
|
||||||
Feature #1786: Round up encumbrance value in the encumbrance bar
|
|
||||||
Feature #2694: Editor: rename "model" column to make its purpose clear
|
|
||||||
Feature #3870: Editor: Terrain Texture Brush Button
|
|
||||||
Feature #3872: Editor: Edit functions in terrain texture editing mode
|
|
||||||
Feature #4054: Launcher: Create menu for settings.cfg options
|
|
||||||
Feature #4064: Option for fast travel services to charge for the first companion
|
|
||||||
Feature #4142: Implement fWereWolfHealth GMST
|
|
||||||
Feature #4174: Multiple quicksaves
|
|
||||||
Feature #4407: Support NiLookAtController
|
|
||||||
Feature #4423: Rebalance soul gem values
|
|
||||||
Task #4015: Use AppVeyor build artifact features to make continuous builds available
|
|
||||||
Editor: New (and more complete) icon set
|
|
||||||
|
|
||||||
0.43.0
|
|
||||||
------
|
|
||||||
|
|
||||||
Bug #815: Different settings cause inconsistent underwater visibility
|
|
||||||
Bug #1452: autosave is not executed when waiting
|
|
||||||
Bug #1555: Closing containers with spacebar doesn't work after touching an item
|
|
||||||
Bug #1692: Can't close container when item is "held"
|
|
||||||
Bug #2405: Maximum distance for guards attacking hostile creatures is incorrect
|
|
||||||
Bug #2445: Spellcasting can be interrupted
|
|
||||||
Bug #2489: Keeping map open not persisted between saves
|
|
||||||
Bug #2594: 1st person view uses wrong body texture with Better bodies
|
|
||||||
Bug #2628: enablestatreviewmenu command doen't read race, class and sign values from current game
|
|
||||||
Bug #2639: Attacking flag isn't reset upon reloading
|
|
||||||
Bug #2698: Snow and rain VFX move with the player
|
|
||||||
Bug #2704: Some creature swim animations not being used
|
|
||||||
Bug #2789: Potential risk of misunderstanding using the colored "owned" crosshair feature
|
|
||||||
Bug #3045: Settings containing '#' cannot be loaded
|
|
||||||
Bug #3097: Drop() doesn't work when an item is held (with the mouse)
|
|
||||||
Bug #3110: GetDetected doesn't work without a reference
|
|
||||||
Bug #3126: Framerate nosedives when adjusting dialogue window size
|
|
||||||
Bug #3243: Ampersand in configuration files isn't escaped automatically
|
|
||||||
Bug #3365: Wrong water reflection along banks
|
|
||||||
Bug #3441: Golden saint always dispelling soul trap / spell priority issue
|
|
||||||
Bug #3528: Disposing of corpses breaks quests
|
|
||||||
Bug #3531: No FPS limit when playing bink videos even though "framerate limit" is set in settings.cfg
|
|
||||||
Bug #3647: Multi-effect spells play audio louder than in Vanilla
|
|
||||||
Bug #3656: NPCs forget where their place in the world is
|
|
||||||
Bug #3665: Music transitions are too abrupt
|
|
||||||
Bug #3679: Spell cast effect should disappear after using rest command
|
|
||||||
Bug #3684: Merchants do not restock empty soul gems if they acquire filled ones.
|
|
||||||
Bug #3694: Wrong magicka bonus applied on character creation
|
|
||||||
Bug #3706: Guards don't try to arrest the player if attacked
|
|
||||||
Bug #3709: Editor: Camera is not positioned correctly on mode switches related to orbital mode
|
|
||||||
Bug #3720: Death counter not cleaned of non-existing IDs when loading a game
|
|
||||||
Bug #3744: "Greater/lesser or equal" operators are not parsed when their signs are swapped
|
|
||||||
Bug #3749: Yagrum Bagarn moves to different position on encountering
|
|
||||||
Bug #3766: DisableLevitation does not remove visuals of preexisting effect
|
|
||||||
Bug #3787: Script commands in result box for voiced dialogue are ignored
|
|
||||||
Bug #3793: OpenMW tries to animate animated references even when they are disabled
|
|
||||||
Bug #3794: Default sound buffer size is too small for mods
|
|
||||||
Bug #3796: Mod 'Undress for me' doesn't work: NPCs re-equip everything
|
|
||||||
Bug #3798: tgm command behaviour differs from vanilla
|
|
||||||
Bug #3804: [Mod] Animated Morrowind: some animations do not loop correctly
|
|
||||||
Bug #3805: Slight enchant miscalculation
|
|
||||||
Bug #3826: Rendering problems with an image in a letter
|
|
||||||
Bug #3833: [Mod] Windows Glow: windows textures are much darker than in original game
|
|
||||||
Bug #3835: Bodyparts with multiple NiTriShapes are not handled correctly
|
|
||||||
Bug #3839: InventoryStore::purgeEffect() removes only first effect with argument ID
|
|
||||||
Bug #3843: Wrong jumping fatigue loss calculations
|
|
||||||
Bug #3850: Boethiah's voice is distorted underwater
|
|
||||||
Bug #3851: NPCs and player say things while underwater
|
|
||||||
Bug #3864: Crash when exiting to Khartag point from Ilunibi
|
|
||||||
Bug #3878: Swapping soul gems while enchanting allows constant effect enchantments using any soul gem
|
|
||||||
Bug #3879: Dialogue option: Go to jail, persists beyond quickload
|
|
||||||
Bug #3891: Journal displays empty entries
|
|
||||||
Bug #3892: Empty space before dialogue entry display
|
|
||||||
Bug #3898: (mod) PositionCell in dialogue results closes dialogue window
|
|
||||||
Bug #3906: "Could not find Data Files location" dialog can appear multiple times
|
|
||||||
Bug #3908: [Wizard] User gets stuck if they cancel out of installing from a CD
|
|
||||||
Bug #3909: Morrowind Content Language dropdown is the only element on the right half of the Settings window
|
|
||||||
Bug #3910: Launcher window can be resized so that it cuts off the scroll
|
|
||||||
Bug #3915: NC text key on nifs doesn't work
|
|
||||||
Bug #3919: Closing inventory while cursor hovers over spell (or other magic menu item) produces left click sound
|
|
||||||
Bug #3922: Combat AI should avoid enemy hits when casts Self-ranged spells
|
|
||||||
Bug #3934: [macOS] Copy/Paste from system clipboard uses Control key instead of Command key
|
|
||||||
Bug #3935: Incorrect attack strength for AI actors
|
|
||||||
Bug #3937: Combat AI: enchanted weapons have too high rating
|
|
||||||
Bug #3942: UI sounds are distorted underwater
|
|
||||||
Bug #3943: CPU/GPU usage should stop when the game is minimised
|
|
||||||
Bug #3944: Attempting to sell stolen items back to their owner does not remove them from your inventory
|
|
||||||
Bug #3955: Player's avatar rendering issues
|
|
||||||
Bug #3956: EditEffectDialog: Cancel button does not update a Range button and an Area slider properly
|
|
||||||
Bug #3957: Weird bodypart rendering if a node has reserved name
|
|
||||||
Bug #3960: Clothes with high cost (> 32768) are not handled properly
|
|
||||||
Bug #3963: When on edge of being burdened the condition doesn't lower as you run.
|
|
||||||
Bug #3971: Editor: Incorrect colour field in cell table
|
|
||||||
Bug #3974: Journal page turning doesn't produce sounds
|
|
||||||
Bug #3978: Instant opening and closing happens when using a Controller with Menus/Containers
|
|
||||||
Bug #3981: Lagging when spells are cast, especially noticeable on new landmasses such as Tamriel Rebuilt
|
|
||||||
Bug #3982: Down sounds instead of Up ones are played when trading
|
|
||||||
Bug #3987: NPCs attack after some taunting with no "Goodbye"
|
|
||||||
Bug #3991: Journal can still be opened at main menu
|
|
||||||
Bug #3995: Dispel cancels every temporary magic effect
|
|
||||||
Bug #4002: Build broken on OpenBSD with clang
|
|
||||||
Bug #4003: Reduce Render Area of Inventory Doll to Fit Within Border
|
|
||||||
Bug #4004: Manis Virmaulese attacks without saying anything
|
|
||||||
Bug #4010: AiWander: "return to the spawn position" feature does not work properly
|
|
||||||
Bug #4016: Closing menus with spacebar will still send certain assigned actions through afterwards
|
|
||||||
Bug #4017: GetPCRunning and GetPCSneaking should check that the PC is actually moving
|
|
||||||
Bug #4024: Poor music track distribution
|
|
||||||
Bug #4025: Custom spell with copy-pasted name always sorts to top of spell list
|
|
||||||
Bug #4027: Editor: OpenMW-CS misreports its own name as "OpenCS", under Mac OS
|
|
||||||
Bug #4033: Archers don't attack if the arrows have run out and there is no other weapon
|
|
||||||
Bug #4037: Editor: New greetings do not work in-game.
|
|
||||||
Bug #4049: Reloading a saved game while falling prevents damage
|
|
||||||
Bug #4056: Draw animation should not be played when player equips a new weapon
|
|
||||||
Bug #4074: Editor: Merging of LAND/LTEX records
|
|
||||||
Bug #4076: Disposition bar is not updated when "goodbye" selected in dialogue
|
|
||||||
Bug #4079: Alchemy skill increases do not take effect until next batch
|
|
||||||
Bug #4093: GetResistFire, getResistFrost and getResistShock doesn't work as in vanilla
|
|
||||||
Bug #4094: Level-up messages for levels past 20 are hardcoded not to be used
|
|
||||||
Bug #4095: Error in framelistener when take all items from a dead corpse
|
|
||||||
Bug #4096: Messagebox with the "%0.f" format should use 0 digit precision
|
|
||||||
Bug #4104: Cycling through weapons does not skip broken ones
|
|
||||||
Bug #4105: birthsign generation menu does not show full details
|
|
||||||
Bug #4107: Editor: Left pane in Preferences window is too narrow
|
|
||||||
Bug #4112: Inventory sort order is inconsistent
|
|
||||||
Bug #4113: 'Resolution not supported in fullscreen' message is inconvenient
|
|
||||||
Bug #4131: Pickpocketing behaviour is different from vanilla
|
|
||||||
Bug #4155: NPCs don't equip a second ring in some cases
|
|
||||||
Bug #4156: Snow doesn't create water ripples
|
|
||||||
Bug #4165: NPCs autoequip new clothing with the same price
|
|
||||||
Feature #452: Rain-induced water ripples
|
|
||||||
Feature #824: Fading for doors and teleport commands
|
|
||||||
Feature #933: Editor: LTEX record table
|
|
||||||
Feature #936: Editor: LAND record table
|
|
||||||
Feature #1374: AI: Resurface to breathe
|
|
||||||
Feature #2320: ess-Importer: convert projectiles
|
|
||||||
Feature #2509: Editor: highlighting occurrences of a word in a script
|
|
||||||
Feature #2748: Editor: Should use one resource manager per document
|
|
||||||
Feature #2834: Have openMW's UI remember what menu items were 'pinned' across boots.
|
|
||||||
Feature #2923: Option to show the damage of the arrows through tooltip.
|
|
||||||
Feature #3099: Disabling inventory while dragging an item forces you to drop it
|
|
||||||
Feature #3274: Editor: Script Editor - Shortcuts and context menu options for commenting code out and uncommenting code respectively
|
|
||||||
Feature #3275: Editor: User Settings- Add an option to reset settings to their default status (per category / all)
|
|
||||||
Feature #3400: Add keyboard shortcuts for menus
|
|
||||||
Feature #3492: Show success rate while enchanting
|
|
||||||
Feature #3530: Editor: Reload data files
|
|
||||||
Feature #3682: Editor: Default key binding reset
|
|
||||||
Feature #3921: Combat AI: aggro priorities
|
|
||||||
Feature #3941: Allow starting at an unnamed exterior cell with --start
|
|
||||||
Feature #3952: Add Visual Studio 2017 support
|
|
||||||
Feature #3953: Combat AI: use "WhenUsed" enchantments
|
|
||||||
Feature #4082: Leave the stack of ingredients or potions grabbed after using an ingredient/potion
|
|
||||||
Task #2258: Windows installer: launch OpenMW tickbox
|
|
||||||
Task #4152: The Windows CI script is moving files around that CMake should be dealing with
|
|
||||||
|
|
||||||
0.42.0
|
|
||||||
------
|
|
||||||
|
|
||||||
Bug #1956: Duplicate objects after loading the game, when a mod was edited
|
|
||||||
Bug #2100: Falling leaves in Vurt's Leafy West Gash II not rendered correctly
|
|
||||||
Bug #2116: Cant fit through some doorways pressed against staircases
|
|
||||||
Bug #2289: Some modal dialogs are not centered on the screen when the window resizes
|
|
||||||
Bug #2409: Softlock when pressing weapon/magic switch keys during chargen, afterwards switches weapons even though a text field is selected
|
|
||||||
Bug #2483: Previous/Next Weapon hotkeys triggered while typing the name of game save
|
|
||||||
Bug #2629: centeroncell, coc causes death / fall damage time to time when teleporting from high
|
|
||||||
Bug #2645: Cycling weapons is possible while console/pause menu is open
|
|
||||||
Bug #2678: Combat with water creatures do not end upon exiting water
|
|
||||||
Bug #2759: Light Problems in Therana's Chamber in Tel Branora
|
|
||||||
Bug #2771: unhandled sdl event of type 0x302
|
|
||||||
Bug #2777: (constant/on cast) disintegrate armor/weapon on self is seemingly not working
|
|
||||||
Bug #2838: Editor: '.' in a record name should be allowed
|
|
||||||
Bug #2909: NPCs appear floating when standing on a slope
|
|
||||||
Bug #3093: Controller movement cannot be used while mouse is moving
|
|
||||||
Bug #3134: Crash possible when using console with open container
|
|
||||||
Bug #3254: AI enemies hit between them.
|
|
||||||
Bug #3344: Editor: Verification results sorting by Type is not alphabetical.
|
|
||||||
Bug #3345: Editor: Cloned and added pathgrids are lost after reopen of saved omwgame file
|
|
||||||
Bug #3355: [MGSO] Physics maxing out in south cornerclub Balmora
|
|
||||||
Bug #3484: Editor: camera position is not set when changing cell via drag&drop
|
|
||||||
Bug #3508: Slowfall kills Jump momentum
|
|
||||||
Bug #3580: Crash: Error ElementBufferObject::remove BufferData<0> out of range
|
|
||||||
Bug #3581: NPCs wander too much
|
|
||||||
Bug #3601: Menu Titles not centered vertically
|
|
||||||
Bug #3607: [Mac OS] Beginning of NPC speech cut off (same issue as closed bug #3453)
|
|
||||||
Bug #3613: Can not map "next weapon" or "next spell" to controller
|
|
||||||
Bug #3617: Enchanted arrows don't explode when hitting the ground
|
|
||||||
Bug #3645: Unable to use steps in Vivec, Palace of Vivec
|
|
||||||
Bug #3650: Tamriel Rebuilt 16.09.1 – Hist Cuirass GND nif is rendered inside a Pink Box
|
|
||||||
Bug #3652: Item icon shadows get stuck in the alchemy GUI
|
|
||||||
Bug #3653: Incorrect swish sounds
|
|
||||||
Bug #3666: NPC collision should not be disabled until death animation has finished
|
|
||||||
Bug #3669: Editor: Text field was missing from book object editing dialogue
|
|
||||||
Bug #3670: Unhandled SDL event of type 0x304
|
|
||||||
Bug #3671: Incorrect local variable value after picking up bittercup
|
|
||||||
Bug #3686: Travelling followers doesn't increase travel fee
|
|
||||||
Bug #3689: Problematic greetings from Antares Big Mod that override the appropriate ones.
|
|
||||||
Bug #3690: Certain summoned creatures do not engage in combat with underwater creatures
|
|
||||||
Bug #3691: Enemies do not initiate combat with player followers on sight
|
|
||||||
Bug #3695: [Regression] Dispel does not always dispel spell effects in 0.41
|
|
||||||
Bug #3699: Crash on MWWorld::ProjectileManager::moveMagicBolts
|
|
||||||
Bug #3700: Climbing on rocks and mountains
|
|
||||||
Bug #3704: Creatures don't auto-equip their shields on creation
|
|
||||||
Bug #3705: AI combat engagement logic differs from vanilla
|
|
||||||
Bug #3707: Animation playing does some very odd things if pc comes in contact with the animated mesh
|
|
||||||
Bug #3712: [Mod] Freeze upon entering Adanumuran with mod Adanumuran Reclaimed
|
|
||||||
Bug #3713: [Regression] Cancelling dialogue or using travel with creatures throws a (possibly game-breaking) exception
|
|
||||||
Bug #3719: Dropped identification papers can't be picked up again
|
|
||||||
Bug #3722: Command spell doesn't bring enemies out of combat
|
|
||||||
Bug #3727: Using "Activate" mid-script-execution invalidates interpreter context
|
|
||||||
Bug #3746: Editor: Book records show attribute IDs instead of skill IDs for teached skills entry.
|
|
||||||
Bug #3755: Followers stop following after loading from savegame
|
|
||||||
Bug #3772: ModStat lowers attribute to 100 if it was greater
|
|
||||||
Bug #3781: Guns in Clean Hunter Rifles mod use crossbow sounds
|
|
||||||
Bug #3797: NPC and creature names don't show up in combat when RMB windows are displayed
|
|
||||||
Bug #3800: Wrong tooltip maximum width
|
|
||||||
Bug #3801: Drowning widget is bugged
|
|
||||||
Bug #3802: BarterOffer shouldn't limit pcMercantile
|
|
||||||
Bug #3813: Some fatal error
|
|
||||||
Bug #3816: Expression parser thinks the -> token is unexpected when a given explicit refID clashes with a journal ID
|
|
||||||
Bug #3822: Custom added creatures are not animated
|
|
||||||
Feature #451: Water sounds
|
|
||||||
Feature #2691: Light particles sometimes not shown in inventory character preview
|
|
||||||
Feature #3523: Light source on magic projectiles
|
|
||||||
Feature #3644: Nif NiSphericalCollider Unknown Record Type
|
|
||||||
Feature #3675: ess-Importer: convert mark location
|
|
||||||
Feature #3693: ess-Importer: convert last known exterior cell
|
|
||||||
Feature #3748: Editor: Replace "Scroll" check box in Book records with "Book Type" combo box.
|
|
||||||
Feature #3751: Editor: Replace "Xyz Blood" check boxes in NPC and Creature records with "Blood Type" combo box
|
|
||||||
Feature #3752: Editor: Replace emitter check boxes in Light records with "Emitter Type" combo box
|
|
||||||
Feature #3756: Editor: Replace "Female" check box in NPC records with "Gender" combo box
|
|
||||||
Feature #3757: Editor: Replace "Female" check box in BodyPart records with "Gender" combo box
|
|
||||||
Task #3092: const version of ContainerStoreIterator
|
|
||||||
Task #3795: /deps folder not in .gitignore
|
|
||||||
|
|
||||||
0.41.0
|
|
||||||
------
|
|
||||||
|
|
||||||
Bug #1138: Casting water walking doesn't move the player out of the water
|
|
||||||
Bug #1931: Rocks from blocked passage in Bamz-Amschend, Radacs Forge can reset and cant be removed again.
|
|
||||||
Bug #2048: Almvisi and Divine Intervention display wrong spell effect
|
|
||||||
Bug #2054: Show effect-indicator for "instant effect" spells and potions
|
|
||||||
Bug #2150: Clockwork City door animation problem
|
|
||||||
Bug #2288: Playback of weapon idle animation not correct
|
|
||||||
Bug #2410: Stat-review window doesn't display starting spells, powers, or abilities
|
|
||||||
Bug #2493: Repairing occasionally very slow
|
|
||||||
Bug #2716: [OSG] Water surface is too transparent from some angles
|
|
||||||
Bug #2859: [MAC OS X] Cannot exit fullscreen once enabled
|
|
||||||
Bug #3091: Editor: will not save addon if global variable value type is null
|
|
||||||
Bug #3277: Editor: Non-functional nested tables in subviews need to be hidden instead of being disabled
|
|
||||||
Bug #3348: Disabled map markers show on minimap
|
|
||||||
Bug #3350: Extending selection to instances with same object results in duplicates.
|
|
||||||
Bug #3353: [Mod] Romance version 3.7 script failed
|
|
||||||
Bug #3376: [Mod] Vampire Embrace script fails to execute
|
|
||||||
Bug #3385: Banners don't animate in stormy weather as they do in the original game
|
|
||||||
Bug #3393: Akulakhan re-enabled after main quest
|
|
||||||
Bug #3427: Editor: OpenMW-CS instances won´t get deleted
|
|
||||||
Bug #3451: Feril Salmyn corpse isn't where it is supposed to be
|
|
||||||
Bug #3497: Zero-weight armor is displayed as "heavy" in inventory tooltip
|
|
||||||
Bug #3499: Idle animations don't always loop
|
|
||||||
Bug #3500: Spark showers at Sotha Sil do not appear until you look at the ceiling
|
|
||||||
Bug #3515: Editor: Moved objects in interior cells are teleported to exterior cells.
|
|
||||||
Bug #3520: Editor: OpenMW-CS cannot find project file when launching the game
|
|
||||||
Bug #3521: Armed NPCs don't use correct melee attacks
|
|
||||||
Bug #3535: Changing cell immediately after dying causes character to freeze.
|
|
||||||
Bug #3542: Unable to rest if unalerted slaughterfish are in the cell with you
|
|
||||||
Bug #3549: Blood effects occur even when a hit is resisted
|
|
||||||
Bug #3551: NPC Todwendy in german version can't interact
|
|
||||||
Bug #3552: Opening the journal when fonts are missing results in a crash
|
|
||||||
Bug #3555: SetInvisible command should not apply graphic effect
|
|
||||||
Bug #3561: Editor: changes from omwaddon are not loaded in [New Addon] mode
|
|
||||||
Bug #3562: Non-hostile NPCs can be disarmed by stealing their weapons via sneaking
|
|
||||||
Bug #3564: Editor: openmw-cs verification results
|
|
||||||
Bug #3568: Items that should be invisible are shown in the inventory
|
|
||||||
Bug #3574: Alchemy: Alembics and retorts are used in reverse
|
|
||||||
Bug #3575: Diaglog choices don't work in mw 0.40
|
|
||||||
Bug #3576: Minor differences in AI reaction to hostile spell effects
|
|
||||||
Bug #3577: not local nolore dialog test
|
|
||||||
Bug #3578: Animation Replacer hangs after one cicle/step
|
|
||||||
Bug #3579: Bound Armor skillups and sounds
|
|
||||||
Bug #3583: Targetted GetCurrentAiPackage returns 0
|
|
||||||
Bug #3584: Persuasion bug
|
|
||||||
Bug #3590: Vendor, Ilen Faveran, auto equips items from stock
|
|
||||||
Bug #3594: Weather doesn't seem to update correctly in Mournhold
|
|
||||||
Bug #3598: Saving doesn't save status of objects
|
|
||||||
Bug #3600: Screen goes black when trying to travel to Sadrith Mora
|
|
||||||
Bug #3608: Water ripples aren't created when walking on water
|
|
||||||
Bug #3626: Argonian NPCs swim like khajiits
|
|
||||||
Bug #3627: Cannot delete "Blessed touch" spell from spellbook
|
|
||||||
Bug #3634: An enchanted throwing weapon consumes charges from the stack in your inventory. (0.40.0)
|
|
||||||
Bug #3635: Levelled items in merchants are "re-rolled" (not bug 2952, see inside)
|
|
||||||
Feature #1118: AI combat: flee
|
|
||||||
Feature #1596: Editor: Render water
|
|
||||||
Feature #2042: Adding a non-portable Light to the inventory should cause the player to glow
|
|
||||||
Feature #3166: Editor: Instance editing mode - rotate sub mode
|
|
||||||
Feature #3167: Editor: Instance editing mode - scale sub mode
|
|
||||||
Feature #3420: ess-Importer: player control flags
|
|
||||||
Feature #3489: You shouldn't be be able to re-cast a bound equipment spell
|
|
||||||
Feature #3496: Zero-weight boots should play light boot footsteps
|
|
||||||
Feature #3516: Water Walking should give a "can't cast" message and fail when you are too deep
|
|
||||||
Feature #3519: Play audio and visual effects for all effects in a spell
|
|
||||||
Feature #3527: Double spell explosion scaling
|
|
||||||
Feature #3534: Play particle textures for spell effects
|
|
||||||
Feature #3539: Make NPCs use opponent's weapon range to decide whether to dodge
|
|
||||||
Feature #3540: Allow dodging for creatures with "biped" flag
|
|
||||||
Feature #3545: Drop shadow for items in menu
|
|
||||||
Feature #3558: Implement same spell range for "on touch" spells as original engine
|
|
||||||
Feature #3560: Allow using telekinesis with touch spells on objects
|
|
||||||
Task #3585: Some objects added by Morrowind Rebirth do not display properly their texture
|
|
||||||
|
|
||||||
0.40.0
|
|
||||||
------
|
|
||||||
|
|
||||||
Bug #1320: AiWander - Creatures in cells without pathgrids do not wander
|
|
||||||
Bug #1873: Death events are triggered at the beginning of the death animation
|
|
||||||
Bug #1996: Resting interrupts magic effects
|
|
||||||
Bug #2399: Vampires can rest in broad daylight and survive the experience
|
|
||||||
Bug #2604: Incorrect magicka recalculation
|
|
||||||
Bug #2721: Telekinesis extends interaction range where it shouldn't
|
|
||||||
Bug #2981: When waiting, NPCs can go where they wouldn't go normally.
|
|
||||||
Bug #3045: Esp files containing the letter '#' in the file name cannot be loaded on startup
|
|
||||||
Bug #3071: Slowfall does not stop momentum when jumping
|
|
||||||
Bug #3085: Plugins can not replace parent cell references with a cell reference of different type
|
|
||||||
Bug #3145: Bug with AI Cliff Racer. He will not attack you, unless you put in front of him.
|
|
||||||
Bug #3149: Editor: Weather tables were missing from regions
|
|
||||||
Bug #3201: Netch shoots over your head
|
|
||||||
Bug #3269: If you deselect a mod and try to load a save made inside a cell added by it, you end bellow the terrain in the grid 0/0
|
|
||||||
Bug #3286: Editor: Script editor tab width
|
|
||||||
Bug #3329: Teleportation spells cause crash to desktop after build update from 0.37 to 0.38.0
|
|
||||||
Bug #3331: Editor: Start Scripts table: Adding a script doesn't refresh the list of Start Scripts and allows to add a single script multiple times
|
|
||||||
Bug #3332: Editor: Scene view: Tool tips only occur when holding the left mouse button
|
|
||||||
Bug #3340: ESS-Importer does not separate item stacks
|
|
||||||
Bug #3342: Editor: Creation of pathgrids did not check if the pathgrid already existed
|
|
||||||
Bug #3346: "Talked to PC" is always 0 for "Hello" dialogue
|
|
||||||
Bug #3349: AITravel doesn't repeat
|
|
||||||
Bug #3370: NPCs wandering to invalid locations after training
|
|
||||||
Bug #3378: "StopCombat" command does not function in vanilla quest
|
|
||||||
Bug #3384: Battle at Nchurdamz - Larienna Macrina does not stop combat after killing Hrelvesuu
|
|
||||||
Bug #3388: Monster Respawn tied to Quicksave
|
|
||||||
Bug #3390: Strange visual effect in Dagoth Ur's chamber
|
|
||||||
Bug #3391: Inappropriate Blight weather behavior at end of main quest
|
|
||||||
Bug #3394: Replaced dialogue inherits some of its old data
|
|
||||||
Bug #3397: Actors that start the game dead always have the same death pose
|
|
||||||
Bug #3401: Sirollus Saccus sells not glass arrows
|
|
||||||
Bug #3402: Editor: Weapon data not being properly set
|
|
||||||
Bug #3405: Mulvisic Othril will not use her chitin throwing stars
|
|
||||||
Bug #3407: Tanisie Verethi will immediately detect the player
|
|
||||||
Bug #3408: Improper behavior of ashmire particles
|
|
||||||
Bug #3412: Ai Wander start time resets when saving/loading the game
|
|
||||||
Bug #3416: 1st person and 3rd person camera isn't converted from .ess correctly
|
|
||||||
Bug #3421: Idling long enough while paralyzed sometimes causes character to get stuck
|
|
||||||
Bug #3423: Sleep interruption inside dungeons too agressive
|
|
||||||
Bug #3424: Pickpocketing sometimes won't work
|
|
||||||
Bug #3432: AiFollow / AiEscort durations handled incorrectly
|
|
||||||
Bug #3434: Dead NPC's and Creatures still contribute to sneak skill increases
|
|
||||||
Bug #3437: Weather-conditioned dialogue should not play in interiors
|
|
||||||
Bug #3439: Effects cast by summon stick around after their death
|
|
||||||
Bug #3440: Parallax maps looks weird
|
|
||||||
Bug #3443: Class graphic for custom class should be Acrobat
|
|
||||||
Bug #3446: OpenMW segfaults when using Atrayonis's "Anthology Solstheim: Tomb of the Snow Prince" mod
|
|
||||||
Bug #3448: After dispelled, invisibility icon is still displayed
|
|
||||||
Bug #3453: First couple of seconds of NPC speech is muted
|
|
||||||
Bug #3455: Portable house mods lock player and npc movement up exiting house.
|
|
||||||
Bug #3456: Equipping an item will undo dispel of constant effect invisibility
|
|
||||||
Bug #3458: Constant effect restore health doesn't work during Wait
|
|
||||||
Bug #3466: It is possible to stack multiple scroll effects of the same type
|
|
||||||
Bug #3471: When two mods delete the same references, many references are not disabled by the engine.
|
|
||||||
Bug #3473: 3rd person camera can be glitched
|
|
||||||
Feature #1424: NPC "Face" function
|
|
||||||
Feature #2974: Editor: Multiple Deletion of Subrecords
|
|
||||||
Feature #3044: Editor: Render path grid v2
|
|
||||||
Feature #3362: Editor: Configurable key bindings
|
|
||||||
Feature #3375: Make sun / moon reflections weather dependent
|
|
||||||
Feature #3386: Editor: Edit pathgrid
|
|
||||||
|
|
||||||
0.39.0
|
|
||||||
------
|
|
||||||
|
|
||||||
Bug #1384: Dark Brotherhood Assassin (and other scripted NPCs?) spawns beneath/inside solid objects
|
|
||||||
Bug #1544: "Drop" drops equipped item in a separate stack
|
|
||||||
Bug #1587: Collision detection glitches
|
|
||||||
Bug #1629: Container UI locks up in Vivec at Jeanne's
|
|
||||||
Bug #1771: Dark Brotherhood Assassin oddity in Eight Plates
|
|
||||||
Bug #1827: Unhandled NiTextureEffect in ex_dwrv_ruin30.nif
|
|
||||||
Bug #2089: When saving while swimming in water in an interior cell, you will be spawned under water on loading
|
|
||||||
Bug #2295: Internal texture not showing, nipixeldata
|
|
||||||
Bug #2363: Corpses don't disappear
|
|
||||||
Bug #2369: Respawns should be timed individually
|
|
||||||
Bug #2393: Сharacter is stuck in the tree
|
|
||||||
Bug #2444: [Mod] NPCs from Animated Morrowind appears not using proper animations
|
|
||||||
Bug #2467: Creatures do not respawn
|
|
||||||
Bug #2515: Ghosts in Ibar-Dad spawn stuck in walls
|
|
||||||
Bug #2610: FixMe script still needs to be implemented
|
|
||||||
Bug #2689: Riekling raider pig constantly screams while running
|
|
||||||
Bug #2719: Vivec don't put their hands on the knees with this replacer (Psymoniser Vivec God Replacement NPC Edition v1.0
|
|
||||||
Bug #2737: Camera shaking when side stepping around object
|
|
||||||
Bug #2760: AI Combat Priority Problem - Use of restoration spell instead of attacking
|
|
||||||
Bug #2806: Stack overflow in LocalScripts::getNext
|
|
||||||
Bug #2807: Collision detection allows player to become stuck inside objects
|
|
||||||
Bug #2814: Stairs to Marandus have improper collision
|
|
||||||
Bug #2925: Ranes Ienith will not appear, breaking the Morag Tong and Thieves Guid questlines
|
|
||||||
Bug #3024: Editor: Creator bar in startscript subview does not accept script ID drops
|
|
||||||
Bug #3046: Sleep creature: Velk is spawned half-underground in the Thirr River Valley
|
|
||||||
Bug #3080: Calling aifollow without operant in local script every frame causes mechanics to overheat + log
|
|
||||||
Bug #3101: Regression: White guar does not move
|
|
||||||
Bug #3108: Game Freeze after Killing Diseased Rat in Foreign Quarter Tomb
|
|
||||||
Bug #3124: Bloodmoon Quest - Rite of the Wolf Giver (BM_WolfGiver) – Innocent victim won't turn werewolf
|
|
||||||
Bug #3125: Improper dialogue window behavior when talking to creatures
|
|
||||||
Bug #3130: Some wandering NPCs disappearing, cannot finish quests
|
|
||||||
Bug #3132: Editor: GMST ID named sMake Enchantment is instead named sMake when making new game from scratch
|
|
||||||
Bug #3133: OpenMW and the OpenCS are writting warnings about scripts that use the function GetDisabled.
|
|
||||||
Bug #3135: Journal entry for The Pigrim's Path missing name
|
|
||||||
Bug #3136: Dropped bow is displaced
|
|
||||||
Bug #3140: Editor: OpenMW-CS fails to open newly converted and saved omwaddon file.
|
|
||||||
Bug #3142: Duplicate Resist Magic message
|
|
||||||
Bug #3143: Azura missing her head
|
|
||||||
Bug #3146: Potion effect showing when ingredient effects are not known
|
|
||||||
Bug #3155: When executing chop attack with a spear, hands turn partly invisible
|
|
||||||
Bug #3161: Fast travel from Silt Strider or Boat Ride will break save files made afterwards
|
|
||||||
Bug #3163: Editor: Objects dropped to scene do not always save
|
|
||||||
Bug #3173: Game Crashes After Casting Recall Spell
|
|
||||||
Bug #3174: Constant effect enchantments play spell animation on dead bodies
|
|
||||||
Bug #3175: Spell effects do not wear down when caster dies
|
|
||||||
Bug #3176: NPCs appearing randomly far away from towns
|
|
||||||
Bug #3177: Submerged corpse floats ontop of water when it shouldn't (Widow Vabdas' Deed quest)
|
|
||||||
Bug #3184: Bacola Closcius in Balmora, South Wall Cornerclub spams magic effects if attacked
|
|
||||||
Bug #3207: Editor: New objects do not render
|
|
||||||
Bug #3212: Arrow of Ranged Silence
|
|
||||||
Bug #3213: Looking at Floor After Magical Transport
|
|
||||||
Bug #3220: The number of remaining ingredients in the alchemy window doesn't go down when failing to brew a potion
|
|
||||||
Bug #3222: Falling through the water in Vivec
|
|
||||||
Bug #3223: Crash at the beginning with MOD (The Symphony)
|
|
||||||
Bug #3228: Purple screen when leveling up.
|
|
||||||
Bug #3233: Infinite disposition via MWDialogue::Filter::testDisposition() glitch
|
|
||||||
Bug #3234: Armor mesh stuck on body in inventory menu
|
|
||||||
Bug #3235: Unlike vanilla, OpenMW don't allow statics and activators cast effects on the player.
|
|
||||||
Bug #3238: Not loading cells when using Poorly Placed Object Fix.esm
|
|
||||||
Bug #3248: Editor: Using the "Next Script" and "Previous Script" buttons changes the record status to "Modified"
|
|
||||||
Bug #3258: Woman biped skeleton
|
|
||||||
Bug #3259: No alternating punches
|
|
||||||
Bug #3262: Crash in class selection menu
|
|
||||||
Bug #3279: Load menu: Deleting a savegame makes scroll bar jump to the top
|
|
||||||
Bug #3326: Starting a new game, getting to class selection, then starting another new game temporarily assigns Acrobat class
|
|
||||||
Bug #3327: Stuck in table after loading when character was sneaking when quicksave
|
|
||||||
Feature #652: Editor: GMST verifier
|
|
||||||
Feature #929: Editor: Info record verifier
|
|
||||||
Feature #1279: Editor: Render cell border markers
|
|
||||||
Feature #2482: Background cell loading and caching of loaded cells
|
|
||||||
Feature #2484: Editor: point lighting
|
|
||||||
Feature #2801: Support NIF bump map textures in osg
|
|
||||||
Feature #2926: Editor: Optional line wrap in script editor wrap lines
|
|
||||||
Feature #3000: Editor: Reimplement 3D scene camera system
|
|
||||||
Feature #3035: Editor: Make scenes a drop target for referenceables
|
|
||||||
Feature #3043: Editor: Render cell markers v2
|
|
||||||
Feature #3164: Editor: Instance Selection Menu
|
|
||||||
Feature #3165: Editor: Instance editing mode - move sub mode
|
|
||||||
Feature #3244: Allow changing water Level of Interiors behaving like exteriors
|
|
||||||
Feature #3250: Editor: Use "Enter" key instead of clicking "Create" button to confirm ID input in Creator Bar
|
|
||||||
Support #3179: Fatal error on startup
|
|
||||||
|
|
||||||
0.38.0
|
|
||||||
------
|
|
||||||
|
|
||||||
Bug #1699: Guard will continuously run into mudcrab
|
|
||||||
Bug #1934: Saw in Dome of Kasia doesnt harm the player
|
|
||||||
Bug #1962: Rat floats when killed near the door
|
|
||||||
Bug #1963: Kwama eggsacks pulse too fast
|
|
||||||
Bug #2198: NPC voice sound source should be placed at their head
|
|
||||||
Bug #2210: OpenMW installation wizard crashes...
|
|
||||||
Bug #2211: Editor: handle DELE subrecord at the end of a record
|
|
||||||
Bug #2413: ESM error Unknown subrecord in Grandmaster of Hlaalu
|
|
||||||
Bug #2537: Bloodmoon quest Ristaag: Sattir not consistently dying, plot fails to advance; same with Grerid
|
|
||||||
Bug #2697: "The Swimmer" moves away after leading you to underwater cave
|
|
||||||
Bug #2724: Loading previous save duplicates containers and harvestables
|
|
||||||
Bug #2769: Inventory doll - Cursor not respecting order of clothes
|
|
||||||
Bug #2865: Scripts silently fail when moving NPCs between cells.
|
|
||||||
Bug #2873: Starting a new game leads to CTD / Fatal Error
|
|
||||||
Bug #2918: Editor: it's not possible to create an omwaddon containing a dot in the file name
|
|
||||||
Bug #2933: Dialog box can't disable a npc if it is in another cell. (Rescue Madura Seran).
|
|
||||||
Bug #2942: atronach sign behavior (spell absorption) changes when trying to receive a blessing at "shrine of tribunal"
|
|
||||||
Bug #2952: Enchantment Merchant Items reshuffled EVERY time 'barter' is clicked
|
|
||||||
Bug #2961: ESM Error: Unknown subrecord if Deus Ex Machina mod is loaded
|
|
||||||
Bug #2972: Resurrecting the player via console does not work when health was 0
|
|
||||||
Bug #2986: Projectile weapons work underwater
|
|
||||||
Bug #2988: "Expected subrecord" bugs showing up.
|
|
||||||
Bug #2991: Can't use keywords in strings for MessageBox
|
|
||||||
Bug #2993: Tribunal:The Shrine of the Dead – Urvel Dulni can't stop to follow the player.
|
|
||||||
Bug #3008: NIFFile Error while loading meshes with a NiLODNode
|
|
||||||
Bug #3010: Engine: items should sink to the ground when dropped under water
|
|
||||||
Bug #3011: NIFFile Error while loading meshes with a NiPointLight
|
|
||||||
Bug #3016: Engine: something wrong with scripting - crash / fatal error
|
|
||||||
Bug #3020: Editor: verify does not check if given "item ID" (as content) for a "container" exists
|
|
||||||
Bug #3026: [MOD: Julan Ashlander Companion] Dialogue not triggering correctly
|
|
||||||
Bug #3028: Tooltips for Health, Magicka and Fatigue show in Options menu even when bars aren't visible
|
|
||||||
Bug #3034: Item count check dialogue option doesn't work (Guards accept gold even if you don't have enough)
|
|
||||||
Bug #3036: Owned tooltip color affects spell tooltips incorrrectly
|
|
||||||
Bug #3037: Fatal error loading old ES_Landscape.esp in Store<ESM::LandTexture>::search
|
|
||||||
Bug #3038: Player sounds come from underneath
|
|
||||||
Bug #3040: Execution of script failed: There is a message box already
|
|
||||||
Bug #3047: [MOD: Julan Ashlander Companion] Scripts KS_Bedscript or KS_JulanNight not working as intended
|
|
||||||
Bug #3048: Fatal Error
|
|
||||||
Bug #3051: High field of view results in first person rendering glitches
|
|
||||||
Bug #3053: Crash on new game at character class selection
|
|
||||||
Bug #3058: Physiched sleeves aren't rendered correctly.
|
|
||||||
Bug #3060: NPCs use wrong landing sound
|
|
||||||
Bug #3062: Mod support regression: Andromeda's fast travel.
|
|
||||||
Bug #3063: Missing Journal Textures without Tribunal and Bloodmoon installed
|
|
||||||
Bug #3077: repeated aifollow causes the distance to stack
|
|
||||||
Bug #3078: Creature Dialogues not showing when certain Function/Conditions are required.
|
|
||||||
Bug #3082: Crash when entering Holamayan Monastery with mesh replacer installed
|
|
||||||
Bug #3086: Party at Boro's House – Creature with Class don't talk under OpenMW
|
|
||||||
Bug #3089: Dreamers spawn too soon
|
|
||||||
Bug #3100: Certain controls erroneously work as a werewolf
|
|
||||||
Bug #3102: Multiple unique soultrap spell sources clone souls.
|
|
||||||
Bug #3105: Summoned creatures and objects disappear at midnight
|
|
||||||
Bug #3112: gamecontrollerdb file creation with wrong extension
|
|
||||||
Bug #3116: Dialogue Function "Same Race" is avoided
|
|
||||||
Bug #3117: Dialogue Bug: Choice conditions are tested when not in a choice
|
|
||||||
Bug #3118: Body Parts are not rendered when used in a pose.
|
|
||||||
Bug #3122: NPC direction is reversed during sneak awareness check
|
|
||||||
Feature #776: Sound effects from one direction don't necessarily affect both speakers in stereo
|
|
||||||
Feature #858: Different fov settings for hands and the game world
|
|
||||||
Feature #1176: Handle movement of objects between cells
|
|
||||||
Feature #2507: Editor: choosing colors for syntax highlighting
|
|
||||||
Feature #2867: Editor: hide script error list when there are no errors
|
|
||||||
Feature #2885: Accept a file format other than nif
|
|
||||||
Feature #2982: player->SetDelete 1 results in: PC can't move, menu can be opened
|
|
||||||
Feature #2996: Editor: make it possible to preset the height of the script check area in a script view
|
|
||||||
Feature #3014: Editor: Tooltips in 3D scene
|
|
||||||
Feature #3064: Werewolf field of view
|
|
||||||
Feature #3074: Quicksave indicator
|
|
||||||
Task #287: const version of Ptr
|
|
||||||
Task #2542: Editor: redo user settings system
|
|
||||||
|
|
||||||
0.37.0
|
|
||||||
------
|
|
||||||
|
|
||||||
Bug #385: Light emitting objects have a too short distance of activation
|
|
||||||
Bug #455: Animation doesn't resize creature's bounding box
|
|
||||||
Bug #602: Only collision model is updated when modifying objects trough console
|
|
||||||
Bug #639: Sky horizon at nighttime
|
|
||||||
Bug #672: incorrect trajectory of the moons
|
|
||||||
Bug #814: incorrect NPC width
|
|
||||||
Bug #827: Inaccurate raycasting for dead actors
|
|
||||||
Bug #996: Can see underwater clearly when at right height/angle
|
|
||||||
Bug #1317: Erene Llenim in Seyda Neen does not walk around
|
|
||||||
Bug #1330: Cliff racers fail to hit the player
|
|
||||||
Bug #1366: Combat AI can't aim down (in order to hit small creatures)
|
|
||||||
Bug #1511: View distance while under water is much too short
|
|
||||||
Bug #1563: Terrain positioned incorrectly and appears to vibrate in far-out cells
|
|
||||||
Bug #1612: First person models clip through walls
|
|
||||||
Bug #1647: Crash switching from full screen to windows mode - D3D9
|
|
||||||
Bug #1650: No textures with directx on windows
|
|
||||||
Bug #1730: Scripts names starting with digit(s) fail to compile
|
|
||||||
Bug #1738: Socucius Ergalla's greetings are doubled during the tutorial
|
|
||||||
Bug #1784: First person weapons always in the same position
|
|
||||||
Bug #1813: Underwater flora lighting up entire area.
|
|
||||||
Bug #1871: Handle controller extrapolation flags
|
|
||||||
Bug #1921: Footstep frequency and velocity do not immediately update when speed attribute changes
|
|
||||||
Bug #2001: OpenMW crashes on start with OpenGL 1.4 drivers
|
|
||||||
Bug #2014: Antialiasing setting does nothing on Linux
|
|
||||||
Bug #2037: Some enemies attack the air when spotting the player
|
|
||||||
Bug #2052: NIF rotation matrices including scales are not supported
|
|
||||||
Bug #2062: Crank in Old Mournhold: Forgotten Sewer turns about the wrong axis
|
|
||||||
Bug #2111: Raindrops in front of fire look wrong
|
|
||||||
Bug #2140: [OpenGL] Water effects, flames and parts of creatures solid black when observed through brazier flame
|
|
||||||
Bug #2147: Trueflame and Hopesfire flame effects not properly aligned with blade
|
|
||||||
Bug #2148: Verminous fabricants have little coloured box beneath their feet
|
|
||||||
Bug #2149: Sparks in Clockwork City should bounce off the floor
|
|
||||||
Bug #2151: Clockwork City dicer trap doesn't activate when you're too close
|
|
||||||
Bug #2186: Mini map contains scrambled pixels that cause the mini map to flicker
|
|
||||||
Bug #2187: NIF file with more than 255 NiBillboardNodes does not load
|
|
||||||
Bug #2191: Editor: Crash when trying to view cell in render view in OpenCS
|
|
||||||
Bug #2270: Objects flicker transparently
|
|
||||||
Bug #2280: Latest 32bit windows build of openmw runns out of vram
|
|
||||||
Bug #2281: NPCs don't scream when they die
|
|
||||||
Bug #2286: Jumping animation restarts when equipping mid-air
|
|
||||||
Bug #2287: Weapon idle animation stops when turning
|
|
||||||
Bug #2355: Light spell doesn't work in 1st person view
|
|
||||||
Bug #2362: Lantern glas opaque to flame effect from certain viewing angles
|
|
||||||
Bug #2364: Light spells are not as bright as in Morrowind
|
|
||||||
Bug #2383: Remove the alpha testing override list
|
|
||||||
Bug #2436: Crash on entering cell "Tower of Tel Fyr, Hall of Fyr"
|
|
||||||
Bug #2457: Player followers should not report crimes
|
|
||||||
Bug #2458: crash in some fighting situations
|
|
||||||
Bug #2464: Hiding an emitter node should make that emitter stop firing particles
|
|
||||||
Bug #2466: Can't load a save created with OpenMW-0.35.0-win64
|
|
||||||
Bug #2468: music from title screen continues after loading savegame
|
|
||||||
Bug #2494: Map not consistent between saves
|
|
||||||
Bug #2504: Dialog scroll should always start at the top
|
|
||||||
Bug #2506: Editor: Undo/Redo shortcuts do not work in script editor
|
|
||||||
Bug #2513: Mannequins in mods appear as dead bodies
|
|
||||||
Bug #2524: Editor: TopicInfo "custom" condition section is missing
|
|
||||||
Bug #2540: Editor: search and verification result table can not be sorted by clicking on the column names
|
|
||||||
Bug #2543: Editor: there is a problem with spell effects
|
|
||||||
Bug #2544: Editor fails to save NPC information correctly.
|
|
||||||
Bug #2545: Editor: delete record in Objects (referenceables) table messes up data
|
|
||||||
Bug #2546: Editor: race base attributes and skill boni are not displayed, thus not editable
|
|
||||||
Bug #2547: Editor: some NPC data is not displayed, thus not editable
|
|
||||||
Bug #2551: Editor: missing data in cell definition
|
|
||||||
Bug #2553: Editor: value filter does not work for float values
|
|
||||||
Bug #2555: Editor: undo leaves the record status as Modified
|
|
||||||
Bug #2559: Make Detect Enchantment marks appear on top of the player arrow
|
|
||||||
Bug #2563: position consoling npc doesn't work without cell reload
|
|
||||||
Bug #2564: Editor: Closing a subview from code does not clean up properly and will lead to crash on opening the next subview
|
|
||||||
Bug #2568: Editor: Setting default window size is ignored
|
|
||||||
Bug #2569: Editor: saving from an esp to omwaddon file results in data loss for TopicInfo
|
|
||||||
Bug #2575: Editor: Deleted record (with Added (ModifiedOnly) status) remains in the Dialog SubView
|
|
||||||
Bug #2576: Editor: Editor doesn't scroll to a newly opened subview, when ScrollBar Only mode is active
|
|
||||||
Bug #2578: Editor: changing Level or Reputation of an NPC crashes the editor
|
|
||||||
Bug #2579: Editor: filters not updated when adding or cloning records
|
|
||||||
Bug #2580: Editor: omwaddon makes OpenMW crash
|
|
||||||
Bug #2581: Editor: focus problems in edit subviews single- and multiline input fields
|
|
||||||
Bug #2582: Editor: object verifier should check for non-existing scripts being referenced
|
|
||||||
Bug #2583: Editor: applying filter to TopicInfo on mods that have added dialouge makes the Editor crash
|
|
||||||
Bug #2586: Editor: some dialogue only editable items do not refresh after undo
|
|
||||||
Bug #2588: Editor: Cancel button exits program
|
|
||||||
Bug #2589: Editor: Regions table - mapcolor does not change correctly
|
|
||||||
Bug #2591: Placeatme - spurious 5th parameter raises error
|
|
||||||
Bug #2593: COC command prints multiple times when GUI is hidden
|
|
||||||
Bug #2598: Editor: scene view of instances has to be zoomed out to displaying something - center camera instance please
|
|
||||||
Bug #2607: water behind an invisible NPC becomes invisible as well
|
|
||||||
Bug #2611: Editor: Sort problem in Objects table when few nested rows are added
|
|
||||||
Bug #2621: crash when a creature has no model
|
|
||||||
Bug #2624: Editor: missing columns in tables
|
|
||||||
Bug #2627: Character sheet doesn't properly update when backing out of CharGen
|
|
||||||
Bug #2642: Editor: endif without if - is not reported as error when "verify" was executed
|
|
||||||
Bug #2644: Editor: rebuild the list of available content files when opening the open/new dialogues
|
|
||||||
Bug #2656: OpenMW & OpenMW-CS: setting "Flies" flag for ghosts has no effect
|
|
||||||
Bug #2659: OpenMW & OpenMW-CS: savegame load fail due to script attached to NPCs
|
|
||||||
Bug #2668: Editor: reputation value in the input field is not stored
|
|
||||||
Bug #2696: Horkers use land idle animations under water
|
|
||||||
Bug #2705: Editor: Sort by Record Type (Objects table) is incorrect
|
|
||||||
Bug #2711: Map notes on an exterior cell that shows up with a map marker on the world map do not show up in the tooltip for that cell's marker on the world map
|
|
||||||
Bug #2714: Editor: Can't reorder rows with the same topic in different letter case
|
|
||||||
Bug #2720: Head tracking for creatures not implemented
|
|
||||||
Bug #2722: Alchemy should only include effects shared by at least 2 ingredients
|
|
||||||
Bug #2723: "ori" console command is not working
|
|
||||||
Bug #2726: Ashlanders in front of Ghostgate start wandering around
|
|
||||||
Bug #2727: ESM writer does not handle encoding when saving the TES3 header
|
|
||||||
Bug #2728: Editor: Incorrect position of an added row in Info tables
|
|
||||||
Bug #2731: Editor: Deleting a record triggers a Qt warning
|
|
||||||
Bug #2733: Editor: Undo doesn't restore the Modified status of a record when a nested data is changed
|
|
||||||
Bug #2734: Editor: The Search doesn't work
|
|
||||||
Bug #2738: Additive moon blending
|
|
||||||
Bug #2746: NIF node names should be case insensitive
|
|
||||||
Bug #2752: Fog depth/density not handled correctly
|
|
||||||
Bug #2753: Editor: line edit in dialogue subview tables shows after a single click
|
|
||||||
Bug #2755: Combat AI changes target too frequently
|
|
||||||
Bug #2761: Can't attack during block animations
|
|
||||||
Bug #2764: Player doesn't raise arm in 3rd person for weathertype 9
|
|
||||||
Bug #2768: Current screen resolution not selected in options when starting OpenMW
|
|
||||||
Bug #2773: Editor: Deleted scripts are editable
|
|
||||||
Bug #2776: ordinators still think I'm wearing their helm even though Khajiit and argonians can't
|
|
||||||
Bug #2779: Slider bars continue to move if you don't release mouse button
|
|
||||||
Bug #2781: sleep interruption is a little off (is this an added feature?)
|
|
||||||
Bug #2782: erroneously able to ready weapon/magic (+sheathe weapon/magic) while paralyzed
|
|
||||||
Bug #2785: Editor: Incorrect GMSTs for newly created omwgame files
|
|
||||||
Bug #2786: Kwama Queen head is inverted under OpenMW
|
|
||||||
Bug #2788: additem and removeitem incorrect gold behavior
|
|
||||||
Bug #2790: --start doesn't trace down
|
|
||||||
Bug #2791: Editor: Listed attributes and skill should not be based on number of NPC objects.
|
|
||||||
Bug #2792: glitched merchantile/infinite free items
|
|
||||||
Bug #2794: Need to ignore quotes in names of script function
|
|
||||||
Bug #2797: Editor: Crash when removing the first row in a nested table
|
|
||||||
Bug #2800: Show an error message when S3TC support is missing
|
|
||||||
Bug #2811: Targetted Open spell effect persists.
|
|
||||||
Bug #2819: Editor: bodypart's race filter not displayed correctly
|
|
||||||
Bug #2820: Editor: table sorting is inverted
|
|
||||||
Bug #2821: Editor: undo/redo command labels are incorrect
|
|
||||||
Bug #2826: locking beds that have been locked via magic psuedo-freezes the game
|
|
||||||
Bug #2830: Script compiler does not accept IDs as instruction/functions arguments if the ID is also a keyword
|
|
||||||
Bug #2832: Cell names are not localized on the world map
|
|
||||||
Bug #2833: [cosmetic] Players swimming at water's surface are slightly too low.
|
|
||||||
Bug #2840: Save/load menu is not entirely localized
|
|
||||||
Bug #2853: [exploit/bug] disintegrate weapon incorrectly applying to lockpicks, probes. creates unbreakable lockpicks
|
|
||||||
Bug #2855: Mouse wheel in journal is not disabled by "Options" panel.
|
|
||||||
Bug #2856: Heart of Lorkhan doesn't visually respond to attacks
|
|
||||||
Bug #2863: Inventory highlights wrong category after load
|
|
||||||
Bug #2864: Illuminated Order 1.0c Bug – The teleport amulet is not placed in the PC inventory.
|
|
||||||
Bug #2866: Editor: use checkbox instead of combobox for boolean values
|
|
||||||
Bug #2875: special cases of fSleepRandMod not behaving properly.
|
|
||||||
Bug #2878: Editor: Verify reports "creature has non-positive level" but there is no level setting
|
|
||||||
Bug #2879: Editor: entered value of field "Buys *" is not saved for a creature
|
|
||||||
Bug #2880: OpenMW & OpenMW-CS: having a scale value of 0.000 makes the game laggy
|
|
||||||
Bug #2882: Freeze when entering cell "Guild of Fighters (Ald'ruhn)" after dropping some items inside
|
|
||||||
Bug #2883: game not playable if mod providing a spell is removed but the list of known spells still contains it
|
|
||||||
Bug #2884: NPC chats about wrong player race
|
|
||||||
Bug #2886: Adding custom races breaks existing numbering of PcRace
|
|
||||||
Bug #2888: Editor: value entered in "AI Wander Idle" is not kept
|
|
||||||
Bug #2889: Editor: creatures made with the CS (not cloned) are always dead
|
|
||||||
Bug #2890: Editor: can't make NPC say a specific "Hello" voice-dialouge
|
|
||||||
Bug #2893: Editor: making a creature use textual dialogue doesn't work.
|
|
||||||
Bug #2901: Editor: gold for trading can not be set for creatures
|
|
||||||
Bug #2907: looking from uderwater part of the PC that is below the surface looks like it would be above the water
|
|
||||||
Bug #2914: Magicka not recalculated on character generation
|
|
||||||
Bug #2915: When paralyzed, you can still enter and exit sneak
|
|
||||||
Bug #2917: chameleon does not work for creatures
|
|
||||||
Bug #2927: Editor: in the automatic script checker local variable caches are not invalidated/updated on modifications of other scripts
|
|
||||||
Bug #2930: Editor: AIWander Idle can not be set for a creature
|
|
||||||
Bug #2932: Editor: you can add rows to "Creature Attack" but you can not enter values
|
|
||||||
Bug #2938: Editor: Can't add a start script.
|
|
||||||
Bug #2944: Spell chance for power to show as 0 on hud when used
|
|
||||||
Bug #2953: Editor: rightclick in an empty place in the menu bar shows an unnamed checkbox
|
|
||||||
Bug #2956: Editor: freezes while editing Filter
|
|
||||||
Bug #2959: space character in field enchantment (of an amulet) prevents rendering of surroundings
|
|
||||||
Bug #2962: OpenMW: Assertion `it != invStore.end()' failed
|
|
||||||
Bug #2964: Recursive script execution can corrupt script runtime data
|
|
||||||
Bug #2973: Editor: placing a chest in the game world and activating it heavily blurrs the character portrait
|
|
||||||
Bug #2978: Editor: Cannot edit alchemy ingredient properties
|
|
||||||
Bug #2980: Editor: Attribute and Skill can be selected for spells that do not require these parameters, leading to non-functional spells
|
|
||||||
Bug #2990: Compiling a script with warning mode 2 and enabled error downgrading leads to infinite recursion
|
|
||||||
Bug #2992: [Mod: Great House Dagoth] Killing Dagoth Gares freezes the game
|
|
||||||
Bug #3007: PlaceItem takes radians instead of degrees + angle reliability
|
|
||||||
Feature #706: Editor: Script Editor enhancements
|
|
||||||
Feature #872: Editor: Colour values in tables
|
|
||||||
Feature #880: Editor: ID auto-complete
|
|
||||||
Feature #928: Editor: Partial sorting in info tables
|
|
||||||
Feature #942: Editor: Dialogue for editing/viewing content file meta information
|
|
||||||
Feature #1057: NiStencilProperty
|
|
||||||
Feature #1278: Editor: Mouse picking in worldspace widget
|
|
||||||
Feature #1280: Editor: Cell border arrows
|
|
||||||
Feature #1401: Editor: Cloning enhancements
|
|
||||||
Feature #1463: Editor: Fine grained configuration of extended revert/delete commands
|
|
||||||
Feature #1591: Editor: Make fields in creation bar drop targets where applicable
|
|
||||||
Feature #1998: Editor: Magic effect record verifier
|
|
||||||
Feature #1999: Editor Sound Gen record verifier
|
|
||||||
Feature #2000: Editor: Pathgrid record verifier
|
|
||||||
Feature #2528: Game Time Tracker
|
|
||||||
Feature #2534: Editor: global search does not auomatically focus the search input field
|
|
||||||
Feature #2535: OpenMW: allow comments in openmw.cfg
|
|
||||||
Feature #2541: Editor: provide a go to the very bottom button for TopicInfo and JournalInfo
|
|
||||||
Feature #2549: Editor: add a horizontal slider to scroll between opened tables
|
|
||||||
Feature #2558: Editor: provide a shortcut for closing the subview that has the focus
|
|
||||||
Feature #2565: Editor: add context menu for dialogue sub view fields with an item matching "Edit 'x'" from the table subview context menu
|
|
||||||
Feature #2585: Editor: Ignore mouse wheel input for numeric values unless the respective widget has the focus
|
|
||||||
Feature #2620: Editor: make the verify-view refreshable
|
|
||||||
Feature #2622: Editor: Make double click behaviour in result tables configurable (see ID tables)
|
|
||||||
Feature #2717: Editor: Add severity column to report tables
|
|
||||||
Feature #2729: Editor: Various dialogue button bar improvements
|
|
||||||
Feature #2739: Profiling overlay
|
|
||||||
Feature #2740: Resource manager optimizations
|
|
||||||
Feature #2741: Make NIF files into proper resources
|
|
||||||
Feature #2742: Use the skinning data in NIF files as-is
|
|
||||||
Feature #2743: Small feature culling
|
|
||||||
Feature #2744: Configurable near clip distance
|
|
||||||
Feature #2745: GUI scaling option
|
|
||||||
Feature #2747: Support anonymous textures
|
|
||||||
Feature #2749: Loading screen optimizations
|
|
||||||
Feature #2751: Character preview optimization
|
|
||||||
Feature #2804: Editor: Merge Tool
|
|
||||||
Feature #2818: Editor: allow copying a record ID to the clipboard
|
|
||||||
Feature #2946: Editor: add script line number in results of search
|
|
||||||
Feature #2963: Editor: Mouse button bindings in 3D scene
|
|
||||||
Feature #2983: Sun Glare fader
|
|
||||||
Feature #2999: Scaling of journal and books
|
|
||||||
Task #2665: Support building with Qt5
|
|
||||||
Task #2725: Editor: Remove Display_YesNo
|
|
||||||
Task #2730: Replace hardcoded column numbers in SimpleDialogueSubView/DialogueSubView
|
|
||||||
Task #2750: Bullet shape instancing optimization
|
|
||||||
Task #2793: Replace grid size setting with half grid size setting
|
|
||||||
Task #3003: Support FFMPEG 2.9 (Debian request)
|
|
||||||
|
|
||||||
0.36.1
|
0.36.1
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca-
|
|
||||||
|
|
||||||
# Set up compilers
|
if [ "${ANALYZE}" ]; then
|
||||||
if [ ! -z "${MATRIX_CC}" ]; then
|
if [ $(lsb_release -sc) = "precise" ]; then
|
||||||
eval "${MATRIX_CC}"
|
echo "yes" | sudo apt-add-repository ppa:ubuntu-toolchain-r/test
|
||||||
|
fi
|
||||||
|
echo "yes" | sudo add-apt-repository "deb http://llvm.org/apt/`lsb_release -sc`/ llvm-toolchain-`lsb_release -sc`-3.6 main"
|
||||||
|
wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key|sudo apt-key add -
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# build libgtest & libgtest_main
|
echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse"
|
||||||
|
echo "yes" | sudo apt-add-repository ppa:openmw/openmw
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -qq libgtest-dev google-mock
|
||||||
|
sudo apt-get install -qq libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev libboost-wave-dev
|
||||||
|
sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavresample-dev
|
||||||
|
sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev libtinyxml-dev libopenal-dev libqt4-dev
|
||||||
|
if [ "${ANALYZE}" ]; then sudo apt-get install -qq clang-3.6; fi
|
||||||
sudo mkdir /usr/src/gtest/build
|
sudo mkdir /usr/src/gtest/build
|
||||||
cd /usr/src/gtest/build
|
cd /usr/src/gtest/build
|
||||||
sudo cmake .. -DBUILD_SHARED_LIBS=1
|
sudo cmake .. -DBUILD_SHARED_LIBS=1
|
||||||
sudo make -j4
|
sudo make -j4
|
||||||
sudo ln -s /usr/src/gtest/build/libgtest.so /usr/lib/libgtest.so
|
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 ~/
|
|
||||||
git clone https://github.com/TES3MP/CrabNet
|
|
||||||
cd CrabNet
|
|
||||||
cmake . -DCRABNET_ENABLE_DLL=OFF -DCRABNET_ENABLE_SAMPLES=OFF -DCMAKE_BUILD_TYPE=Release
|
|
||||||
make -j3
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
export CXX=clang++
|
||||||
|
export CC=clang
|
||||||
|
|
||||||
|
brew tap openmw/openmw
|
||||||
brew update
|
brew update
|
||||||
|
brew unlink boost
|
||||||
brew outdated cmake || brew upgrade cmake
|
brew install openmw-mygui openmw-bullet openmw-sdl2 openmw-ffmpeg openmw/openmw/qt unshield
|
||||||
brew outdated pkgconfig || brew upgrade pkgconfig
|
|
||||||
brew install qt
|
|
||||||
|
|
||||||
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-100d2e0.zip -o ~/openmw-deps.zip
|
|
||||||
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null
|
|
||||||
|
|
|
@ -1,39 +1,7 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
free -m
|
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
|
export CODE_COVERAGE=1
|
||||||
# Set up compilers
|
if [ "${CC}" = "clang" ]; then export CODE_COVERAGE=0; fi
|
||||||
if [ ! -z "${MATRIX_CC}" ]; then
|
${ANALYZE}cmake .. -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBINDIR=/usr/games -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DUSE_SYSTEM_TINYXML=TRUE
|
||||||
eval "${MATRIX_CC}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
export RAKNET_ROOT=~/CrabNet
|
|
||||||
|
|
||||||
export CODE_COVERAGE=0
|
|
||||||
if [ ! -z "${ANALYZE}" ]; then
|
|
||||||
CODE_COVERAGE=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
${ANALYZE}cmake .. \
|
|
||||||
-DDESIRED_QT_VERSION=5 \
|
|
||||||
-DBUILD_OPENMW_MP=ON \
|
|
||||||
-DBUILD_BROWSER=ON \
|
|
||||||
-DBUILD_MASTER=ON \
|
|
||||||
-DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} \
|
|
||||||
-DBUILD_BSATOOL=OFF \
|
|
||||||
-DBUILD_ESMTOOL=OFF \
|
|
||||||
-DBUILD_ESSIMPORTER=OFF \
|
|
||||||
-DBUILD_LAUNCHER=OFF \
|
|
||||||
-DBUILD_MWINIIMPORTER=OFF \
|
|
||||||
-DBUILD_MYGUI_PLUGIN=OFF \
|
|
||||||
-DBUILD_OPENCS=OFF \
|
|
||||||
-DBUILD_WIZARD=OFF \
|
|
||||||
-DBUILD_UNITTESTS=1 \
|
|
||||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
|
||||||
-DBINDIR=/usr/games \
|
|
||||||
-DCMAKE_BUILD_TYPE="None" \
|
|
||||||
-DUSE_SYSTEM_TINYXML=TRUE \
|
|
||||||
-DRakNet_LIBRARY_RELEASE=~/CrabNet/lib/libRakNetLibStatic.a \
|
|
||||||
-DRakNet_LIBRARY_DEBUG=~/CrabNet/lib/libRakNetLibStatic.a
|
|
||||||
|
|
|
@ -1,732 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# set -x # turn-on for debugging
|
|
||||||
|
|
||||||
MISSINGTOOLS=0
|
|
||||||
|
|
||||||
command -v 7z >/dev/null 2>&1 || { echo "Error: 7z (7zip) is not on the path."; MISSINGTOOLS=1; }
|
|
||||||
command -v cmake >/dev/null 2>&1 || { echo "Error: cmake (CMake) is not on the path."; MISSINGTOOLS=1; }
|
|
||||||
|
|
||||||
if [ $MISSINGTOOLS -ne 0 ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
WORKINGDIR="$(pwd)"
|
|
||||||
case "$WORKINGDIR" in
|
|
||||||
*[[:space:]]*)
|
|
||||||
echo "Error: Working directory contains spaces."
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
APPVEYOR=${APPVEYOR:-}
|
|
||||||
CI=${CI:-}
|
|
||||||
STEP=${STEP:-}
|
|
||||||
|
|
||||||
VERBOSE=""
|
|
||||||
STRIP=""
|
|
||||||
SKIP_DOWNLOAD=""
|
|
||||||
SKIP_EXTRACT=""
|
|
||||||
KEEP=""
|
|
||||||
UNITY_BUILD=""
|
|
||||||
VS_VERSION=""
|
|
||||||
PLATFORM=""
|
|
||||||
CONFIGURATION=""
|
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
|
||||||
ARGSTR=$1
|
|
||||||
shift
|
|
||||||
|
|
||||||
if [ ${ARGSTR:0:1} != "-" ]; then
|
|
||||||
echo "Unknown argument $ARGSTR"
|
|
||||||
echo "Try '$0 -h'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
for (( i=1; i<${#ARGSTR}; i++ )); do
|
|
||||||
ARG=${ARGSTR:$i:1}
|
|
||||||
case $ARG in
|
|
||||||
V )
|
|
||||||
VERBOSE=true ;;
|
|
||||||
|
|
||||||
d )
|
|
||||||
SKIP_DOWNLOAD=true ;;
|
|
||||||
|
|
||||||
e )
|
|
||||||
SKIP_EXTRACT=true ;;
|
|
||||||
|
|
||||||
k )
|
|
||||||
KEEP=true ;;
|
|
||||||
|
|
||||||
u )
|
|
||||||
UNITY_BUILD=true ;;
|
|
||||||
|
|
||||||
v )
|
|
||||||
VS_VERSION=$1
|
|
||||||
shift ;;
|
|
||||||
|
|
||||||
p )
|
|
||||||
PLATFORM=$1
|
|
||||||
shift ;;
|
|
||||||
|
|
||||||
c )
|
|
||||||
CONFIGURATION=$1
|
|
||||||
shift ;;
|
|
||||||
|
|
||||||
h )
|
|
||||||
cat <<EOF
|
|
||||||
Usage: $0 [-cdehkpuvV]
|
|
||||||
Options:
|
|
||||||
-c <Release/Debug>
|
|
||||||
Set the configuration, can also be set with environment variable CONFIGURATION.
|
|
||||||
-d
|
|
||||||
Skip checking the downloads.
|
|
||||||
-e
|
|
||||||
Skip extracting dependencies.
|
|
||||||
-h
|
|
||||||
Show this message.
|
|
||||||
-k
|
|
||||||
Keep the old build directory, default is to delete it.
|
|
||||||
-p <Win32/Win64>
|
|
||||||
Set the build platform, can also be set with environment variable PLATFORM.
|
|
||||||
-u
|
|
||||||
Configure for unity builds.
|
|
||||||
-v <2013/2015/2017>
|
|
||||||
Choose the Visual Studio version to use.
|
|
||||||
-V
|
|
||||||
Run verbosely
|
|
||||||
EOF
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
|
|
||||||
* )
|
|
||||||
echo "Unknown argument $ARG."
|
|
||||||
echo "Try '$0 -h'"
|
|
||||||
exit 1 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z $VERBOSE ]; then
|
|
||||||
STRIP="> /dev/null 2>&1"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z $APPVEYOR ]; then
|
|
||||||
echo "Running prebuild outside of Appveyor."
|
|
||||||
|
|
||||||
DIR=$(echo "$0" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,")
|
|
||||||
cd $(dirname "$DIR")/..
|
|
||||||
else
|
|
||||||
echo "Running prebuild in Appveyor."
|
|
||||||
|
|
||||||
cd "$APPVEYOR_BUILD_FOLDER"
|
|
||||||
fi
|
|
||||||
|
|
||||||
run_cmd() {
|
|
||||||
CMD="$1"
|
|
||||||
shift
|
|
||||||
|
|
||||||
if [ -z $VERBOSE ]; then
|
|
||||||
eval $CMD $@ > output.log 2>&1
|
|
||||||
RET=$?
|
|
||||||
|
|
||||||
if [ $RET -ne 0 ]; then
|
|
||||||
if [ -z $APPVEYOR ]; then
|
|
||||||
echo "Command $CMD failed, output can be found in $(real_pwd)/output.log"
|
|
||||||
else
|
|
||||||
echo
|
|
||||||
echo "Command $CMD failed;"
|
|
||||||
cat output.log
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
rm output.log
|
|
||||||
fi
|
|
||||||
|
|
||||||
return $RET
|
|
||||||
else
|
|
||||||
eval $CMD $@
|
|
||||||
return $?
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
download() {
|
|
||||||
if [ $# -lt 3 ]; then
|
|
||||||
echo "Invalid parameters to download."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
NAME=$1
|
|
||||||
shift
|
|
||||||
|
|
||||||
echo "$NAME..."
|
|
||||||
|
|
||||||
while [ $# -gt 1 ]; do
|
|
||||||
URL=$1
|
|
||||||
FILE=$2
|
|
||||||
shift
|
|
||||||
shift
|
|
||||||
|
|
||||||
if ! [ -f $FILE ]; then
|
|
||||||
printf " Downloading $FILE... "
|
|
||||||
|
|
||||||
if [ -z $VERBOSE ]; then
|
|
||||||
curl --silent --retry 10 -kLy 5 -o $FILE $URL
|
|
||||||
RET=$?
|
|
||||||
else
|
|
||||||
curl --retry 10 -kLy 5 -o $FILE $URL
|
|
||||||
RET=$?
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $RET -ne 0 ]; then
|
|
||||||
echo "Failed!"
|
|
||||||
else
|
|
||||||
echo "Done."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo " $FILE exists, skipping."
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ $# -ne 0 ]; then
|
|
||||||
echo "Missing parameter."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
real_pwd() {
|
|
||||||
pwd | sed "s,/\(.\),\1:,"
|
|
||||||
}
|
|
||||||
|
|
||||||
CMAKE_OPTS=""
|
|
||||||
add_cmake_opts() {
|
|
||||||
CMAKE_OPTS="$CMAKE_OPTS $@"
|
|
||||||
}
|
|
||||||
|
|
||||||
RUNTIME_DLLS=""
|
|
||||||
add_runtime_dlls() {
|
|
||||||
RUNTIME_DLLS="$RUNTIME_DLLS $@"
|
|
||||||
}
|
|
||||||
|
|
||||||
OSG_PLUGINS=""
|
|
||||||
add_osg_dlls() {
|
|
||||||
OSG_PLUGINS="$OSG_PLUGINS $@"
|
|
||||||
}
|
|
||||||
|
|
||||||
QT_PLATFORMS=""
|
|
||||||
add_qt_platform_dlls() {
|
|
||||||
QT_PLATFORMS="$QT_PLATFORMS $@"
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ -z $PLATFORM ]; then
|
|
||||||
PLATFORM="$(uname -m)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z $CONFIGURATION ]; then
|
|
||||||
CONFIGURATION="Debug"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z $VS_VERSION ]; then
|
|
||||||
VS_VERSION="2013"
|
|
||||||
fi
|
|
||||||
|
|
||||||
case $VS_VERSION in
|
|
||||||
15|15.0|2017 )
|
|
||||||
GENERATOR="Visual Studio 15 2017"
|
|
||||||
TOOLSET="vc141"
|
|
||||||
MSVC_REAL_VER="15"
|
|
||||||
MSVC_VER="14.1"
|
|
||||||
MSVC_YEAR="2015"
|
|
||||||
MSVC_DISPLAY_YEAR="2017"
|
|
||||||
;;
|
|
||||||
|
|
||||||
14|14.0|2015 )
|
|
||||||
GENERATOR="Visual Studio 14 2015"
|
|
||||||
TOOLSET="vc140"
|
|
||||||
MSVC_REAL_VER="14"
|
|
||||||
MSVC_VER="14.0"
|
|
||||||
MSVC_YEAR="2015"
|
|
||||||
MSVC_DISPLAY_YEAR="2015"
|
|
||||||
;;
|
|
||||||
|
|
||||||
12|12.0|2013 )
|
|
||||||
GENERATOR="Visual Studio 12 2013"
|
|
||||||
TOOLSET="vc120"
|
|
||||||
MSVC_REAL_VER="12"
|
|
||||||
MSVC_VER="12.0"
|
|
||||||
MSVC_YEAR="2013"
|
|
||||||
MSVC_DISPLAY_YEAR="2013"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case $PLATFORM in
|
|
||||||
x64|x86_64|x86-64|win64|Win64 )
|
|
||||||
ARCHNAME="x86-64"
|
|
||||||
ARCHSUFFIX="64"
|
|
||||||
BITS="64"
|
|
||||||
|
|
||||||
BASE_OPTS="-G\"$GENERATOR Win64\""
|
|
||||||
add_cmake_opts "-G\"$GENERATOR Win64\""
|
|
||||||
;;
|
|
||||||
|
|
||||||
x32|x86|i686|i386|win32|Win32 )
|
|
||||||
ARCHNAME="x86"
|
|
||||||
ARCHSUFFIX="86"
|
|
||||||
BITS="32"
|
|
||||||
|
|
||||||
BASE_OPTS="-G\"$GENERATOR\""
|
|
||||||
add_cmake_opts "-G\"$GENERATOR\""
|
|
||||||
;;
|
|
||||||
|
|
||||||
* )
|
|
||||||
echo "Unknown platform $PLATFORM."
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case $CONFIGURATION in
|
|
||||||
debug|Debug|DEBUG )
|
|
||||||
CONFIGURATION=Debug
|
|
||||||
BUILD_CONFIG=Debug
|
|
||||||
;;
|
|
||||||
|
|
||||||
release|Release|RELEASE )
|
|
||||||
CONFIGURATION=Release
|
|
||||||
BUILD_CONFIG=Release
|
|
||||||
;;
|
|
||||||
|
|
||||||
relwithdebinfo|RelWithDebInfo|RELWITHDEBINFO )
|
|
||||||
CONFIGURATION=Release
|
|
||||||
BUILD_CONFIG=RelWithDebInfo
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if ! [ -z $UNITY_BUILD ]; then
|
|
||||||
add_cmake_opts "-DOPENMW_UNITY_BUILD=True"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "==================================="
|
|
||||||
echo "Starting prebuild on MSVC${MSVC_DISPLAY_YEAR} WIN${BITS}"
|
|
||||||
echo "==================================="
|
|
||||||
echo
|
|
||||||
|
|
||||||
# cd OpenMW/AppVeyor-test
|
|
||||||
mkdir -p deps
|
|
||||||
cd deps
|
|
||||||
|
|
||||||
DEPS="$(pwd)"
|
|
||||||
|
|
||||||
if [ -z $SKIP_DOWNLOAD ]; then
|
|
||||||
echo "Downloading dependency packages."
|
|
||||||
echo
|
|
||||||
|
|
||||||
# Boost
|
|
||||||
if [ -z $APPVEYOR ]; then
|
|
||||||
download "Boost 1.67.0" \
|
|
||||||
"https://sourceforge.net/projects/boost/files/boost-binaries/1.67.0/boost_1_67_0-msvc-${MSVC_VER}-${BITS}.exe" \
|
|
||||||
"boost-1.67.0-msvc${MSVC_YEAR}-win${BITS}.exe"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Bullet
|
|
||||||
download "Bullet 2.86" \
|
|
||||||
"https://www.lysator.liu.se/~ace/OpenMW/deps/Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z" \
|
|
||||||
"Bullet-2.86-msvc${MSVC_YEAR}-win${BITS}.7z"
|
|
||||||
|
|
||||||
# FFmpeg
|
|
||||||
download "FFmpeg 3.2.4" \
|
|
||||||
"https://ffmpeg.zeranoe.com/builds/win${BITS}/shared/ffmpeg-3.2.4-win${BITS}-shared.zip" \
|
|
||||||
"ffmpeg-3.2.4-win${BITS}.zip" \
|
|
||||||
"https://ffmpeg.zeranoe.com/builds/win${BITS}/dev/ffmpeg-3.2.4-win${BITS}-dev.zip" \
|
|
||||||
"ffmpeg-3.2.4-dev-win${BITS}.zip"
|
|
||||||
|
|
||||||
# MyGUI
|
|
||||||
download "MyGUI 3.2.2" \
|
|
||||||
"https://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z" \
|
|
||||||
"MyGUI-3.2.2-msvc${MSVC_YEAR}-win${BITS}.7z"
|
|
||||||
|
|
||||||
# OpenAL
|
|
||||||
download "OpenAL-Soft 1.17.2" \
|
|
||||||
"http://kcat.strangesoft.net/openal-binaries/openal-soft-1.17.2-bin.zip" \
|
|
||||||
"OpenAL-Soft-1.17.2.zip"
|
|
||||||
|
|
||||||
# OSG
|
|
||||||
download "OpenSceneGraph 3.4.1-scrawl" \
|
|
||||||
"https://www.lysator.liu.se/~ace/OpenMW/deps/OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" \
|
|
||||||
"OSG-3.4.1-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z"
|
|
||||||
|
|
||||||
# Qt
|
|
||||||
if [ -z $APPVEYOR ]; then
|
|
||||||
if [ $BITS == "64" ]; then
|
|
||||||
QT_SUFFIX="_64"
|
|
||||||
else
|
|
||||||
QT_SUFFIX=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
download "Qt 5.7.0" \
|
|
||||||
"https://download.qt.io/archive/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" \
|
|
||||||
"https://www.lysator.liu.se/~ace/OpenMW/deps/qt-5-install.qs" \
|
|
||||||
"qt-5-install.qs"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# SDL2
|
|
||||||
download "SDL 2.0.7" \
|
|
||||||
"https://www.libsdl.org/release/SDL2-devel-2.0.7-VC.zip" \
|
|
||||||
"SDL2-2.0.7.zip"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd .. #/..
|
|
||||||
|
|
||||||
# Set up dependencies
|
|
||||||
BUILD_DIR="MSVC${MSVC_DISPLAY_YEAR}_${BITS}"
|
|
||||||
if [ -z $KEEP ]; then
|
|
||||||
echo
|
|
||||||
echo "(Re)Creating build directory."
|
|
||||||
|
|
||||||
rm -rf "$BUILD_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "${BUILD_DIR}/deps"
|
|
||||||
cd "${BUILD_DIR}/deps"
|
|
||||||
|
|
||||||
DEPS_INSTALL="$(pwd)"
|
|
||||||
cd $DEPS
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "Extracting dependencies, this might take a while..."
|
|
||||||
echo "---------------------------------------------------"
|
|
||||||
echo
|
|
||||||
|
|
||||||
|
|
||||||
# Boost
|
|
||||||
if [ -z $APPVEYOR ]; then
|
|
||||||
printf "Boost 1.67.0... "
|
|
||||||
else
|
|
||||||
if [ $MSVC_VER -eq 12.0 ]; then
|
|
||||||
printf "Boost 1.58.0 AppVeyor... "
|
|
||||||
else
|
|
||||||
printf "Boost 1.67.0 AppVeyor... "
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
{
|
|
||||||
if [ -z $APPVEYOR ]; then
|
|
||||||
cd $DEPS_INSTALL
|
|
||||||
|
|
||||||
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
|
|
||||||
# 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. "
|
|
||||||
elif [ -z $SKIP_EXTRACT ]; then
|
|
||||||
rm -rf Boost
|
|
||||||
CI_EXTRA_INNO_OPTIONS=""
|
|
||||||
[ -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
|
|
||||||
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
|
|
||||||
-DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}"
|
|
||||||
add_cmake_opts -DBoost_COMPILER="-${TOOLSET}"
|
|
||||||
echo Done.
|
|
||||||
else
|
|
||||||
# Appveyor unstable has all the boost we need already
|
|
||||||
if [ $MSVC_REAL_VER -eq 12 ]; then
|
|
||||||
BOOST_SDK="c:/Libraries/boost_1_58_0"
|
|
||||||
else
|
|
||||||
BOOST_SDK="c:/Libraries/boost_1_67_0"
|
|
||||||
fi
|
|
||||||
if [ $MSVC_REAL_VER -eq 15 ]; then
|
|
||||||
LIB_SUFFIX="1"
|
|
||||||
else
|
|
||||||
LIB_SUFFIX="0"
|
|
||||||
fi
|
|
||||||
|
|
||||||
add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \
|
|
||||||
-DBOOST_LIBRARYDIR="${BOOST_SDK}/lib${BITS}-msvc-${MSVC_VER}.${LIB_SUFFIX}"
|
|
||||||
add_cmake_opts -DBoost_COMPILER="-${TOOLSET}"
|
|
||||||
|
|
||||||
echo Done.
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
cd $DEPS
|
|
||||||
echo
|
|
||||||
# Bullet
|
|
||||||
printf "Bullet 2.86... "
|
|
||||||
{
|
|
||||||
cd $DEPS_INSTALL
|
|
||||||
if [ -d Bullet ]; then
|
|
||||||
printf -- "Exists. (No version checking) "
|
|
||||||
elif [ -z $SKIP_EXTRACT ]; then
|
|
||||||
rm -rf Bullet
|
|
||||||
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
|
|
||||||
fi
|
|
||||||
export BULLET_ROOT="$(real_pwd)/Bullet"
|
|
||||||
echo Done.
|
|
||||||
}
|
|
||||||
cd $DEPS
|
|
||||||
echo
|
|
||||||
# FFmpeg
|
|
||||||
printf "FFmpeg 3.2.4... "
|
|
||||||
{
|
|
||||||
cd $DEPS_INSTALL
|
|
||||||
if [ -d FFmpeg ] && grep "FFmpeg version: 3.2.4" FFmpeg/README.txt > /dev/null; then
|
|
||||||
printf "Exists. "
|
|
||||||
elif [ -z $SKIP_EXTRACT ]; then
|
|
||||||
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-dev-win${BITS}.zip" $STRIP
|
|
||||||
mv "ffmpeg-3.2.4-win${BITS}-shared" FFmpeg
|
|
||||||
cp -r "ffmpeg-3.2.4-win${BITS}-dev/"* FFmpeg/
|
|
||||||
rm -rf "ffmpeg-3.2.4-win${BITS}-dev"
|
|
||||||
fi
|
|
||||||
export FFMPEG_HOME="$(real_pwd)/FFmpeg"
|
|
||||||
add_runtime_dlls "$(pwd)/FFmpeg/bin/"{avcodec-57,avformat-57,avutil-55,swresample-2,swscale-4}.dll
|
|
||||||
if [ $BITS -eq 32 ]; then
|
|
||||||
add_cmake_opts "-DCMAKE_EXE_LINKER_FLAGS=\"/machine:X86 /safeseh:no\""
|
|
||||||
fi
|
|
||||||
echo Done.
|
|
||||||
}
|
|
||||||
cd $DEPS
|
|
||||||
echo
|
|
||||||
# MyGUI
|
|
||||||
printf "MyGUI 3.2.2... "
|
|
||||||
{
|
|
||||||
cd $DEPS_INSTALL
|
|
||||||
if [ -d MyGUI ] && \
|
|
||||||
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_PATCH 2" MyGUI/include/MYGUI/MyGUI_Prerequest.h > /dev/null
|
|
||||||
then
|
|
||||||
printf "Exists. "
|
|
||||||
elif [ -z $SKIP_EXTRACT ]; then
|
|
||||||
rm -rf MyGUI
|
|
||||||
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
|
|
||||||
fi
|
|
||||||
export MYGUI_HOME="$(real_pwd)/MyGUI"
|
|
||||||
if [ $CONFIGURATION == "Debug" ]; then
|
|
||||||
SUFFIX="_d"
|
|
||||||
else
|
|
||||||
SUFFIX=""
|
|
||||||
fi
|
|
||||||
add_runtime_dlls "$(pwd)/MyGUI/bin/${CONFIGURATION}/MyGUIEngine${SUFFIX}.dll"
|
|
||||||
echo Done.
|
|
||||||
}
|
|
||||||
cd $DEPS
|
|
||||||
echo
|
|
||||||
# OpenAL
|
|
||||||
printf "OpenAL-Soft 1.17.2... "
|
|
||||||
{
|
|
||||||
if [ -d openal-soft-1.17.2-bin ]; then
|
|
||||||
printf "Exists. "
|
|
||||||
elif [ -z $SKIP_EXTRACT ]; then
|
|
||||||
rm -rf openal-soft-1.17.2-bin
|
|
||||||
eval 7z x -y OpenAL-Soft-1.17.2.zip $STRIP
|
|
||||||
fi
|
|
||||||
OPENAL_SDK="$(real_pwd)/openal-soft-1.17.2-bin"
|
|
||||||
add_cmake_opts -DOPENAL_INCLUDE_DIR="${OPENAL_SDK}/include/AL" \
|
|
||||||
-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"
|
|
||||||
echo Done.
|
|
||||||
}
|
|
||||||
cd $DEPS
|
|
||||||
echo
|
|
||||||
# OSG
|
|
||||||
printf "OSG 3.4.1-scrawl... "
|
|
||||||
{
|
|
||||||
cd $DEPS_INSTALL
|
|
||||||
if [ -d OSG ] && \
|
|
||||||
grep "OPENSCENEGRAPH_MAJOR_VERSION 3" OSG/include/osg/Version > /dev/null && \
|
|
||||||
grep "OPENSCENEGRAPH_MINOR_VERSION 4" OSG/include/osg/Version > /dev/null && \
|
|
||||||
grep "OPENSCENEGRAPH_PATCH_VERSION 1" OSG/include/osg/Version > /dev/null
|
|
||||||
then
|
|
||||||
printf "Exists. "
|
|
||||||
elif [ -z $SKIP_EXTRACT ]; then
|
|
||||||
rm -rf OSG
|
|
||||||
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
|
|
||||||
fi
|
|
||||||
OSG_SDK="$(real_pwd)/OSG"
|
|
||||||
add_cmake_opts -DOSG_DIR="$OSG_SDK"
|
|
||||||
if [ $CONFIGURATION == "Debug" ]; then
|
|
||||||
SUFFIX="d"
|
|
||||||
else
|
|
||||||
SUFFIX=""
|
|
||||||
fi
|
|
||||||
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
|
|
||||||
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
|
|
||||||
echo Done.
|
|
||||||
}
|
|
||||||
cd $DEPS
|
|
||||||
echo
|
|
||||||
# Qt
|
|
||||||
if [ -z $APPVEYOR ]; then
|
|
||||||
printf "Qt 5.7.0... "
|
|
||||||
else
|
|
||||||
printf "Qt 5.10 AppVeyor... "
|
|
||||||
fi
|
|
||||||
{
|
|
||||||
if [ $BITS -eq 64 ]; then
|
|
||||||
SUFFIX="_64"
|
|
||||||
else
|
|
||||||
SUFFIX=""
|
|
||||||
fi
|
|
||||||
if [ -z $APPVEYOR ]; then
|
|
||||||
cd $DEPS_INSTALL
|
|
||||||
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
|
|
||||||
printf "Exists. "
|
|
||||||
elif [ -z $SKIP_EXTRACT ]; then
|
|
||||||
rm -rf Qt
|
|
||||||
cp "${DEPS}/qt-5-install.qs" 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
|
|
||||||
printf -- "(Installation might take a while) "
|
|
||||||
"${DEPS}/qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" --script qt-install.qs --silent
|
|
||||||
mv qt-install.qs Qt/
|
|
||||||
echo Done.
|
|
||||||
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}
|
|
||||||
fi
|
|
||||||
cd $QT_SDK
|
|
||||||
add_cmake_opts -DDESIRED_QT_VERSION=5 \
|
|
||||||
-DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
|
|
||||||
-DCMAKE_PREFIX_PATH="$QT_SDK"
|
|
||||||
if [ $CONFIGURATION == "Debug" ]; then
|
|
||||||
SUFFIX="d"
|
|
||||||
else
|
|
||||||
SUFFIX=""
|
|
||||||
fi
|
|
||||||
add_runtime_dlls "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
|
|
||||||
add_qt_platform_dlls "$(pwd)/plugins/platforms/qwindows${SUFFIX}.dll"
|
|
||||||
echo Done.
|
|
||||||
else
|
|
||||||
QT_SDK="C:/Qt/5.10/msvc${MSVC_DISPLAY_YEAR}${SUFFIX}"
|
|
||||||
add_cmake_opts -DDESIRED_QT_VERSION=5 \
|
|
||||||
-DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \
|
|
||||||
-DCMAKE_PREFIX_PATH="$QT_SDK"
|
|
||||||
if [ $CONFIGURATION == "Debug" ]; then
|
|
||||||
SUFFIX="d"
|
|
||||||
else
|
|
||||||
SUFFIX=""
|
|
||||||
fi
|
|
||||||
DIR=$(echo "${QT_SDK}" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,")
|
|
||||||
add_runtime_dlls "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${SUFFIX}.dll
|
|
||||||
add_qt_platform_dlls "${DIR}/plugins/platforms/qwindows${SUFFIX}.dll"
|
|
||||||
echo Done.
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
cd $DEPS
|
|
||||||
echo
|
|
||||||
# SDL2
|
|
||||||
printf "SDL 2.0.7... "
|
|
||||||
{
|
|
||||||
if [ -d SDL2-2.0.7 ]; then
|
|
||||||
printf "Exists. "
|
|
||||||
elif [ -z $SKIP_EXTRACT ]; then
|
|
||||||
rm -rf SDL2-2.0.7
|
|
||||||
eval 7z x -y SDL2-2.0.7.zip $STRIP
|
|
||||||
fi
|
|
||||||
export SDL2DIR="$(real_pwd)/SDL2-2.0.7"
|
|
||||||
add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll"
|
|
||||||
echo Done.
|
|
||||||
}
|
|
||||||
echo
|
|
||||||
cd $DEPS_INSTALL/..
|
|
||||||
echo
|
|
||||||
echo "Setting up OpenMW build..."
|
|
||||||
add_cmake_opts -DBUILD_BSATOOL=no \
|
|
||||||
-DBUILD_ESMTOOL=no \
|
|
||||||
-DBUILD_MYGUI_PLUGIN=no \
|
|
||||||
-DOPENMW_MP_BUILD=on
|
|
||||||
if [ ! -z $CI ]; then
|
|
||||||
case $STEP in
|
|
||||||
components )
|
|
||||||
echo " Building subproject: Components."
|
|
||||||
add_cmake_opts -DBUILD_ESSIMPORTER=no \
|
|
||||||
-DBUILD_LAUNCHER=no \
|
|
||||||
-DBUILD_MWINIIMPORTER=no \
|
|
||||||
-DBUILD_OPENCS=no \
|
|
||||||
-DBUILD_OPENMW=no \
|
|
||||||
-DBUILD_WIZARD=no
|
|
||||||
;;
|
|
||||||
openmw )
|
|
||||||
echo " Building subproject: OpenMW."
|
|
||||||
add_cmake_opts -DBUILD_ESSIMPORTER=no \
|
|
||||||
-DBUILD_LAUNCHER=no \
|
|
||||||
-DBUILD_MWINIIMPORTER=no \
|
|
||||||
-DBUILD_OPENCS=no \
|
|
||||||
-DBUILD_WIZARD=no
|
|
||||||
;;
|
|
||||||
opencs )
|
|
||||||
echo " Building subproject: OpenCS."
|
|
||||||
add_cmake_opts -DBUILD_ESSIMPORTER=no \
|
|
||||||
-DBUILD_LAUNCHER=no \
|
|
||||||
-DBUILD_MWINIIMPORTER=no \
|
|
||||||
-DBUILD_OPENMW=no \
|
|
||||||
-DBUILD_WIZARD=no
|
|
||||||
;;
|
|
||||||
misc )
|
|
||||||
echo " Building subprojects: Misc."
|
|
||||||
add_cmake_opts -DBUILD_OPENCS=no \
|
|
||||||
-DBUILD_OPENMW=no
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
# NOTE: Disable this when/if we want to run test cases
|
|
||||||
#if [ -z $CI ]; then
|
|
||||||
echo "- Copying Runtime DLLs..."
|
|
||||||
mkdir -p $BUILD_CONFIG
|
|
||||||
for DLL in $RUNTIME_DLLS; do
|
|
||||||
TARGET="$(basename "$DLL")"
|
|
||||||
if [[ "$DLL" == *":"* ]]; then
|
|
||||||
IFS=':'; SPLIT=( ${DLL} ); unset IFS
|
|
||||||
DLL=${SPLIT[0]}
|
|
||||||
TARGET=${SPLIT[1]}
|
|
||||||
fi
|
|
||||||
echo " ${TARGET}."
|
|
||||||
cp "$DLL" "$BUILD_CONFIG/$TARGET"
|
|
||||||
done
|
|
||||||
echo
|
|
||||||
echo "- OSG Plugin DLLs..."
|
|
||||||
mkdir -p $BUILD_CONFIG/osgPlugins-3.4.1
|
|
||||||
for DLL in $OSG_PLUGINS; do
|
|
||||||
echo " $(basename $DLL)."
|
|
||||||
cp "$DLL" $BUILD_CONFIG/osgPlugins-3.4.1
|
|
||||||
done
|
|
||||||
echo
|
|
||||||
echo "- Qt Platform DLLs..."
|
|
||||||
mkdir -p ${BUILD_CONFIG}/platforms
|
|
||||||
for DLL in $QT_PLATFORMS; do
|
|
||||||
echo " $(basename $DLL)"
|
|
||||||
cp "$DLL" "${BUILD_CONFIG}/platforms"
|
|
||||||
done
|
|
||||||
echo
|
|
||||||
#fi
|
|
||||||
if [ -z $VERBOSE ]; then
|
|
||||||
printf -- "- Configuring... "
|
|
||||||
else
|
|
||||||
echo "- cmake .. $CMAKE_OPTS"
|
|
||||||
fi
|
|
||||||
run_cmd cmake .. $CMAKE_OPTS
|
|
||||||
RET=$?
|
|
||||||
if [ -z $VERBOSE ]; then
|
|
||||||
if [ $RET -eq 0 ]; then
|
|
||||||
echo Done.
|
|
||||||
else
|
|
||||||
echo Failed.
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
exit $RET
|
|
|
@ -1,21 +1,5 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
export CXX=clang++
|
|
||||||
export CC=clang
|
|
||||||
|
|
||||||
DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps"
|
|
||||||
QT_PATH=`brew --prefix qt`
|
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
|
cmake -DCMAKE_FRAMEWORK_PATH="/usr/local/lib/macosx/Release" -DCMAKE_EXE_LINKER_FLAGS="-F/usr/local/lib/macosx/Release" -DCMAKE_CXX_FLAGS="-stdlib=libstdc++" -DCMAKE_BUILD_TYPE=Debug -DBUILD_MYGUI_PLUGIN=OFF -G"Unix Makefiles" ..
|
||||||
cmake \
|
|
||||||
-D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \
|
|
||||||
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.9" \
|
|
||||||
-D CMAKE_OSX_SYSROOT="macosx10.13" \
|
|
||||||
-D CMAKE_BUILD_TYPE=Release \
|
|
||||||
-D OPENMW_OSX_DEPLOYMENT=TRUE \
|
|
||||||
-D DESIRED_QT_VERSION=5 \
|
|
||||||
-D BUILD_ESMTOOL=FALSE \
|
|
||||||
-D BUILD_MYGUI_PLUGIN=FALSE \
|
|
||||||
-G"Unix Makefiles" \
|
|
||||||
..
|
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
APPVEYOR=""
|
|
||||||
CI=""
|
|
||||||
|
|
||||||
PACKAGE=""
|
|
||||||
PLATFORM=""
|
|
||||||
CONFIGURATION=""
|
|
||||||
VS_VERSION=""
|
|
||||||
|
|
||||||
if [ -z $PLATFORM ]; then
|
|
||||||
PLATFORM=`uname -m`
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z $CONFIGURATION ]; then
|
|
||||||
CONFIGURATION="Debug"
|
|
||||||
fi
|
|
||||||
|
|
||||||
case $VS_VERSION in
|
|
||||||
14|14.0|2015 )
|
|
||||||
GENERATOR="Visual Studio 14 2015"
|
|
||||||
MSVC_YEAR="2015"
|
|
||||||
MSVC_VER="14.0"
|
|
||||||
;;
|
|
||||||
|
|
||||||
# 12|2013|
|
|
||||||
* )
|
|
||||||
GENERATOR="Visual Studio 12 2013"
|
|
||||||
MSVC_YEAR="2013"
|
|
||||||
MVSC_VER="12.0"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case $PLATFORM in
|
|
||||||
x64|x86_64|x86-64|win64|Win64 )
|
|
||||||
BITS=64
|
|
||||||
;;
|
|
||||||
|
|
||||||
x32|x86|i686|i386|win32|Win32 )
|
|
||||||
BITS=32
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case $CONFIGURATION in
|
|
||||||
debug|Debug|DEBUG )
|
|
||||||
CONFIGURATION=Debug
|
|
||||||
;;
|
|
||||||
|
|
||||||
release|Release|RELEASE )
|
|
||||||
CONFIGURATION=Release
|
|
||||||
;;
|
|
||||||
|
|
||||||
relwithdebinfo|RelWithDebInfo|RELWITHDEBINFO )
|
|
||||||
CONFIGURATION=RelWithDebInfo
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -z $APPVEYOR ]; then
|
|
||||||
echo "Running ${BITS}-bit MSVC${MSVC_YEAR} ${CONFIGURATION} build outside of Appveyor."
|
|
||||||
|
|
||||||
DIR=$(echo "$0" | sed "s,\\\\,/,g" | sed "s,\(.\):,/\\1,")
|
|
||||||
cd $(dirname "$DIR")/..
|
|
||||||
else
|
|
||||||
echo "Running ${BITS}-bit MSVC${MSVC_YEAR} ${CONFIGURATION} build in Appveyor."
|
|
||||||
|
|
||||||
cd $APPVEYOR_BUILD_FOLDER
|
|
||||||
fi
|
|
||||||
|
|
||||||
BUILD_DIR="MSVC${MSVC_YEAR}_${BITS}"
|
|
||||||
cd ${BUILD_DIR}
|
|
||||||
|
|
||||||
which msbuild > /dev/null
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
msbuild() {
|
|
||||||
/c/Program\ Files\ \(x86\)/MSBuild/${MSVC_VER}/Bin/MSBuild.exe "$@"
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z $APPVEYOR ]; then
|
|
||||||
msbuild OpenMW.sln //t:Build //p:Configuration=${CONFIGURATION} //m:8
|
|
||||||
else
|
|
||||||
msbuild OpenMW.sln //t:Build //p:Configuration=${CONFIGURATION} //m:8 //logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
|
||||||
fi
|
|
||||||
|
|
||||||
RET=$?
|
|
||||||
if [ $RET -eq 0 ] && [ ! -z $PACKAGE ]; then
|
|
||||||
msbuild PACKAGE.vcxproj //t:Build //m:8
|
|
||||||
RET=$?
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit $RET
|
|
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
OUTPUT=$(grep -nRP '\t' --include=\*.{cpp,hpp,c,h} --exclude=ui_\* apps components)
|
|
||||||
|
|
||||||
if [[ $OUTPUT ]] ; then
|
|
||||||
echo "Error: Tab characters found!"
|
|
||||||
echo $OUTPUT
|
|
||||||
exit 1
|
|
||||||
fi
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
cd build
|
|
||||||
|
|
||||||
DATE=`date +'%d%m%Y'`
|
|
||||||
SHORT_COMMIT=`git rev-parse --short ${TRAVIS_COMMIT}`
|
|
||||||
TARGET_FILENAME="OpenMW-${DATE}-${SHORT_COMMIT}.dmg"
|
|
||||||
|
|
||||||
if ! curl --ssl -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}" --silent | grep $SHORT_COMMIT > /dev/null; then
|
|
||||||
curl --ssl --ftp-create-dirs -T *.dmg -u $OSX_FTP_USER:$OSX_FTP_PASSWORD "${OSX_FTP_URL}${TARGET_FILENAME}"
|
|
||||||
fi
|
|
1021
CMakeLists.txt
1021
CMakeLists.txt
File diff suppressed because it is too large
Load diff
115
CONTRIBUTING.md
115
CONTRIBUTING.md
|
@ -1,115 +0,0 @@
|
||||||
How to contribute to OpenMW
|
|
||||||
=======================
|
|
||||||
|
|
||||||
Not sure what to do with all your free time? Pick out a task from here:
|
|
||||||
|
|
||||||
https://gitlab.com/OpenMW/openmw/issues
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
* Tasks set to 'openmw-future' are usually out of the current scope of the project and can't be started yet.
|
|
||||||
* Bugs that are not 'Confirmed' should be confirmed first.
|
|
||||||
* Often, it's best to start a discussion about possible solutions before you jump into coding, especially for larger features.
|
|
||||||
|
|
||||||
Aside from coding, you can also help by triaging the issues list. Check for bugs that are 'Unconfirmed' and try to confirm them on your end, working out any details that may be necessary. Check for bugs that do not conform to [Bug reporting guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) and improve them to do so!
|
|
||||||
|
|
||||||
There are various [Tools](https://wiki.openmw.org/index.php?title=Tools) to facilitate testing/development.
|
|
||||||
|
|
||||||
Pull Request Guidelines
|
|
||||||
=======================
|
|
||||||
|
|
||||||
To facilitate the review process, your pull request description should include the following, if applicable:
|
|
||||||
|
|
||||||
* A link back to the bug report or forum discussion that prompted the change. Note: when linking bugs, use the syntax ```[Bug #xyz](https://bugs.openmw.org/issues/#xyz)``` to create a clickable link. Writing only 'Bug #xyz' will unfortunately create a link to the Github pull request with that number instead.
|
|
||||||
* Summary of the changes made
|
|
||||||
* Reasoning / motivation behind the change
|
|
||||||
* What testing you have carried out to verify the change
|
|
||||||
|
|
||||||
Furthermore, we advise to:
|
|
||||||
|
|
||||||
* Avoid stuffing unrelated commits into one pull request. As a rule of thumb, each feature and each bugfix should go into a separate PR, unless they are closely related or dependent upon each other. Small pull requests are easier to review, and are less likely to require further changes before we can merge them. A "mega" pull request with lots of unrelated commits in it is likely to get held up in review for a long time.
|
|
||||||
* Feel free to submit incomplete pull requests. Even if the work can not be merged yet, pull requests are a great place to collect early feedback. Just make sure to mark it as *[Incomplete]* or *[Do not merge yet]* in the title.
|
|
||||||
* If you plan on contributing often, please read the [Developer Reference](https://wiki.openmw.org/index.php?title=Developer_Reference) on our wiki, especially the [Policies and Standards](https://wiki.openmw.org/index.php?title=Policies_and_Standards).
|
|
||||||
* Make sure each of your changes has a clear objective. Unnecessary changes may lead to merge conflicts, clutter the commit history and slow down review. Code formatting 'fixes' should be avoided, unless you were already changing that particular line anyway.
|
|
||||||
* Reference the bug / feature ticket(s) in your commit message (e.g. 'Bug #123') to make it easier to keep track of what we changed for what reason. Our bugtracker will show those commits next to the ticket. If your commit message includes 'Fixes #123', that bug/feature will automatically be set to 'Closed' when your commit is merged.
|
|
||||||
* When pulling changes from master, prefer rebase over merge. Consider using a merge if there are conflicts or for long-running PRs.
|
|
||||||
|
|
||||||
Guidelines for original engine "fixes"
|
|
||||||
=================================
|
|
||||||
|
|
||||||
From time to time you may be tempted to "fix" what you think was a "bug" in the original game engine.
|
|
||||||
|
|
||||||
Unfortunately, the definition of what is a "bug" is not so clear. Consider that your "bug" is actually a feature unless proven otherwise:
|
|
||||||
|
|
||||||
* We have no way of knowing what the original developers really intended (short of asking them, good luck with that).
|
|
||||||
* What may seem like an illogical mechanic can actually be part of an attempt to balance the game.
|
|
||||||
* Many people will actually <i>like</i> these "bugs" because that is what they remember the game for.
|
|
||||||
* Exploits may be part of the fun of an open-world game - they reward knowledge with power. There are too many of them to plug them all, anyway.
|
|
||||||
|
|
||||||
OpenMW, in its default configuration, is meant to be a faithful reimplementation of Morrowind, minus things like crash bugs, stability issues and severe design errors. However, we try to avoid touching anything that affects the core gameplay, the balancing of the game or introduces incompatibilities with existing mod content.
|
|
||||||
|
|
||||||
That said, we may sometimes evaluate such issues on an individual basis. Common exceptions to the above would be:
|
|
||||||
|
|
||||||
* Issues so glaring that they would severely limit the capabilities of the engine in the future (for example, the scripting engine not being allowed to access objects in remote cells)
|
|
||||||
* Bugs where the intent is very obvious, and that have little to no balancing impact (e.g. the bug were being tired made it easier to repair items, instead of harder)
|
|
||||||
* Bugs that were fixed in an official patch for Morrowind
|
|
||||||
|
|
||||||
Feature additions policy
|
|
||||||
=====================
|
|
||||||
|
|
||||||
We get it, you have waited so long for feature XYZ to be available in Morrowind and now that OpenMW is here you can not wait to implement your ingenious idea and share it with the world.
|
|
||||||
|
|
||||||
Unfortunately, since maintaining features comes at a cost and our resources are limited, we have to be a little selective in what features we allow into the main repository. Generally:
|
|
||||||
|
|
||||||
* Features should be as generic and non-redundant as possible.
|
|
||||||
* Any feature that is also possible with modding should be done as a mod instead.
|
|
||||||
* In the future, OpenMW plans to expand the scope of what is possible with modding, e.g. by moving certain game logic into editable scripts.
|
|
||||||
* Currently, modders can edit OpenMW's GUI skins and layout XML files, although there are still a few missing hooks (e.g. scripting support) in order to make this into a powerful way of modding.
|
|
||||||
* If a feature introduces new game UI strings, that reduces its chance of being accepted because we do not currently have any way of localizing these to the user's Morrowind installation language.
|
|
||||||
|
|
||||||
If you are in doubt of your feature being within our scope, it is probably best to start a forum discussion first. See the [settings documentation](https://openmw.readthedocs.io/en/stable/reference/modding/settings/index.html) and [Features list](https://wiki.openmw.org/index.php?title=Features) for some examples of features that were deemed acceptable.
|
|
||||||
|
|
||||||
Reviewing pull requests
|
|
||||||
=======================
|
|
||||||
|
|
||||||
We welcome any help in reviewing open PRs. You don't need to be a developer to comment on new features. We also encourage ["junior" developers to review senior's work](https://pagefault.blog/2018/04/08/why-junior-devs-should-review-seniors-commits/).
|
|
||||||
|
|
||||||
This review process is divided into two sections because complaining about code or style issues hardly makes sense until the functionality of the PR is deemed OK. Anyone can help with the Functionality Review while most parts of the Code Review require you to have programming experience.
|
|
||||||
|
|
||||||
In addition to the checklist below, make sure to check that the Pull Request Guidelines (first half of this document) were followed.
|
|
||||||
|
|
||||||
First review
|
|
||||||
============
|
|
||||||
|
|
||||||
1. Ask for missing information or clarifications. Compare against the project's design goals and roadmap.
|
|
||||||
2. Check if the automated tests are passing. If they are not, make the PR author aware of the issue and potentially link to the error line on Travis CI or Appveyor. If the error appears unrelated to the PR and/or the master branch is failing with the same error, our CI has broken and needs to be fixed independently of any open PRs. Raise this issue on the forums, bug tracker or with the relevant maintainer. The PR can be merged in this case as long as you've built it yourself to make sure it does build.
|
|
||||||
3. Make sure that the new code has been tested thoroughly, either by asking the author or, preferably, testing yourself. In a complex project like OpenMW, it is easy to make mistakes, typos, etc. Therefore, prefer testing all code changes, no matter how trivial they look. When you have tested a PR that no one has tested so far, post a comment letting us know.
|
|
||||||
4. On long running PRs, request the author to update its description with the current state or a checklist of things left to do.
|
|
||||||
|
|
||||||
Code Review
|
|
||||||
===========
|
|
||||||
|
|
||||||
1. Carefully review each line for issues the author may not have thought of, paying special attention to 'special' cases. Often, people build their code with a particular mindset and forget about other configurations or unexpected interactions.
|
|
||||||
2. If any changes are workarounds for an issue in an upstream library, make sure the issue was reported upstream so we can eventually drop the workaround when the issue is fixed and the new version of that library is a build dependency.
|
|
||||||
3. Make sure PRs do not turn into arguments about hardly related issues. If the PR author disagrees with an established part of the project (e.g. supported build environments), they should open a forum discussion or bug report and in the meantime adjust the PR to adhere to the established way, rather than leaving the PR hanging on a dispute.
|
|
||||||
4. Check if the code matches our style guidelines.
|
|
||||||
5. Check to make sure the commit history is clean. Squashing should be considered if the review process has made the commit history particularly long. Commits that don't build should be avoided because they are a nuisance for ```git bisect```.
|
|
||||||
|
|
||||||
Merging
|
|
||||||
=======
|
|
||||||
|
|
||||||
To be able to merge PRs, commit priviledges are required. If you do not have the priviledges, just ping someone that does have them with a short comment like "Looks good to me @user".
|
|
||||||
|
|
||||||
The person to merge the PR may either use github's Merge button or if using the command line, use the ```--no-ff``` flag (so a merge commit is created, just like with Github's merge button) and include the pull request number in the commit description.
|
|
||||||
|
|
||||||
Dealing with regressions
|
|
||||||
========================
|
|
||||||
|
|
||||||
The master branch should always be in a working state that is not worse than the previous release in any way. If a regression is found, the first and foremost priority should be to get the regression fixed quickly, either by reverting the change that caused it or finding a better solution. Please avoid leaving the project in the 'broken' state for an extensive period of time while proper solutions are found. If the solution takes more than a day or so then it is usually better to revert the offending change first and reapply it later when fixed.
|
|
||||||
|
|
||||||
Other resources
|
|
||||||
===============
|
|
||||||
|
|
||||||
[GitHub blog - how to write the perfect pull request](https://blog.github.com/2015-01-21-how-to-write-the-perfect-pull-request/)
|
|
||||||
|
|
118
README.md
118
README.md
|
@ -1,49 +1,99 @@
|
||||||
TES3MP
|
OpenMW
|
||||||
======
|
======
|
||||||
|
|
||||||
Copyright (c) 2008-2015, OpenMW Team
|
[![Build Status](https://img.shields.io/travis/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740)
|
||||||
Copyright (c) 2016-2019, Stanislav Zhukov & David Cernat
|
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/TES3MP/openmw-tes3mp.svg?branch=0.7.0)](https://travis-ci.org/TES3MP/openmw-tes3mp)
|
OpenMW is an attempt at recreating the engine for the popular role-playing game
|
||||||
|
Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work.
|
||||||
|
|
||||||
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.
|
* Version: 0.36.1
|
||||||
|
* License: GPL (see docs/license/GPL3.txt for more information)
|
||||||
* TES3MP version: 0.7.0-alpha
|
* Website: http://www.openmw.org
|
||||||
* OpenMW version: 0.44.0
|
* IRC: #openmw on irc.freenode.net
|
||||||
* 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 docs/license/DejaVu Font License.txt for more information)
|
||||||
|
|
||||||
Project status
|
Getting Started
|
||||||
--------------
|
|
||||||
|
|
||||||
[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).
|
|
||||||
|
|
||||||
Remaining gameplay problems mostly relate to AI and the synchronization of clientside script variables.
|
|
||||||
|
|
||||||
Donations
|
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
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).
|
* [Official forums](https://forum.openmw.org/)
|
||||||
|
* [Installation instructions](https://wiki.openmw.org/index.php?title=Installation_Instructions)
|
||||||
|
* [Build from source](https://wiki.openmw.org/index.php?title=Development_Environment_Setup)
|
||||||
|
* [Testing the game](https://wiki.openmw.org/index.php?title=Testing)
|
||||||
|
* [How to contribute](https://wiki.openmw.org/index.php?title=Contribution_Wanted)
|
||||||
|
* [Report a bug](http://bugs.openmw.org/projects/openmw) - read the [guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) before submitting your first bug!
|
||||||
|
* [Known issues](http://bugs.openmw.org/projects/openmw/issues?utf8=%E2%9C%93&set_filter=1&f%5B%5D=status_id&op%5Bstatus_id%5D=%3D&v%5Bstatus_id%5D%5B%5D=7&f%5B%5D=tracker_id&op%5Btracker_id%5D=%3D&v%5Btracker_id%5D%5B%5D=1&f%5B%5D=&c%5B%5D=project&c%5B%5D=tracker&c%5B%5D=status&c%5B%5D=priority&c%5B%5D=subject&c%5B%5D=assigned_to&c%5B%5D=updated_on&group_by=tracker)
|
||||||
|
|
||||||
Contributing
|
The data path
|
||||||
---------------
|
-------------
|
||||||
|
|
||||||
Helping us with documentation, bug hunting and video showcases is always greatly appreciated.
|
The data path tells OpenMW where to find your Morrowind files. If you run the launcher, OpenMW should be able to pick up the location of these files on its own, if both Morrowind and OpenMW are installed properly (installing Morrowind under WINE is considered a proper install).
|
||||||
|
|
||||||
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.
|
Command line options
|
||||||
|
--------------------
|
||||||
|
|
||||||
Feel free to contact the [team members](https://github.com/TES3MP/openmw-tes3mp/blob/master/tes3mp-credits.md) for any questions you might have.
|
Syntax: openmw <options>
|
||||||
|
Allowed options:
|
||||||
|
--help print help message
|
||||||
|
--version print version information and quit
|
||||||
|
--data arg (=data) set data directories (later directories
|
||||||
|
have higher priority)
|
||||||
|
--data-local arg set local data directory (highest
|
||||||
|
priority)
|
||||||
|
--fallback-archive arg (=fallback-archive)
|
||||||
|
set fallback BSA archives (later
|
||||||
|
archives have higher priority)
|
||||||
|
--resources arg (=resources) set resources directory
|
||||||
|
--start arg set initial cell
|
||||||
|
--content arg content file(s): esm/esp, or
|
||||||
|
omwgame/omwaddon
|
||||||
|
--no-sound [=arg(=1)] (=0) disable all sounds
|
||||||
|
--script-verbose [=arg(=1)] (=0) verbose script output
|
||||||
|
--script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue
|
||||||
|
scripts) at startup
|
||||||
|
--script-all-dialogue [=arg(=1)] (=0) compile all dialogue scripts at startup
|
||||||
|
--script-console [=arg(=1)] (=0) enable console-only script
|
||||||
|
functionality
|
||||||
|
--script-run arg select a file containing a list of
|
||||||
|
console commands that is executed on
|
||||||
|
startup
|
||||||
|
--script-warn [=arg(=1)] (=1) handling of warnings when compiling
|
||||||
|
scripts
|
||||||
|
0 - ignore warning
|
||||||
|
1 - show warning but consider script as
|
||||||
|
correctly compiled anyway
|
||||||
|
2 - treat warnings as errors
|
||||||
|
--script-blacklist arg ignore the specified script (if the use
|
||||||
|
of the blacklist is enabled)
|
||||||
|
--script-blacklist-use [=arg(=1)] (=1)
|
||||||
|
enable script blacklisting
|
||||||
|
--load-savegame arg load a save game file on game startup
|
||||||
|
(specify an absolute filename or a
|
||||||
|
filename relative to the current
|
||||||
|
working directory)
|
||||||
|
--skip-menu [=arg(=1)] (=0) skip main menu on game startup
|
||||||
|
--new-game [=arg(=1)] (=0) run new game sequence (ignored if
|
||||||
|
skip-menu=0)
|
||||||
|
--fs-strict [=arg(=1)] (=0) strict file system handling (no case
|
||||||
|
folding)
|
||||||
|
--encoding arg (=win1252) Character encoding used in OpenMW game
|
||||||
|
messages:
|
||||||
|
|
||||||
Getting started
|
win1250 - Central and Eastern European
|
||||||
---------------
|
such as Polish, Czech, Slovak,
|
||||||
|
Hungarian, Slovene, Bosnian, Croatian,
|
||||||
|
Serbian (Latin script), Romanian and
|
||||||
|
Albanian languages
|
||||||
|
|
||||||
* [Quickstart guide](https://github.com/TES3MP/openmw-tes3mp/wiki/Quickstart-guide)
|
win1251 - Cyrillic alphabet such as
|
||||||
* [Steam group](https://steamcommunity.com/groups/mwmulti) and its [detailed FAQ](https://steamcommunity.com/groups/mwmulti/discussions/1/353916184342480541/)
|
Russian, Bulgarian, Serbian Cyrillic
|
||||||
* [TES3MP section on OpenMW forums](https://forum.openmw.org/viewforum.php?f=45)
|
and other languages
|
||||||
* [Discord server](https://discord.gg/ECJk293)
|
|
||||||
* [Subreddit](https://www.reddit.com/r/tes3mp)
|
win1252 - Western European (Latin)
|
||||||
* [Known issues and bug reports](https://github.com/TES3MP/openmw-tes3mp/issues)
|
alphabet, used by default
|
||||||
|
--fallback arg fallback values
|
||||||
|
--no-grab Don't grab mouse cursor
|
||||||
|
--export-fonts [=arg(=1)] (=0) Export Morrowind .fnt fonts to PNG
|
||||||
|
image and XML file in current directory
|
||||||
|
--activate-dist arg (=-1) activation distance override
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 14)
|
|
||||||
|
|
||||||
set(BROWSER_UI
|
|
||||||
${CMAKE_SOURCE_DIR}/files/tes3mp/ui/Main.ui
|
|
||||||
${CMAKE_SOURCE_DIR}/files/tes3mp/ui/ServerInfo.ui
|
|
||||||
)
|
|
||||||
set(BROWSER
|
|
||||||
main.cpp
|
|
||||||
MainWindow.cpp
|
|
||||||
ServerModel.cpp
|
|
||||||
ServerInfoDialog.cpp
|
|
||||||
MySortFilterProxyModel.cpp
|
|
||||||
netutils/HTTPNetwork.cpp
|
|
||||||
netutils/Utils.cpp
|
|
||||||
netutils/QueryClient.cpp
|
|
||||||
PingUpdater.cpp
|
|
||||||
PingHelper.cpp
|
|
||||||
QueryHelper.cpp
|
|
||||||
${CMAKE_SOURCE_DIR}/files/tes3mp/browser.rc
|
|
||||||
)
|
|
||||||
|
|
||||||
set(BROWSER_HEADER_MOC
|
|
||||||
MainWindow.hpp
|
|
||||||
ServerModel.hpp
|
|
||||||
ServerInfoDialog.hpp
|
|
||||||
MySortFilterProxyModel.hpp
|
|
||||||
PingUpdater.hpp
|
|
||||||
PingHelper.hpp
|
|
||||||
QueryHelper.hpp
|
|
||||||
)
|
|
||||||
|
|
||||||
set(BROWSER_HEADER
|
|
||||||
${BROWSER_HEADER_MOC}
|
|
||||||
netutils/HTTPNetwork.hpp
|
|
||||||
netutils/Utils.hpp
|
|
||||||
netutils/QueryClient.hpp
|
|
||||||
Types.hpp
|
|
||||||
)
|
|
||||||
|
|
||||||
source_group(browser FILES ${BROWSER} ${BROWSER_HEADER})
|
|
||||||
|
|
||||||
set(QT_USE_QTGUI 1)
|
|
||||||
|
|
||||||
# Set some platform specific settings
|
|
||||||
if(WIN32)
|
|
||||||
set(GUI_TYPE WIN32)
|
|
||||||
set(QT_USE_QTMAIN TRUE)
|
|
||||||
endif(WIN32)
|
|
||||||
|
|
||||||
if (DESIRED_QT_VERSION MATCHES 4)
|
|
||||||
message(SEND_ERROR "QT4 is not supported.")
|
|
||||||
#include(${QT_USE_FILE})
|
|
||||||
#QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc)
|
|
||||||
#QT4_WRAP_CPP(MOC_SRCS ${BROWSER_HEADER_MOC})
|
|
||||||
#QT4_WRAP_UI(UI_HDRS ${BROWSER_UI})
|
|
||||||
else()
|
|
||||||
QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc)
|
|
||||||
QT5_WRAP_CPP(MOC_SRCS ${BROWSER_HEADER_MOC})
|
|
||||||
QT5_WRAP_UI(UI_HDRS ${BROWSER_UI})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
|
||||||
if(NOT WIN32)
|
|
||||||
include_directories(${LIBUNSHIELD_INCLUDE_DIR})
|
|
||||||
endif(NOT WIN32)
|
|
||||||
|
|
||||||
# Main executable
|
|
||||||
add_executable(tes3mp-browser
|
|
||||||
${GUI_TYPE}
|
|
||||||
${BROWSER}
|
|
||||||
${BROWSER_HEADER}
|
|
||||||
${RCC_SRCS}
|
|
||||||
${MOC_SRCS}
|
|
||||||
${UI_HDRS}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (WIN32)
|
|
||||||
INSTALL(TARGETS tes3mp-browser RUNTIME DESTINATION ".")
|
|
||||||
endif (WIN32)
|
|
||||||
|
|
||||||
target_link_libraries(tes3mp-browser
|
|
||||||
${SDL2_LIBRARY_ONLY}
|
|
||||||
${RakNet_LIBRARY}
|
|
||||||
components
|
|
||||||
)
|
|
||||||
|
|
||||||
if (DESIRED_QT_VERSION MATCHES 4)
|
|
||||||
# target_link_libraries(tes3mp-browser ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY})
|
|
||||||
# if(WIN32)
|
|
||||||
# target_link_libraries(tes3mp-browser ${QT_QTMAIN_LIBRARY})
|
|
||||||
# endif(WIN32)
|
|
||||||
else()
|
|
||||||
qt5_use_modules(tes3mp-browser Widgets Core)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (BUILD_WITH_CODE_COVERAGE)
|
|
||||||
add_definitions (--coverage)
|
|
||||||
target_link_libraries(tes3mp-browser gcov)
|
|
||||||
endif()
|
|
|
@ -1,246 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 06.01.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "MainWindow.hpp"
|
|
||||||
#include "QueryHelper.hpp"
|
|
||||||
#include "PingHelper.hpp"
|
|
||||||
#include "ServerInfoDialog.hpp"
|
|
||||||
#include "components/files/configurationmanager.hpp"
|
|
||||||
#include <qdebug.h>
|
|
||||||
#include <QInputDialog>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
|
|
||||||
|
|
||||||
using namespace Process;
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget *parent)
|
|
||||||
{
|
|
||||||
setupUi(this);
|
|
||||||
|
|
||||||
mGameInvoker = new ProcessInvoker();
|
|
||||||
|
|
||||||
browser = new ServerModel;
|
|
||||||
favorites = new ServerModel;
|
|
||||||
proxyModel = new MySortFilterProxyModel(this);
|
|
||||||
proxyModel->setSourceModel(browser);
|
|
||||||
tblServerBrowser->setModel(proxyModel);
|
|
||||||
tblFavorites->setModel(proxyModel);
|
|
||||||
|
|
||||||
// Remove Favorites tab while it remains broken
|
|
||||||
tabWidget->removeTab(1);
|
|
||||||
|
|
||||||
tblServerBrowser->hideColumn(ServerData::ADDR);
|
|
||||||
tblFavorites->hideColumn(ServerData::ADDR);
|
|
||||||
|
|
||||||
PingHelper::Get().SetModel((ServerModel*)proxyModel->sourceModel());
|
|
||||||
queryHelper = new QueryHelper(proxyModel->sourceModel());
|
|
||||||
connect(queryHelper, &QueryHelper::started, [this](){actionRefresh->setEnabled(false);});
|
|
||||||
connect(queryHelper, &QueryHelper::finished, [this](){actionRefresh->setEnabled(true);});
|
|
||||||
|
|
||||||
connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabSwitched(int)));
|
|
||||||
connect(actionAdd, SIGNAL(triggered(bool)), this, SLOT(addServer()));
|
|
||||||
connect(actionAdd_by_IP, SIGNAL(triggered(bool)), this, SLOT(addServerByIP()));
|
|
||||||
connect(actionDelete, SIGNAL(triggered(bool)), this, SLOT(deleteServer()));
|
|
||||||
connect(actionRefresh, SIGNAL(triggered(bool)), queryHelper, SLOT(refresh()));
|
|
||||||
connect(actionPlay, SIGNAL(triggered(bool)), this, SLOT(play()));
|
|
||||||
connect(tblServerBrowser, SIGNAL(clicked(QModelIndex)), this, SLOT(serverSelected()));
|
|
||||||
connect(tblFavorites, SIGNAL(clicked(QModelIndex)), this, SLOT(serverSelected()));
|
|
||||||
connect(tblFavorites, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(play()));
|
|
||||||
connect(tblServerBrowser, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(play()));
|
|
||||||
connect(cBoxNotFull, SIGNAL(toggled(bool)), this, SLOT(notFullSwitch(bool)));
|
|
||||||
connect(cBoxWithPlayers, SIGNAL(toggled(bool)), this, SLOT(havePlayersSwitch(bool)));
|
|
||||||
connect(cBBoxWOPass, SIGNAL(toggled(bool)), this, SLOT(noPasswordSwitch(bool)));
|
|
||||||
connect(comboLatency, SIGNAL(currentIndexChanged(int)), this, SLOT(maxLatencyChanged(int)));
|
|
||||||
connect(leGamemode, SIGNAL(textChanged(const QString &)), this, SLOT(gamemodeChanged(const QString &)));
|
|
||||||
loadFavorites();
|
|
||||||
queryHelper->refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
|
||||||
{
|
|
||||||
delete mGameInvoker;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::addServerAndUpdate(const QString &addr)
|
|
||||||
{
|
|
||||||
favorites->insertRow(0);
|
|
||||||
QModelIndex mi = favorites->index(0, ServerData::ADDR);
|
|
||||||
favorites->setData(mi, addr, Qt::EditRole);
|
|
||||||
//NetController::get()->updateInfo(favorites, mi);
|
|
||||||
//QueryClient::Update(RakNet::SystemAddress())
|
|
||||||
/*auto data = QueryClient::Get().Query();
|
|
||||||
if (data.empty())
|
|
||||||
return;
|
|
||||||
transform(data.begin(), data.end(), back_inserter());*/
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::addServer()
|
|
||||||
{
|
|
||||||
int id = tblServerBrowser->selectionModel()->currentIndex().row();
|
|
||||||
|
|
||||||
if (id >= 0)
|
|
||||||
{
|
|
||||||
int sourceId = proxyModel->mapToSource(proxyModel->index(id, ServerData::ADDR)).row();
|
|
||||||
favorites->myData.push_back(browser->myData[sourceId]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::addServerByIP()
|
|
||||||
{
|
|
||||||
bool ok;
|
|
||||||
QString text = QInputDialog::getText(this, tr("Add Server by address"), tr("Address:"), QLineEdit::Normal, "", &ok);
|
|
||||||
if (ok && !text.isEmpty())
|
|
||||||
addServerAndUpdate(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::deleteServer()
|
|
||||||
{
|
|
||||||
if (tabWidget->currentIndex() != 1)
|
|
||||||
return;
|
|
||||||
int id = tblFavorites->selectionModel()->currentIndex().row();
|
|
||||||
if (id >= 0)
|
|
||||||
{
|
|
||||||
int sourceId = proxyModel->mapToSource(proxyModel->index(id, ServerData::ADDR)).row();
|
|
||||||
favorites->removeRow(sourceId);
|
|
||||||
if (favorites->myData.isEmpty())
|
|
||||||
{
|
|
||||||
actionPlay->setEnabled(false);
|
|
||||||
actionDelete->setEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::play()
|
|
||||||
{
|
|
||||||
QTableView *curTable = tabWidget->currentIndex() ? tblFavorites : tblServerBrowser;
|
|
||||||
int id = curTable->selectionModel()->currentIndex().row();
|
|
||||||
if (id < 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
ServerModel *sm = ((ServerModel*)proxyModel->sourceModel());
|
|
||||||
|
|
||||||
int sourceId = proxyModel->mapToSource(proxyModel->index(id, ServerData::ADDR)).row();
|
|
||||||
ServerInfoDialog infoDialog(sm->myData[sourceId].addr, this);
|
|
||||||
|
|
||||||
if (!infoDialog.exec())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!infoDialog.isUpdated())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QStringList arguments;
|
|
||||||
arguments.append(QLatin1String("--connect=") + sm->myData[sourceId].addr.toLatin1());
|
|
||||||
|
|
||||||
if (sm->myData[sourceId].GetPassword() == 1)
|
|
||||||
{
|
|
||||||
bool ok;
|
|
||||||
QString passw = QInputDialog::getText(this, tr("Connecting to: ") + sm->myData[sourceId].addr, tr("Password: "),
|
|
||||||
QLineEdit::Password, "", &ok);
|
|
||||||
if (!ok)
|
|
||||||
return;
|
|
||||||
arguments.append(QLatin1String("--password=") + passw.toLatin1());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mGameInvoker->startProcess(QLatin1String("tes3mp"), arguments, true))
|
|
||||||
return qApp->quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::tabSwitched(int index)
|
|
||||||
{
|
|
||||||
if (index == 0)
|
|
||||||
{
|
|
||||||
proxyModel->setSourceModel(browser);
|
|
||||||
actionDelete->setEnabled(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
proxyModel->setSourceModel(favorites);
|
|
||||||
}
|
|
||||||
actionPlay->setEnabled(false);
|
|
||||||
actionAdd->setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::serverSelected()
|
|
||||||
{
|
|
||||||
actionPlay->setEnabled(true);
|
|
||||||
if (tabWidget->currentIndex() == 0)
|
|
||||||
actionAdd->setEnabled(true);
|
|
||||||
if (tabWidget->currentIndex() == 1)
|
|
||||||
actionDelete->setEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::closeEvent(QCloseEvent *event)
|
|
||||||
{
|
|
||||||
Files::ConfigurationManager cfgMgr;
|
|
||||||
QString cfgPath = QString::fromStdString((cfgMgr.getUserConfigPath() / "favorites.dat").string());
|
|
||||||
|
|
||||||
QJsonArray saveData;
|
|
||||||
for (auto server : favorites->myData)
|
|
||||||
saveData.push_back(server.addr);
|
|
||||||
|
|
||||||
QFile file(cfgPath);
|
|
||||||
|
|
||||||
if (!file.open(QIODevice::WriteOnly))
|
|
||||||
{
|
|
||||||
qDebug() << "Cannot save " << cfgPath;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
file.write(QJsonDocument(saveData).toJson());
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void MainWindow::loadFavorites()
|
|
||||||
{
|
|
||||||
Files::ConfigurationManager cfgMgr;
|
|
||||||
QString cfgPath = QString::fromStdString((cfgMgr.getUserConfigPath() / "favorites.dat").string());
|
|
||||||
|
|
||||||
QFile file(cfgPath);
|
|
||||||
if (!file.open(QIODevice::ReadOnly))
|
|
||||||
{
|
|
||||||
qDebug() << "Cannot open " << cfgPath;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonDocument jsonDoc(QJsonDocument::fromJson(file.readAll()));
|
|
||||||
|
|
||||||
for (auto server : jsonDoc.array())
|
|
||||||
addServerAndUpdate(server.toString());
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::notFullSwitch(bool state)
|
|
||||||
{
|
|
||||||
proxyModel->filterFullServer(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::havePlayersSwitch(bool state)
|
|
||||||
{
|
|
||||||
proxyModel->filterEmptyServers(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::noPasswordSwitch(bool state)
|
|
||||||
{
|
|
||||||
proxyModel->filterPassworded(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::maxLatencyChanged(int index)
|
|
||||||
{
|
|
||||||
int maxLatency = index * 50;
|
|
||||||
proxyModel->pingLessThan(maxLatency);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::gamemodeChanged(const QString &text)
|
|
||||||
{
|
|
||||||
proxyModel->setFilterFixedString(text);
|
|
||||||
proxyModel->setFilterKeyColumn(ServerData::MODNAME);
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 06.01.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef NEWLAUNCHER_MAIN_HPP
|
|
||||||
#define NEWLAUNCHER_MAIN_HPP
|
|
||||||
|
|
||||||
|
|
||||||
#include "ui_Main.h"
|
|
||||||
#include "ServerModel.hpp"
|
|
||||||
#include "MySortFilterProxyModel.hpp"
|
|
||||||
#include <components/process/processinvoker.hpp>
|
|
||||||
|
|
||||||
class QueryHelper;
|
|
||||||
|
|
||||||
class MainWindow : public QMainWindow, private Ui::MainWindow
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit MainWindow(QWidget *parent = nullptr);
|
|
||||||
~MainWindow() override;
|
|
||||||
protected:
|
|
||||||
void closeEvent(QCloseEvent * event) Q_DECL_OVERRIDE;
|
|
||||||
void addServerAndUpdate(const QString &addr);
|
|
||||||
protected slots:
|
|
||||||
void tabSwitched(int index);
|
|
||||||
void addServer();
|
|
||||||
void addServerByIP();
|
|
||||||
void deleteServer();
|
|
||||||
void play();
|
|
||||||
void serverSelected();
|
|
||||||
void notFullSwitch(bool state);
|
|
||||||
void havePlayersSwitch(bool state);
|
|
||||||
void noPasswordSwitch(bool state);
|
|
||||||
void maxLatencyChanged(int index);
|
|
||||||
void gamemodeChanged(const QString &text);
|
|
||||||
private:
|
|
||||||
QueryHelper *queryHelper;
|
|
||||||
Process::ProcessInvoker *mGameInvoker;
|
|
||||||
ServerModel *browser, *favorites;
|
|
||||||
MySortFilterProxyModel *proxyModel;
|
|
||||||
void loadFavorites();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //NEWLAUNCHER_MAIN_HPP
|
|
|
@ -1,84 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 30.01.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "MySortFilterProxyModel.hpp"
|
|
||||||
#include "ServerModel.hpp"
|
|
||||||
|
|
||||||
#include <qdebug.h>
|
|
||||||
#include <apps/browser/netutils/Utils.hpp>
|
|
||||||
|
|
||||||
bool MySortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
|
||||||
{
|
|
||||||
|
|
||||||
QModelIndex pingIndex = sourceModel()->index(sourceRow, ServerData::PING, sourceParent);
|
|
||||||
QModelIndex plIndex = sourceModel()->index(sourceRow, ServerData::PLAYERS, sourceParent);
|
|
||||||
QModelIndex maxPlIndex = sourceModel()->index(sourceRow, ServerData::MAX_PLAYERS, sourceParent);
|
|
||||||
QModelIndex passwordIndex = sourceModel()->index(sourceRow, ServerData::PASSW, sourceParent);
|
|
||||||
|
|
||||||
bool pingOk;
|
|
||||||
int ping = sourceModel()->data(pingIndex).toInt(&pingOk);
|
|
||||||
int players = sourceModel()->data(plIndex).toInt();
|
|
||||||
int maxPlayers = sourceModel()->data(maxPlIndex).toInt();
|
|
||||||
|
|
||||||
if (maxPing > 0 && (ping == -1 || ping > maxPing || !pingOk))
|
|
||||||
return false;
|
|
||||||
if (filterEmpty && players == 0)
|
|
||||||
return false;
|
|
||||||
if (filterFull && players >= maxPlayers)
|
|
||||||
return false;
|
|
||||||
if(filterPasswEnabled && sourceModel()->data(passwordIndex).toString() == "Yes")
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MySortFilterProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
|
|
||||||
{
|
|
||||||
if(sortColumn() == ServerData::PING)
|
|
||||||
{
|
|
||||||
bool valid;
|
|
||||||
|
|
||||||
int pingright = sourceModel()->data(source_right).toInt(&valid);
|
|
||||||
pingright = valid ? pingright : PING_UNREACHABLE;
|
|
||||||
|
|
||||||
int pingleft = sourceModel()->data(source_left).toInt(&valid);
|
|
||||||
pingleft = valid ? pingleft : PING_UNREACHABLE;
|
|
||||||
return pingleft < pingright;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return QSortFilterProxyModel::lessThan(source_left, source_right);
|
|
||||||
}
|
|
||||||
|
|
||||||
MySortFilterProxyModel::MySortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
|
|
||||||
{
|
|
||||||
filterEmpty = false;
|
|
||||||
filterFull = false;
|
|
||||||
filterPasswEnabled = false;
|
|
||||||
maxPing = 0;
|
|
||||||
setSortCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MySortFilterProxyModel::filterEmptyServers(bool state)
|
|
||||||
{
|
|
||||||
filterEmpty = state;
|
|
||||||
invalidateFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MySortFilterProxyModel::filterFullServer(bool state)
|
|
||||||
{
|
|
||||||
filterFull = state;
|
|
||||||
invalidateFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MySortFilterProxyModel::pingLessThan(int maxPing)
|
|
||||||
{
|
|
||||||
this->maxPing = maxPing;
|
|
||||||
invalidateFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MySortFilterProxyModel::filterPassworded(bool state)
|
|
||||||
{
|
|
||||||
filterPasswEnabled = state;
|
|
||||||
invalidateFilter();
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 30.01.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef OPENMW_MYSORTFILTERPROXYMODEL_HPP
|
|
||||||
#define OPENMW_MYSORTFILTERPROXYMODEL_HPP
|
|
||||||
|
|
||||||
|
|
||||||
#include <QSortFilterProxyModel>
|
|
||||||
|
|
||||||
class MySortFilterProxyModel : public QSortFilterProxyModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
protected:
|
|
||||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const Q_DECL_FINAL;
|
|
||||||
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const Q_DECL_FINAL;
|
|
||||||
public:
|
|
||||||
explicit MySortFilterProxyModel(QObject *parent);
|
|
||||||
void filterFullServer(bool state);
|
|
||||||
void filterEmptyServers(bool state);
|
|
||||||
void filterPassworded(bool state);
|
|
||||||
void pingLessThan(int maxPing);
|
|
||||||
private:
|
|
||||||
bool filterEmpty, filterFull, filterPasswEnabled;
|
|
||||||
int maxPing;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //OPENMW_MYSORTFILTERPROXYMODEL_HPP
|
|
|
@ -1,56 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 03.05.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "PingHelper.hpp"
|
|
||||||
#include "ServerModel.hpp"
|
|
||||||
#include <QDebug>
|
|
||||||
#include "PingUpdater.hpp"
|
|
||||||
|
|
||||||
void PingHelper::Add(int row, const AddrPair &addrPair)
|
|
||||||
{
|
|
||||||
pingUpdater->addServer(row, addrPair);
|
|
||||||
if (!pingThread->isRunning())
|
|
||||||
pingThread->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PingHelper::Reset()
|
|
||||||
{
|
|
||||||
//if (pingThread->isRunning())
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PingHelper::Stop()
|
|
||||||
{
|
|
||||||
emit pingUpdater->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PingHelper::SetModel(QAbstractTableModel *model)
|
|
||||||
{
|
|
||||||
this->model = model;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PingHelper::update(int row, unsigned ping)
|
|
||||||
{
|
|
||||||
model->setData(model->index(row, ServerData::PING), ping);
|
|
||||||
}
|
|
||||||
|
|
||||||
PingHelper &PingHelper::Get()
|
|
||||||
{
|
|
||||||
static PingHelper helper;
|
|
||||||
return helper;
|
|
||||||
}
|
|
||||||
|
|
||||||
PingHelper::PingHelper() : QObject()
|
|
||||||
{
|
|
||||||
pingThread = new QThread;
|
|
||||||
pingUpdater = new PingUpdater;
|
|
||||||
pingUpdater->moveToThread(pingThread);
|
|
||||||
|
|
||||||
connect(pingThread, SIGNAL(started()), pingUpdater, SLOT(process()));
|
|
||||||
connect(pingUpdater, SIGNAL(start()), pingThread, SLOT(start()));
|
|
||||||
connect(pingUpdater, SIGNAL(finished()), pingThread, SLOT(quit()));
|
|
||||||
connect(this, SIGNAL(stop()), pingUpdater, SLOT(stop()));
|
|
||||||
//connect(pingUpdater, SIGNAL(finished()), pingUpdater, SLOT(deleteLater()));
|
|
||||||
connect(pingUpdater, SIGNAL(updateModel(int, unsigned)), this, SLOT(update(int, unsigned)));
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 03.05.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef OPENMW_PINGHELPER_HPP
|
|
||||||
#define OPENMW_PINGHELPER_HPP
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QAbstractTableModel>
|
|
||||||
#include <QThread>
|
|
||||||
#include "Types.hpp"
|
|
||||||
|
|
||||||
class PingUpdater;
|
|
||||||
|
|
||||||
class PingHelper : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
|
|
||||||
void Reset();
|
|
||||||
void Add(int row, const AddrPair &addrPair);
|
|
||||||
void Stop();
|
|
||||||
void SetModel(QAbstractTableModel *model);
|
|
||||||
//void UpdateImmedialy(PingUpdater::AddrPair addrPair);
|
|
||||||
static PingHelper &Get();
|
|
||||||
|
|
||||||
PingHelper(const PingHelper&) = delete;
|
|
||||||
PingHelper& operator=(const PingHelper&) = delete;
|
|
||||||
private:
|
|
||||||
PingHelper();
|
|
||||||
signals:
|
|
||||||
void stop();
|
|
||||||
public slots:
|
|
||||||
void update(int row, unsigned ping);
|
|
||||||
private:
|
|
||||||
QThread *pingThread;
|
|
||||||
PingUpdater *pingUpdater;
|
|
||||||
QAbstractTableModel *model;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //OPENMW_PINGHELPER_HPP
|
|
|
@ -1,50 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 02.05.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "PingUpdater.hpp"
|
|
||||||
#include "netutils/Utils.hpp"
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QModelIndex>
|
|
||||||
#include <QThread>
|
|
||||||
|
|
||||||
void PingUpdater::stop()
|
|
||||||
{
|
|
||||||
servers.clear();
|
|
||||||
run = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PingUpdater::addServer(int row, const AddrPair &addr)
|
|
||||||
{
|
|
||||||
servers.push_back({row, addr});
|
|
||||||
run = true;
|
|
||||||
emit start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PingUpdater::process()
|
|
||||||
{
|
|
||||||
while (run)
|
|
||||||
{
|
|
||||||
if (servers.count() == 0)
|
|
||||||
{
|
|
||||||
QThread::msleep(1000);
|
|
||||||
if (servers.count() == 0)
|
|
||||||
{
|
|
||||||
qDebug() << "PingUpdater stopped due to inactivity";
|
|
||||||
run = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerRow server = servers.back();
|
|
||||||
servers.pop_back();
|
|
||||||
|
|
||||||
unsigned ping = PingRakNetServer(server.second.first.toLatin1(), server.second.second);
|
|
||||||
|
|
||||||
qDebug() << "Pong from" << server.second.first + "|" + QString::number(server.second.second)
|
|
||||||
<< ":" << ping << "ms" << "Sizeof servers: " << servers.size();
|
|
||||||
|
|
||||||
emit updateModel(server.first, ping);
|
|
||||||
}
|
|
||||||
emit finished();
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 02.05.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef OPENMW_PINGUPDATER_HPP
|
|
||||||
#define OPENMW_PINGUPDATER_HPP
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QVector>
|
|
||||||
|
|
||||||
#include "Types.hpp"
|
|
||||||
|
|
||||||
class PingUpdater : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
void addServer(int row, const AddrPair &addrPair);
|
|
||||||
public slots:
|
|
||||||
void stop();
|
|
||||||
void process();
|
|
||||||
signals:
|
|
||||||
void start();
|
|
||||||
void updateModel(int row, unsigned ping);
|
|
||||||
void finished();
|
|
||||||
private:
|
|
||||||
QVector<ServerRow> servers;
|
|
||||||
bool run;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //OPENMW_PINGUPDATER_HPP
|
|
|
@ -1,87 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 27.05.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "netutils/QueryClient.hpp"
|
|
||||||
#include "netutils/Utils.hpp"
|
|
||||||
#include "QueryHelper.hpp"
|
|
||||||
#include "PingHelper.hpp"
|
|
||||||
|
|
||||||
QueryUpdate *queryUpdate;
|
|
||||||
|
|
||||||
|
|
||||||
QueryHelper::QueryHelper(QAbstractItemModel *model)
|
|
||||||
{
|
|
||||||
qRegisterMetaType<QueryData>("QueryData");
|
|
||||||
queryThread = new QThread;
|
|
||||||
queryUpdate = new QueryUpdate;
|
|
||||||
_model = model;
|
|
||||||
connect(queryThread, SIGNAL(started()), queryUpdate, SLOT(process()));
|
|
||||||
connect(queryUpdate, SIGNAL(finished()), queryThread, SLOT(quit()));
|
|
||||||
connect(queryUpdate, &QueryUpdate::finished, [this](){emit finished();});
|
|
||||||
connect(queryUpdate, SIGNAL(updateModel(const QString&, unsigned short, const QueryData&)),
|
|
||||||
this, SLOT(update(const QString&, unsigned short, const QueryData&)));
|
|
||||||
queryUpdate->moveToThread(queryThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QueryHelper::refresh()
|
|
||||||
{
|
|
||||||
if (!queryThread->isRunning())
|
|
||||||
{
|
|
||||||
_model->removeRows(0, _model->rowCount());
|
|
||||||
PingHelper::Get().Stop();
|
|
||||||
queryThread->start();
|
|
||||||
emit started();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QueryHelper::terminate()
|
|
||||||
{
|
|
||||||
queryThread->terminate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QueryHelper::update(const QString &addr, unsigned short port, const QueryData& data)
|
|
||||||
{
|
|
||||||
ServerModel *model = ((ServerModel*)_model);
|
|
||||||
model->insertRow(model->rowCount());
|
|
||||||
int row = model->rowCount() - 1;
|
|
||||||
|
|
||||||
QModelIndex mi = model->index(row, ServerData::ADDR);
|
|
||||||
model->setData(mi, addr + ":" + QString::number(port));
|
|
||||||
|
|
||||||
mi = model->index(row, ServerData::PLAYERS);
|
|
||||||
model->setData(mi, (int)data.players.size());
|
|
||||||
|
|
||||||
mi = model->index(row, ServerData::MAX_PLAYERS);
|
|
||||||
model->setData(mi, data.GetMaxPlayers());
|
|
||||||
|
|
||||||
mi = model->index(row, ServerData::HOSTNAME);
|
|
||||||
model->setData(mi, data.GetName());
|
|
||||||
|
|
||||||
mi = model->index(row, ServerData::MODNAME);
|
|
||||||
model->setData(mi, data.GetGameMode());
|
|
||||||
|
|
||||||
mi = model->index(row, ServerData::VERSION);
|
|
||||||
model->setData(mi, data.GetVersion());
|
|
||||||
|
|
||||||
mi = model->index(row, ServerData::PASSW);
|
|
||||||
model->setData(mi, data.GetPassword() == 1);
|
|
||||||
|
|
||||||
mi = model->index(row, ServerData::PING);
|
|
||||||
model->setData(mi, PING_UNREACHABLE);
|
|
||||||
PingHelper::Get().Add(row, {addr, port});
|
|
||||||
}
|
|
||||||
|
|
||||||
void QueryUpdate::process()
|
|
||||||
{
|
|
||||||
auto data = QueryClient::Get().Query();
|
|
||||||
if (QueryClient::Get().Status() != ID_MASTER_QUERY)
|
|
||||||
{
|
|
||||||
emit finished();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &server : data)
|
|
||||||
emit updateModel(server.first.ToString(false), server.first.GetPort(), server.second);
|
|
||||||
emit finished();
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 27.05.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef OPENMW_QUERYHELPER_HPP
|
|
||||||
#define OPENMW_QUERYHELPER_HPP
|
|
||||||
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <vector>
|
|
||||||
#include <QAbstractItemModel>
|
|
||||||
#include <components/openmw-mp/Master/MasterData.hpp>
|
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(QueryData)
|
|
||||||
|
|
||||||
class QueryHelper : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit QueryHelper(QAbstractItemModel *model);
|
|
||||||
public slots:
|
|
||||||
void refresh();
|
|
||||||
void terminate();
|
|
||||||
private slots:
|
|
||||||
void update(const QString &addr, unsigned short port, const QueryData& data);
|
|
||||||
signals:
|
|
||||||
void finished();
|
|
||||||
void started();
|
|
||||||
private:
|
|
||||||
QThread *queryThread;
|
|
||||||
QAbstractItemModel *_model;
|
|
||||||
};
|
|
||||||
|
|
||||||
class QueryUpdate : public QObject
|
|
||||||
{
|
|
||||||
friend class QueryHelper;
|
|
||||||
Q_OBJECT
|
|
||||||
signals:
|
|
||||||
void finished();
|
|
||||||
void updateModel(const QString &addr, unsigned short port, const QueryData& data);
|
|
||||||
public slots:
|
|
||||||
void process();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif //OPENMW_QUERYHELPER_HPP
|
|
|
@ -1,107 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 07.01.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <apps/browser/netutils/QueryClient.hpp>
|
|
||||||
#include "qdebug.h"
|
|
||||||
|
|
||||||
#include "ServerInfoDialog.hpp"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <utility>
|
|
||||||
#include <QThread>
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
using namespace RakNet;
|
|
||||||
|
|
||||||
ThrWorker::ThrWorker(ServerInfoDialog *dialog, QString addr, unsigned short port): addr(std::move(addr)), port(port), stopped(false)
|
|
||||||
{
|
|
||||||
this->dialog = dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThrWorker::process()
|
|
||||||
{
|
|
||||||
stopped = false;
|
|
||||||
auto newSD = QueryClient::Get().Update(SystemAddress(addr.toUtf8(), port));
|
|
||||||
if (dialog != nullptr)
|
|
||||||
dialog->setData(newSD);
|
|
||||||
stopped = true;
|
|
||||||
emit finished();
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerInfoDialog::ServerInfoDialog(const QString &addr, QWidget *parent): QDialog(parent)
|
|
||||||
{
|
|
||||||
setupUi(this);
|
|
||||||
refreshThread = new QThread;
|
|
||||||
|
|
||||||
QStringList list = addr.split(':');
|
|
||||||
worker = new ThrWorker(this, list[0].toLatin1(), list[1].toUShort());
|
|
||||||
worker->moveToThread(refreshThread);
|
|
||||||
connect(refreshThread, SIGNAL(started()), worker, SLOT(process()));
|
|
||||||
connect(worker, SIGNAL(finished()), refreshThread, SLOT(quit()));
|
|
||||||
connect(refreshThread, SIGNAL(finished()), this, SLOT(refresh()));
|
|
||||||
|
|
||||||
connect(btnRefresh, &QPushButton::clicked, [this]{
|
|
||||||
if (!refreshThread->isRunning())
|
|
||||||
refreshThread->start();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerInfoDialog::~ServerInfoDialog()
|
|
||||||
{
|
|
||||||
worker->dialog = nullptr;
|
|
||||||
if (!refreshThread->isRunning())
|
|
||||||
refreshThread->terminate();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ServerInfoDialog::isUpdated()
|
|
||||||
{
|
|
||||||
return sd.first != UNASSIGNED_SYSTEM_ADDRESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerInfoDialog::setData(std::pair<RakNet::SystemAddress, QueryData> &newSD)
|
|
||||||
{
|
|
||||||
sd = newSD;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerInfoDialog::refresh()
|
|
||||||
{
|
|
||||||
if (sd.first != UNASSIGNED_SYSTEM_ADDRESS)
|
|
||||||
{
|
|
||||||
leAddr->setText(sd.first.ToString(true, ':'));
|
|
||||||
lblName->setText(sd.second.GetName());
|
|
||||||
int ping = PingRakNetServer(sd.first.ToString(false), sd.first.GetPort());
|
|
||||||
lblPing->setNum(ping);
|
|
||||||
btnConnect->setDisabled(ping == PING_UNREACHABLE);
|
|
||||||
|
|
||||||
listPlayers->clear();
|
|
||||||
for (const auto &player : sd.second.players)
|
|
||||||
listPlayers->addItem(QString::fromStdString(player));
|
|
||||||
|
|
||||||
listPlugins->clear();
|
|
||||||
for (const auto &plugin : sd.second.plugins)
|
|
||||||
listPlugins->addItem(QString::fromStdString(plugin.name));
|
|
||||||
|
|
||||||
listRules->clear();
|
|
||||||
const static vector<std::string> defaultRules {"gamemode", "maxPlayers", "name", "passw", "players", "version"};
|
|
||||||
for (auto &rule : sd.second.rules)
|
|
||||||
{
|
|
||||||
if (::find(defaultRules.begin(), defaultRules.end(), rule.first) != defaultRules.end())
|
|
||||||
continue;
|
|
||||||
QString ruleStr = QString::fromStdString(rule.first) + " : ";
|
|
||||||
if (rule.second.type == 's')
|
|
||||||
ruleStr += QString::fromStdString(rule.second.str);
|
|
||||||
else
|
|
||||||
ruleStr += QString::number(rule.second.val);
|
|
||||||
listRules->addItem(ruleStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
lblPlayers->setText(QString::number(sd.second.players.size()) + " / " + QString::number(sd.second.GetMaxPlayers()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int ServerInfoDialog::exec()
|
|
||||||
{
|
|
||||||
if (!refreshThread->isRunning())
|
|
||||||
refreshThread->start();
|
|
||||||
return QDialog::exec();
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 07.01.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef NEWLAUNCHER_SERVERINFODIALOG_HPP
|
|
||||||
#define NEWLAUNCHER_SERVERINFODIALOG_HPP
|
|
||||||
|
|
||||||
#include "ui_ServerInfo.h"
|
|
||||||
#include <apps/browser/netutils/Utils.hpp>
|
|
||||||
#include <RakNetTypes.h>
|
|
||||||
#include <components/openmw-mp/Master/MasterData.hpp>
|
|
||||||
|
|
||||||
class ThrWorker;
|
|
||||||
|
|
||||||
class ServerInfoDialog : public QDialog, public Ui::Dialog
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit ServerInfoDialog(const QString &addr, QWidget *parent = nullptr);
|
|
||||||
~ServerInfoDialog() override;
|
|
||||||
bool isUpdated();
|
|
||||||
void setData(std::pair<RakNet::SystemAddress, QueryData> &newSD);
|
|
||||||
public slots:
|
|
||||||
void refresh();
|
|
||||||
int exec() Q_DECL_OVERRIDE;
|
|
||||||
private:
|
|
||||||
QThread *refreshThread;
|
|
||||||
ThrWorker* worker;
|
|
||||||
std::pair<RakNet::SystemAddress, QueryData> sd;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ThrWorker: public QObject
|
|
||||||
{
|
|
||||||
friend class ServerInfoDialog;
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
ThrWorker(ServerInfoDialog *dialog, QString addr, unsigned short port);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void process();
|
|
||||||
signals:
|
|
||||||
void finished();
|
|
||||||
private:
|
|
||||||
QString addr;
|
|
||||||
unsigned short port;
|
|
||||||
bool stopped;
|
|
||||||
ServerInfoDialog *dialog;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //NEWLAUNCHER_SERVERINFODIALOG_HPP
|
|
|
@ -1,191 +0,0 @@
|
||||||
#include <qmessagebox.h>
|
|
||||||
#include "ServerModel.hpp"
|
|
||||||
#include <qdebug.h>
|
|
||||||
#include <apps/browser/netutils/Utils.hpp>
|
|
||||||
|
|
||||||
ServerModel::ServerModel(QObject *parent) : QAbstractTableModel(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
/*QHash<int, QByteArray> ServerModel::roleNames() const
|
|
||||||
{
|
|
||||||
return roles;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
QVariant ServerModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (!index.isValid())
|
|
||||||
return QVariant();
|
|
||||||
if (index.row() < 0 || index.row() > myData.size())
|
|
||||||
return QVariant();
|
|
||||||
|
|
||||||
const ServerData &sd = myData.at(index.row());
|
|
||||||
|
|
||||||
if (role == Qt::DisplayRole)
|
|
||||||
{
|
|
||||||
QVariant var;
|
|
||||||
switch (index.column())
|
|
||||||
{
|
|
||||||
case ServerData::ADDR:
|
|
||||||
var = sd.addr;
|
|
||||||
break;
|
|
||||||
case ServerData::PASSW:
|
|
||||||
var = (int)(sd.rules.at("passw").val) == 1 ? "Yes" : "No";
|
|
||||||
break;
|
|
||||||
case ServerData::VERSION:
|
|
||||||
var = QString(sd.rules.at("version").str.c_str());
|
|
||||||
break;
|
|
||||||
case ServerData::PLAYERS:
|
|
||||||
var = (int) sd.rules.at("players").val;
|
|
||||||
break;
|
|
||||||
case ServerData::MAX_PLAYERS:
|
|
||||||
var = (int) sd.rules.at("maxPlayers").val;
|
|
||||||
break;
|
|
||||||
case ServerData::HOSTNAME:
|
|
||||||
var = QString(sd.rules.at("name").str.c_str());
|
|
||||||
break;
|
|
||||||
case ServerData::PING:
|
|
||||||
var = sd.ping == PING_UNREACHABLE ? QVariant("Unreachable") : sd.ping;
|
|
||||||
break;
|
|
||||||
case ServerData::MODNAME:
|
|
||||||
if (sd.rules.at("gamemode").str == "")
|
|
||||||
var = "default";
|
|
||||||
else
|
|
||||||
var = QString(sd.rules.at("gamemode").str.c_str());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return var;
|
|
||||||
}
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant ServerModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
||||||
{
|
|
||||||
QVariant var;
|
|
||||||
if (orientation == Qt::Horizontal)
|
|
||||||
{
|
|
||||||
if (role == Qt::SizeHintRole)
|
|
||||||
{
|
|
||||||
/*if (section == ServerData::HOSTNAME)
|
|
||||||
var = QSize(200, 25);*/
|
|
||||||
}
|
|
||||||
else if (role == Qt::DisplayRole)
|
|
||||||
{
|
|
||||||
|
|
||||||
switch (section)
|
|
||||||
{
|
|
||||||
case ServerData::ADDR:
|
|
||||||
var = "Address";
|
|
||||||
break;
|
|
||||||
case ServerData::PASSW:
|
|
||||||
var = "Password";
|
|
||||||
break;
|
|
||||||
case ServerData::VERSION:
|
|
||||||
var = "Version";
|
|
||||||
break;
|
|
||||||
case ServerData::HOSTNAME:
|
|
||||||
var = "Host name";
|
|
||||||
break;
|
|
||||||
case ServerData::PLAYERS:
|
|
||||||
var = "Players";
|
|
||||||
break;
|
|
||||||
case ServerData::MAX_PLAYERS:
|
|
||||||
var = "Max players";
|
|
||||||
break;
|
|
||||||
case ServerData::PING:
|
|
||||||
var = "Ping";
|
|
||||||
break;
|
|
||||||
case ServerData::MODNAME:
|
|
||||||
var = "Game mode";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return var;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ServerModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
return myData.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
int ServerModel::columnCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
return ServerData::LAST;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ServerModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
||||||
{
|
|
||||||
if (index.isValid() && role == Qt::EditRole)
|
|
||||||
{
|
|
||||||
int row = index.row();
|
|
||||||
int col = index.column();
|
|
||||||
|
|
||||||
ServerData &sd = myData[row];
|
|
||||||
bool ok = true;
|
|
||||||
switch(col)
|
|
||||||
{
|
|
||||||
case ServerData::ADDR:
|
|
||||||
sd.addr = value.toString();
|
|
||||||
ok = !sd.addr.isEmpty();
|
|
||||||
break;
|
|
||||||
case ServerData::PASSW:
|
|
||||||
sd.SetPassword(value.toBool());
|
|
||||||
break;
|
|
||||||
case ServerData::VERSION:
|
|
||||||
sd.SetVersion(value.toString().toUtf8());
|
|
||||||
ok = !sd.addr.isEmpty();
|
|
||||||
break;
|
|
||||||
case ServerData::PLAYERS:
|
|
||||||
sd.SetPlayers(value.toInt(&ok));
|
|
||||||
break;
|
|
||||||
case ServerData::MAX_PLAYERS:
|
|
||||||
sd.SetMaxPlayers(value.toInt(&ok));
|
|
||||||
break;
|
|
||||||
case ServerData::HOSTNAME:
|
|
||||||
sd.SetName(value.toString().toUtf8());
|
|
||||||
ok = !sd.addr.isEmpty();
|
|
||||||
break;
|
|
||||||
case ServerData::PING:
|
|
||||||
sd.ping = value.toInt(&ok);
|
|
||||||
break;
|
|
||||||
case ServerData::MODNAME:
|
|
||||||
sd.SetGameMode(value.toString().toUtf8());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ok)
|
|
||||||
emit(dataChanged(index, index));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ServerModel::insertRows(int position, int count, const QModelIndex &index)
|
|
||||||
{
|
|
||||||
Q_UNUSED(index);
|
|
||||||
beginInsertRows(QModelIndex(), position, position + count - 1);
|
|
||||||
|
|
||||||
myData.insert(position, count, {});
|
|
||||||
|
|
||||||
endInsertRows();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ServerModel::removeRows(int position, int count, const QModelIndex &parent)
|
|
||||||
{
|
|
||||||
if (count == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
beginRemoveRows(parent, position, position + count - 1);
|
|
||||||
myData.erase(myData.begin()+position, myData.begin() + position + count);
|
|
||||||
endRemoveRows();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QModelIndex ServerModel::index(int row, int column, const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
|
|
||||||
QModelIndex index = QAbstractTableModel::index(row, column, parent);
|
|
||||||
return index;
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
#ifndef SERVERMODEL_FONTMODEL_HPP
|
|
||||||
#define SERVERMODEL_FONTMODEL_HPP
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <vector>
|
|
||||||
#include <QString>
|
|
||||||
#include <QAbstractTableModel>
|
|
||||||
#include <components/openmw-mp/Master/MasterData.hpp>
|
|
||||||
|
|
||||||
struct ServerData : public QueryData
|
|
||||||
{
|
|
||||||
QString addr;
|
|
||||||
int ping;
|
|
||||||
enum IDS
|
|
||||||
{
|
|
||||||
ADDR,
|
|
||||||
HOSTNAME,
|
|
||||||
PLAYERS,
|
|
||||||
MAX_PLAYERS,
|
|
||||||
PASSW,
|
|
||||||
MODNAME,
|
|
||||||
PING,
|
|
||||||
VERSION,
|
|
||||||
LAST
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
class ServerModel: public QAbstractTableModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit ServerModel(QObject *parent = nullptr);
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_FINAL;
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_FINAL;
|
|
||||||
int columnCount(const QModelIndex &parent) const Q_DECL_FINAL;
|
|
||||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_FINAL;
|
|
||||||
|
|
||||||
bool insertRows(int row, int count, const QModelIndex &index = QModelIndex()) Q_DECL_FINAL;
|
|
||||||
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) Q_DECL_FINAL;
|
|
||||||
|
|
||||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_FINAL;
|
|
||||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_FINAL;
|
|
||||||
|
|
||||||
|
|
||||||
public:
|
|
||||||
//QHash<int, QByteArray> roles;
|
|
||||||
QVector<ServerData> myData;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //SERVERMODEL_FONTMODEL_HPP
|
|
|
@ -1,15 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 07.05.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef OPENMW_TYPES_HPP
|
|
||||||
#define OPENMW_TYPES_HPP
|
|
||||||
|
|
||||||
#include <QPair>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
typedef QPair <QString, unsigned short> AddrPair;
|
|
||||||
typedef QPair <int, AddrPair> ServerRow;
|
|
||||||
|
|
||||||
|
|
||||||
#endif //OPENMW_TYPES_HPP
|
|
|
@ -1,53 +0,0 @@
|
||||||
#include <QApplication>
|
|
||||||
#include <components/settings/settings.hpp>
|
|
||||||
#include <components/files/configurationmanager.hpp>
|
|
||||||
#include <apps/browser/netutils/QueryClient.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[])
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
MainWindow d;
|
|
||||||
|
|
||||||
d.show();
|
|
||||||
return app.exec();
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 07.01.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <RakPeer.h>
|
|
||||||
#include <HTTPConnection2.h>
|
|
||||||
#include <TCPInterface.h>
|
|
||||||
#include <RakSleep.h>
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include "HTTPNetwork.hpp"
|
|
||||||
|
|
||||||
using namespace RakNet;
|
|
||||||
|
|
||||||
HTTPNetwork::HTTPNetwork(std::string addr, unsigned short port) : address(addr), port(port)
|
|
||||||
{
|
|
||||||
httpConnection = HTTPConnection2::GetInstance();
|
|
||||||
tcpInterface = new TCPInterface;
|
|
||||||
tcpInterface->Start(0, 64);
|
|
||||||
tcpInterface->AttachPlugin(httpConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTPNetwork::~HTTPNetwork()
|
|
||||||
{
|
|
||||||
delete tcpInterface;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string HTTPNetwork::answer()
|
|
||||||
{
|
|
||||||
RakNet::SystemAddress sa;
|
|
||||||
RakNet::Packet *packet;
|
|
||||||
RakNet::SystemAddress hostReceived;
|
|
||||||
RakNet::RakString response;
|
|
||||||
RakNet::RakString transmitted, hostTransmitted;
|
|
||||||
int contentOffset = 0;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// This is kind of crappy, but for TCP plugins, always do HasCompletedConnectionAttempt,
|
|
||||||
// then Receive(), then HasFailedConnectionAttempt(),HasLostConnection()
|
|
||||||
sa = tcpInterface->HasCompletedConnectionAttempt();
|
|
||||||
if (sa != RakNet::UNASSIGNED_SYSTEM_ADDRESS)
|
|
||||||
printf("Connected to master server: %s\n", sa.ToString());
|
|
||||||
|
|
||||||
sa = tcpInterface->HasFailedConnectionAttempt();
|
|
||||||
if (sa != RakNet::UNASSIGNED_SYSTEM_ADDRESS)
|
|
||||||
{
|
|
||||||
printf("Failed to connect to master server: %s\n", sa.ToString());
|
|
||||||
return "FAIL_CONNECT";
|
|
||||||
}
|
|
||||||
sa = tcpInterface->HasLostConnection();
|
|
||||||
if (sa != RakNet::UNASSIGNED_SYSTEM_ADDRESS)
|
|
||||||
{
|
|
||||||
printf("Lost connection to master server: %s\n", sa.ToString());
|
|
||||||
return "LOST_CONNECTION";
|
|
||||||
}
|
|
||||||
|
|
||||||
for (packet = tcpInterface->Receive(); packet; tcpInterface->DeallocatePacket(
|
|
||||||
packet), packet = tcpInterface->Receive());
|
|
||||||
|
|
||||||
if (httpConnection->GetResponse(transmitted, hostTransmitted, response, hostReceived, contentOffset))
|
|
||||||
{
|
|
||||||
if (contentOffset < 0)
|
|
||||||
return "NO_CONTENT"; // no content
|
|
||||||
tcpInterface->CloseConnection(sa);
|
|
||||||
|
|
||||||
return (response.C_String() + contentOffset);
|
|
||||||
}
|
|
||||||
RakSleep(30);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string HTTPNetwork::getData(const char *uri)
|
|
||||||
{
|
|
||||||
RakNet::RakString createRequest = RakNet::RakString::FormatForGET(uri);
|
|
||||||
|
|
||||||
if (!httpConnection->TransmitRequest(createRequest, address.c_str(), port))
|
|
||||||
return "UNKNOWN_ADDRESS";
|
|
||||||
return answer();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string HTTPNetwork::getDataPOST(const char *uri, const char* body, const char* contentType)
|
|
||||||
{
|
|
||||||
RakNet::RakString createRequest = RakNet::RakString::FormatForPOST(uri, contentType, body);
|
|
||||||
|
|
||||||
if (!httpConnection->TransmitRequest(createRequest, address.c_str(), port))
|
|
||||||
return "UNKNOWN_ADDRESS";
|
|
||||||
return answer();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string HTTPNetwork::getDataPUT(const char *uri, const char* body, const char* contentType)
|
|
||||||
{
|
|
||||||
RakNet::RakString createRequest = RakNet::RakString::FormatForPUT(uri, contentType, body);
|
|
||||||
|
|
||||||
if (!httpConnection->TransmitRequest(createRequest, address.c_str(), port))
|
|
||||||
return "UNKNOWN_ADDRESS";
|
|
||||||
return answer();
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 07.01.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef NEWLAUNCHER_HTTPNETWORK_HPP
|
|
||||||
#define NEWLAUNCHER_HTTPNETWORK_HPP
|
|
||||||
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace RakNet
|
|
||||||
{
|
|
||||||
class TCPInterface;
|
|
||||||
class HTTPConnection2;
|
|
||||||
}
|
|
||||||
|
|
||||||
class HTTPNetwork
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
HTTPNetwork(std::string addr, unsigned short port);
|
|
||||||
~HTTPNetwork();
|
|
||||||
std::string getData(const char *uri);
|
|
||||||
std::string getDataPOST(const char *uri, const char* body, const char* contentType = "application/json");
|
|
||||||
std::string getDataPUT(const char *uri, const char* body, const char* contentType = "application/json");
|
|
||||||
|
|
||||||
protected:
|
|
||||||
RakNet::TCPInterface *tcpInterface;
|
|
||||||
RakNet::HTTPConnection2 *httpConnection;
|
|
||||||
std::string address;
|
|
||||||
unsigned short port;
|
|
||||||
std::string answer();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //NEWLAUNCHER_HTTPNETWORK_HPP
|
|
|
@ -1,212 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 24.04.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "QueryClient.hpp"
|
|
||||||
#include <RakSleep.h>
|
|
||||||
#include <components/openmw-mp/NetworkMessages.hpp>
|
|
||||||
#include <iostream>
|
|
||||||
#include <components/openmw-mp/Version.hpp>
|
|
||||||
#include <qdebug.h>
|
|
||||||
|
|
||||||
using namespace RakNet;
|
|
||||||
using namespace std;
|
|
||||||
using namespace mwmp;
|
|
||||||
|
|
||||||
QueryClient::QueryClient()
|
|
||||||
{
|
|
||||||
peer = RakPeerInterface::GetInstance();
|
|
||||||
pmq = new PacketMasterQuery(peer);
|
|
||||||
pmu = new PacketMasterUpdate(peer);
|
|
||||||
RakNet::SocketDescriptor sd;
|
|
||||||
peer->Startup(8, &sd, 1);
|
|
||||||
status = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryClient::~QueryClient()
|
|
||||||
{
|
|
||||||
delete pmq;
|
|
||||||
delete pmu;
|
|
||||||
RakPeerInterface::DestroyInstance(peer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QueryClient::SetServer(const string &addr, unsigned short port)
|
|
||||||
{
|
|
||||||
masterAddr = SystemAddress(addr.c_str(), port);
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryClient &QueryClient::Get()
|
|
||||||
{
|
|
||||||
static QueryClient myInstance;
|
|
||||||
return myInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
map<SystemAddress, QueryData> QueryClient::Query()
|
|
||||||
{
|
|
||||||
map<SystemAddress, QueryData> query;
|
|
||||||
BitStream bs;
|
|
||||||
bs.Write((unsigned char) (ID_MASTER_QUERY));
|
|
||||||
qDebug() << "Locking mutex in QueryClient::Query()";
|
|
||||||
mxServers.lock();
|
|
||||||
status = -1;
|
|
||||||
int attempts = 3;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (Connect() == IS_NOT_CONNECTED)
|
|
||||||
{
|
|
||||||
qDebug() << "Unlocking mutex in QueryClient::Query()";
|
|
||||||
mxServers.unlock();
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
int code = peer->Send(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, CHANNEL_MASTER, masterAddr, false);
|
|
||||||
|
|
||||||
if (code == 0)
|
|
||||||
{
|
|
||||||
qDebug() << "Unlocking mutex in QueryClient::Query()";
|
|
||||||
mxServers.unlock();
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
pmq->SetServers(&query);
|
|
||||||
status = GetAnswer(ID_MASTER_QUERY);
|
|
||||||
RakSleep(100);
|
|
||||||
}
|
|
||||||
while(status != ID_MASTER_QUERY && attempts-- > 0);
|
|
||||||
if(status != ID_MASTER_QUERY)
|
|
||||||
qDebug() << "Getting query was failed";
|
|
||||||
qDebug() << "Unlocking mutex in QueryClient::Query()";
|
|
||||||
peer->CloseConnection(masterAddr, true);
|
|
||||||
mxServers.unlock();
|
|
||||||
qDebug() <<"Answer" << (status == ID_MASTER_QUERY ? "ok." : "wrong.");
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
pair<SystemAddress, QueryData> QueryClient::Update(const RakNet::SystemAddress &addr)
|
|
||||||
{
|
|
||||||
qDebug() << "Locking mutex in QueryClient::Update(RakNet::SystemAddress addr)";
|
|
||||||
pair<SystemAddress, QueryData> server;
|
|
||||||
BitStream bs;
|
|
||||||
bs.Write((unsigned char) (ID_MASTER_UPDATE));
|
|
||||||
bs.Write(addr);
|
|
||||||
|
|
||||||
mxServers.lock();
|
|
||||||
status = -1;
|
|
||||||
int attempts = 3;
|
|
||||||
pmu->SetServer(&server);
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (Connect() == IS_NOT_CONNECTED)
|
|
||||||
{
|
|
||||||
qDebug() << IS_NOT_CONNECTED;
|
|
||||||
qDebug() << "Unlocking mutex in QueryClient::Update(RakNet::SystemAddress addr)";
|
|
||||||
mxServers.unlock();
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
peer->Send(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, CHANNEL_MASTER, masterAddr, false);
|
|
||||||
status = GetAnswer(ID_MASTER_UPDATE);
|
|
||||||
RakSleep(100);
|
|
||||||
}
|
|
||||||
while(status != ID_MASTER_UPDATE && attempts-- > 0);
|
|
||||||
if(status != ID_MASTER_UPDATE)
|
|
||||||
qDebug() << "Getting update was failed";
|
|
||||||
peer->CloseConnection(masterAddr, true);
|
|
||||||
qDebug() << "Unlocking mutex in QueryClient::Update(RakNet::SystemAddress addr)";
|
|
||||||
mxServers.unlock();
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
MASTER_PACKETS QueryClient::GetAnswer(MASTER_PACKETS waitingPacket)
|
|
||||||
{
|
|
||||||
RakNet::Packet *packet;
|
|
||||||
bool update = true;
|
|
||||||
unsigned char pid = 0;
|
|
||||||
int id = -1;
|
|
||||||
while (update)
|
|
||||||
{
|
|
||||||
for (packet = peer->Receive(); packet; peer->DeallocatePacket(packet), packet = peer->Receive())
|
|
||||||
{
|
|
||||||
BitStream data(packet->data, packet->length, false);
|
|
||||||
pmq->SetReadStream(&data);
|
|
||||||
pmu->SetReadStream(&data);
|
|
||||||
data.Read(pid);
|
|
||||||
switch(pid)
|
|
||||||
{
|
|
||||||
case ID_CONNECTION_LOST:
|
|
||||||
qDebug() << "ID_CONNECTION_LOST";
|
|
||||||
case ID_DISCONNECTION_NOTIFICATION:
|
|
||||||
qDebug() << "Disconnected";
|
|
||||||
update = false;
|
|
||||||
break;
|
|
||||||
case ID_MASTER_QUERY:
|
|
||||||
qDebug() << "ID_MASTER_QUERY";
|
|
||||||
if (waitingPacket == ID_MASTER_QUERY)
|
|
||||||
pmq->Read();
|
|
||||||
else
|
|
||||||
qDebug() << "Got wrong packet";
|
|
||||||
update = false;
|
|
||||||
id = pid;
|
|
||||||
break;
|
|
||||||
case ID_MASTER_UPDATE:
|
|
||||||
qDebug() << "ID_MASTER_UPDATE";
|
|
||||||
if (waitingPacket == ID_MASTER_UPDATE)
|
|
||||||
pmu->Read();
|
|
||||||
else
|
|
||||||
qDebug() << "Got wrong packet";
|
|
||||||
update = false;
|
|
||||||
id = pid;
|
|
||||||
break;
|
|
||||||
case ID_MASTER_ANNOUNCE:
|
|
||||||
qDebug() << "ID_MASTER_ANNOUNCE";
|
|
||||||
update = false;
|
|
||||||
id = pid;
|
|
||||||
break;
|
|
||||||
case ID_CONNECTION_REQUEST_ACCEPTED:
|
|
||||||
qDebug() << "ID_CONNECTION_REQUEST_ACCEPTED";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RakSleep(500);
|
|
||||||
}
|
|
||||||
return (MASTER_PACKETS)(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionState QueryClient::Connect()
|
|
||||||
{
|
|
||||||
|
|
||||||
ConnectionAttemptResult car = peer->Connect(masterAddr.ToString(false), masterAddr.GetPort(), TES3MP_MASTERSERVER_PASSW,
|
|
||||||
strlen(TES3MP_MASTERSERVER_PASSW), nullptr, 0, 5, 500);
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
ConnectionState state = peer->GetConnectionState(masterAddr);
|
|
||||||
switch (state)
|
|
||||||
{
|
|
||||||
case IS_CONNECTED:
|
|
||||||
qDebug() << "Connected";
|
|
||||||
return IS_CONNECTED;
|
|
||||||
case IS_NOT_CONNECTED:
|
|
||||||
case IS_DISCONNECTED:
|
|
||||||
case IS_SILENTLY_DISCONNECTING:
|
|
||||||
case IS_DISCONNECTING:
|
|
||||||
{
|
|
||||||
qDebug() << "Cannot connect to the master server. Code:"<< state;
|
|
||||||
return IS_NOT_CONNECTED;
|
|
||||||
}
|
|
||||||
case IS_PENDING:
|
|
||||||
case IS_CONNECTING:
|
|
||||||
qDebug() << "Pending";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
RakSleep(500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int QueryClient::Status()
|
|
||||||
{
|
|
||||||
return status;
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 24.04.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef OPENMW_QUERYCLIENT_HPP
|
|
||||||
#define OPENMW_QUERYCLIENT_HPP
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <RakPeerInterface.h>
|
|
||||||
#include <components/openmw-mp/Master/PacketMasterQuery.hpp>
|
|
||||||
#include <components/openmw-mp/Master/PacketMasterUpdate.hpp>
|
|
||||||
#include <apps/browser/ServerModel.hpp>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
class QueryClient
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
QueryClient(QueryClient const &) = delete;
|
|
||||||
QueryClient(QueryClient &&) = delete;
|
|
||||||
QueryClient &operator=(QueryClient const &) = delete;
|
|
||||||
QueryClient &operator=(QueryClient &&) = delete;
|
|
||||||
|
|
||||||
static QueryClient &Get();
|
|
||||||
void SetServer(const std::string &addr, unsigned short port);
|
|
||||||
std::map<RakNet::SystemAddress, QueryData> Query();
|
|
||||||
std::pair<RakNet::SystemAddress, QueryData> Update(const RakNet::SystemAddress &addr);
|
|
||||||
int Status();
|
|
||||||
private:
|
|
||||||
RakNet::ConnectionState Connect();
|
|
||||||
MASTER_PACKETS GetAnswer(MASTER_PACKETS packet);
|
|
||||||
protected:
|
|
||||||
QueryClient();
|
|
||||||
~QueryClient();
|
|
||||||
private:
|
|
||||||
int status;
|
|
||||||
RakNet::RakPeerInterface *peer;
|
|
||||||
RakNet::SystemAddress masterAddr;
|
|
||||||
mwmp::PacketMasterQuery *pmq;
|
|
||||||
mwmp::PacketMasterUpdate *pmu;
|
|
||||||
std::pair<RakNet::SystemAddress, ServerData> server;
|
|
||||||
std::mutex mxServers;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //OPENMW_QUERYCLIENT_HPP
|
|
|
@ -1,167 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 07.01.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <RakPeer.h>
|
|
||||||
#include <MessageIdentifiers.h>
|
|
||||||
#include <RakSleep.h>
|
|
||||||
#include <GetTime.h>
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <components/openmw-mp/Version.hpp>
|
|
||||||
|
|
||||||
#include "Utils.hpp"
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
unsigned int PingRakNetServer(const char *addr, unsigned short port)
|
|
||||||
{
|
|
||||||
RakNet::Packet *packet;
|
|
||||||
bool done = false;
|
|
||||||
RakNet::TimeMS time = PING_UNREACHABLE;
|
|
||||||
|
|
||||||
RakNet::SocketDescriptor socketDescriptor{0, ""};
|
|
||||||
RakNet::RakPeerInterface *peer = RakNet::RakPeerInterface::GetInstance();
|
|
||||||
peer->Startup(1, &socketDescriptor, 1);
|
|
||||||
if (!peer->Ping(addr, port, false))
|
|
||||||
return time;
|
|
||||||
RakNet::TimeMS start = RakNet::GetTimeMS();
|
|
||||||
while (!done)
|
|
||||||
{
|
|
||||||
RakNet::TimeMS now = RakNet::GetTimeMS();
|
|
||||||
if (now - start >= PING_UNREACHABLE)
|
|
||||||
break;
|
|
||||||
|
|
||||||
packet = peer->Receive();
|
|
||||||
if (!packet)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
switch (packet->data[0])
|
|
||||||
{
|
|
||||||
case ID_DISCONNECTION_NOTIFICATION:
|
|
||||||
case ID_CONNECTION_LOST:
|
|
||||||
done = true;
|
|
||||||
break;
|
|
||||||
case ID_CONNECTED_PING:
|
|
||||||
case ID_UNCONNECTED_PONG:
|
|
||||||
{
|
|
||||||
RakNet::BitStream bsIn(&packet->data[1], packet->length, false);
|
|
||||||
bsIn.Read(time);
|
|
||||||
time = now - time;
|
|
||||||
done = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
peer->DeallocatePacket(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
peer->Shutdown(0);
|
|
||||||
RakNet::RakPeerInterface::DestroyInstance(peer);
|
|
||||||
return time > PING_UNREACHABLE ? PING_UNREACHABLE : time;
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerExtendedData getExtendedData(const char *addr, unsigned short port)
|
|
||||||
{
|
|
||||||
ServerExtendedData data;
|
|
||||||
RakNet::SocketDescriptor socketDescriptor = {0, ""};
|
|
||||||
RakNet::RakPeerInterface *peer = RakNet::RakPeerInterface::GetInstance();
|
|
||||||
peer->Startup(1, &socketDescriptor, 1);
|
|
||||||
|
|
||||||
stringstream sstr;
|
|
||||||
sstr << TES3MP_VERSION;
|
|
||||||
sstr << TES3MP_PROTO_VERSION;
|
|
||||||
|
|
||||||
std::string msg;
|
|
||||||
|
|
||||||
if (peer->Connect(addr, port, sstr.str().c_str(), (int)(sstr.str().size()), nullptr, 0, 3, 500, 0) != RakNet::CONNECTION_ATTEMPT_STARTED)
|
|
||||||
msg = "Connection attempt failed.\n";
|
|
||||||
|
|
||||||
|
|
||||||
int queue = 0;
|
|
||||||
while (queue == 0)
|
|
||||||
{
|
|
||||||
for (RakNet::Packet *packet = peer->Receive(); packet; peer->DeallocatePacket(
|
|
||||||
packet), packet = peer->Receive())
|
|
||||||
{
|
|
||||||
switch (packet->data[0])
|
|
||||||
{
|
|
||||||
case ID_CONNECTION_ATTEMPT_FAILED:
|
|
||||||
{
|
|
||||||
msg = "Connection failed.\n"
|
|
||||||
"Either the IP address is wrong or a firewall on either system is blocking\n"
|
|
||||||
"UDP packets on the port you have chosen.";
|
|
||||||
queue = -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ID_INVALID_PASSWORD:
|
|
||||||
{
|
|
||||||
msg = "Connection failed.\n"
|
|
||||||
"The client or server is outdated.\n";
|
|
||||||
queue = -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ID_CONNECTION_REQUEST_ACCEPTED:
|
|
||||||
{
|
|
||||||
msg = "Connection accepted.\n";
|
|
||||||
queue = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ID_DISCONNECTION_NOTIFICATION:
|
|
||||||
throw runtime_error("ID_DISCONNECTION_NOTIFICATION.\n");
|
|
||||||
case ID_CONNECTION_BANNED:
|
|
||||||
throw runtime_error("ID_CONNECTION_BANNED.\n");
|
|
||||||
case ID_CONNECTION_LOST:
|
|
||||||
throw runtime_error("ID_CONNECTION_LOST.\n");
|
|
||||||
default:
|
|
||||||
printf("Connection message with identifier %i has arrived in initialization.\n", packet->data[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
puts(msg.c_str());
|
|
||||||
|
|
||||||
if (queue == -1) // connection is failed
|
|
||||||
return data;
|
|
||||||
|
|
||||||
{
|
|
||||||
RakNet::BitStream bs;
|
|
||||||
bs.Write((unsigned char) (ID_USER_PACKET_ENUM + 1));
|
|
||||||
peer->Send(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
RakNet::Packet *packet;
|
|
||||||
bool done = false;
|
|
||||||
while (!done)
|
|
||||||
{
|
|
||||||
for (packet = peer->Receive(); packet; peer->DeallocatePacket(packet), packet = peer->Receive())
|
|
||||||
{
|
|
||||||
if (packet->data[0] == (ID_USER_PACKET_ENUM + 1))
|
|
||||||
{
|
|
||||||
RakNet::BitStream bs(packet->data, packet->length, false);
|
|
||||||
bs.IgnoreBytes(1);
|
|
||||||
size_t length = 0;
|
|
||||||
bs.Read(length);
|
|
||||||
for (size_t i = 0; i < length; i++)
|
|
||||||
{
|
|
||||||
RakNet::RakString str;
|
|
||||||
bs.Read(str);
|
|
||||||
data.players.emplace_back(str.C_String());
|
|
||||||
}
|
|
||||||
bs.Read(length);
|
|
||||||
for (size_t i = 0; i < length; i++)
|
|
||||||
{
|
|
||||||
RakNet::RakString str;
|
|
||||||
bs.Read(str);
|
|
||||||
data.plugins.emplace_back(str.C_String());
|
|
||||||
}
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
peer->Shutdown(0);
|
|
||||||
RakSleep(10);
|
|
||||||
RakNet::RakPeerInterface::DestroyInstance(peer);
|
|
||||||
return data;
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 07.01.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef NEWLAUNCHER_PING_HPP
|
|
||||||
#define NEWLAUNCHER_PING_HPP
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
|
|
||||||
#define PING_UNREACHABLE 999
|
|
||||||
|
|
||||||
unsigned int PingRakNetServer(const char *addr, unsigned short port);
|
|
||||||
|
|
||||||
struct ServerExtendedData
|
|
||||||
{
|
|
||||||
std::vector<std::string> players;
|
|
||||||
std::vector<std::string> plugins;
|
|
||||||
};
|
|
||||||
|
|
||||||
ServerExtendedData getExtendedData(const char *addr, unsigned short port);
|
|
||||||
|
|
||||||
#endif //NEWLAUNCHER_PING_HPP
|
|
|
@ -4,13 +4,12 @@ set(BSATOOL
|
||||||
source_group(apps\\bsatool FILES ${BSATOOL})
|
source_group(apps\\bsatool FILES ${BSATOOL})
|
||||||
|
|
||||||
# Main executable
|
# Main executable
|
||||||
openmw_add_executable(bsatool
|
add_executable(bsatool
|
||||||
${BSATOOL}
|
${BSATOOL}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(bsatool
|
target_link_libraries(bsatool
|
||||||
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
${Boost_LIBRARIES}
|
||||||
${Boost_FILESYSTEM_LIBRARY}
|
|
||||||
components
|
components
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
#include <boost/program_options.hpp>
|
#include <boost/program_options.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
@ -237,14 +237,12 @@ int extract(Bsa::BSAFile& bsa, Arguments& info)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a stream for the file to extract
|
// Get a stream for the file to extract
|
||||||
Files::IStreamPtr stream = bsa.getFile(archivePath.c_str());
|
Ogre::DataStreamPtr data = bsa.getFile(archivePath.c_str());
|
||||||
|
|
||||||
bfs::ofstream out(target, std::ios::binary);
|
bfs::ofstream out(target, std::ios::binary);
|
||||||
|
|
||||||
// Write the file to disk
|
// Write the file to disk
|
||||||
std::cout << "Extracting " << info.extractfile << " to " << target << std::endl;
|
std::cout << "Extracting " << info.extractfile << " to " << target << std::endl;
|
||||||
|
out.write(data->getAsString().c_str(), data->size());
|
||||||
out << stream->rdbuf();
|
|
||||||
out.close();
|
out.close();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -278,12 +276,12 @@ int extractAll(Bsa::BSAFile& bsa, Arguments& info)
|
||||||
|
|
||||||
// Get a stream for the file to extract
|
// Get a stream for the file to extract
|
||||||
// (inefficient because getFile iter on the list again)
|
// (inefficient because getFile iter on the list again)
|
||||||
Files::IStreamPtr data = bsa.getFile(archivePath);
|
Ogre::DataStreamPtr data = bsa.getFile(archivePath);
|
||||||
bfs::ofstream out(target, std::ios::binary);
|
bfs::ofstream out(target, std::ios::binary);
|
||||||
|
|
||||||
// Write the file to disk
|
// Write the file to disk
|
||||||
std::cout << "Extracting " << target << std::endl;
|
std::cout << "Extracting " << target << std::endl;
|
||||||
out << data->rdbuf();
|
out.write(data->getAsString().c_str(), data->size());
|
||||||
out.close();
|
out.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,12 @@ set(ESMTOOL
|
||||||
source_group(apps\\esmtool FILES ${ESMTOOL})
|
source_group(apps\\esmtool FILES ${ESMTOOL})
|
||||||
|
|
||||||
# Main executable
|
# Main executable
|
||||||
openmw_add_executable(esmtool
|
add_executable(esmtool
|
||||||
${ESMTOOL}
|
${ESMTOOL}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(esmtool
|
target_link_libraries(esmtool
|
||||||
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
${Boost_LIBRARIES}
|
||||||
components
|
components
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <fstream>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include <boost/program_options.hpp>
|
#include <boost/program_options.hpp>
|
||||||
|
|
||||||
|
@ -28,8 +26,7 @@ struct ESMData
|
||||||
std::vector<ESM::Header::MasterData> masters;
|
std::vector<ESM::Header::MasterData> masters;
|
||||||
|
|
||||||
std::deque<EsmTool::RecordBase *> mRecords;
|
std::deque<EsmTool::RecordBase *> mRecords;
|
||||||
// Value: (Reference, Deleted flag)
|
std::map<ESM::Cell *, std::deque<ESM::CellRef> > mCellRefs;
|
||||||
std::map<ESM::Cell *, std::deque<std::pair<ESM::CellRef, bool> > > mCellRefs;
|
|
||||||
std::map<int, int> mRecordStats;
|
std::map<int, int> mRecordStats;
|
||||||
|
|
||||||
static const std::set<int> sLabeledRec;
|
static const std::set<int> sLabeledRec;
|
||||||
|
@ -257,7 +254,7 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
|
||||||
while(cell.getNextRef(esm, ref, deleted))
|
while(cell.getNextRef(esm, ref, deleted))
|
||||||
{
|
{
|
||||||
if (save) {
|
if (save) {
|
||||||
info.data.mCellRefs[&cell].push_back(std::make_pair(ref, deleted));
|
info.data.mCellRefs[&cell].push_back(ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(quiet) continue;
|
if(quiet) continue;
|
||||||
|
@ -287,7 +284,7 @@ void printRaw(ESM::ESMReader &esm)
|
||||||
esm.getRecHeader();
|
esm.getRecHeader();
|
||||||
while(esm.hasMoreSubs())
|
while(esm.hasMoreSubs())
|
||||||
{
|
{
|
||||||
size_t offs = esm.getFileOffset();
|
uint64_t offs = esm.getOffset();
|
||||||
esm.getSubName();
|
esm.getSubName();
|
||||||
esm.skipHSub();
|
esm.skipHSub();
|
||||||
n = esm.retSubName();
|
n = esm.retSubName();
|
||||||
|
@ -354,58 +351,61 @@ int load(Arguments& info)
|
||||||
uint32_t flags;
|
uint32_t flags;
|
||||||
esm.getRecHeader(flags);
|
esm.getRecHeader(flags);
|
||||||
|
|
||||||
EsmTool::RecordBase *record = EsmTool::RecordBase::create(n);
|
|
||||||
if (record == 0)
|
|
||||||
{
|
|
||||||
if (std::find(skipped.begin(), skipped.end(), n.intval) == skipped.end())
|
|
||||||
{
|
|
||||||
std::cout << "Skipping " << n.toString() << " records." << std::endl;
|
|
||||||
skipped.push_back(n.intval);
|
|
||||||
}
|
|
||||||
|
|
||||||
esm.skipRecord();
|
|
||||||
if (quiet) break;
|
|
||||||
std::cout << " Skipping\n";
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
record->setFlags(static_cast<int>(flags));
|
|
||||||
record->setPrintPlain(info.plain_given);
|
|
||||||
record->load(esm);
|
|
||||||
|
|
||||||
// Is the user interested in this record type?
|
// Is the user interested in this record type?
|
||||||
bool interested = true;
|
bool interested = true;
|
||||||
if (!info.types.empty())
|
if (!info.types.empty())
|
||||||
{
|
{
|
||||||
std::vector<std::string>::iterator match;
|
std::vector<std::string>::iterator match;
|
||||||
match = std::find(info.types.begin(), info.types.end(), n.toString());
|
match = std::find(info.types.begin(), info.types.end(),
|
||||||
|
n.toString());
|
||||||
if (match == info.types.end()) interested = false;
|
if (match == info.types.end()) interested = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, record->getId()))
|
std::string id = esm.getHNOString("NAME");
|
||||||
|
if (id.empty())
|
||||||
|
id = esm.getHNOString("INAM");
|
||||||
|
|
||||||
|
if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, id))
|
||||||
interested = false;
|
interested = false;
|
||||||
|
|
||||||
if(!quiet && interested)
|
if(!quiet && interested)
|
||||||
|
std::cout << "\nRecord: " << n.toString()
|
||||||
|
<< " '" << id << "'\n";
|
||||||
|
|
||||||
|
EsmTool::RecordBase *record = EsmTool::RecordBase::create(n);
|
||||||
|
|
||||||
|
if (record == 0) {
|
||||||
|
if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end())
|
||||||
{
|
{
|
||||||
std::cout << "\nRecord: " << n.toString() << " '" << record->getId() << "'\n";
|
std::cout << "Skipping " << n.toString() << " records." << std::endl;
|
||||||
record->print();
|
skipped.push_back(n.val);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (record->getType().intval == ESM::REC_CELL && loadCells && interested)
|
esm.skipRecord();
|
||||||
{
|
if (quiet) break;
|
||||||
|
std::cout << " Skipping\n";
|
||||||
|
} else {
|
||||||
|
if (record->getType().val == ESM::REC_GMST) {
|
||||||
|
// preset id for GameSetting record
|
||||||
|
record->cast<ESM::GameSetting>()->get().mId = id;
|
||||||
|
}
|
||||||
|
record->setId(id);
|
||||||
|
record->setFlags((int) flags);
|
||||||
|
record->setPrintPlain(info.plain_given);
|
||||||
|
record->load(esm);
|
||||||
|
if (!quiet && interested) record->print();
|
||||||
|
|
||||||
|
if (record->getType().val == ESM::REC_CELL && loadCells && interested) {
|
||||||
loadCell(record->cast<ESM::Cell>()->get(), esm, info);
|
loadCell(record->cast<ESM::Cell>()->get(), esm, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (save)
|
if (save) {
|
||||||
{
|
|
||||||
info.data.mRecords.push_back(record);
|
info.data.mRecords.push_back(record);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
delete record;
|
delete record;
|
||||||
}
|
}
|
||||||
++info.data.mRecordStats[n.intval];
|
++info.data.mRecordStats[n.val];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch(std::exception &e) {
|
} catch(std::exception &e) {
|
||||||
|
@ -443,18 +443,23 @@ int clone(Arguments& info)
|
||||||
size_t recordCount = info.data.mRecords.size();
|
size_t recordCount = info.data.mRecords.size();
|
||||||
|
|
||||||
int digitCount = 1; // For a nicer output
|
int digitCount = 1; // For a nicer output
|
||||||
if (recordCount > 0)
|
if (recordCount > 9) ++digitCount;
|
||||||
digitCount = (int)std::log10(recordCount) + 1;
|
if (recordCount > 99) ++digitCount;
|
||||||
|
if (recordCount > 999) ++digitCount;
|
||||||
|
if (recordCount > 9999) ++digitCount;
|
||||||
|
if (recordCount > 99999) ++digitCount;
|
||||||
|
if (recordCount > 999999) ++digitCount;
|
||||||
|
|
||||||
std::cout << "Loaded " << recordCount << " records:" << std::endl << std::endl;
|
std::cout << "Loaded " << recordCount << " records:" << std::endl << std::endl;
|
||||||
|
|
||||||
|
ESM::NAME name;
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
typedef std::map<int, int> Stats;
|
typedef std::map<int, int> Stats;
|
||||||
Stats &stats = info.data.mRecordStats;
|
Stats &stats = info.data.mRecordStats;
|
||||||
for (Stats::iterator it = stats.begin(); it != stats.end(); ++it)
|
for (Stats::iterator it = stats.begin(); it != stats.end(); ++it)
|
||||||
{
|
{
|
||||||
ESM::NAME name;
|
name.val = it->first;
|
||||||
name.intval = it->first;
|
|
||||||
int amount = it->second;
|
int amount = it->second;
|
||||||
std::cout << std::setw(digitCount) << amount << " " << name.toString() << " ";
|
std::cout << std::setw(digitCount) << amount << " " << name.toString() << " ";
|
||||||
|
|
||||||
|
@ -487,24 +492,33 @@ int clone(Arguments& info)
|
||||||
for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it)
|
for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it)
|
||||||
{
|
{
|
||||||
EsmTool::RecordBase *record = *it;
|
EsmTool::RecordBase *record = *it;
|
||||||
const ESM::NAME& typeName = record->getType();
|
|
||||||
|
|
||||||
esm.startRecord(typeName.toString(), record->getFlags());
|
name.val = record->getType().val;
|
||||||
|
|
||||||
|
esm.startRecord(name.toString(), record->getFlags());
|
||||||
|
|
||||||
|
// TODO wrap this with std::set
|
||||||
|
if (ESMData::sLabeledRec.count(name.val) > 0) {
|
||||||
|
esm.writeHNCString("NAME", record->getId());
|
||||||
|
} else {
|
||||||
|
esm.writeHNOString("NAME", record->getId());
|
||||||
|
}
|
||||||
|
|
||||||
record->save(esm);
|
record->save(esm);
|
||||||
if (typeName.intval == ESM::REC_CELL) {
|
|
||||||
|
if (name.val == ESM::REC_CELL) {
|
||||||
ESM::Cell *ptr = &record->cast<ESM::Cell>()->get();
|
ESM::Cell *ptr = &record->cast<ESM::Cell>()->get();
|
||||||
if (!info.data.mCellRefs[ptr].empty()) {
|
if (!info.data.mCellRefs[ptr].empty()) {
|
||||||
typedef std::deque<std::pair<ESM::CellRef, bool> > RefList;
|
typedef std::deque<ESM::CellRef> RefList;
|
||||||
RefList &refs = info.data.mCellRefs[ptr];
|
RefList &refs = info.data.mCellRefs[ptr];
|
||||||
for (RefList::iterator refIt = refs.begin(); refIt != refs.end(); ++refIt)
|
for (RefList::iterator refIt = refs.begin(); refIt != refs.end(); ++refIt)
|
||||||
{
|
{
|
||||||
refIt->first.save(esm, refIt->second);
|
refIt->save(esm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
esm.endRecord(typeName.toString());
|
esm.endRecord(name.toString());
|
||||||
|
|
||||||
saved++;
|
saved++;
|
||||||
int perc = (int)((saved / (float)recordCount)*100);
|
int perc = (int)((saved / (float)recordCount)*100);
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <components/esm/loadrace.hpp>
|
#include <components/esm/loadrace.hpp>
|
||||||
#include <components/esm/loadspel.hpp>
|
#include <components/esm/loadspel.hpp>
|
||||||
#include <components/esm/loadweap.hpp>
|
#include <components/esm/loadweap.hpp>
|
||||||
|
#include <components/esm/aipackage.hpp>
|
||||||
|
|
||||||
#include <boost/format.hpp>
|
#include <boost/format.hpp>
|
||||||
|
|
||||||
|
@ -829,12 +830,12 @@ std::string npcFlags(int flags)
|
||||||
std::string properties = "";
|
std::string properties = "";
|
||||||
if (flags == 0) properties += "[None] ";
|
if (flags == 0) properties += "[None] ";
|
||||||
// Mythicmods and the ESM component differ. Mythicmods says
|
// Mythicmods and the ESM component differ. Mythicmods says
|
||||||
// 0x8=None and 0x10=AutoCalc, while our code previously defined
|
// 0x8=None and 0x10=AutoCalc, while our code defines 0x8 as
|
||||||
// 0x8 as AutoCalc. The former seems to be correct. All Bethesda
|
// AutoCalc. The former seems to be correct. All Bethesda
|
||||||
// records have bit 0x8 set. Previously, suspiciously large portion
|
// records have bit 0x8 set. A suspiciously large portion of
|
||||||
// of females had autocalc turned off.
|
// females have autocalc turned off.
|
||||||
if (flags & 0x00000008) properties += "Unknown ";
|
if (flags & ESM::NPC::Autocalc) properties += "Unknown ";
|
||||||
if (flags & ESM::NPC::Autocalc) properties += "Autocalc ";
|
if (flags & 0x00000010) properties += "Autocalc ";
|
||||||
if (flags & ESM::NPC::Female) properties += "Female ";
|
if (flags & ESM::NPC::Female) properties += "Female ";
|
||||||
if (flags & ESM::NPC::Respawn) properties += "Respawn ";
|
if (flags & ESM::NPC::Respawn) properties += "Respawn ";
|
||||||
if (flags & ESM::NPC::Essential) properties += "Essential ";
|
if (flags & ESM::NPC::Essential) properties += "Essential ";
|
||||||
|
@ -846,8 +847,8 @@ std::string npcFlags(int flags)
|
||||||
// however the only unknown bit occurs on ALL records, and
|
// however the only unknown bit occurs on ALL records, and
|
||||||
// relatively few NPCs have this bit set.
|
// relatively few NPCs have this bit set.
|
||||||
int unused = (0xFFFFFFFF ^
|
int unused = (0xFFFFFFFF ^
|
||||||
(0x00000008|
|
(ESM::NPC::Autocalc|
|
||||||
ESM::NPC::Autocalc|
|
0x00000010|
|
||||||
ESM::NPC::Female|
|
ESM::NPC::Female|
|
||||||
ESM::NPC::Respawn|
|
ESM::NPC::Respawn|
|
||||||
ESM::NPC::Essential|
|
ESM::NPC::Essential|
|
||||||
|
|
|
@ -179,7 +179,7 @@ RecordBase::create(ESM::NAME type)
|
||||||
{
|
{
|
||||||
RecordBase *record = 0;
|
RecordBase *record = 0;
|
||||||
|
|
||||||
switch (type.intval) {
|
switch (type.val) {
|
||||||
case ESM::REC_ACTI:
|
case ESM::REC_ACTI:
|
||||||
{
|
{
|
||||||
record = new EsmTool::Record<ESM::Activator>;
|
record = new EsmTool::Record<ESM::Activator>;
|
||||||
|
@ -405,7 +405,6 @@ void Record<ESM::Activator>::print()
|
||||||
std::cout << " Name: " << mData.mName << std::endl;
|
std::cout << " Name: " << mData.mName << std::endl;
|
||||||
std::cout << " Model: " << mData.mModel << std::endl;
|
std::cout << " Model: " << mData.mModel << std::endl;
|
||||||
std::cout << " Script: " << mData.mScript << std::endl;
|
std::cout << " Script: " << mData.mScript << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -420,7 +419,6 @@ void Record<ESM::Potion>::print()
|
||||||
std::cout << " Value: " << mData.mData.mValue << std::endl;
|
std::cout << " Value: " << mData.mData.mValue << std::endl;
|
||||||
std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl;
|
std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl;
|
||||||
printEffectList(mData.mEffects);
|
printEffectList(mData.mEffects);
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -449,7 +447,6 @@ void Record<ESM::Armor>::print()
|
||||||
if (pit->mFemale != "")
|
if (pit->mFemale != "")
|
||||||
std::cout << " Female Name: " << pit->mFemale << std::endl;
|
std::cout << " Female Name: " << pit->mFemale << std::endl;
|
||||||
}
|
}
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -464,7 +461,6 @@ void Record<ESM::Apparatus>::print()
|
||||||
std::cout << " Weight: " << mData.mData.mWeight << std::endl;
|
std::cout << " Weight: " << mData.mData.mWeight << std::endl;
|
||||||
std::cout << " Value: " << mData.mData.mValue << std::endl;
|
std::cout << " Value: " << mData.mData.mValue << std::endl;
|
||||||
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
|
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -478,7 +474,6 @@ void Record<ESM::BodyPart>::print()
|
||||||
std::cout << " Part: " << meshPartLabel(mData.mData.mPart)
|
std::cout << " Part: " << meshPartLabel(mData.mData.mPart)
|
||||||
<< " (" << (int)mData.mData.mPart << ")" << std::endl;
|
<< " (" << (int)mData.mData.mPart << ")" << std::endl;
|
||||||
std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl;
|
std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -494,7 +489,7 @@ void Record<ESM::Book>::print()
|
||||||
std::cout << " Weight: " << mData.mData.mWeight << std::endl;
|
std::cout << " Weight: " << mData.mData.mWeight << std::endl;
|
||||||
std::cout << " Value: " << mData.mData.mValue << std::endl;
|
std::cout << " Value: " << mData.mData.mValue << std::endl;
|
||||||
std::cout << " IsScroll: " << mData.mData.mIsScroll << std::endl;
|
std::cout << " IsScroll: " << mData.mData.mIsScroll << std::endl;
|
||||||
std::cout << " SkillId: " << mData.mData.mSkillId << std::endl;
|
std::cout << " SkillID: " << mData.mData.mSkillID << std::endl;
|
||||||
std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl;
|
std::cout << " Enchantment Points: " << mData.mData.mEnchant << std::endl;
|
||||||
if (mPrintPlain)
|
if (mPrintPlain)
|
||||||
{
|
{
|
||||||
|
@ -507,7 +502,6 @@ void Record<ESM::Book>::print()
|
||||||
{
|
{
|
||||||
std::cout << " Text: [skipped]" << std::endl;
|
std::cout << " Text: [skipped]" << std::endl;
|
||||||
}
|
}
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -519,7 +513,6 @@ void Record<ESM::BirthSign>::print()
|
||||||
std::vector<std::string>::iterator pit;
|
std::vector<std::string>::iterator pit;
|
||||||
for (pit = mData.mPowers.mList.begin(); pit != mData.mPowers.mList.end(); ++pit)
|
for (pit = mData.mPowers.mList.begin(); pit != mData.mPowers.mList.end(); ++pit)
|
||||||
std::cout << " Power: " << *pit << std::endl;
|
std::cout << " Power: " << *pit << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -548,7 +541,6 @@ void Record<ESM::Cell>::print()
|
||||||
std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl;
|
std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl;
|
||||||
std::cout << " Water Level Int: " << mData.mWaterInt << std::endl;
|
std::cout << " Water Level Int: " << mData.mWaterInt << std::endl;
|
||||||
std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl;
|
std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,7 +563,6 @@ void Record<ESM::Class>::print()
|
||||||
for (int i = 0; i != 5; i++)
|
for (int i = 0; i != 5; i++)
|
||||||
std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][1])
|
std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][1])
|
||||||
<< " (" << mData.mData.mSkills[i][1] << ")" << std::endl;
|
<< " (" << mData.mData.mSkills[i][1] << ")" << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -598,7 +589,6 @@ void Record<ESM::Clothing>::print()
|
||||||
if (pit->mFemale != "")
|
if (pit->mFemale != "")
|
||||||
std::cout << " Female Name: " << pit->mFemale << std::endl;
|
std::cout << " Female Name: " << pit->mFemale << std::endl;
|
||||||
}
|
}
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -614,7 +604,6 @@ void Record<ESM::Container>::print()
|
||||||
for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); ++cit)
|
for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); ++cit)
|
||||||
std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount
|
std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount
|
||||||
<< " Item: " << cit->mItem.toString() << std::endl;
|
<< " Item: " << cit->mItem.toString() << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -681,7 +670,6 @@ void Record<ESM::Creature>::print()
|
||||||
std::vector<ESM::AIPackage>::iterator pit;
|
std::vector<ESM::AIPackage>::iterator pit;
|
||||||
for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit)
|
for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit)
|
||||||
printAIPackage(*pit);
|
printAIPackage(*pit);
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -689,12 +677,11 @@ void Record<ESM::Dialogue>::print()
|
||||||
{
|
{
|
||||||
std::cout << " Type: " << dialogTypeLabel(mData.mType)
|
std::cout << " Type: " << dialogTypeLabel(mData.mType)
|
||||||
<< " (" << (int)mData.mType << ")" << std::endl;
|
<< " (" << (int)mData.mType << ")" << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
// Sadly, there are no DialInfos, because the loader dumps as it
|
// Sadly, there are no DialInfos, because the loader dumps as it
|
||||||
// loads, rather than loading and then dumping. :-( Anyone mind if
|
// loads, rather than loading and then dumping. :-( Anyone mind if
|
||||||
// I change this?
|
// I change this?
|
||||||
ESM::Dialogue::InfoContainer::iterator iit;
|
ESM::Dialogue::InfoContainer::iterator iit;
|
||||||
for (iit = mData.mInfo.begin(); iit != mData.mInfo.end(); ++iit)
|
for (iit = mData.mInfo.begin(); iit != mData.mInfo.end(); iit++)
|
||||||
std::cout << "INFO!" << iit->mId << std::endl;
|
std::cout << "INFO!" << iit->mId << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -706,7 +693,6 @@ void Record<ESM::Door>::print()
|
||||||
std::cout << " Script: " << mData.mScript << std::endl;
|
std::cout << " Script: " << mData.mScript << std::endl;
|
||||||
std::cout << " OpenSound: " << mData.mOpenSound << std::endl;
|
std::cout << " OpenSound: " << mData.mOpenSound << std::endl;
|
||||||
std::cout << " CloseSound: " << mData.mCloseSound << std::endl;
|
std::cout << " CloseSound: " << mData.mCloseSound << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -718,7 +704,6 @@ void Record<ESM::Enchantment>::print()
|
||||||
std::cout << " Charge: " << mData.mData.mCharge << std::endl;
|
std::cout << " Charge: " << mData.mData.mCharge << std::endl;
|
||||||
std::cout << " AutoCalc: " << mData.mData.mAutocalc << std::endl;
|
std::cout << " AutoCalc: " << mData.mData.mAutocalc << std::endl;
|
||||||
printEffectList(mData.mEffects);
|
printEffectList(mData.mEffects);
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -752,14 +737,12 @@ void Record<ESM::Faction>::print()
|
||||||
std::map<std::string, int>::iterator rit;
|
std::map<std::string, int>::iterator rit;
|
||||||
for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); ++rit)
|
for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); ++rit)
|
||||||
std::cout << " Reaction: " << rit->second << " = " << rit->first << std::endl;
|
std::cout << " Reaction: " << rit->second << " = " << rit->first << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
void Record<ESM::Global>::print()
|
void Record<ESM::Global>::print()
|
||||||
{
|
{
|
||||||
std::cout << " " << mData.mValue << std::endl;
|
std::cout << " " << mData.mValue << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -826,7 +809,6 @@ void Record<ESM::DialInfo>::print()
|
||||||
std::cout << " Result Script: [skipped]" << std::endl;
|
std::cout << " Result Script: [skipped]" << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -850,7 +832,6 @@ void Record<ESM::Ingredient>::print()
|
||||||
std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i])
|
std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i])
|
||||||
<< " (" << mData.mData.mAttributes[i] << ")" << std::endl;
|
<< " (" << mData.mData.mAttributes[i] << ")" << std::endl;
|
||||||
}
|
}
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -860,15 +841,19 @@ void Record<ESM::Land>::print()
|
||||||
std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl;
|
std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl;
|
||||||
std::cout << " DataTypes: " << mData.mDataTypes << std::endl;
|
std::cout << " DataTypes: " << mData.mDataTypes << std::endl;
|
||||||
|
|
||||||
if (const ESM::Land::LandData *data = mData.getLandData (mData.mDataTypes))
|
// Seems like this should done with reference counting in the
|
||||||
|
// loader to me. But I'm not really knowledgable about this
|
||||||
|
// record type yet. --Cory
|
||||||
|
bool wasLoaded = (mData.mDataLoaded != 0);
|
||||||
|
if (mData.mDataTypes) mData.loadData(mData.mDataTypes);
|
||||||
|
if (mData.mDataLoaded)
|
||||||
{
|
{
|
||||||
std::cout << " Height Offset: " << data->mHeightOffset << std::endl;
|
std::cout << " Height Offset: " << mData.mLandData->mHeightOffset << std::endl;
|
||||||
// Lots of missing members.
|
// Lots of missing members.
|
||||||
std::cout << " Unknown1: " << data->mUnk1 << std::endl;
|
std::cout << " Unknown1: " << mData.mLandData->mUnk1 << std::endl;
|
||||||
std::cout << " Unknown2: " << data->mUnk2 << std::endl;
|
std::cout << " Unknown2: " << mData.mLandData->mUnk2 << std::endl;
|
||||||
}
|
}
|
||||||
mData.unloadData();
|
if (!wasLoaded) mData.unloadData();
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -881,7 +866,6 @@ void Record<ESM::CreatureLevList>::print()
|
||||||
for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit)
|
for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit)
|
||||||
std::cout << " Creature: Level: " << iit->mLevel
|
std::cout << " Creature: Level: " << iit->mLevel
|
||||||
<< " Creature: " << iit->mId << std::endl;
|
<< " Creature: " << iit->mId << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -894,7 +878,6 @@ void Record<ESM::ItemLevList>::print()
|
||||||
for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit)
|
for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit)
|
||||||
std::cout << " Inventory: Level: " << iit->mLevel
|
std::cout << " Inventory: Level: " << iit->mLevel
|
||||||
<< " Item: " << iit->mId << std::endl;
|
<< " Item: " << iit->mId << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -915,7 +898,6 @@ void Record<ESM::Light>::print()
|
||||||
std::cout << " Duration: " << mData.mData.mTime << std::endl;
|
std::cout << " Duration: " << mData.mData.mTime << std::endl;
|
||||||
std::cout << " Radius: " << mData.mData.mRadius << std::endl;
|
std::cout << " Radius: " << mData.mData.mRadius << std::endl;
|
||||||
std::cout << " Color: " << mData.mData.mColor << std::endl;
|
std::cout << " Color: " << mData.mData.mColor << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -930,7 +912,6 @@ void Record<ESM::Lockpick>::print()
|
||||||
std::cout << " Value: " << mData.mData.mValue << std::endl;
|
std::cout << " Value: " << mData.mData.mValue << std::endl;
|
||||||
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
|
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
|
||||||
std::cout << " Uses: " << mData.mData.mUses << std::endl;
|
std::cout << " Uses: " << mData.mData.mUses << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -945,7 +926,6 @@ void Record<ESM::Probe>::print()
|
||||||
std::cout << " Value: " << mData.mData.mValue << std::endl;
|
std::cout << " Value: " << mData.mData.mValue << std::endl;
|
||||||
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
|
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
|
||||||
std::cout << " Uses: " << mData.mData.mUses << std::endl;
|
std::cout << " Uses: " << mData.mData.mUses << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -960,7 +940,6 @@ void Record<ESM::Repair>::print()
|
||||||
std::cout << " Value: " << mData.mData.mValue << std::endl;
|
std::cout << " Value: " << mData.mData.mValue << std::endl;
|
||||||
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
|
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
|
||||||
std::cout << " Uses: " << mData.mData.mUses << std::endl;
|
std::cout << " Uses: " << mData.mData.mUses << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -969,7 +948,6 @@ void Record<ESM::LandTexture>::print()
|
||||||
std::cout << " Id: " << mData.mId << std::endl;
|
std::cout << " Id: " << mData.mId << std::endl;
|
||||||
std::cout << " Index: " << mData.mIndex << std::endl;
|
std::cout << " Index: " << mData.mIndex << std::endl;
|
||||||
std::cout << " Texture: " << mData.mTexture << std::endl;
|
std::cout << " Texture: " << mData.mTexture << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -1020,7 +998,6 @@ void Record<ESM::Miscellaneous>::print()
|
||||||
std::cout << " Weight: " << mData.mData.mWeight << std::endl;
|
std::cout << " Weight: " << mData.mData.mWeight << std::endl;
|
||||||
std::cout << " Value: " << mData.mData.mValue << std::endl;
|
std::cout << " Value: " << mData.mData.mValue << std::endl;
|
||||||
std::cout << " Is Key: " << mData.mData.mIsKey << std::endl;
|
std::cout << " Is Key: " << mData.mData.mIsKey << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -1040,47 +1017,45 @@ void Record<ESM::NPC>::print()
|
||||||
|
|
||||||
if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS)
|
||||||
{
|
{
|
||||||
std::cout << " Level: " << mData.mNpdt.mLevel << std::endl;
|
std::cout << " Level: " << mData.mNpdt12.mLevel << std::endl;
|
||||||
std::cout << " Reputation: " << (int)mData.mNpdt.mReputation << std::endl;
|
std::cout << " Reputation: " << (int)mData.mNpdt12.mReputation << std::endl;
|
||||||
std::cout << " Disposition: " << (int)mData.mNpdt.mDisposition << std::endl;
|
std::cout << " Disposition: " << (int)mData.mNpdt12.mDisposition << std::endl;
|
||||||
std::cout << " Rank: " << (int)mData.mNpdt.mRank << std::endl;
|
std::cout << " Rank: " << (int)mData.mNpdt12.mRank << std::endl;
|
||||||
//Why do we want to print these fields? They are padding in the struct and contain
|
std::cout << " Unknown1: "
|
||||||
// nothing of real value. Now we don't deal with NPDTstruct12 in runtime either...
|
<< (unsigned int)((unsigned char)mData.mNpdt12.mUnknown1) << std::endl;
|
||||||
//std::cout << " Unknown1: "
|
std::cout << " Unknown2: "
|
||||||
// << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown1) << std::endl;
|
<< (unsigned int)((unsigned char)mData.mNpdt12.mUnknown2) << std::endl;
|
||||||
//std::cout << " Unknown2: "
|
std::cout << " Unknown3: "
|
||||||
// << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown2) << std::endl;
|
<< (unsigned int)((unsigned char)mData.mNpdt12.mUnknown3) << std::endl;
|
||||||
//std::cout << " Unknown3: "
|
std::cout << " Gold: " << mData.mNpdt12.mGold << std::endl;
|
||||||
// << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown3) << std::endl;
|
|
||||||
std::cout << " Gold: " << mData.mNpdt.mGold << std::endl;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
std::cout << " Level: " << mData.mNpdt.mLevel << std::endl;
|
std::cout << " Level: " << mData.mNpdt52.mLevel << std::endl;
|
||||||
std::cout << " Reputation: " << (int)mData.mNpdt.mReputation << std::endl;
|
std::cout << " Reputation: " << (int)mData.mNpdt52.mReputation << std::endl;
|
||||||
std::cout << " Disposition: " << (int)mData.mNpdt.mDisposition << std::endl;
|
std::cout << " Disposition: " << (int)mData.mNpdt52.mDisposition << std::endl;
|
||||||
std::cout << " Rank: " << (int)mData.mNpdt.mRank << std::endl;
|
std::cout << " Rank: " << (int)mData.mNpdt52.mRank << std::endl;
|
||||||
std::cout << " FactionID: " << (int)mData.mNpdt.mFactionID << std::endl;
|
std::cout << " FactionID: " << (int)mData.mNpdt52.mFactionID << std::endl;
|
||||||
|
|
||||||
std::cout << " Attributes:" << std::endl;
|
std::cout << " Attributes:" << std::endl;
|
||||||
std::cout << " Strength: " << (int)mData.mNpdt.mStrength << std::endl;
|
std::cout << " Strength: " << (int)mData.mNpdt52.mStrength << std::endl;
|
||||||
std::cout << " Intelligence: " << (int)mData.mNpdt.mIntelligence << std::endl;
|
std::cout << " Intelligence: " << (int)mData.mNpdt52.mIntelligence << std::endl;
|
||||||
std::cout << " Willpower: " << (int)mData.mNpdt.mWillpower << std::endl;
|
std::cout << " Willpower: " << (int)mData.mNpdt52.mWillpower << std::endl;
|
||||||
std::cout << " Agility: " << (int)mData.mNpdt.mAgility << std::endl;
|
std::cout << " Agility: " << (int)mData.mNpdt52.mAgility << std::endl;
|
||||||
std::cout << " Speed: " << (int)mData.mNpdt.mSpeed << std::endl;
|
std::cout << " Speed: " << (int)mData.mNpdt52.mSpeed << std::endl;
|
||||||
std::cout << " Endurance: " << (int)mData.mNpdt.mEndurance << std::endl;
|
std::cout << " Endurance: " << (int)mData.mNpdt52.mEndurance << std::endl;
|
||||||
std::cout << " Personality: " << (int)mData.mNpdt.mPersonality << std::endl;
|
std::cout << " Personality: " << (int)mData.mNpdt52.mPersonality << std::endl;
|
||||||
std::cout << " Luck: " << (int)mData.mNpdt.mLuck << std::endl;
|
std::cout << " Luck: " << (int)mData.mNpdt52.mLuck << std::endl;
|
||||||
|
|
||||||
std::cout << " Skills:" << std::endl;
|
std::cout << " Skills:" << std::endl;
|
||||||
for (int i = 0; i != ESM::Skill::Length; i++)
|
for (int i = 0; i != ESM::Skill::Length; i++)
|
||||||
std::cout << " " << skillLabel(i) << ": "
|
std::cout << " " << skillLabel(i) << ": "
|
||||||
<< (int)(mData.mNpdt.mSkills[i]) << std::endl;
|
<< (int)(mData.mNpdt52.mSkills[i]) << std::endl;
|
||||||
|
|
||||||
std::cout << " Health: " << mData.mNpdt.mHealth << std::endl;
|
std::cout << " Health: " << mData.mNpdt52.mHealth << std::endl;
|
||||||
std::cout << " Magicka: " << mData.mNpdt.mMana << std::endl;
|
std::cout << " Magicka: " << mData.mNpdt52.mMana << std::endl;
|
||||||
std::cout << " Fatigue: " << mData.mNpdt.mFatigue << std::endl;
|
std::cout << " Fatigue: " << mData.mNpdt52.mFatigue << std::endl;
|
||||||
std::cout << " Unknown: " << (int)mData.mNpdt.mUnknown << std::endl;
|
std::cout << " Unknown: " << (int)mData.mNpdt52.mUnknown << std::endl;
|
||||||
std::cout << " Gold: " << mData.mNpdt.mGold << std::endl;
|
std::cout << " Gold: " << mData.mNpdt52.mGold << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ESM::ContItem>::iterator cit;
|
std::vector<ESM::ContItem>::iterator cit;
|
||||||
|
@ -1108,8 +1083,6 @@ void Record<ESM::NPC>::print()
|
||||||
std::vector<ESM::AIPackage>::iterator pit;
|
std::vector<ESM::AIPackage>::iterator pit;
|
||||||
for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit)
|
for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit)
|
||||||
printAIPackage(*pit);
|
printAIPackage(*pit);
|
||||||
|
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -1125,7 +1098,7 @@ void Record<ESM::Pathgrid>::print()
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
ESM::Pathgrid::PointList::iterator pit;
|
ESM::Pathgrid::PointList::iterator pit;
|
||||||
for (pit = mData.mPoints.begin(); pit != mData.mPoints.end(); ++pit)
|
for (pit = mData.mPoints.begin(); pit != mData.mPoints.end(); pit++)
|
||||||
{
|
{
|
||||||
std::cout << " Point[" << i << "]:" << std::endl;
|
std::cout << " Point[" << i << "]:" << std::endl;
|
||||||
std::cout << " Coordinates: (" << pit->mX << ","
|
std::cout << " Coordinates: (" << pit->mX << ","
|
||||||
|
@ -1137,15 +1110,13 @@ void Record<ESM::Pathgrid>::print()
|
||||||
}
|
}
|
||||||
i = 0;
|
i = 0;
|
||||||
ESM::Pathgrid::EdgeList::iterator eit;
|
ESM::Pathgrid::EdgeList::iterator eit;
|
||||||
for (eit = mData.mEdges.begin(); eit != mData.mEdges.end(); ++eit)
|
for (eit = mData.mEdges.begin(); eit != mData.mEdges.end(); eit++)
|
||||||
{
|
{
|
||||||
std::cout << " Edge[" << i << "]: " << eit->mV0 << " -> " << eit->mV1 << std::endl;
|
std::cout << " Edge[" << i << "]: " << eit->mV0 << " -> " << eit->mV1 << std::endl;
|
||||||
if (eit->mV0 >= mData.mData.mS2 || eit->mV1 >= mData.mData.mS2)
|
if (eit->mV0 >= mData.mData.mS2 || eit->mV1 >= mData.mData.mS2)
|
||||||
std::cout << " BAD POINT IN EDGE!" << std::endl;
|
std::cout << " BAD POINT IN EDGE!" << std::endl;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -1186,8 +1157,6 @@ void Record<ESM::Race>::print()
|
||||||
std::vector<std::string>::iterator sit;
|
std::vector<std::string>::iterator sit;
|
||||||
for (sit = mData.mPowers.mList.begin(); sit != mData.mPowers.mList.end(); ++sit)
|
for (sit = mData.mPowers.mList.begin(); sit != mData.mPowers.mList.end(); ++sit)
|
||||||
std::cout << " Power: " << *sit << std::endl;
|
std::cout << " Power: " << *sit << std::endl;
|
||||||
|
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -1247,8 +1216,6 @@ void Record<ESM::Script>::print()
|
||||||
{
|
{
|
||||||
std::cout << " Script: [skipped]" << std::endl;
|
std::cout << " Script: [skipped]" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -1272,7 +1239,6 @@ void Record<ESM::SoundGenerator>::print()
|
||||||
std::cout << " Sound: " << mData.mSound << std::endl;
|
std::cout << " Sound: " << mData.mSound << std::endl;
|
||||||
std::cout << " Type: " << soundTypeLabel(mData.mType)
|
std::cout << " Type: " << soundTypeLabel(mData.mType)
|
||||||
<< " (" << mData.mType << ")" << std::endl;
|
<< " (" << mData.mType << ")" << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -1283,7 +1249,6 @@ void Record<ESM::Sound>::print()
|
||||||
if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0)
|
if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0)
|
||||||
std::cout << " Range: " << (int)mData.mData.mMinRange << " - "
|
std::cout << " Range: " << (int)mData.mData.mMinRange << " - "
|
||||||
<< (int)mData.mData.mMaxRange << std::endl;
|
<< (int)mData.mData.mMaxRange << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -1295,7 +1260,6 @@ void Record<ESM::Spell>::print()
|
||||||
std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl;
|
std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl;
|
||||||
std::cout << " Cost: " << mData.mData.mCost << std::endl;
|
std::cout << " Cost: " << mData.mData.mCost << std::endl;
|
||||||
printEffectList(mData.mEffects);
|
printEffectList(mData.mEffects);
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -1303,7 +1267,6 @@ void Record<ESM::StartScript>::print()
|
||||||
{
|
{
|
||||||
std::cout << "Start Script: " << mData.mId << std::endl;
|
std::cout << "Start Script: " << mData.mId << std::endl;
|
||||||
std::cout << "Start Data: " << mData.mData << std::endl;
|
std::cout << "Start Data: " << mData.mData << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -1344,37 +1307,6 @@ void Record<ESM::Weapon>::print()
|
||||||
if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0)
|
if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0)
|
||||||
std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-"
|
std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-"
|
||||||
<< (int)mData.mData.mThrust[1] << std::endl;
|
<< (int)mData.mData.mThrust[1] << std::endl;
|
||||||
std::cout << " Deleted: " << mIsDeleted << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
|
||||||
std::string Record<ESM::Cell>::getId() const
|
|
||||||
{
|
|
||||||
return mData.mName;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
|
||||||
std::string Record<ESM::Land>::getId() const
|
|
||||||
{
|
|
||||||
return ""; // No ID for Land record
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
|
||||||
std::string Record<ESM::MagicEffect>::getId() const
|
|
||||||
{
|
|
||||||
return ""; // No ID for MagicEffect record
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
|
||||||
std::string Record<ESM::Pathgrid>::getId() const
|
|
||||||
{
|
|
||||||
return ""; // No ID for Pathgrid record
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
|
||||||
std::string Record<ESM::Skill>::getId() const
|
|
||||||
{
|
|
||||||
return ""; // No ID for Skill record
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // end namespace
|
} // end namespace
|
||||||
|
|
|
@ -32,7 +32,13 @@ namespace EsmTool
|
||||||
|
|
||||||
virtual ~RecordBase() {}
|
virtual ~RecordBase() {}
|
||||||
|
|
||||||
virtual std::string getId() const = 0;
|
const std::string &getId() const {
|
||||||
|
return mId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setId(const std::string &id) {
|
||||||
|
mId = id;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t getFlags() const {
|
uint32_t getFlags() const {
|
||||||
return mFlags;
|
return mFlags;
|
||||||
|
@ -67,38 +73,23 @@ namespace EsmTool
|
||||||
class Record : public RecordBase
|
class Record : public RecordBase
|
||||||
{
|
{
|
||||||
T mData;
|
T mData;
|
||||||
bool mIsDeleted;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Record()
|
|
||||||
: mIsDeleted(false)
|
|
||||||
{}
|
|
||||||
|
|
||||||
std::string getId() const {
|
|
||||||
return mData.mId;
|
|
||||||
}
|
|
||||||
|
|
||||||
T &get() {
|
T &get() {
|
||||||
return mData;
|
return mData;
|
||||||
}
|
}
|
||||||
|
|
||||||
void save(ESM::ESMWriter &esm) {
|
void save(ESM::ESMWriter &esm) {
|
||||||
mData.save(esm, mIsDeleted);
|
mData.save(esm);
|
||||||
}
|
}
|
||||||
|
|
||||||
void load(ESM::ESMReader &esm) {
|
void load(ESM::ESMReader &esm) {
|
||||||
mData.load(esm, mIsDeleted);
|
mData.load(esm);
|
||||||
}
|
}
|
||||||
|
|
||||||
void print();
|
void print();
|
||||||
};
|
};
|
||||||
|
|
||||||
template<> std::string Record<ESM::Cell>::getId() const;
|
|
||||||
template<> std::string Record<ESM::Land>::getId() const;
|
|
||||||
template<> std::string Record<ESM::MagicEffect>::getId() const;
|
|
||||||
template<> std::string Record<ESM::Pathgrid>::getId() const;
|
|
||||||
template<> std::string Record<ESM::Skill>::getId() const;
|
|
||||||
|
|
||||||
template<> void Record<ESM::Activator>::print();
|
template<> void Record<ESM::Activator>::print();
|
||||||
template<> void Record<ESM::Potion>::print();
|
template<> void Record<ESM::Potion>::print();
|
||||||
template<> void Record<ESM::Armor>::print();
|
template<> void Record<ESM::Armor>::print();
|
||||||
|
|
|
@ -16,8 +16,6 @@ set(ESSIMPORTER_FILES
|
||||||
importjour.cpp
|
importjour.cpp
|
||||||
importscri.cpp
|
importscri.cpp
|
||||||
importscpt.cpp
|
importscpt.cpp
|
||||||
importproj.cpp
|
|
||||||
importsplm.cpp
|
|
||||||
importercontext.cpp
|
importercontext.cpp
|
||||||
converter.cpp
|
converter.cpp
|
||||||
convertacdt.cpp
|
convertacdt.cpp
|
||||||
|
@ -30,13 +28,12 @@ set(ESSIMPORTER_FILES
|
||||||
convertplayer.cpp
|
convertplayer.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
openmw_add_executable(openmw-essimporter
|
add_executable(openmw-essimporter
|
||||||
${ESSIMPORTER_FILES}
|
${ESSIMPORTER_FILES}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(openmw-essimporter
|
target_link_libraries(openmw-essimporter
|
||||||
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
${Boost_LIBRARIES}
|
||||||
${Boost_FILESYSTEM_LIBRARY}
|
|
||||||
components
|
components
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,7 +41,3 @@ if (BUILD_WITH_CODE_COVERAGE)
|
||||||
add_definitions (--coverage)
|
add_definitions (--coverage)
|
||||||
target_link_libraries(openmw-essimporter gcov)
|
target_link_libraries(openmw-essimporter gcov)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
|
||||||
INSTALL(TARGETS openmw-essimporter RUNTIME DESTINATION ".")
|
|
||||||
endif(WIN32)
|
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
#include <string>
|
|
||||||
#include <iostream>
|
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
#include <components/misc/stringops.hpp>
|
|
||||||
|
|
||||||
#include "convertacdt.hpp"
|
#include "convertacdt.hpp"
|
||||||
|
|
||||||
namespace ESSImport
|
namespace ESSImport
|
||||||
|
@ -47,57 +41,12 @@ namespace ESSImport
|
||||||
{
|
{
|
||||||
for (int i=0; i<ESM::Skill::Length; ++i)
|
for (int i=0; i<ESM::Skill::Length; ++i)
|
||||||
{
|
{
|
||||||
npcStats.mSkills[i].mMod = actorData.mSkills[i][1];
|
npcStats.mSkills[i].mRegular.mMod = actorData.mSkills[i][1];
|
||||||
npcStats.mSkills[i].mCurrent = actorData.mSkills[i][1];
|
npcStats.mSkills[i].mRegular.mCurrent = actorData.mSkills[i][1];
|
||||||
npcStats.mSkills[i].mBase = actorData.mSkills[i][0];
|
npcStats.mSkills[i].mRegular.mBase = actorData.mSkills[i][0];
|
||||||
}
|
}
|
||||||
|
|
||||||
npcStats.mTimeToStartDrowning = actorData.mACDT.mBreathMeter;
|
npcStats.mTimeToStartDrowning = actorData.mACDT.mBreathMeter;
|
||||||
}
|
}
|
||||||
|
|
||||||
void convertANIS (const ANIS& anis, ESM::AnimationState& state)
|
|
||||||
{
|
|
||||||
static const char* animGroups[] =
|
|
||||||
{
|
|
||||||
"Idle", "Idle2", "Idle3", "Idle4", "Idle5", "Idle6", "Idle7", "Idle8", "Idle9", "Idlehh", "Idle1h", "Idle2c",
|
|
||||||
"Idle2w", "IdleSwim", "IdleSpell", "IdleCrossbow", "IdleSneak", "IdleStorm", "Torch", "Hit1", "Hit2", "Hit3",
|
|
||||||
"Hit4", "Hit5", "SwimHit1", "SwimHit2", "SwimHit3", "Death1", "Death2", "Death3", "Death4", "Death5",
|
|
||||||
"DeathKnockDown", "DeathKnockOut", "KnockDown", "KnockOut", "SwimDeath", "SwimDeath2", "SwimDeath3",
|
|
||||||
"SwimDeathKnockDown", "SwimDeathKnockOut", "SwimKnockOut", "SwimKnockDown", "SwimWalkForward",
|
|
||||||
"SwimWalkBack", "SwimWalkLeft", "SwimWalkRight", "SwimRunForward", "SwimRunBack", "SwimRunLeft",
|
|
||||||
"SwimRunRight", "SwimTurnLeft", "SwimTurnRight", "WalkForward", "WalkBack", "WalkLeft", "WalkRight",
|
|
||||||
"TurnLeft", "TurnRight", "RunForward", "RunBack", "RunLeft", "RunRight", "SneakForward", "SneakBack",
|
|
||||||
"SneakLeft", "SneakRight", "Jump", "WalkForwardhh", "WalkBackhh", "WalkLefthh", "WalkRighthh",
|
|
||||||
"TurnLefthh", "TurnRighthh", "RunForwardhh", "RunBackhh", "RunLefthh", "RunRighthh", "SneakForwardhh",
|
|
||||||
"SneakBackhh", "SneakLefthh", "SneakRighthh", "Jumphh", "WalkForward1h", "WalkBack1h", "WalkLeft1h",
|
|
||||||
"WalkRight1h", "TurnLeft1h", "TurnRight1h", "RunForward1h", "RunBack1h", "RunLeft1h", "RunRight1h",
|
|
||||||
"SneakForward1h", "SneakBack1h", "SneakLeft1h", "SneakRight1h", "Jump1h", "WalkForward2c", "WalkBack2c",
|
|
||||||
"WalkLeft2c", "WalkRight2c", "TurnLeft2c", "TurnRight2c", "RunForward2c", "RunBack2c", "RunLeft2c",
|
|
||||||
"RunRight2c", "SneakForward2c", "SneakBack2c", "SneakLeft2c", "SneakRight2c", "Jump2c", "WalkForward2w",
|
|
||||||
"WalkBack2w", "WalkLeft2w", "WalkRight2w", "TurnLeft2w", "TurnRight2w", "RunForward2w", "RunBack2w",
|
|
||||||
"RunLeft2w", "RunRight2w", "SneakForward2w", "SneakBack2w", "SneakLeft2w", "SneakRight2w", "Jump2w",
|
|
||||||
"SpellCast", "SpellTurnLeft", "SpellTurnRight", "Attack1", "Attack2", "Attack3", "SwimAttack1",
|
|
||||||
"SwimAttack2", "SwimAttack3", "HandToHand", "Crossbow", "BowAndArrow", "ThrowWeapon", "WeaponOneHand",
|
|
||||||
"WeaponTwoHand", "WeaponTwoWide", "Shield", "PickProbe", "InventoryHandToHand", "InventoryWeaponOneHand",
|
|
||||||
"InventoryWeaponTwoHand", "InventoryWeaponTwoWide"
|
|
||||||
};
|
|
||||||
|
|
||||||
if (anis.mGroupIndex < (sizeof(animGroups) / sizeof(*animGroups)))
|
|
||||||
{
|
|
||||||
std::string group(animGroups[anis.mGroupIndex]);
|
|
||||||
Misc::StringUtils::lowerCaseInPlace(group);
|
|
||||||
|
|
||||||
ESM::AnimationState::ScriptedAnimation scriptedAnim;
|
|
||||||
scriptedAnim.mGroup = group;
|
|
||||||
scriptedAnim.mTime = anis.mTime;
|
|
||||||
scriptedAnim.mAbsolute = true;
|
|
||||||
// Neither loop count nor queueing seems to be supported by the ess format.
|
|
||||||
scriptedAnim.mLoopCount = std::numeric_limits<size_t>::max();
|
|
||||||
state.mScriptedAnims.push_back(scriptedAnim);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
// TODO: Handle 0xFF index, which seems to be used for finished animations.
|
|
||||||
std::cerr << "unknown animation group index: " << static_cast<unsigned int>(anis.mGroupIndex) << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#include <components/esm/creaturestats.hpp>
|
#include <components/esm/creaturestats.hpp>
|
||||||
#include <components/esm/npcstats.hpp>
|
#include <components/esm/npcstats.hpp>
|
||||||
#include <components/esm/loadskil.hpp>
|
#include <components/esm/loadskil.hpp>
|
||||||
#include <components/esm/animationstate.hpp>
|
|
||||||
|
|
||||||
#include "importacdt.hpp"
|
#include "importacdt.hpp"
|
||||||
|
|
||||||
|
@ -19,8 +18,6 @@ namespace ESSImport
|
||||||
void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats);
|
void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats);
|
||||||
|
|
||||||
void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats);
|
void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats);
|
||||||
|
|
||||||
void convertANIS (const ANIS& anis, ESM::AnimationState& state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#include "converter.hpp"
|
#include "converter.hpp"
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include <osgDB/WriteFile>
|
#include <OgreImage.h>
|
||||||
|
#include <OgreColourValue.h>
|
||||||
|
|
||||||
#include <components/esm/creaturestate.hpp>
|
#include <components/esm/creaturestate.hpp>
|
||||||
#include <components/esm/containerstate.hpp>
|
#include <components/esm/containerstate.hpp>
|
||||||
|
@ -15,14 +15,12 @@
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
void convertImage(char* data, int size, int width, int height, GLenum pf, const std::string& out)
|
void convertImage(char* data, int size, int width, int height, Ogre::PixelFormat pf, const std::string& out)
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::Image> image (new osg::Image);
|
Ogre::Image screenshot;
|
||||||
image->allocateImage(width, height, 1, pf, GL_UNSIGNED_BYTE);
|
Ogre::DataStreamPtr stream (new Ogre::MemoryDataStream(data, size));
|
||||||
memcpy(image->data(), data, size);
|
screenshot.loadRawData(stream, width, height, 1, pf);
|
||||||
image->flipVertical();
|
screenshot.save(out);
|
||||||
|
|
||||||
osgDB::writeImageFile(*image, out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,9 +33,6 @@ namespace
|
||||||
objstate.mCount = 0;
|
objstate.mCount = 0;
|
||||||
convertSCRI(cellref.mSCRI, objstate.mLocals);
|
convertSCRI(cellref.mSCRI, objstate.mLocals);
|
||||||
objstate.mHasLocals = !objstate.mLocals.mVariables.empty();
|
objstate.mHasLocals = !objstate.mLocals.mVariables.empty();
|
||||||
|
|
||||||
if (cellref.mHasANIS)
|
|
||||||
convertANIS(cellref.mANIS, objstate.mAnimationState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isIndexedRefId(const std::string& indexedRefId)
|
bool isIndexedRefId(const std::string& indexedRefId)
|
||||||
|
@ -54,36 +49,6 @@ namespace
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void splitIndexedRefId(const std::string& indexedRefId, int& refIndex, std::string& refId)
|
|
||||||
{
|
|
||||||
std::stringstream stream;
|
|
||||||
stream << std::hex << indexedRefId.substr(indexedRefId.size()-8,8);
|
|
||||||
stream >> refIndex;
|
|
||||||
|
|
||||||
refId = indexedRefId.substr(0,indexedRefId.size()-8);
|
|
||||||
}
|
|
||||||
|
|
||||||
int convertActorId(const std::string& indexedRefId, ESSImport::Context& context)
|
|
||||||
{
|
|
||||||
if (isIndexedRefId(indexedRefId))
|
|
||||||
{
|
|
||||||
int refIndex;
|
|
||||||
std::string refId;
|
|
||||||
splitIndexedRefId(indexedRefId, refIndex, refId);
|
|
||||||
|
|
||||||
auto it = context.mActorIdMap.find(std::make_pair(refIndex, refId));
|
|
||||||
if (it == context.mActorIdMap.end())
|
|
||||||
return -1;
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
else if (indexedRefId == "PlayerSaveGame")
|
|
||||||
{
|
|
||||||
return context.mPlayer.mObject.mCreatureStats.mActorId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ESSImport
|
namespace ESSImport
|
||||||
|
@ -106,20 +71,17 @@ namespace ESSImport
|
||||||
data.resize(esm.getSubSize());
|
data.resize(esm.getSubSize());
|
||||||
esm.getExact(&data[0], data.size());
|
esm.getExact(&data[0], data.size());
|
||||||
|
|
||||||
mGlobalMapImage = new osg::Image;
|
Ogre::DataStreamPtr stream (new Ogre::MemoryDataStream(&data[0], data.size()));
|
||||||
mGlobalMapImage->allocateImage(maph.size, maph.size, 1, GL_RGB, GL_UNSIGNED_BYTE);
|
mGlobalMapImage.loadRawData(stream, maph.size, maph.size, 1, Ogre::PF_BYTE_RGB);
|
||||||
memcpy(mGlobalMapImage->data(), &data[0], data.size());
|
|
||||||
|
|
||||||
// to match openmw size
|
// to match openmw size
|
||||||
// FIXME: filtering?
|
mGlobalMapImage.resize(maph.size*2, maph.size*2, Ogre::Image::FILTER_BILINEAR);
|
||||||
mGlobalMapImage->scaleImage(maph.size*2, maph.size*2, 1, GL_UNSIGNED_BYTE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConvertFMAP::write(ESM::ESMWriter &esm)
|
void ConvertFMAP::write(ESM::ESMWriter &esm)
|
||||||
{
|
{
|
||||||
int numcells = mGlobalMapImage->s() / 18; // NB truncating, doesn't divide perfectly
|
int numcells = mGlobalMapImage.getWidth() / 18; // NB truncating, doesn't divide perfectly
|
||||||
// with the 512x512 map the game has by default
|
// with the 512x512 map the game has by default
|
||||||
int cellSize = mGlobalMapImage->s()/numcells;
|
int cellSize = mGlobalMapImage.getWidth()/numcells;
|
||||||
|
|
||||||
// Note the upper left corner of the (0,0) cell should be at (width/2, height/2)
|
// Note the upper left corner of the (0,0) cell should be at (width/2, height/2)
|
||||||
|
|
||||||
|
@ -128,14 +90,12 @@ namespace ESSImport
|
||||||
mContext->mGlobalMapState.mBounds.mMinY = -(numcells-1)/2;
|
mContext->mGlobalMapState.mBounds.mMinY = -(numcells-1)/2;
|
||||||
mContext->mGlobalMapState.mBounds.mMaxY = numcells/2;
|
mContext->mGlobalMapState.mBounds.mMaxY = numcells/2;
|
||||||
|
|
||||||
osg::ref_ptr<osg::Image> image2 (new osg::Image);
|
Ogre::Image image2;
|
||||||
|
std::vector<Ogre::uint8> data;
|
||||||
int width = cellSize*numcells;
|
int width = cellSize*numcells;
|
||||||
int height = cellSize*numcells;
|
int height = cellSize*numcells;
|
||||||
std::vector<unsigned char> data;
|
|
||||||
data.resize(width*height*4, 0);
|
data.resize(width*height*4, 0);
|
||||||
|
image2.loadDynamicImage(&data[0], width, height, Ogre::PF_BYTE_RGBA);
|
||||||
image2->allocateImage(width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE);
|
|
||||||
memcpy(image2->data(), &data[0], data.size());
|
|
||||||
|
|
||||||
for (std::set<std::pair<int, int> >::const_iterator it = mContext->mExploredCells.begin(); it != mContext->mExploredCells.end(); ++it)
|
for (std::set<std::pair<int, int> >::const_iterator it = mContext->mExploredCells.begin(); it != mContext->mExploredCells.end(); ++it)
|
||||||
{
|
{
|
||||||
|
@ -148,8 +108,8 @@ namespace ESSImport
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int imageLeftSrc = mGlobalMapImage->s()/2;
|
int imageLeftSrc = mGlobalMapImage.getWidth()/2;
|
||||||
int imageTopSrc = mGlobalMapImage->t()/2;
|
int imageTopSrc = mGlobalMapImage.getHeight()/2;
|
||||||
imageLeftSrc += it->first * cellSize;
|
imageLeftSrc += it->first * cellSize;
|
||||||
imageTopSrc -= it->second * cellSize;
|
imageTopSrc -= it->second * cellSize;
|
||||||
int imageLeftDst = width/2;
|
int imageLeftDst = width/2;
|
||||||
|
@ -158,31 +118,13 @@ namespace ESSImport
|
||||||
imageTopDst -= it->second * cellSize;
|
imageTopDst -= it->second * cellSize;
|
||||||
for (int x=0; x<cellSize; ++x)
|
for (int x=0; x<cellSize; ++x)
|
||||||
for (int y=0; y<cellSize; ++y)
|
for (int y=0; y<cellSize; ++y)
|
||||||
{
|
image2.setColourAt(mGlobalMapImage.getColourAt(imageLeftSrc+x, imageTopSrc+y, 0)
|
||||||
unsigned int col = *(unsigned int*)mGlobalMapImage->data(imageLeftSrc+x, imageTopSrc+y, 0);
|
, imageLeftDst+x, imageTopDst+y, 0);
|
||||||
*(unsigned int*)image2->data(imageLeftDst+x, imageTopDst+y, 0) = col;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::stringstream ostream;
|
Ogre::DataStreamPtr encoded = image2.encode("png");
|
||||||
osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("png");
|
mContext->mGlobalMapState.mImageData.resize(encoded->size());
|
||||||
if (!readerwriter)
|
encoded->read(&mContext->mGlobalMapState.mImageData[0], encoded->size());
|
||||||
{
|
|
||||||
std::cerr << "Error: can't write global map image, no png readerwriter found" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
image2->flipVertical();
|
|
||||||
|
|
||||||
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image2, ostream);
|
|
||||||
if (!result.success())
|
|
||||||
{
|
|
||||||
std::cerr << "Error: can't write global map image: " << result.message() << " code " << result.status() << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string outData = ostream.str();
|
|
||||||
mContext->mGlobalMapState.mImageData = std::vector<char>(outData.begin(), outData.end());
|
|
||||||
|
|
||||||
esm.startRecord(ESM::REC_GMAP);
|
esm.startRecord(ESM::REC_GMAP);
|
||||||
mContext->mGlobalMapState.save(esm);
|
mContext->mGlobalMapState.save(esm);
|
||||||
|
@ -192,9 +134,9 @@ namespace ESSImport
|
||||||
void ConvertCell::read(ESM::ESMReader &esm)
|
void ConvertCell::read(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
ESM::Cell cell;
|
ESM::Cell cell;
|
||||||
bool isDeleted = false;
|
std::string id = esm.getHNString("NAME");
|
||||||
|
cell.mName = id;
|
||||||
cell.load(esm, isDeleted, false);
|
cell.load(esm, false);
|
||||||
|
|
||||||
// I wonder what 0x40 does?
|
// I wonder what 0x40 does?
|
||||||
if (cell.isExterior() && cell.mData.mFlags & 0x20)
|
if (cell.isExterior() && cell.mData.mFlags & 0x20)
|
||||||
|
@ -203,7 +145,7 @@ namespace ESSImport
|
||||||
}
|
}
|
||||||
|
|
||||||
// note if the player is in a nameless exterior cell, we will assign the cellId later based on player position
|
// note if the player is in a nameless exterior cell, we will assign the cellId later based on player position
|
||||||
if (cell.mName == mContext->mPlayerCellName)
|
if (id == mContext->mPlayerCellName)
|
||||||
{
|
{
|
||||||
mContext->mPlayer.mCellId = cell.getCellId();
|
mContext->mPlayer.mCellId = cell.getCellId();
|
||||||
}
|
}
|
||||||
|
@ -252,7 +194,7 @@ namespace ESSImport
|
||||||
std::ostringstream filename;
|
std::ostringstream filename;
|
||||||
filename << "fog_" << cell.mData.mX << "_" << cell.mData.mY << ".tga";
|
filename << "fog_" << cell.mData.mX << "_" << cell.mData.mY << ".tga";
|
||||||
|
|
||||||
convertImage((char*)&newcell.mFogOfWar[0], newcell.mFogOfWar.size()*4, 16, 16, GL_RGBA, filename.str());
|
convertImage((char*)&newcell.mFogOfWar[0], newcell.mFogOfWar.size()*4, 16, 16, Ogre::PF_BYTE_RGBA, filename.str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,7 +253,7 @@ namespace ESSImport
|
||||||
if (cell.isExterior())
|
if (cell.isExterior())
|
||||||
mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell;
|
mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell;
|
||||||
else
|
else
|
||||||
mIntCells[cell.mName] = newcell;
|
mIntCells[id] = newcell;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm)
|
void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm)
|
||||||
|
@ -353,9 +295,12 @@ namespace ESSImport
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
std::stringstream stream;
|
||||||
|
stream << std::hex << cellref.mIndexedRefId.substr(cellref.mIndexedRefId.size()-8,8);
|
||||||
int refIndex;
|
int refIndex;
|
||||||
splitIndexedRefId(cellref.mIndexedRefId, refIndex, out.mRefID);
|
stream >> refIndex;
|
||||||
|
|
||||||
|
out.mRefID = cellref.mIndexedRefId.substr(0,cellref.mIndexedRefId.size()-8);
|
||||||
std::string idLower = Misc::StringUtils::lowerCase(out.mRefID);
|
std::string idLower = Misc::StringUtils::lowerCase(out.mRefID);
|
||||||
|
|
||||||
std::map<std::pair<int, std::string>, NPCC>::const_iterator npccIt = mContext->mNpcChanges.find(
|
std::map<std::pair<int, std::string>, NPCC>::const_iterator npccIt = mContext->mNpcChanges.find(
|
||||||
|
@ -375,10 +320,6 @@ namespace ESSImport
|
||||||
convertNpcData(cellref, objstate.mNpcStats);
|
convertNpcData(cellref, objstate.mNpcStats);
|
||||||
convertNPCC(npccIt->second, objstate);
|
convertNPCC(npccIt->second, objstate);
|
||||||
convertCellRef(cellref, objstate);
|
convertCellRef(cellref, objstate);
|
||||||
|
|
||||||
objstate.mCreatureStats.mActorId = mContext->generateActorId();
|
|
||||||
mContext->mActorIdMap.insert(std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId));
|
|
||||||
|
|
||||||
esm.writeHNT ("OBJE", ESM::REC_NPC_);
|
esm.writeHNT ("OBJE", ESM::REC_NPC_);
|
||||||
objstate.save(esm);
|
objstate.save(esm);
|
||||||
continue;
|
continue;
|
||||||
|
@ -415,10 +356,6 @@ namespace ESSImport
|
||||||
convertACSC(cellref.mACSC, objstate.mCreatureStats);
|
convertACSC(cellref.mACSC, objstate.mCreatureStats);
|
||||||
convertCREC(crecIt->second, objstate);
|
convertCREC(crecIt->second, objstate);
|
||||||
convertCellRef(cellref, objstate);
|
convertCellRef(cellref, objstate);
|
||||||
|
|
||||||
objstate.mCreatureStats.mActorId = mContext->generateActorId();
|
|
||||||
mContext->mActorIdMap.insert(std::make_pair(std::make_pair(refIndex, out.mRefID), objstate.mCreatureStats.mActorId));
|
|
||||||
|
|
||||||
esm.writeHNT ("OBJE", ESM::REC_CREA);
|
esm.writeHNT ("OBJE", ESM::REC_CREA);
|
||||||
objstate.save(esm);
|
objstate.save(esm);
|
||||||
continue;
|
continue;
|
||||||
|
@ -449,73 +386,4 @@ namespace ESSImport
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConvertPROJ::read(ESM::ESMReader& esm)
|
|
||||||
{
|
|
||||||
mProj.load(esm);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConvertPROJ::write(ESM::ESMWriter& esm)
|
|
||||||
{
|
|
||||||
for (const PROJ::PNAM& pnam : mProj.mProjectiles)
|
|
||||||
{
|
|
||||||
if (!pnam.isMagic())
|
|
||||||
{
|
|
||||||
ESM::ProjectileState out;
|
|
||||||
convertBaseState(out, pnam);
|
|
||||||
|
|
||||||
out.mBowId = pnam.mBowId.toString();
|
|
||||||
out.mVelocity = pnam.mVelocity;
|
|
||||||
out.mAttackStrength = pnam.mAttackStrength;
|
|
||||||
|
|
||||||
esm.startRecord(ESM::REC_PROJ);
|
|
||||||
out.save(esm);
|
|
||||||
esm.endRecord(ESM::REC_PROJ);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ESM::MagicBoltState out;
|
|
||||||
convertBaseState(out, pnam);
|
|
||||||
|
|
||||||
auto it = std::find_if(mContext->mActiveSpells.begin(), mContext->mActiveSpells.end(),
|
|
||||||
[&pnam](const SPLM::ActiveSpell& spell) -> bool { return spell.mIndex == pnam.mSplmIndex; });
|
|
||||||
|
|
||||||
if (it == mContext->mActiveSpells.end())
|
|
||||||
{
|
|
||||||
std::cerr << "Warning: Skipped conversion for magic projectile \"" << pnam.mArrowId.toString() << "\" (invalid spell link)" << std::endl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
out.mSpellId = it->mSPDT.mId.toString();
|
|
||||||
out.mSpeed = pnam.mSpeed * 0.001f; // not sure where this factor comes from
|
|
||||||
|
|
||||||
esm.startRecord(ESM::REC_MPRJ);
|
|
||||||
out.save(esm);
|
|
||||||
esm.endRecord(ESM::REC_MPRJ);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConvertPROJ::convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam)
|
|
||||||
{
|
|
||||||
base.mId = pnam.mArrowId.toString();
|
|
||||||
base.mPosition = pnam.mPosition;
|
|
||||||
|
|
||||||
osg::Quat orient;
|
|
||||||
orient.makeRotate(osg::Vec3f(0,1,0), pnam.mVelocity);
|
|
||||||
base.mOrientation = orient;
|
|
||||||
|
|
||||||
base.mActorId = convertActorId(pnam.mActorId.toString(), *mContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConvertSPLM::read(ESM::ESMReader& esm)
|
|
||||||
{
|
|
||||||
mSPLM.load(esm);
|
|
||||||
mContext->mActiveSpells = mSPLM.mActiveSpells;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConvertSPLM::write(ESM::ESMWriter& esm)
|
|
||||||
{
|
|
||||||
std::cerr << "Warning: Skipped active spell conversion (not implemented)" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
#ifndef OPENMW_ESSIMPORT_CONVERTER_H
|
#ifndef OPENMW_ESSIMPORT_CONVERTER_H
|
||||||
#define OPENMW_ESSIMPORT_CONVERTER_H
|
#define OPENMW_ESSIMPORT_CONVERTER_H
|
||||||
|
|
||||||
#include <limits>
|
#include <OgreImage.h>
|
||||||
|
|
||||||
#include <osg/Image>
|
|
||||||
#include <osg/ref_ptr>
|
|
||||||
|
|
||||||
#include <components/esm/esmreader.hpp>
|
#include <components/esm/esmreader.hpp>
|
||||||
#include <components/esm/esmwriter.hpp>
|
#include <components/esm/esmwriter.hpp>
|
||||||
|
@ -22,7 +19,6 @@
|
||||||
#include <components/esm/globalscript.hpp>
|
#include <components/esm/globalscript.hpp>
|
||||||
#include <components/esm/queststate.hpp>
|
#include <components/esm/queststate.hpp>
|
||||||
#include <components/esm/stolenitems.hpp>
|
#include <components/esm/stolenitems.hpp>
|
||||||
#include <components/esm/projectilestate.hpp>
|
|
||||||
|
|
||||||
#include "importcrec.hpp"
|
#include "importcrec.hpp"
|
||||||
#include "importcntc.hpp"
|
#include "importcntc.hpp"
|
||||||
|
@ -36,8 +32,6 @@
|
||||||
#include "importques.hpp"
|
#include "importques.hpp"
|
||||||
#include "importjour.hpp"
|
#include "importjour.hpp"
|
||||||
#include "importscpt.hpp"
|
#include "importscpt.hpp"
|
||||||
#include "importproj.h"
|
|
||||||
#include "importsplm.h"
|
|
||||||
|
|
||||||
#include "convertacdt.hpp"
|
#include "convertacdt.hpp"
|
||||||
#include "convertnpcc.hpp"
|
#include "convertnpcc.hpp"
|
||||||
|
@ -57,8 +51,6 @@ public:
|
||||||
|
|
||||||
void setContext(Context& context) { mContext = &context; }
|
void setContext(Context& context) { mContext = &context; }
|
||||||
|
|
||||||
/// @note The load method of ESM records accept the deleted flag as a parameter.
|
|
||||||
/// I don't know can the DELE sub-record appear in saved games, so the deleted flag will be ignored.
|
|
||||||
virtual void read(ESM::ESMReader& esm)
|
virtual void read(ESM::ESMReader& esm)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -83,11 +75,10 @@ public:
|
||||||
|
|
||||||
virtual void read(ESM::ESMReader& esm)
|
virtual void read(ESM::ESMReader& esm)
|
||||||
{
|
{
|
||||||
|
std::string id = esm.getHNString("NAME");
|
||||||
T record;
|
T record;
|
||||||
bool isDeleted = false;
|
record.load(esm);
|
||||||
|
mRecords[id] = record;
|
||||||
record.load(esm, isDeleted);
|
|
||||||
mRecords[record.mId] = record;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void write(ESM::ESMWriter& esm)
|
virtual void write(ESM::ESMWriter& esm)
|
||||||
|
@ -95,6 +86,7 @@ public:
|
||||||
for (typename std::map<std::string, T>::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it)
|
for (typename std::map<std::string, T>::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it)
|
||||||
{
|
{
|
||||||
esm.startRecord(T::sRecordId);
|
esm.startRecord(T::sRecordId);
|
||||||
|
esm.writeHNString("NAME", it->first);
|
||||||
it->second.save(esm);
|
it->second.save(esm);
|
||||||
esm.endRecord(T::sRecordId);
|
esm.endRecord(T::sRecordId);
|
||||||
}
|
}
|
||||||
|
@ -110,21 +102,20 @@ public:
|
||||||
virtual void read(ESM::ESMReader &esm)
|
virtual void read(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
ESM::NPC npc;
|
ESM::NPC npc;
|
||||||
bool isDeleted = false;
|
std::string id = esm.getHNString("NAME");
|
||||||
|
npc.load(esm);
|
||||||
npc.load(esm, isDeleted);
|
if (id != "player")
|
||||||
if (npc.mId != "player")
|
|
||||||
{
|
{
|
||||||
// Handles changes to the NPC struct, but since there is no index here
|
// Handles changes to the NPC struct, but since there is no index here
|
||||||
// it will apply to ALL instances of the class. seems to be the reason for the
|
// it will apply to ALL instances of the class. seems to be the reason for the
|
||||||
// "feature" in MW where changing AI settings of one guard will change it for all guards of that refID.
|
// "feature" in MW where changing AI settings of one guard will change it for all guards of that refID.
|
||||||
mContext->mNpcs[Misc::StringUtils::lowerCase(npc.mId)] = npc;
|
mContext->mNpcs[Misc::StringUtils::lowerCase(id)] = npc;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt.mLevel;
|
mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt52.mLevel;
|
||||||
mContext->mPlayerBase = npc;
|
mContext->mPlayerBase = npc;
|
||||||
ESM::SpellState::SpellParams empty;
|
std::map<const int, float> empty;
|
||||||
// FIXME: player start spells and birthsign spells aren't listed here,
|
// FIXME: player start spells and birthsign spells aren't listed here,
|
||||||
// need to fix openmw to account for this
|
// need to fix openmw to account for this
|
||||||
for (std::vector<std::string>::const_iterator it = npc.mSpells.mList.begin(); it != npc.mSpells.mList.end(); ++it)
|
for (std::vector<std::string>::const_iterator it = npc.mSpells.mList.begin(); it != npc.mSpells.mList.end(); ++it)
|
||||||
|
@ -148,10 +139,9 @@ public:
|
||||||
{
|
{
|
||||||
// See comment in ConvertNPC
|
// See comment in ConvertNPC
|
||||||
ESM::Creature creature;
|
ESM::Creature creature;
|
||||||
bool isDeleted = false;
|
std::string id = esm.getHNString("NAME");
|
||||||
|
creature.load(esm);
|
||||||
creature.load(esm, isDeleted);
|
mContext->mCreatures[Misc::StringUtils::lowerCase(id)] = creature;
|
||||||
mContext->mCreatures[Misc::StringUtils::lowerCase(creature.mId)] = creature;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -164,19 +154,18 @@ class ConvertGlobal : public DefaultConverter<ESM::Global>
|
||||||
public:
|
public:
|
||||||
virtual void read(ESM::ESMReader &esm)
|
virtual void read(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
|
std::string id = esm.getHNString("NAME");
|
||||||
ESM::Global global;
|
ESM::Global global;
|
||||||
bool isDeleted = false;
|
global.load(esm);
|
||||||
|
if (Misc::StringUtils::ciEqual(id, "gamehour"))
|
||||||
global.load(esm, isDeleted);
|
|
||||||
if (Misc::StringUtils::ciEqual(global.mId, "gamehour"))
|
|
||||||
mContext->mHour = global.mValue.getFloat();
|
mContext->mHour = global.mValue.getFloat();
|
||||||
if (Misc::StringUtils::ciEqual(global.mId, "day"))
|
if (Misc::StringUtils::ciEqual(id, "day"))
|
||||||
mContext->mDay = global.mValue.getInteger();
|
mContext->mDay = global.mValue.getInteger();
|
||||||
if (Misc::StringUtils::ciEqual(global.mId, "month"))
|
if (Misc::StringUtils::ciEqual(id, "month"))
|
||||||
mContext->mMonth = global.mValue.getInteger();
|
mContext->mMonth = global.mValue.getInteger();
|
||||||
if (Misc::StringUtils::ciEqual(global.mId, "year"))
|
if (Misc::StringUtils::ciEqual(id, "year"))
|
||||||
mContext->mYear = global.mValue.getInteger();
|
mContext->mYear = global.mValue.getInteger();
|
||||||
mRecords[global.mId] = global;
|
mRecords[id] = global;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -185,14 +174,14 @@ class ConvertClass : public DefaultConverter<ESM::Class>
|
||||||
public:
|
public:
|
||||||
virtual void read(ESM::ESMReader &esm)
|
virtual void read(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
|
std::string id = esm.getHNString("NAME");
|
||||||
ESM::Class class_;
|
ESM::Class class_;
|
||||||
bool isDeleted = false;
|
class_.load(esm);
|
||||||
|
|
||||||
class_.load(esm, isDeleted);
|
if (id == "NEWCLASSID_CHARGEN")
|
||||||
if (class_.mId == "NEWCLASSID_CHARGEN")
|
|
||||||
mContext->mCustomPlayerClassName = class_.mName;
|
mContext->mCustomPlayerClassName = class_.mName;
|
||||||
|
|
||||||
mRecords[class_.mId] = class_;
|
mRecords[id] = class_;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -201,14 +190,13 @@ class ConvertBook : public DefaultConverter<ESM::Book>
|
||||||
public:
|
public:
|
||||||
virtual void read(ESM::ESMReader &esm)
|
virtual void read(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
|
std::string id = esm.getHNString("NAME");
|
||||||
ESM::Book book;
|
ESM::Book book;
|
||||||
bool isDeleted = false;
|
book.load(esm);
|
||||||
|
if (book.mData.mSkillID == -1)
|
||||||
|
mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(id));
|
||||||
|
|
||||||
book.load(esm, isDeleted);
|
mRecords[id] = book;
|
||||||
if (book.mData.mSkillId == -1)
|
|
||||||
mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(book.mId));
|
|
||||||
|
|
||||||
mRecords[book.mId] = book;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -274,34 +262,23 @@ private:
|
||||||
class ConvertPCDT : public Converter
|
class ConvertPCDT : public Converter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ConvertPCDT()
|
ConvertPCDT() : mFirstPersonCam(true) {}
|
||||||
: mFirstPersonCam(true),
|
|
||||||
mTeleportingEnabled(true),
|
|
||||||
mLevitationEnabled(true)
|
|
||||||
{}
|
|
||||||
|
|
||||||
virtual void read(ESM::ESMReader &esm)
|
virtual void read(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
PCDT pcdt;
|
PCDT pcdt;
|
||||||
pcdt.load(esm);
|
pcdt.load(esm);
|
||||||
|
|
||||||
convertPCDT(pcdt, mContext->mPlayer, mContext->mDialogueState.mKnownTopics, mFirstPersonCam, mTeleportingEnabled, mLevitationEnabled, mContext->mControlsState);
|
convertPCDT(pcdt, mContext->mPlayer, mContext->mDialogueState.mKnownTopics, mFirstPersonCam);
|
||||||
}
|
}
|
||||||
virtual void write(ESM::ESMWriter &esm)
|
virtual void write(ESM::ESMWriter &esm)
|
||||||
{
|
{
|
||||||
esm.startRecord(ESM::REC_ENAB);
|
|
||||||
esm.writeHNT("TELE", mTeleportingEnabled);
|
|
||||||
esm.writeHNT("LEVT", mLevitationEnabled);
|
|
||||||
esm.endRecord(ESM::REC_ENAB);
|
|
||||||
|
|
||||||
esm.startRecord(ESM::REC_CAM_);
|
esm.startRecord(ESM::REC_CAM_);
|
||||||
esm.writeHNT("FIRS", mFirstPersonCam);
|
esm.writeHNT("FIRS", mFirstPersonCam);
|
||||||
esm.endRecord(ESM::REC_CAM_);
|
esm.endRecord(ESM::REC_CAM_);
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
bool mFirstPersonCam;
|
bool mFirstPersonCam;
|
||||||
bool mTeleportingEnabled;
|
|
||||||
bool mLevitationEnabled;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ConvertCNTC : public Converter
|
class ConvertCNTC : public Converter
|
||||||
|
@ -334,7 +311,7 @@ public:
|
||||||
virtual void write(ESM::ESMWriter &esm);
|
virtual void write(ESM::ESMWriter &esm);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
osg::ref_ptr<osg::Image> mGlobalMapImage;
|
Ogre::Image mGlobalMapImage;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ConvertCell : public Converter
|
class ConvertCell : public Converter
|
||||||
|
@ -391,12 +368,11 @@ class ConvertFACT : public Converter
|
||||||
public:
|
public:
|
||||||
virtual void read(ESM::ESMReader& esm)
|
virtual void read(ESM::ESMReader& esm)
|
||||||
{
|
{
|
||||||
|
std::string id = esm.getHNString("NAME");
|
||||||
ESM::Faction faction;
|
ESM::Faction faction;
|
||||||
bool isDeleted = false;
|
faction.load(esm);
|
||||||
|
|
||||||
faction.load(esm, isDeleted);
|
|
||||||
std::string id = Misc::StringUtils::lowerCase(faction.mId);
|
|
||||||
|
|
||||||
|
Misc::StringUtils::toLower(id);
|
||||||
for (std::map<std::string, int>::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it)
|
for (std::map<std::string, int>::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it)
|
||||||
{
|
{
|
||||||
std::string faction2 = Misc::StringUtils::lowerCase(it->first);
|
std::string faction2 = Misc::StringUtils::lowerCase(it->first);
|
||||||
|
@ -412,7 +388,7 @@ public:
|
||||||
virtual void read(ESM::ESMReader &esm)
|
virtual void read(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
std::string itemid = esm.getHNString("NAME");
|
std::string itemid = esm.getHNString("NAME");
|
||||||
Misc::StringUtils::lowerCaseInPlace(itemid);
|
Misc::StringUtils::toLower(itemid);
|
||||||
|
|
||||||
while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM"))
|
while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM"))
|
||||||
{
|
{
|
||||||
|
@ -528,40 +504,60 @@ class ConvertGAME : public Converter
|
||||||
public:
|
public:
|
||||||
ConvertGAME() : mHasGame(false) {}
|
ConvertGAME() : mHasGame(false) {}
|
||||||
|
|
||||||
|
std::string toString(int weatherId)
|
||||||
|
{
|
||||||
|
switch (weatherId)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return "clear";
|
||||||
|
case 1:
|
||||||
|
return "cloudy";
|
||||||
|
case 2:
|
||||||
|
return "foggy";
|
||||||
|
case 3:
|
||||||
|
return "overcast";
|
||||||
|
case 4:
|
||||||
|
return "rain";
|
||||||
|
case 5:
|
||||||
|
return "thunderstorm";
|
||||||
|
case 6:
|
||||||
|
return "ashstorm";
|
||||||
|
case 7:
|
||||||
|
return "blight";
|
||||||
|
case 8:
|
||||||
|
return "snow";
|
||||||
|
case 9:
|
||||||
|
return "blizzard";
|
||||||
|
case -1:
|
||||||
|
return "";
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
std::stringstream error;
|
||||||
|
error << "unknown weather id: " << weatherId;
|
||||||
|
throw std::runtime_error(error.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
virtual void read(ESM::ESMReader &esm)
|
virtual void read(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
mGame.load(esm);
|
mGame.load(esm);
|
||||||
mHasGame = true;
|
mHasGame = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int validateWeatherID(int weatherID)
|
|
||||||
{
|
|
||||||
if(weatherID >= -1 && weatherID < 10)
|
|
||||||
{
|
|
||||||
return weatherID;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::stringstream error;
|
|
||||||
error << "Invalid weather ID:" << weatherID << std::endl;
|
|
||||||
throw std::runtime_error(error.str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void write(ESM::ESMWriter &esm)
|
virtual void write(ESM::ESMWriter &esm)
|
||||||
{
|
{
|
||||||
if (!mHasGame)
|
if (!mHasGame)
|
||||||
return;
|
return;
|
||||||
esm.startRecord(ESM::REC_WTHR);
|
esm.startRecord(ESM::REC_WTHR);
|
||||||
ESM::WeatherState weather;
|
ESM::WeatherState weather;
|
||||||
weather.mTimePassed = 0.0f;
|
weather.mCurrentWeather = toString(mGame.mGMDT.mCurrentWeather);
|
||||||
weather.mFastForward = false;
|
weather.mNextWeather = toString(mGame.mGMDT.mNextWeather);
|
||||||
weather.mWeatherUpdateTime = mGame.mGMDT.mTimeOfNextTransition - mContext->mHour;
|
weather.mRemainingTransitionTime = mGame.mGMDT.mWeatherTransition/100.f*(0.015f*24*3600);
|
||||||
weather.mTransitionFactor = 1 - (mGame.mGMDT.mWeatherTransition / 100.0f);
|
weather.mHour = mContext->mHour;
|
||||||
weather.mCurrentWeather = validateWeatherID(mGame.mGMDT.mCurrentWeather);
|
weather.mWindSpeed = 0.f;
|
||||||
weather.mNextWeather = validateWeatherID(mGame.mGMDT.mNextWeather);
|
weather.mTimePassed = 0.0;
|
||||||
weather.mQueuedWeather = -1;
|
weather.mFirstUpdate = false;
|
||||||
// TODO: Determine how ModRegion modifiers are saved in Morrowind.
|
|
||||||
weather.save(esm);
|
weather.save(esm);
|
||||||
esm.endRecord(ESM::REC_WTHR);
|
esm.endRecord(ESM::REC_WTHR);
|
||||||
}
|
}
|
||||||
|
@ -596,27 +592,6 @@ private:
|
||||||
std::vector<ESM::GlobalScript> mScripts;
|
std::vector<ESM::GlobalScript> mScripts;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Projectile converter
|
|
||||||
class ConvertPROJ : public Converter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual int getStage() override { return 2; }
|
|
||||||
virtual void read(ESM::ESMReader& esm) override;
|
|
||||||
virtual void write(ESM::ESMWriter& esm) override;
|
|
||||||
private:
|
|
||||||
void convertBaseState(ESM::BaseProjectileState& base, const PROJ::PNAM& pnam);
|
|
||||||
PROJ mProj;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ConvertSPLM : public Converter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual void read(ESM::ESMReader& esm) override;
|
|
||||||
virtual void write(ESM::ESMWriter& esm) override;
|
|
||||||
private:
|
|
||||||
SPLM mSPLM;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "convertinventory.hpp"
|
#include "convertinventory.hpp"
|
||||||
|
|
||||||
#include <components/misc/stringops.hpp>
|
#include <components/misc/stringops.hpp>
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
namespace ESSImport
|
namespace ESSImport
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include "convertplayer.hpp"
|
#include "convertplayer.hpp"
|
||||||
|
|
||||||
#include <components/misc/stringops.hpp>
|
|
||||||
|
|
||||||
namespace ESSImport
|
namespace ESSImport
|
||||||
{
|
{
|
||||||
|
|
||||||
void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector<std::string>& outDialogueTopics, bool& firstPersonCam, bool& teleportingEnabled, bool& levitationEnabled, ESM::ControlsState& controls)
|
void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector<std::string>& outDialogueTopics, bool& firstPersonCam)
|
||||||
{
|
{
|
||||||
out.mBirthsign = pcdt.mBirthsign;
|
out.mBirthsign = pcdt.mBirthsign;
|
||||||
out.mObject.mNpcStats.mBounty = pcdt.mBounty;
|
out.mObject.mNpcStats.mBounty = pcdt.mBounty;
|
||||||
|
@ -17,72 +15,24 @@ namespace ESSImport
|
||||||
faction.mReputation = it->mReputation;
|
faction.mReputation = it->mReputation;
|
||||||
out.mObject.mNpcStats.mFactions[Misc::StringUtils::lowerCase(it->mFactionName.toString())] = faction;
|
out.mObject.mNpcStats.mFactions[Misc::StringUtils::lowerCase(it->mFactionName.toString())] = faction;
|
||||||
}
|
}
|
||||||
for (int i=0; i<3; ++i)
|
|
||||||
out.mObject.mNpcStats.mSpecIncreases[i] = pcdt.mPNAM.mSpecIncreases[i];
|
|
||||||
for (int i=0; i<8; ++i)
|
for (int i=0; i<8; ++i)
|
||||||
out.mObject.mNpcStats.mSkillIncrease[i] = pcdt.mPNAM.mSkillIncreases[i];
|
out.mObject.mNpcStats.mSkillIncrease[i] = pcdt.mPNAM.mSkillIncreases[i];
|
||||||
for (int i=0; i<27; ++i)
|
for (int i=0; i<27; ++i)
|
||||||
out.mObject.mNpcStats.mSkills[i].mProgress = pcdt.mPNAM.mSkillProgress[i];
|
out.mObject.mNpcStats.mSkills[i].mRegular.mProgress = pcdt.mPNAM.mSkillProgress[i];
|
||||||
out.mObject.mNpcStats.mLevelProgress = pcdt.mPNAM.mLevelProgress;
|
out.mObject.mNpcStats.mLevelProgress = pcdt.mPNAM.mLevelProgress;
|
||||||
|
|
||||||
if (pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_WeaponDrawn)
|
if (pcdt.mPNAM.mDrawState & PCDT::DrawState_Weapon)
|
||||||
out.mObject.mCreatureStats.mDrawState = 1;
|
out.mObject.mCreatureStats.mDrawState = 1;
|
||||||
if (pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_SpellDrawn)
|
if (pcdt.mPNAM.mDrawState & PCDT::DrawState_Spell)
|
||||||
out.mObject.mCreatureStats.mDrawState = 2;
|
out.mObject.mCreatureStats.mDrawState = 2;
|
||||||
|
|
||||||
firstPersonCam = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_ThirdPerson);
|
firstPersonCam = (pcdt.mPNAM.mCameraState == PCDT::CameraState_FirstPerson);
|
||||||
teleportingEnabled = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_TeleportingDisabled);
|
|
||||||
levitationEnabled = !(pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_LevitationDisabled);
|
|
||||||
|
|
||||||
for (std::vector<std::string>::const_iterator it = pcdt.mKnownDialogueTopics.begin();
|
for (std::vector<std::string>::const_iterator it = pcdt.mKnownDialogueTopics.begin();
|
||||||
it != pcdt.mKnownDialogueTopics.end(); ++it)
|
it != pcdt.mKnownDialogueTopics.end(); ++it)
|
||||||
{
|
{
|
||||||
outDialogueTopics.push_back(Misc::StringUtils::lowerCase(*it));
|
outDialogueTopics.push_back(Misc::StringUtils::lowerCase(*it));
|
||||||
}
|
}
|
||||||
|
|
||||||
controls.mViewSwitchDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_ViewSwitchDisabled;
|
|
||||||
controls.mControlsDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_ControlsDisabled;
|
|
||||||
controls.mJumpingDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_JumpingDisabled;
|
|
||||||
controls.mLookingDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_LookingDisabled;
|
|
||||||
controls.mVanityModeDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_VanityModeDisabled;
|
|
||||||
controls.mWeaponDrawingDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_WeaponDrawingDisabled;
|
|
||||||
controls.mSpellDrawingDisabled = pcdt.mPNAM.mPlayerFlags & PCDT::PlayerFlags_SpellDrawingDisabled;
|
|
||||||
|
|
||||||
if (pcdt.mHasMark)
|
|
||||||
{
|
|
||||||
out.mHasMark = 1;
|
|
||||||
|
|
||||||
const PCDT::PNAM::MarkLocation& mark = pcdt.mPNAM.mMarkLocation;
|
|
||||||
|
|
||||||
ESM::CellId cell;
|
|
||||||
cell.mWorldspace = ESM::CellId::sDefaultWorldspace;
|
|
||||||
cell.mPaged = true;
|
|
||||||
|
|
||||||
cell.mIndex.mX = mark.mCellX;
|
|
||||||
cell.mIndex.mY = mark.mCellY;
|
|
||||||
|
|
||||||
// TODO: Figure out a better way to detect interiors. (0, 0) is a valid exterior cell.
|
|
||||||
if (mark.mCellX == 0 && mark.mCellY == 0)
|
|
||||||
{
|
|
||||||
cell.mWorldspace = pcdt.mMNAM;
|
|
||||||
cell.mPaged = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
out.mMarkedCell = cell;
|
|
||||||
out.mMarkedPosition.pos[0] = mark.mX;
|
|
||||||
out.mMarkedPosition.pos[1] = mark.mY;
|
|
||||||
out.mMarkedPosition.pos[2] = mark.mZ;
|
|
||||||
out.mMarkedPosition.rot[0] = out.mMarkedPosition.rot[1] = 0.0f;
|
|
||||||
out.mMarkedPosition.rot[2] = mark.mRotZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pcdt.mHasENAM)
|
|
||||||
{
|
|
||||||
const int cellSize = 8192;
|
|
||||||
out.mLastKnownExteriorPosition[0] = (pcdt.mENAM.mCellX + 0.5f) * cellSize;
|
|
||||||
out.mLastKnownExteriorPosition[1] = (pcdt.mENAM.mCellY + 0.5f) * cellSize;
|
|
||||||
out.mLastKnownExteriorPosition[2] = 0.0f;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,11 @@
|
||||||
#include "importplayer.hpp"
|
#include "importplayer.hpp"
|
||||||
|
|
||||||
#include <components/esm/player.hpp>
|
#include <components/esm/player.hpp>
|
||||||
#include <components/esm/controlsstate.hpp>
|
|
||||||
|
|
||||||
namespace ESSImport
|
namespace ESSImport
|
||||||
{
|
{
|
||||||
|
|
||||||
void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector<std::string>& outDialogueTopics, bool& firstPersonCam, bool& teleportingEnabled, bool& levitationEnabled, ESM::ControlsState& controls);
|
void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector<std::string>& outDialogueTopics, bool& firstPersonCam);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,21 +10,7 @@ namespace ESSImport
|
||||||
void ActorData::load(ESM::ESMReader &esm)
|
void ActorData::load(ESM::ESMReader &esm)
|
||||||
{
|
{
|
||||||
if (esm.isNextSub("ACTN"))
|
if (esm.isNextSub("ACTN"))
|
||||||
{
|
|
||||||
/*
|
|
||||||
Activation flags:
|
|
||||||
ActivationFlag_UseEnabled = 1
|
|
||||||
ActivationFlag_OnActivate = 2
|
|
||||||
ActivationFlag_OnDeath = 10h
|
|
||||||
ActivationFlag_OnKnockout = 20h
|
|
||||||
ActivationFlag_OnMurder = 40h
|
|
||||||
ActivationFlag_DoorOpening = 100h
|
|
||||||
ActivationFlag_DoorClosing = 200h
|
|
||||||
ActivationFlag_DoorJammedOpening = 400h
|
|
||||||
ActivationFlag_DoorJammedClosing = 800h
|
|
||||||
*/
|
|
||||||
esm.skipHSub();
|
esm.skipHSub();
|
||||||
}
|
|
||||||
|
|
||||||
if (esm.isNextSub("STPR"))
|
if (esm.isNextSub("STPR"))
|
||||||
esm.skipHSub();
|
esm.skipHSub();
|
||||||
|
@ -32,8 +18,7 @@ namespace ESSImport
|
||||||
if (esm.isNextSub("MNAM"))
|
if (esm.isNextSub("MNAM"))
|
||||||
esm.skipHSub();
|
esm.skipHSub();
|
||||||
|
|
||||||
bool isDeleted = false;
|
ESM::CellRef::loadData(esm);
|
||||||
ESM::CellRef::loadData(esm, isDeleted);
|
|
||||||
|
|
||||||
mHasACDT = false;
|
mHasACDT = false;
|
||||||
if (esm.isNextSub("ACDT"))
|
if (esm.isNextSub("ACDT"))
|
||||||
|
@ -75,7 +60,7 @@ namespace ESSImport
|
||||||
// unsure at which point between TGTN and CRED
|
// unsure at which point between TGTN and CRED
|
||||||
if (esm.isNextSub("AADT"))
|
if (esm.isNextSub("AADT"))
|
||||||
{
|
{
|
||||||
// occurred when a creature was in the middle of its attack, 44 bytes
|
// occured when a creature was in the middle of its attack, 44 bytes
|
||||||
esm.skipHSub();
|
esm.skipHSub();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,13 +108,8 @@ namespace ESSImport
|
||||||
|
|
||||||
if (esm.isNextSub("ND3D"))
|
if (esm.isNextSub("ND3D"))
|
||||||
esm.skipHSub();
|
esm.skipHSub();
|
||||||
|
|
||||||
mHasANIS = false;
|
|
||||||
if (esm.isNextSub("ANIS"))
|
if (esm.isNextSub("ANIS"))
|
||||||
{
|
esm.skipHSub();
|
||||||
mHasANIS = true;
|
|
||||||
esm.getHT(mANIS);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,12 +55,6 @@ namespace ESSImport
|
||||||
unsigned char mCorpseClearCountdown; // hours?
|
unsigned char mCorpseClearCountdown; // hours?
|
||||||
unsigned char mUnknown3[71];
|
unsigned char mUnknown3[71];
|
||||||
};
|
};
|
||||||
struct ANIS
|
|
||||||
{
|
|
||||||
unsigned char mGroupIndex;
|
|
||||||
unsigned char mUnknown[3];
|
|
||||||
float mTime;
|
|
||||||
};
|
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
struct ActorData : public ESM::CellRef
|
struct ActorData : public ESM::CellRef
|
||||||
|
@ -83,9 +77,6 @@ namespace ESSImport
|
||||||
|
|
||||||
SCRI mSCRI;
|
SCRI mSCRI;
|
||||||
|
|
||||||
bool mHasANIS;
|
|
||||||
ANIS mANIS; // scripted animation state
|
|
||||||
|
|
||||||
void load(ESM::ESMReader& esm);
|
void load(ESM::ESMReader& esm);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
#include "importer.hpp"
|
#include "importer.hpp"
|
||||||
|
#include <boost/shared_ptr.hpp>
|
||||||
|
|
||||||
#include <iomanip>
|
#include <OgreRoot.h>
|
||||||
|
|
||||||
#include <boost/filesystem/fstream.hpp>
|
|
||||||
|
|
||||||
#include <osgDB/ReadFile>
|
|
||||||
#include <osg/ImageUtils>
|
|
||||||
|
|
||||||
#include <components/esm/esmreader.hpp>
|
#include <components/esm/esmreader.hpp>
|
||||||
#include <components/esm/esmwriter.hpp>
|
#include <components/esm/esmwriter.hpp>
|
||||||
|
@ -36,49 +32,13 @@ namespace
|
||||||
|
|
||||||
void writeScreenshot(const ESM::Header& fileHeader, ESM::SavedGame& out)
|
void writeScreenshot(const ESM::Header& fileHeader, ESM::SavedGame& out)
|
||||||
{
|
{
|
||||||
if (fileHeader.mSCRS.size() != 128*128*4)
|
Ogre::Image screenshot;
|
||||||
{
|
std::vector<unsigned char> screenshotData = fileHeader.mSCRS; // MemoryDataStream doesn't work with const data :(
|
||||||
std::cerr << "Error: unexpected screenshot size " << std::endl;
|
Ogre::DataStreamPtr screenshotStream (new Ogre::MemoryDataStream(&screenshotData[0], screenshotData.size()));
|
||||||
return;
|
screenshot.loadRawData(screenshotStream, 128, 128, 1, Ogre::PF_BYTE_BGRA);
|
||||||
}
|
Ogre::DataStreamPtr encoded = screenshot.encode("jpg");
|
||||||
|
out.mScreenshot.resize(encoded->size());
|
||||||
osg::ref_ptr<osg::Image> image (new osg::Image);
|
encoded->read(&out.mScreenshot[0], encoded->size());
|
||||||
image->allocateImage(128, 128, 1, GL_RGB, GL_UNSIGNED_BYTE);
|
|
||||||
|
|
||||||
// need to convert pixel format from BGRA to RGB as the jpg readerwriter doesn't support it otherwise
|
|
||||||
std::vector<unsigned char>::const_iterator it = fileHeader.mSCRS.begin();
|
|
||||||
for (int y=0; y<128; ++y)
|
|
||||||
{
|
|
||||||
for (int x=0; x<128; ++x)
|
|
||||||
{
|
|
||||||
assert(image->data(x,y));
|
|
||||||
*(image->data(x,y)+2) = *it++;
|
|
||||||
*(image->data(x,y)+1) = *it++;
|
|
||||||
*image->data(x,y) = *it++;
|
|
||||||
++it; // skip alpha
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
image->flipVertical();
|
|
||||||
|
|
||||||
std::stringstream ostream;
|
|
||||||
|
|
||||||
osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg");
|
|
||||||
if (!readerwriter)
|
|
||||||
{
|
|
||||||
std::cerr << "Error: can't write screenshot: no jpg readerwriter found" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*image, ostream);
|
|
||||||
if (!result.success())
|
|
||||||
{
|
|
||||||
std::cerr << "Error: can't write screenshot: " << result.message() << " code " << result.status() << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string data = ostream.str();
|
|
||||||
out.mScreenshot = std::vector<char>(data.begin(), data.end());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -167,9 +127,7 @@ namespace ESSImport
|
||||||
|
|
||||||
if (i >= file2.mRecords.size())
|
if (i >= file2.mRecords.size())
|
||||||
{
|
{
|
||||||
std::ios::fmtflags f(std::cout.flags());
|
|
||||||
std::cout << "Record in file1 not present in file2: (1) 0x" << std::hex << rec.mFileOffset << std::endl;
|
std::cout << "Record in file1 not present in file2: (1) 0x" << std::hex << rec.mFileOffset << std::endl;
|
||||||
std::cout.flags(f);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,9 +135,7 @@ namespace ESSImport
|
||||||
|
|
||||||
if (rec.mName != rec2.mName)
|
if (rec.mName != rec2.mName)
|
||||||
{
|
{
|
||||||
std::ios::fmtflags f(std::cout.flags());
|
|
||||||
std::cout << "Different record name at (2) 0x" << std::hex << rec2.mFileOffset << std::endl;
|
std::cout << "Different record name at (2) 0x" << std::hex << rec2.mFileOffset << std::endl;
|
||||||
std::cout.flags(f);
|
|
||||||
return; // TODO: try to recover
|
return; // TODO: try to recover
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,9 +146,7 @@ namespace ESSImport
|
||||||
|
|
||||||
if (j >= rec2.mSubrecords.size())
|
if (j >= rec2.mSubrecords.size())
|
||||||
{
|
{
|
||||||
std::ios::fmtflags f(std::cout.flags());
|
|
||||||
std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset << std::endl;
|
std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset << std::endl;
|
||||||
std::cout.flags(f);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,10 +154,8 @@ namespace ESSImport
|
||||||
|
|
||||||
if (sub.mName != sub2.mName)
|
if (sub.mName != sub2.mName)
|
||||||
{
|
{
|
||||||
std::ios::fmtflags f(std::cout.flags());
|
|
||||||
std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset
|
std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset
|
||||||
<< " (2) 0x" << sub2.mFileOffset << std::endl;
|
<< " (2) 0x" << sub2.mFileOffset << std::endl;
|
||||||
std::cout.flags(f);
|
|
||||||
break; // TODO: try to recover
|
break; // TODO: try to recover
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,8 +164,6 @@ namespace ESSImport
|
||||||
if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end())
|
if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
std::ios::fmtflags f(std::cout.flags());
|
|
||||||
|
|
||||||
std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset
|
std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset
|
||||||
<< " (2) 0x" << sub2.mFileOffset << std::endl;
|
<< " (2) 0x" << sub2.mFileOffset << std::endl;
|
||||||
|
|
||||||
|
@ -246,7 +196,6 @@ namespace ESSImport
|
||||||
std::cout << "\033[0m";
|
std::cout << "\033[0m";
|
||||||
}
|
}
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
std::cout.flags(f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,6 +203,10 @@ namespace ESSImport
|
||||||
|
|
||||||
void Importer::run()
|
void Importer::run()
|
||||||
{
|
{
|
||||||
|
// construct Ogre::Root to gain access to image codecs
|
||||||
|
Ogre::LogManager logman;
|
||||||
|
Ogre::Root root;
|
||||||
|
|
||||||
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding));
|
ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding));
|
||||||
ESM::ESMReader esm;
|
ESM::ESMReader esm;
|
||||||
esm.open(mEssFile);
|
esm.open(mEssFile);
|
||||||
|
@ -271,50 +224,48 @@ namespace ESSImport
|
||||||
const unsigned int recSTLN = ESM::FourCC<'S','T','L','N'>::value;
|
const unsigned int recSTLN = ESM::FourCC<'S','T','L','N'>::value;
|
||||||
const unsigned int recGAME = ESM::FourCC<'G','A','M','E'>::value;
|
const unsigned int recGAME = ESM::FourCC<'G','A','M','E'>::value;
|
||||||
const unsigned int recJOUR = ESM::FourCC<'J','O','U','R'>::value;
|
const unsigned int recJOUR = ESM::FourCC<'J','O','U','R'>::value;
|
||||||
const unsigned int recSPLM = ESM::FourCC<'S','P','L','M'>::value;
|
|
||||||
|
|
||||||
std::map<unsigned int, std::shared_ptr<Converter> > converters;
|
std::map<unsigned int, boost::shared_ptr<Converter> > converters;
|
||||||
converters[ESM::REC_GLOB] = std::shared_ptr<Converter>(new ConvertGlobal());
|
converters[ESM::REC_GLOB] = boost::shared_ptr<Converter>(new ConvertGlobal());
|
||||||
converters[ESM::REC_BOOK] = std::shared_ptr<Converter>(new ConvertBook());
|
converters[ESM::REC_BOOK] = boost::shared_ptr<Converter>(new ConvertBook());
|
||||||
converters[ESM::REC_NPC_] = std::shared_ptr<Converter>(new ConvertNPC());
|
converters[ESM::REC_NPC_] = boost::shared_ptr<Converter>(new ConvertNPC());
|
||||||
converters[ESM::REC_CREA] = std::shared_ptr<Converter>(new ConvertCREA());
|
converters[ESM::REC_CREA] = boost::shared_ptr<Converter>(new ConvertCREA());
|
||||||
converters[ESM::REC_NPCC] = std::shared_ptr<Converter>(new ConvertNPCC());
|
converters[ESM::REC_NPCC] = boost::shared_ptr<Converter>(new ConvertNPCC());
|
||||||
converters[ESM::REC_CREC] = std::shared_ptr<Converter>(new ConvertCREC());
|
converters[ESM::REC_CREC] = boost::shared_ptr<Converter>(new ConvertCREC());
|
||||||
converters[recREFR ] = std::shared_ptr<Converter>(new ConvertREFR());
|
converters[recREFR ] = boost::shared_ptr<Converter>(new ConvertREFR());
|
||||||
converters[recPCDT ] = std::shared_ptr<Converter>(new ConvertPCDT());
|
converters[recPCDT ] = boost::shared_ptr<Converter>(new ConvertPCDT());
|
||||||
converters[recFMAP ] = std::shared_ptr<Converter>(new ConvertFMAP());
|
converters[recFMAP ] = boost::shared_ptr<Converter>(new ConvertFMAP());
|
||||||
converters[recKLST ] = std::shared_ptr<Converter>(new ConvertKLST());
|
converters[recKLST ] = boost::shared_ptr<Converter>(new ConvertKLST());
|
||||||
converters[recSTLN ] = std::shared_ptr<Converter>(new ConvertSTLN());
|
converters[recSTLN ] = boost::shared_ptr<Converter>(new ConvertSTLN());
|
||||||
converters[recGAME ] = std::shared_ptr<Converter>(new ConvertGAME());
|
converters[recGAME ] = boost::shared_ptr<Converter>(new ConvertGAME());
|
||||||
converters[ESM::REC_CELL] = std::shared_ptr<Converter>(new ConvertCell());
|
converters[ESM::REC_CELL] = boost::shared_ptr<Converter>(new ConvertCell());
|
||||||
converters[ESM::REC_ALCH] = std::shared_ptr<Converter>(new DefaultConverter<ESM::Potion>());
|
converters[ESM::REC_ALCH] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Potion>());
|
||||||
converters[ESM::REC_CLAS] = std::shared_ptr<Converter>(new ConvertClass());
|
converters[ESM::REC_CLAS] = boost::shared_ptr<Converter>(new ConvertClass());
|
||||||
converters[ESM::REC_SPEL] = std::shared_ptr<Converter>(new DefaultConverter<ESM::Spell>());
|
converters[ESM::REC_SPEL] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Spell>());
|
||||||
converters[ESM::REC_ARMO] = std::shared_ptr<Converter>(new DefaultConverter<ESM::Armor>());
|
converters[ESM::REC_ARMO] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Armor>());
|
||||||
converters[ESM::REC_WEAP] = std::shared_ptr<Converter>(new DefaultConverter<ESM::Weapon>());
|
converters[ESM::REC_WEAP] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Weapon>());
|
||||||
converters[ESM::REC_CLOT] = std::shared_ptr<Converter>(new DefaultConverter<ESM::Clothing>());
|
converters[ESM::REC_CLOT] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Clothing>());
|
||||||
converters[ESM::REC_ENCH] = std::shared_ptr<Converter>(new DefaultConverter<ESM::Enchantment>());
|
converters[ESM::REC_ENCH] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Enchantment>());
|
||||||
converters[ESM::REC_WEAP] = std::shared_ptr<Converter>(new DefaultConverter<ESM::Weapon>());
|
converters[ESM::REC_WEAP] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::Weapon>());
|
||||||
converters[ESM::REC_LEVC] = std::shared_ptr<Converter>(new DefaultConverter<ESM::CreatureLevList>());
|
converters[ESM::REC_LEVC] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::CreatureLevList>());
|
||||||
converters[ESM::REC_LEVI] = std::shared_ptr<Converter>(new DefaultConverter<ESM::ItemLevList>());
|
converters[ESM::REC_LEVI] = boost::shared_ptr<Converter>(new DefaultConverter<ESM::ItemLevList>());
|
||||||
converters[ESM::REC_CNTC] = std::shared_ptr<Converter>(new ConvertCNTC());
|
converters[ESM::REC_CNTC] = boost::shared_ptr<Converter>(new ConvertCNTC());
|
||||||
converters[ESM::REC_FACT] = std::shared_ptr<Converter>(new ConvertFACT());
|
converters[ESM::REC_FACT] = boost::shared_ptr<Converter>(new ConvertFACT());
|
||||||
converters[ESM::REC_INFO] = std::shared_ptr<Converter>(new ConvertINFO());
|
converters[ESM::REC_INFO] = boost::shared_ptr<Converter>(new ConvertINFO());
|
||||||
converters[ESM::REC_DIAL] = std::shared_ptr<Converter>(new ConvertDIAL());
|
converters[ESM::REC_DIAL] = boost::shared_ptr<Converter>(new ConvertDIAL());
|
||||||
converters[ESM::REC_QUES] = std::shared_ptr<Converter>(new ConvertQUES());
|
converters[ESM::REC_QUES] = boost::shared_ptr<Converter>(new ConvertQUES());
|
||||||
converters[recJOUR ] = std::shared_ptr<Converter>(new ConvertJOUR());
|
converters[recJOUR ] = boost::shared_ptr<Converter>(new ConvertJOUR());
|
||||||
converters[ESM::REC_SCPT] = std::shared_ptr<Converter>(new ConvertSCPT());
|
converters[ESM::REC_SCPT] = boost::shared_ptr<Converter>(new ConvertSCPT());
|
||||||
converters[ESM::REC_PROJ] = std::shared_ptr<Converter>(new ConvertPROJ());
|
|
||||||
converters[recSPLM] = std::shared_ptr<Converter>(new ConvertSPLM());
|
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - REGN (weather in certain regions?)
|
// - REGN (weather in certain regions?)
|
||||||
// - VFXM
|
// - VFXM
|
||||||
// - SPLM (active spell effects)
|
// - SPLM (active spell effects)
|
||||||
|
// - PROJ (magic projectiles in air)
|
||||||
|
|
||||||
std::set<unsigned int> unknownRecords;
|
std::set<unsigned int> unknownRecords;
|
||||||
|
|
||||||
for (std::map<unsigned int, std::shared_ptr<Converter> >::const_iterator it = converters.begin();
|
for (std::map<unsigned int, boost::shared_ptr<Converter> >::const_iterator it = converters.begin();
|
||||||
it != converters.end(); ++it)
|
it != converters.end(); ++it)
|
||||||
{
|
{
|
||||||
it->second->setContext(context);
|
it->second->setContext(context);
|
||||||
|
@ -325,19 +276,15 @@ namespace ESSImport
|
||||||
ESM::NAME n = esm.getRecName();
|
ESM::NAME n = esm.getRecName();
|
||||||
esm.getRecHeader();
|
esm.getRecHeader();
|
||||||
|
|
||||||
std::map<unsigned int, std::shared_ptr<Converter> >::iterator it = converters.find(n.intval);
|
std::map<unsigned int, boost::shared_ptr<Converter> >::iterator it = converters.find(n.val);
|
||||||
if (it != converters.end())
|
if (it != converters.end())
|
||||||
{
|
{
|
||||||
it->second->read(esm);
|
it->second->read(esm);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (unknownRecords.insert(n.intval).second)
|
if (unknownRecords.insert(n.val).second)
|
||||||
{
|
std::cerr << "unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl;
|
||||||
std::ios::fmtflags f(std::cerr.flags());
|
|
||||||
std::cerr << "Error: unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl;
|
|
||||||
std::cerr.flags(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
esm.skipRecord();
|
esm.skipRecord();
|
||||||
}
|
}
|
||||||
|
@ -345,7 +292,7 @@ namespace ESSImport
|
||||||
|
|
||||||
ESM::ESMWriter writer;
|
ESM::ESMWriter writer;
|
||||||
|
|
||||||
writer.setFormat (ESM::SavedGame::sCurrentFormat);
|
writer.setFormat (ESM::Header::CurrentFormat);
|
||||||
|
|
||||||
std::ofstream stream(mOutFile.c_str(), std::ios::binary);
|
std::ofstream stream(mOutFile.c_str(), std::ios::binary);
|
||||||
// all unused
|
// all unused
|
||||||
|
@ -377,7 +324,7 @@ namespace ESSImport
|
||||||
profile.mPlayerClassName = context.mCustomPlayerClassName;
|
profile.mPlayerClassName = context.mCustomPlayerClassName;
|
||||||
else
|
else
|
||||||
profile.mPlayerClassId = context.mPlayerBase.mClass;
|
profile.mPlayerClassId = context.mPlayerBase.mClass;
|
||||||
profile.mPlayerLevel = context.mPlayerBase.mNpdt.mLevel;
|
profile.mPlayerLevel = context.mPlayerBase.mNpdt52.mLevel;
|
||||||
profile.mPlayerName = header.mGameData.mPlayerName.toString();
|
profile.mPlayerName = header.mGameData.mPlayerName.toString();
|
||||||
|
|
||||||
writeScreenshot(header, profile);
|
writeScreenshot(header, profile);
|
||||||
|
@ -388,7 +335,7 @@ namespace ESSImport
|
||||||
|
|
||||||
// Writing order should be Dynamic Store -> Cells -> Player,
|
// Writing order should be Dynamic Store -> Cells -> Player,
|
||||||
// so that references to dynamic records can be recognized when loading
|
// so that references to dynamic records can be recognized when loading
|
||||||
for (std::map<unsigned int, std::shared_ptr<Converter> >::const_iterator it = converters.begin();
|
for (std::map<unsigned int, boost::shared_ptr<Converter> >::const_iterator it = converters.begin();
|
||||||
it != converters.end(); ++it)
|
it != converters.end(); ++it)
|
||||||
{
|
{
|
||||||
if (it->second->getStage() != 0)
|
if (it->second->getStage() != 0)
|
||||||
|
@ -397,11 +344,11 @@ namespace ESSImport
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.startRecord(ESM::REC_NPC_);
|
writer.startRecord(ESM::REC_NPC_);
|
||||||
context.mPlayerBase.mId = "player";
|
writer.writeHNString("NAME", "player");
|
||||||
context.mPlayerBase.save(writer);
|
context.mPlayerBase.save(writer);
|
||||||
writer.endRecord(ESM::REC_NPC_);
|
writer.endRecord(ESM::REC_NPC_);
|
||||||
|
|
||||||
for (std::map<unsigned int, std::shared_ptr<Converter> >::const_iterator it = converters.begin();
|
for (std::map<unsigned int, boost::shared_ptr<Converter> >::const_iterator it = converters.begin();
|
||||||
it != converters.end(); ++it)
|
it != converters.end(); ++it)
|
||||||
{
|
{
|
||||||
if (it->second->getStage() != 1)
|
if (it->second->getStage() != 1)
|
||||||
|
@ -422,26 +369,9 @@ namespace ESSImport
|
||||||
context.mPlayer.save(writer);
|
context.mPlayer.save(writer);
|
||||||
writer.endRecord(ESM::REC_PLAY);
|
writer.endRecord(ESM::REC_PLAY);
|
||||||
|
|
||||||
writer.startRecord(ESM::REC_ACTC);
|
|
||||||
writer.writeHNT("COUN", context.mNextActorId);
|
|
||||||
writer.endRecord(ESM::REC_ACTC);
|
|
||||||
|
|
||||||
// Stage 2 requires cell references to be written / actors IDs assigned
|
|
||||||
for (std::map<unsigned int, std::shared_ptr<Converter> >::const_iterator it = converters.begin();
|
|
||||||
it != converters.end(); ++it)
|
|
||||||
{
|
|
||||||
if (it->second->getStage() != 2)
|
|
||||||
continue;
|
|
||||||
it->second->write(writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.startRecord (ESM::REC_DIAS);
|
writer.startRecord (ESM::REC_DIAS);
|
||||||
context.mDialogueState.save(writer);
|
context.mDialogueState.save(writer);
|
||||||
writer.endRecord(ESM::REC_DIAS);
|
writer.endRecord(ESM::REC_DIAS);
|
||||||
|
|
||||||
writer.startRecord(ESM::REC_INPU);
|
|
||||||
context.mControlsState.save(writer);
|
|
||||||
writer.endRecord(ESM::REC_INPU);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,12 @@
|
||||||
#include <components/esm/globalmap.hpp>
|
#include <components/esm/globalmap.hpp>
|
||||||
#include <components/esm/loadcrea.hpp>
|
#include <components/esm/loadcrea.hpp>
|
||||||
#include <components/esm/loadnpc.hpp>
|
#include <components/esm/loadnpc.hpp>
|
||||||
#include <components/esm/controlsstate.hpp>
|
|
||||||
|
|
||||||
#include "importnpcc.hpp"
|
#include "importnpcc.hpp"
|
||||||
#include "importcrec.hpp"
|
#include "importcrec.hpp"
|
||||||
#include "importcntc.hpp"
|
#include "importcntc.hpp"
|
||||||
#include "importplayer.hpp"
|
#include "importplayer.hpp"
|
||||||
#include "importsplm.h"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,8 +32,6 @@ namespace ESSImport
|
||||||
|
|
||||||
ESM::DialogueState mDialogueState;
|
ESM::DialogueState mDialogueState;
|
||||||
|
|
||||||
ESM::ControlsState mControlsState;
|
|
||||||
|
|
||||||
// cells which should show an explored overlay on the global map
|
// cells which should show an explored overlay on the global map
|
||||||
std::set<std::pair<int, int> > mExploredCells;
|
std::set<std::pair<int, int> > mExploredCells;
|
||||||
|
|
||||||
|
@ -48,48 +45,27 @@ namespace ESSImport
|
||||||
std::map<std::pair<int, std::string>, NPCC> mNpcChanges;
|
std::map<std::pair<int, std::string>, NPCC> mNpcChanges;
|
||||||
std::map<std::pair<int, std::string>, CNTC> mContainerChanges;
|
std::map<std::pair<int, std::string>, CNTC> mContainerChanges;
|
||||||
|
|
||||||
std::map<std::pair<int, std::string>, int> mActorIdMap;
|
|
||||||
int mNextActorId;
|
|
||||||
|
|
||||||
std::map<std::string, ESM::Creature> mCreatures;
|
std::map<std::string, ESM::Creature> mCreatures;
|
||||||
std::map<std::string, ESM::NPC> mNpcs;
|
std::map<std::string, ESM::NPC> mNpcs;
|
||||||
|
|
||||||
std::vector<SPLM::ActiveSpell> mActiveSpells;
|
|
||||||
|
|
||||||
Context()
|
Context()
|
||||||
: mDay(0)
|
|
||||||
, mMonth(0)
|
|
||||||
, mYear(0)
|
|
||||||
, mHour(0.f)
|
|
||||||
, mNextActorId(0)
|
|
||||||
{
|
{
|
||||||
mPlayer.mAutoMove = 0;
|
mPlayer.mAutoMove = 0;
|
||||||
ESM::CellId playerCellId;
|
ESM::CellId playerCellId;
|
||||||
playerCellId.mPaged = true;
|
playerCellId.mPaged = true;
|
||||||
playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0;
|
playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0;
|
||||||
mPlayer.mCellId = playerCellId;
|
mPlayer.mCellId = playerCellId;
|
||||||
mPlayer.mLastKnownExteriorPosition[0]
|
//mPlayer.mLastKnownExteriorPosition
|
||||||
= mPlayer.mLastKnownExteriorPosition[1]
|
mPlayer.mHasMark = 0; // TODO
|
||||||
= mPlayer.mLastKnownExteriorPosition[2]
|
mPlayer.mCurrentCrimeId = 0; // TODO
|
||||||
= 0.0f;
|
|
||||||
mPlayer.mHasMark = 0;
|
|
||||||
mPlayer.mCurrentCrimeId = -1; // TODO
|
|
||||||
mPlayer.mPaidCrimeId = -1;
|
|
||||||
mPlayer.mObject.blank();
|
mPlayer.mObject.blank();
|
||||||
mPlayer.mObject.mEnabled = true;
|
|
||||||
mPlayer.mObject.mRef.mRefID = "player"; // REFR.mRefID would be PlayerSaveGame
|
mPlayer.mObject.mRef.mRefID = "player"; // REFR.mRefID would be PlayerSaveGame
|
||||||
mPlayer.mObject.mCreatureStats.mActorId = generateActorId();
|
|
||||||
|
|
||||||
mGlobalMapState.mBounds.mMinX = 0;
|
mGlobalMapState.mBounds.mMinX = 0;
|
||||||
mGlobalMapState.mBounds.mMaxX = 0;
|
mGlobalMapState.mBounds.mMaxX = 0;
|
||||||
mGlobalMapState.mBounds.mMinY = 0;
|
mGlobalMapState.mBounds.mMinY = 0;
|
||||||
mGlobalMapState.mBounds.mMaxY = 0;
|
mGlobalMapState.mBounds.mMaxY = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int generateActorId()
|
|
||||||
{
|
|
||||||
return mNextActorId++;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,36 +20,25 @@ 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);
|
// seems that a stack of items can have a set of subrecords for each item? rings0000.ess
|
||||||
bool separateStacks = false;
|
// doesn't make any sense to me, if the values were different then the items shouldn't stack in the first place?
|
||||||
for (unsigned int i=0;i<itemCount;++i)
|
// I guess we should double check the stacking logic in OpenMW
|
||||||
|
for (int i=0;i<std::abs(item.mCount);++i)
|
||||||
{
|
{
|
||||||
bool newStack = esm.isNextSub("XIDX");
|
if (esm.isNextSub("XIDX")) // index in the stack?
|
||||||
if (newStack)
|
esm.skipHSub();
|
||||||
{
|
|
||||||
unsigned int idx;
|
|
||||||
esm.getHT(idx);
|
|
||||||
separateStacks = true;
|
|
||||||
item.mCount = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
item.mSCRI.load(esm);
|
item.mSCRI.load(esm);
|
||||||
|
|
||||||
// for XSOL and XCHG seen so far, but probably others too
|
// for XSOL and XCHG seen so far, but probably others too
|
||||||
bool isDeleted = false;
|
item.ESM::CellRef::loadData(esm);
|
||||||
item.ESM::CellRef::loadData(esm, isDeleted);
|
|
||||||
|
|
||||||
int charge=-1;
|
int charge=-1;
|
||||||
esm.getHNOT(charge, "XHLT");
|
esm.getHNOT(charge, "XHLT");
|
||||||
item.mChargeInt = charge;
|
item.mChargeInt = charge;
|
||||||
|
|
||||||
if (newStack)
|
|
||||||
mItems.push_back(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!separateStacks)
|
|
||||||
mItems.push_back(item);
|
mItems.push_back(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,9 @@ namespace ESSImport
|
||||||
mKnownDialogueTopics.push_back(esm.getHString());
|
mKnownDialogueTopics.push_back(esm.getHString());
|
||||||
}
|
}
|
||||||
|
|
||||||
mHasMark = false;
|
|
||||||
if (esm.isNextSub("MNAM"))
|
if (esm.isNextSub("MNAM"))
|
||||||
{
|
esm.skipHSub(); // If this field is here it seems to specify the interior cell the player is in,
|
||||||
mHasMark = true;
|
// but it's not always here, so it's kinda useless
|
||||||
mMNAM = esm.getHString();
|
|
||||||
}
|
|
||||||
|
|
||||||
esm.getHNT(mPNAM, "PNAM");
|
esm.getHNT(mPNAM, "PNAM");
|
||||||
|
|
||||||
|
@ -37,14 +34,6 @@ namespace ESSImport
|
||||||
if (esm.isNextSub("NAM9"))
|
if (esm.isNextSub("NAM9"))
|
||||||
esm.skipHSub();
|
esm.skipHSub();
|
||||||
|
|
||||||
// Rest state. You shouldn't even be able to save during rest, but skip just in case.
|
|
||||||
if (esm.isNextSub("RNAM"))
|
|
||||||
/*
|
|
||||||
int hoursLeft;
|
|
||||||
float x, y, z; // resting position
|
|
||||||
*/
|
|
||||||
esm.skipHSub(); // 16 bytes
|
|
||||||
|
|
||||||
mBounty = 0;
|
mBounty = 0;
|
||||||
esm.getHNOT(mBounty, "CNAM");
|
esm.getHNOT(mBounty, "CNAM");
|
||||||
|
|
||||||
|
@ -61,12 +50,8 @@ namespace ESSImport
|
||||||
if (esm.isNextSub("NAM3"))
|
if (esm.isNextSub("NAM3"))
|
||||||
esm.skipHSub();
|
esm.skipHSub();
|
||||||
|
|
||||||
mHasENAM = false;
|
|
||||||
if (esm.isNextSub("ENAM"))
|
if (esm.isNextSub("ENAM"))
|
||||||
{
|
esm.skipHSub();
|
||||||
mHasENAM = true;
|
|
||||||
esm.getHT(mENAM);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (esm.isNextSub("LNAM"))
|
if (esm.isNextSub("LNAM"))
|
||||||
esm.skipHSub();
|
esm.skipHSub();
|
||||||
|
@ -78,19 +63,12 @@ namespace ESSImport
|
||||||
mFactions.push_back(fnam);
|
mFactions.push_back(fnam);
|
||||||
}
|
}
|
||||||
|
|
||||||
mHasAADT = false;
|
if (esm.isNextSub("AADT"))
|
||||||
if (esm.isNextSub("AADT")) // Attack animation data?
|
esm.skipHSub(); // 44 bytes, no clue
|
||||||
{
|
|
||||||
mHasAADT = true;
|
|
||||||
esm.getHT(mAADT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (esm.isNextSub("KNAM"))
|
if (esm.isNextSub("KNAM"))
|
||||||
esm.skipHSub(); // assigned Quick Keys, I think
|
esm.skipHSub(); // assigned Quick Keys, I think
|
||||||
|
|
||||||
if (esm.isNextSub("ANIS"))
|
|
||||||
esm.skipHSub(); // 16 bytes
|
|
||||||
|
|
||||||
if (esm.isNextSub("WERE"))
|
if (esm.isNextSub("WERE"))
|
||||||
{
|
{
|
||||||
// some werewolf data, 152 bytes
|
// some werewolf data, 152 bytes
|
||||||
|
@ -98,6 +76,10 @@ namespace ESSImport
|
||||||
esm.getSubHeader();
|
esm.getSubHeader();
|
||||||
esm.skip(152);
|
esm.skip(152);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unsure if before or after WERE
|
||||||
|
if (esm.isNextSub("ANIS"))
|
||||||
|
esm.skipHSub();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,23 +38,15 @@ struct PCDT
|
||||||
|
|
||||||
std::vector<std::string> mKnownDialogueTopics;
|
std::vector<std::string> mKnownDialogueTopics;
|
||||||
|
|
||||||
enum PlayerFlags
|
enum DrawState_
|
||||||
{
|
{
|
||||||
PlayerFlags_ViewSwitchDisabled = 0x1,
|
DrawState_Weapon = 0x80,
|
||||||
PlayerFlags_ControlsDisabled = 0x4,
|
DrawState_Spell = 0x100
|
||||||
PlayerFlags_Sleeping = 0x10,
|
};
|
||||||
PlayerFlags_Waiting = 0x40,
|
enum CameraState
|
||||||
PlayerFlags_WeaponDrawn = 0x80,
|
{
|
||||||
PlayerFlags_SpellDrawn = 0x100,
|
CameraState_FirstPerson = 0x8,
|
||||||
PlayerFlags_InJail = 0x200,
|
CameraState_ThirdPerson = 0xa
|
||||||
PlayerFlags_JumpingDisabled = 0x1000,
|
|
||||||
PlayerFlags_LookingDisabled = 0x2000,
|
|
||||||
PlayerFlags_VanityModeDisabled = 0x4000,
|
|
||||||
PlayerFlags_WeaponDrawingDisabled = 0x8000,
|
|
||||||
PlayerFlags_SpellDrawingDisabled = 0x10000,
|
|
||||||
PlayerFlags_ThirdPerson = 0x20000,
|
|
||||||
PlayerFlags_TeleportingDisabled = 0x40000,
|
|
||||||
PlayerFlags_LevitationDisabled = 0x80000
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma pack(push)
|
#pragma pack(push)
|
||||||
|
@ -71,53 +63,18 @@ struct PCDT
|
||||||
|
|
||||||
struct PNAM
|
struct PNAM
|
||||||
{
|
{
|
||||||
struct MarkLocation
|
short mDrawState; // DrawState
|
||||||
{
|
short mCameraState; // CameraState
|
||||||
float mX, mY, mZ; // worldspace position
|
|
||||||
float mRotZ; // Z angle in radians
|
|
||||||
int mCellX, mCellY; // grid coordinates; for interior cells this is always (0, 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
int mPlayerFlags; // controls, camera and draw state
|
|
||||||
unsigned int mLevelProgress;
|
unsigned int mLevelProgress;
|
||||||
float mSkillProgress[27]; // skill progress, non-uniform scaled
|
float mSkillProgress[27]; // skill progress, non-uniform scaled
|
||||||
unsigned char mSkillIncreases[8]; // number of skill increases for each attribute
|
unsigned char mSkillIncreases[8]; // number of skill increases for each attribute
|
||||||
int mTelekinesisRangeBonus; // in units; seems redundant
|
unsigned char mUnknown3[88];
|
||||||
float mVisionBonus; // range: <0.0, 1.0>; affected by light spells and Get/Mod/SetPCVisionBonus
|
|
||||||
int mDetectKeyMagnitude; // seems redundant
|
|
||||||
int mDetectEnchantmentMagnitude; // seems redundant
|
|
||||||
int mDetectAnimalMagnitude; // seems redundant
|
|
||||||
MarkLocation mMarkLocation;
|
|
||||||
unsigned char mUnknown3[40];
|
|
||||||
unsigned char mSpecIncreases[3]; // number of skill increases for each specialization
|
|
||||||
unsigned char mUnknown4;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ENAM
|
|
||||||
{
|
|
||||||
int mCellX;
|
|
||||||
int mCellY;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AADT // 44 bytes
|
|
||||||
{
|
|
||||||
int animGroupIndex; // See convertANIS() for the mapping.
|
|
||||||
unsigned char mUnknown5[40];
|
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
std::vector<FNAM> mFactions;
|
std::vector<FNAM> mFactions;
|
||||||
PNAM mPNAM;
|
PNAM mPNAM;
|
||||||
|
|
||||||
bool mHasMark;
|
|
||||||
std::string mMNAM; // mark cell name; can also be sDefaultCellname or region name
|
|
||||||
|
|
||||||
bool mHasENAM;
|
|
||||||
ENAM mENAM; // last exterior cell
|
|
||||||
|
|
||||||
bool mHasAADT;
|
|
||||||
AADT mAADT;
|
|
||||||
|
|
||||||
void load(ESM::ESMReader& esm);
|
void load(ESM::ESMReader& esm);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
#include "importproj.h"
|
|
||||||
|
|
||||||
#include <components/esm/esmreader.hpp>
|
|
||||||
|
|
||||||
namespace ESSImport
|
|
||||||
{
|
|
||||||
|
|
||||||
void ESSImport::PROJ::load(ESM::ESMReader& esm)
|
|
||||||
{
|
|
||||||
while (esm.isNextSub("PNAM"))
|
|
||||||
{
|
|
||||||
PNAM pnam;
|
|
||||||
esm.getHT(pnam);
|
|
||||||
mProjectiles.push_back(pnam);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
#ifndef OPENMW_ESSIMPORT_IMPORTPROJ_H
|
|
||||||
#define OPENMW_ESSIMPORT_IMPORTPROJ_H
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <components/esm/esmcommon.hpp>
|
|
||||||
#include <components/esm/util.hpp>
|
|
||||||
|
|
||||||
namespace ESM
|
|
||||||
{
|
|
||||||
class ESMReader;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ESSImport
|
|
||||||
{
|
|
||||||
|
|
||||||
struct PROJ
|
|
||||||
{
|
|
||||||
|
|
||||||
#pragma pack(push)
|
|
||||||
#pragma pack(1)
|
|
||||||
struct PNAM // 184 bytes
|
|
||||||
{
|
|
||||||
float mAttackStrength;
|
|
||||||
float mSpeed;
|
|
||||||
unsigned char mUnknown[4*2];
|
|
||||||
float mFlightTime;
|
|
||||||
int mSplmIndex; // reference to a SPLM record (0 for ballistic projectiles)
|
|
||||||
unsigned char mUnknown2[4];
|
|
||||||
ESM::Vector3 mVelocity;
|
|
||||||
ESM::Vector3 mPosition;
|
|
||||||
unsigned char mUnknown3[4*9];
|
|
||||||
ESM::NAME32 mActorId; // indexed refID (with the exception of "PlayerSaveGame")
|
|
||||||
ESM::NAME32 mArrowId;
|
|
||||||
ESM::NAME32 mBowId;
|
|
||||||
|
|
||||||
bool isMagic() const { return mSplmIndex != 0; }
|
|
||||||
};
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
std::vector<PNAM> mProjectiles;
|
|
||||||
|
|
||||||
void load(ESM::ESMReader& esm);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -13,7 +13,7 @@ namespace ESM
|
||||||
namespace ESSImport
|
namespace ESSImport
|
||||||
{
|
{
|
||||||
|
|
||||||
/// Local variable assignments for a running script
|
/// Local variable assigments for a running script
|
||||||
struct SCRI
|
struct SCRI
|
||||||
{
|
{
|
||||||
std::string mScript;
|
std::string mScript;
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
#include "importsplm.h"
|
|
||||||
|
|
||||||
#include <components/esm/esmreader.hpp>
|
|
||||||
|
|
||||||
namespace ESSImport
|
|
||||||
{
|
|
||||||
|
|
||||||
void SPLM::load(ESM::ESMReader& esm)
|
|
||||||
{
|
|
||||||
while (esm.isNextSub("NAME"))
|
|
||||||
{
|
|
||||||
ActiveSpell spell;
|
|
||||||
esm.getHT(spell.mIndex);
|
|
||||||
esm.getHNT(spell.mSPDT, "SPDT");
|
|
||||||
spell.mTarget = esm.getHNOString("TNAM");
|
|
||||||
|
|
||||||
while (esm.isNextSub("NPDT"))
|
|
||||||
{
|
|
||||||
ActiveEffect effect;
|
|
||||||
esm.getHT(effect.mNPDT);
|
|
||||||
|
|
||||||
// Effect-specific subrecords can follow:
|
|
||||||
// - INAM for disintegration and bound effects
|
|
||||||
// - CNAM for summoning and command effects
|
|
||||||
// - VNAM for vampirism
|
|
||||||
// NOTE: There can be multiple INAMs per effect.
|
|
||||||
// TODO: Needs more research.
|
|
||||||
|
|
||||||
esm.skipHSubUntil("NAM0"); // sentinel
|
|
||||||
esm.getSubName();
|
|
||||||
esm.skipHSub();
|
|
||||||
|
|
||||||
spell.mActiveEffects.push_back(effect);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char xnam; // sentinel
|
|
||||||
esm.getHNT(xnam, "XNAM");
|
|
||||||
|
|
||||||
mActiveSpells.push_back(spell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
#ifndef OPENMW_ESSIMPORT_IMPORTSPLM_H
|
|
||||||
#define OPENMW_ESSIMPORT_IMPORTSPLM_H
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <components/esm/esmcommon.hpp>
|
|
||||||
#include <components/esm/util.hpp>
|
|
||||||
|
|
||||||
namespace ESM
|
|
||||||
{
|
|
||||||
class ESMReader;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ESSImport
|
|
||||||
{
|
|
||||||
|
|
||||||
struct SPLM
|
|
||||||
{
|
|
||||||
|
|
||||||
#pragma pack(push)
|
|
||||||
#pragma pack(1)
|
|
||||||
struct SPDT // 160 bytes
|
|
||||||
{
|
|
||||||
int mType; // 1 = spell, 2 = enchantment, 3 = potion
|
|
||||||
ESM::NAME32 mId; // base ID of a spell/enchantment/potion
|
|
||||||
unsigned char mUnknown[4*4];
|
|
||||||
ESM::NAME32 mCasterId;
|
|
||||||
ESM::NAME32 mSourceId; // empty for spells
|
|
||||||
unsigned char mUnknown2[4*11];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct NPDT // 56 bytes
|
|
||||||
{
|
|
||||||
ESM::NAME32 mAffectedActorId;
|
|
||||||
unsigned char mUnknown[4*2];
|
|
||||||
int mMagnitude;
|
|
||||||
float mSecondsActive;
|
|
||||||
unsigned char mUnknown2[4*2];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct INAM // 40 bytes
|
|
||||||
{
|
|
||||||
int mUnknown;
|
|
||||||
unsigned char mUnknown2;
|
|
||||||
ESM::FIXED_STRING<35> mItemId; // disintegrated item / bound item / item to re-equip after expiration
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CNAM // 36 bytes
|
|
||||||
{
|
|
||||||
int mUnknown; // seems to always be 0
|
|
||||||
ESM::NAME32 mSummonedOrCommandedActor[32];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VNAM // 4 bytes
|
|
||||||
{
|
|
||||||
int mUnknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
struct ActiveEffect
|
|
||||||
{
|
|
||||||
NPDT mNPDT;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ActiveSpell
|
|
||||||
{
|
|
||||||
int mIndex;
|
|
||||||
SPDT mSPDT;
|
|
||||||
std::string mTarget;
|
|
||||||
std::vector<ActiveEffect> mActiveEffects;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<ActiveSpell> mActiveSpells;
|
|
||||||
|
|
||||||
void load(ESM::ESMReader& esm);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#include <boost/program_options.hpp>
|
#include <boost/program_options.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
#include <boost/filesystem/fstream.hpp>
|
||||||
|
|
||||||
#include <components/files/configurationmanager.hpp>
|
#include <components/files/configurationmanager.hpp>
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@ set(LAUNCHER
|
||||||
playpage.cpp
|
playpage.cpp
|
||||||
textslotmsgbox.cpp
|
textslotmsgbox.cpp
|
||||||
settingspage.cpp
|
settingspage.cpp
|
||||||
advancedpage.cpp
|
|
||||||
|
|
||||||
utils/cellnameloader.cpp
|
settings/graphicssettings.cpp
|
||||||
|
|
||||||
utils/profilescombobox.cpp
|
utils/profilescombobox.cpp
|
||||||
utils/textinputdialog.cpp
|
utils/textinputdialog.cpp
|
||||||
utils/lineedit.cpp
|
utils/lineedit.cpp
|
||||||
|
@ -23,9 +23,9 @@ set(LAUNCHER_HEADER
|
||||||
playpage.hpp
|
playpage.hpp
|
||||||
textslotmsgbox.hpp
|
textslotmsgbox.hpp
|
||||||
settingspage.hpp
|
settingspage.hpp
|
||||||
advancedpage.hpp
|
|
||||||
|
|
||||||
utils/cellnameloader.hpp
|
settings/graphicssettings.hpp
|
||||||
|
|
||||||
utils/profilescombobox.hpp
|
utils/profilescombobox.hpp
|
||||||
utils/textinputdialog.hpp
|
utils/textinputdialog.hpp
|
||||||
utils/lineedit.hpp
|
utils/lineedit.hpp
|
||||||
|
@ -39,9 +39,7 @@ set(LAUNCHER_HEADER_MOC
|
||||||
playpage.hpp
|
playpage.hpp
|
||||||
textslotmsgbox.hpp
|
textslotmsgbox.hpp
|
||||||
settingspage.hpp
|
settingspage.hpp
|
||||||
advancedpage.hpp
|
|
||||||
|
|
||||||
utils/cellnameloader.hpp
|
|
||||||
utils/textinputdialog.hpp
|
utils/textinputdialog.hpp
|
||||||
utils/profilescombobox.hpp
|
utils/profilescombobox.hpp
|
||||||
utils/lineedit.hpp
|
utils/lineedit.hpp
|
||||||
|
@ -55,11 +53,11 @@ set(LAUNCHER_UI
|
||||||
${CMAKE_SOURCE_DIR}/files/ui/playpage.ui
|
${CMAKE_SOURCE_DIR}/files/ui/playpage.ui
|
||||||
${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
|
${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
|
||||||
${CMAKE_SOURCE_DIR}/files/ui/settingspage.ui
|
${CMAKE_SOURCE_DIR}/files/ui/settingspage.ui
|
||||||
${CMAKE_SOURCE_DIR}/files/ui/advancedpage.ui
|
|
||||||
)
|
)
|
||||||
|
|
||||||
source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER})
|
source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER})
|
||||||
|
|
||||||
|
find_package(Qt4 REQUIRED)
|
||||||
set(QT_USE_QTGUI 1)
|
set(QT_USE_QTGUI 1)
|
||||||
|
|
||||||
# Set some platform specific settings
|
# Set some platform specific settings
|
||||||
|
@ -68,24 +66,19 @@ if(WIN32)
|
||||||
set(QT_USE_QTMAIN TRUE)
|
set(QT_USE_QTMAIN TRUE)
|
||||||
endif(WIN32)
|
endif(WIN32)
|
||||||
|
|
||||||
if (DESIRED_QT_VERSION MATCHES 4)
|
|
||||||
include(${QT_USE_FILE})
|
|
||||||
QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc)
|
QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc)
|
||||||
QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC})
|
QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC})
|
||||||
QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI})
|
QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI})
|
||||||
else()
|
|
||||||
QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc)
|
|
||||||
QT5_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC})
|
|
||||||
QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
|
include(${QT_USE_FILE})
|
||||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||||
if(NOT WIN32)
|
if(NOT WIN32)
|
||||||
include_directories(${LIBUNSHIELD_INCLUDE_DIR})
|
include_directories(${LIBUNSHIELD_INCLUDE_DIR})
|
||||||
endif(NOT WIN32)
|
endif(NOT WIN32)
|
||||||
|
|
||||||
# Main executable
|
# Main executable
|
||||||
openmw_add_executable(openmw-launcher
|
add_executable(openmw-launcher
|
||||||
${GUI_TYPE}
|
${GUI_TYPE}
|
||||||
${LAUNCHER}
|
${LAUNCHER}
|
||||||
${LAUNCHER_HEADER}
|
${LAUNCHER_HEADER}
|
||||||
|
@ -94,27 +87,18 @@ openmw_add_executable(openmw-launcher
|
||||||
${UI_HDRS}
|
${UI_HDRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (WIN32)
|
|
||||||
INSTALL(TARGETS openmw-launcher RUNTIME DESTINATION ".")
|
|
||||||
endif (WIN32)
|
|
||||||
|
|
||||||
target_link_libraries(openmw-launcher
|
target_link_libraries(openmw-launcher
|
||||||
|
${Boost_LIBRARIES}
|
||||||
|
${OGRE_LIBRARIES}
|
||||||
|
${OGRE_STATIC_PLUGINS}
|
||||||
${SDL2_LIBRARY_ONLY}
|
${SDL2_LIBRARY_ONLY}
|
||||||
|
${QT_LIBRARIES}
|
||||||
components
|
components
|
||||||
)
|
)
|
||||||
|
|
||||||
if (DESIRED_QT_VERSION MATCHES 4)
|
|
||||||
target_link_libraries(openmw-launcher ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY})
|
|
||||||
if(WIN32)
|
|
||||||
target_link_libraries(openmw-launcher ${QT_QTMAIN_LIBRARY})
|
|
||||||
endif(WIN32)
|
|
||||||
else()
|
|
||||||
target_link_libraries(openmw-launcher Qt5::Widgets Qt5::Core)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (BUILD_WITH_CODE_COVERAGE)
|
if (BUILD_WITH_CODE_COVERAGE)
|
||||||
add_definitions (--coverage)
|
add_definitions (--coverage)
|
||||||
target_link_libraries(openmw-launcher gcov)
|
target_link_libraries(openmw-launcher gcov)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,176 +0,0 @@
|
||||||
#include "advancedpage.hpp"
|
|
||||||
|
|
||||||
#include <components/config/gamesettings.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,
|
|
||||||
Config::GameSettings &gameSettings,
|
|
||||||
Settings::Manager &engineSettings, QWidget *parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
, mCfgMgr(cfg)
|
|
||||||
, mGameSettings(gameSettings)
|
|
||||||
, mEngineSettings(engineSettings)
|
|
||||||
{
|
|
||||||
setObjectName ("AdvancedPage");
|
|
||||||
setupUi(this);
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
// 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
|
|
||||||
loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
|
|
||||||
loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
|
|
||||||
loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
|
|
||||||
loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
|
|
||||||
loadSettingBool(chargeForEveryFollowerCheckBox, "charge for every follower travelling", "Game");
|
|
||||||
loadSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game");
|
|
||||||
loadSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game");
|
|
||||||
|
|
||||||
// Input Settings
|
|
||||||
loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input");
|
|
||||||
loadSettingBool(grabCursorCheckBox, "grab cursor", "Input");
|
|
||||||
loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
|
|
||||||
|
|
||||||
// Saves Settings
|
|
||||||
loadSettingBool(timePlayedCheckbox, "timeplayed", "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
|
|
||||||
QString screenshotFormatString = QString::fromStdString(mEngineSettings.getString("screenshot format", "General")).toUpper();
|
|
||||||
if (screenshotFormatComboBox->findText(screenshotFormatString) == -1)
|
|
||||||
screenshotFormatComboBox->addItem(screenshotFormatString);
|
|
||||||
screenshotFormatComboBox->setCurrentIndex(screenshotFormatComboBox->findText(screenshotFormatString));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::AdvancedPage::saveSettings()
|
|
||||||
{
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
|
|
||||||
saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
|
|
||||||
saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
|
|
||||||
saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
|
|
||||||
saveSettingBool(chargeForEveryFollowerCheckBox, "charge for every follower travelling", "Game");
|
|
||||||
saveSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game");
|
|
||||||
saveSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game");
|
|
||||||
|
|
||||||
// Input Settings
|
|
||||||
saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input");
|
|
||||||
saveSettingBool(grabCursorCheckBox, "grab cursor", "Input");
|
|
||||||
saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
|
|
||||||
|
|
||||||
// Saves Settings
|
|
||||||
saveSettingBool(timePlayedCheckbox, "timeplayed", "Saves");
|
|
||||||
int maximumQuicksaves = maximumQuicksavesComboBox->value();
|
|
||||||
if (maximumQuicksaves != mEngineSettings.getInt("max quicksaves", "Saves")) {
|
|
||||||
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
|
|
||||||
std::string screenshotFormatString = screenshotFormatComboBox->currentText().toLower().toStdString();
|
|
||||||
if (screenshotFormatString != mEngineSettings.getString("screenshot format", "General"))
|
|
||||||
mEngineSettings.setString("screenshot format", "General", screenshotFormatString);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::AdvancedPage::loadSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) {
|
|
||||||
if (mEngineSettings.getBool(setting, group))
|
|
||||||
checkbox->setCheckState(Qt::Checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::string &setting, const std::string &group) {
|
|
||||||
bool cValue = checkbox->checkState();
|
|
||||||
if (cValue != mEngineSettings.getBool(setting, group))
|
|
||||||
mEngineSettings.setBool(setting, group, cValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames)
|
|
||||||
{
|
|
||||||
loadCellsForAutocomplete(cellNames);
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
#ifndef ADVANCEDPAGE_H
|
|
||||||
#define ADVANCEDPAGE_H
|
|
||||||
|
|
||||||
#include <QWidget>
|
|
||||||
|
|
||||||
#include "ui_advancedpage.h"
|
|
||||||
|
|
||||||
#include <components/settings/settings.hpp>
|
|
||||||
|
|
||||||
namespace Files { struct ConfigurationManager; }
|
|
||||||
namespace Config { class GameSettings; }
|
|
||||||
|
|
||||||
namespace Launcher
|
|
||||||
{
|
|
||||||
class AdvancedPage : public QWidget, private Ui::AdvancedPage
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
AdvancedPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings,
|
|
||||||
Settings::Manager &engineSettings, QWidget *parent = 0);
|
|
||||||
|
|
||||||
bool loadSettings();
|
|
||||||
void saveSettings();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void slotLoadedCellsChanged(QStringList cellNames);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void on_skipMenuCheckBox_stateChanged(int state);
|
|
||||||
void on_runScriptAfterStartupBrowseButton_clicked();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Files::ConfigurationManager &mCfgMgr;
|
|
||||||
Config::GameSettings &mGameSettings;
|
|
||||||
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 saveSettingBool(QCheckBox *checkbox, const std::string& setting, const std::string& group);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -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()
|
||||||
|
@ -86,7 +75,7 @@ bool Launcher::DataFilesPage::loadSettings()
|
||||||
QStringList profiles = mLauncherSettings.getContentLists();
|
QStringList profiles = mLauncherSettings.getContentLists();
|
||||||
QString currentProfile = mLauncherSettings.getCurrentContentListName();
|
QString currentProfile = mLauncherSettings.getCurrentContentListName();
|
||||||
|
|
||||||
qDebug() << "The current profile is: " << currentProfile;
|
qDebug() << "current profile is: " << currentProfile;
|
||||||
|
|
||||||
foreach (const QString &item, profiles)
|
foreach (const QString &item, profiles)
|
||||||
addProfile (item, false);
|
addProfile (item, false);
|
||||||
|
@ -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,5 @@
|
||||||
#include "graphicspage.hpp"
|
#include "graphicspage.hpp"
|
||||||
|
|
||||||
#include <boost/math/common_factor.hpp>
|
|
||||||
#include <csignal>
|
|
||||||
#include <QDesktopWidget>
|
#include <QDesktopWidget>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
@ -12,11 +10,19 @@
|
||||||
#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 <OgreRoot.h>
|
||||||
|
#include <OgreRenderSystem.h>
|
||||||
|
|
||||||
|
#include <boost/math/common_factor.hpp>
|
||||||
|
|
||||||
#include <components/files/configurationmanager.hpp>
|
#include <components/files/configurationmanager.hpp>
|
||||||
|
|
||||||
|
#include <components/contentselector/model/naturalsort.hpp>
|
||||||
|
|
||||||
|
#include "settings/graphicssettings.hpp"
|
||||||
|
|
||||||
QString getAspect(int x, int y)
|
QString getAspect(int x, int y)
|
||||||
{
|
{
|
||||||
int gcd = boost::math::gcd (x, y);
|
int gcd = boost::math::gcd (x, y);
|
||||||
|
@ -29,10 +35,14 @@ QString getAspect(int x, int y)
|
||||||
return QString(QString::number(xaspect) + ":" + QString::number(yaspect));
|
return QString(QString::number(xaspect) + ":" + QString::number(yaspect));
|
||||||
}
|
}
|
||||||
|
|
||||||
Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent)
|
Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSetting, QWidget *parent)
|
||||||
: QWidget(parent)
|
: mOgre(NULL)
|
||||||
|
, mSelectedRenderSystem(NULL)
|
||||||
|
, mOpenGLRenderSystem(NULL)
|
||||||
|
, mDirect3DRenderSystem(NULL)
|
||||||
, mCfgMgr(cfg)
|
, mCfgMgr(cfg)
|
||||||
, mEngineSettings(engineSettings)
|
, mGraphicsSettings(graphicsSetting)
|
||||||
|
, QWidget(parent)
|
||||||
{
|
{
|
||||||
setObjectName ("GraphicsPage");
|
setObjectName ("GraphicsPage");
|
||||||
setupUi(this);
|
setupUi(this);
|
||||||
|
@ -42,34 +52,81 @@ Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, Settings:
|
||||||
customWidthSpinBox->setMaximum(res.width());
|
customWidthSpinBox->setMaximum(res.width());
|
||||||
customHeightSpinBox->setMaximum(res.height());
|
customHeightSpinBox->setMaximum(res.height());
|
||||||
|
|
||||||
|
connect(rendererComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(rendererChanged(const QString&)));
|
||||||
connect(fullScreenCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotFullScreenChanged(int)));
|
connect(fullScreenCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotFullScreenChanged(int)));
|
||||||
connect(standardRadioButton, SIGNAL(toggled(bool)), this, SLOT(slotStandardToggled(bool)));
|
connect(standardRadioButton, SIGNAL(toggled(bool)), this, SLOT(slotStandardToggled(bool)));
|
||||||
connect(screenComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(screenChanged(int)));
|
connect(screenComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(screenChanged(int)));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Launcher::GraphicsPage::connectToSdl() {
|
bool Launcher::GraphicsPage::setupOgre()
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mOgre = mOgreInit.init(mCfgMgr.getLogPath().string() + "/launcherOgre.log");
|
||||||
|
}
|
||||||
|
catch(Ogre::Exception &ex)
|
||||||
|
{
|
||||||
|
QString ogreError = QString::fromUtf8(ex.getFullDescription().c_str());
|
||||||
|
QMessageBox msgBox;
|
||||||
|
msgBox.setWindowTitle("Error creating Ogre::Root");
|
||||||
|
msgBox.setIcon(QMessageBox::Critical);
|
||||||
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msgBox.setText(tr("<br><b>Failed to create the Ogre::Root object</b><br><br> \
|
||||||
|
Press \"Show Details...\" for more information.<br>"));
|
||||||
|
msgBox.setDetailedText(ogreError);
|
||||||
|
msgBox.exec();
|
||||||
|
|
||||||
|
qCritical("Error creating Ogre::Root, the error reported was:\n %s", qPrintable(ogreError));
|
||||||
return false;
|
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.
|
// Get the available renderers and put them in the combobox
|
||||||
|
const Ogre::RenderSystemList &renderers = mOgre->getAvailableRenderers();
|
||||||
|
|
||||||
|
for (Ogre::RenderSystemList::const_iterator r = renderers.begin(); r != renderers.end(); ++r) {
|
||||||
|
mSelectedRenderSystem = *r;
|
||||||
|
rendererComboBox->addItem((*r)->getName().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString openGLName = QString("OpenGL Rendering Subsystem");
|
||||||
|
QString direct3DName = QString("Direct3D9 Rendering Subsystem");
|
||||||
|
|
||||||
|
// Create separate rendersystems
|
||||||
|
mOpenGLRenderSystem = mOgre->getRenderSystemByName(openGLName.toStdString());
|
||||||
|
mDirect3DRenderSystem = mOgre->getRenderSystemByName(direct3DName.toStdString());
|
||||||
|
|
||||||
|
if (!mOpenGLRenderSystem && !mDirect3DRenderSystem) {
|
||||||
|
QMessageBox msgBox;
|
||||||
|
msgBox.setWindowTitle(tr("Error creating renderer"));
|
||||||
|
msgBox.setIcon(QMessageBox::Critical);
|
||||||
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msgBox.setText(tr("<br><b>Could not select a valid render system</b><br><br> \
|
||||||
|
Please make sure Ogre plugins were installed correctly.<br>"));
|
||||||
|
msgBox.exec();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now fill the GUI elements
|
||||||
|
int index = rendererComboBox->findText(mGraphicsSettings.value(QString("Video/render system")));
|
||||||
|
if ( index != -1) {
|
||||||
|
rendererComboBox->setCurrentIndex(index);
|
||||||
|
} else {
|
||||||
|
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
|
||||||
|
rendererComboBox->setCurrentIndex(rendererComboBox->findText(direct3DName));
|
||||||
|
#else
|
||||||
|
rendererComboBox->setCurrentIndex(rendererComboBox->findText(openGLName));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
antiAliasingComboBox->clear();
|
||||||
|
antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem));
|
||||||
|
|
||||||
return true;
|
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 +146,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,27 +153,28 @@ bool Launcher::GraphicsPage::loadSettings()
|
||||||
{
|
{
|
||||||
if (!setupSDL())
|
if (!setupSDL())
|
||||||
return false;
|
return false;
|
||||||
|
if (!mOgre && !setupOgre())
|
||||||
|
return false;
|
||||||
|
|
||||||
if (mEngineSettings.getBool("vsync", "Video"))
|
if (mGraphicsSettings.value(QString("Video/vsync")) == QLatin1String("true"))
|
||||||
vSyncCheckBox->setCheckState(Qt::Checked);
|
vSyncCheckBox->setCheckState(Qt::Checked);
|
||||||
|
|
||||||
if (mEngineSettings.getBool("fullscreen", "Video"))
|
if (mGraphicsSettings.value(QString("Video/fullscreen")) == QLatin1String("true"))
|
||||||
fullScreenCheckBox->setCheckState(Qt::Checked);
|
fullScreenCheckBox->setCheckState(Qt::Checked);
|
||||||
|
|
||||||
if (mEngineSettings.getBool("window border", "Video"))
|
if (mGraphicsSettings.value(QString("Video/window border")) == QLatin1String("true"))
|
||||||
windowBorderCheckBox->setCheckState(Qt::Checked);
|
windowBorderCheckBox->setCheckState(Qt::Checked);
|
||||||
|
|
||||||
// aaValue is the actual value (0, 1, 2, 4, 8, 16)
|
int aaIndex = antiAliasingComboBox->findText(mGraphicsSettings.value(QString("Video/antialiasing")));
|
||||||
int aaValue = mEngineSettings.getInt("antialiasing", "Video");
|
|
||||||
// aaIndex is the index into the allowed values in the pull down.
|
|
||||||
int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue));
|
|
||||||
if (aaIndex != -1)
|
if (aaIndex != -1)
|
||||||
antiAliasingComboBox->setCurrentIndex(aaIndex);
|
antiAliasingComboBox->setCurrentIndex(aaIndex);
|
||||||
|
|
||||||
int width = mEngineSettings.getInt("resolution x", "Video");
|
QString width = mGraphicsSettings.value(QString("Video/resolution x"));
|
||||||
int height = mEngineSettings.getInt("resolution y", "Video");
|
QString height = mGraphicsSettings.value(QString("Video/resolution y"));
|
||||||
QString resolution = QString::number(width) + QString(" x ") + QString::number(height);
|
QString resolution = width + QString(" x ") + height;
|
||||||
screenComboBox->setCurrentIndex(mEngineSettings.getInt("screen", "Video"));
|
QString screen = mGraphicsSettings.value(QString("Video/screen"));
|
||||||
|
|
||||||
|
screenComboBox->setCurrentIndex(screen.toInt());
|
||||||
|
|
||||||
int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith);
|
int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith);
|
||||||
|
|
||||||
|
@ -128,8 +183,9 @@ bool Launcher::GraphicsPage::loadSettings()
|
||||||
resolutionComboBox->setCurrentIndex(resIndex);
|
resolutionComboBox->setCurrentIndex(resIndex);
|
||||||
} else {
|
} else {
|
||||||
customRadioButton->toggle();
|
customRadioButton->toggle();
|
||||||
customWidthSpinBox->setValue(width);
|
customWidthSpinBox->setValue(width.toInt());
|
||||||
customHeightSpinBox->setValue(height);
|
customHeightSpinBox->setValue(height.toInt());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -137,46 +193,65 @@ bool Launcher::GraphicsPage::loadSettings()
|
||||||
|
|
||||||
void Launcher::GraphicsPage::saveSettings()
|
void Launcher::GraphicsPage::saveSettings()
|
||||||
{
|
{
|
||||||
// Ensure we only set the new settings if they changed. This is to avoid cluttering the
|
vSyncCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/vsync"), QString("true"))
|
||||||
// user settings file (which by definition should only contain settings the user has touched)
|
: mGraphicsSettings.setValue(QString("Video/vsync"), QString("false"));
|
||||||
bool cVSync = vSyncCheckBox->checkState();
|
|
||||||
if (cVSync != mEngineSettings.getBool("vsync", "Video"))
|
|
||||||
mEngineSettings.setBool("vsync", "Video", cVSync);
|
|
||||||
|
|
||||||
bool cFullScreen = fullScreenCheckBox->checkState();
|
fullScreenCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("true"))
|
||||||
if (cFullScreen != mEngineSettings.getBool("fullscreen", "Video"))
|
: mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("false"));
|
||||||
mEngineSettings.setBool("fullscreen", "Video", cFullScreen);
|
|
||||||
|
|
||||||
bool cWindowBorder = windowBorderCheckBox->checkState();
|
windowBorderCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/window border"), QString("true"))
|
||||||
if (cWindowBorder != mEngineSettings.getBool("window border", "Video"))
|
: mGraphicsSettings.setValue(QString("Video/window border"), QString("false"));
|
||||||
mEngineSettings.setBool("window border", "Video", cWindowBorder);
|
|
||||||
|
mGraphicsSettings.setValue(QString("Video/antialiasing"), antiAliasingComboBox->currentText());
|
||||||
|
mGraphicsSettings.setValue(QString("Video/render system"), rendererComboBox->currentText());
|
||||||
|
|
||||||
int cAAValue = antiAliasingComboBox->currentText().toInt();
|
|
||||||
if (cAAValue != mEngineSettings.getInt("antialiasing", "Video"))
|
|
||||||
mEngineSettings.setInt("antialiasing", "Video", cAAValue);
|
|
||||||
|
|
||||||
int cWidth = 0;
|
|
||||||
int cHeight = 0;
|
|
||||||
if (standardRadioButton->isChecked()) {
|
if (standardRadioButton->isChecked()) {
|
||||||
QRegExp resolutionRe(QString("(\\d+) x (\\d+).*"));
|
QRegExp resolutionRe(QString("(\\d+) x (\\d+).*"));
|
||||||
|
|
||||||
if (resolutionRe.exactMatch(resolutionComboBox->currentText().simplified())) {
|
if (resolutionRe.exactMatch(resolutionComboBox->currentText().simplified())) {
|
||||||
cWidth = resolutionRe.cap(1).toInt();
|
mGraphicsSettings.setValue(QString("Video/resolution x"), resolutionRe.cap(1));
|
||||||
cHeight = resolutionRe.cap(2).toInt();
|
mGraphicsSettings.setValue(QString("Video/resolution y"), resolutionRe.cap(2));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cWidth = customWidthSpinBox->value();
|
mGraphicsSettings.setValue(QString("Video/resolution x"), QString::number(customWidthSpinBox->value()));
|
||||||
cHeight = customHeightSpinBox->value();
|
mGraphicsSettings.setValue(QString("Video/resolution y"), QString::number(customHeightSpinBox->value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cWidth != mEngineSettings.getInt("resolution x", "Video"))
|
mGraphicsSettings.setValue(QString("Video/screen"), QString::number(screenComboBox->currentIndex()));
|
||||||
mEngineSettings.setInt("resolution x", "Video", cWidth);
|
}
|
||||||
|
|
||||||
if (cHeight != mEngineSettings.getInt("resolution y", "Video"))
|
QStringList Launcher::GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer)
|
||||||
mEngineSettings.setInt("resolution y", "Video", cHeight);
|
{
|
||||||
|
QStringList result;
|
||||||
|
|
||||||
int cScreen = screenComboBox->currentIndex();
|
uint row = 0;
|
||||||
if (cScreen != mEngineSettings.getInt("screen", "Video"))
|
Ogre::ConfigOptionMap options = renderer->getConfigOptions();
|
||||||
mEngineSettings.setInt("screen", "Video", cScreen);
|
|
||||||
|
for (Ogre::ConfigOptionMap::iterator i = options.begin (); i != options.end (); ++i, ++row)
|
||||||
|
{
|
||||||
|
Ogre::StringVector::iterator opt_it;
|
||||||
|
uint idx = 0;
|
||||||
|
|
||||||
|
for (opt_it = i->second.possibleValues.begin();
|
||||||
|
opt_it != i->second.possibleValues.end(); ++opt_it, ++idx)
|
||||||
|
{
|
||||||
|
if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0) {
|
||||||
|
result << ((key == "FSAA") ? QString("MSAA ") : QString("")) + QString::fromUtf8((*opt_it).c_str()).simplified();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort ascending
|
||||||
|
qSort(result.begin(), result.end(), naturalSortLessThanCI);
|
||||||
|
|
||||||
|
// Replace the zero option with Off
|
||||||
|
int index = result.indexOf("MSAA 0");
|
||||||
|
|
||||||
|
if (index != -1)
|
||||||
|
result.replace(index, tr("Off"));
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
|
QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen)
|
||||||
|
@ -241,6 +316,15 @@ QRect Launcher::GraphicsPage::getMaximumResolution()
|
||||||
return max;
|
return max;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Launcher::GraphicsPage::rendererChanged(const QString &renderer)
|
||||||
|
{
|
||||||
|
mSelectedRenderSystem = mOgre->getRenderSystemByName(renderer.toStdString());
|
||||||
|
|
||||||
|
antiAliasingComboBox->clear();
|
||||||
|
|
||||||
|
antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem));
|
||||||
|
}
|
||||||
|
|
||||||
void Launcher::GraphicsPage::screenChanged(int screen)
|
void Launcher::GraphicsPage::screenChanged(int screen)
|
||||||
{
|
{
|
||||||
if (screen >= 0) {
|
if (screen >= 0) {
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include <components/ogreinit/ogreinit.hpp>
|
||||||
|
|
||||||
#include "ui_graphicspage.h"
|
#include "ui_graphicspage.h"
|
||||||
|
|
||||||
#include <components/settings/settings.hpp>
|
namespace Ogre { class Root; class RenderSystem; }
|
||||||
|
|
||||||
namespace Files { struct ConfigurationManager; }
|
namespace Files { struct ConfigurationManager; }
|
||||||
|
|
||||||
|
@ -18,12 +20,13 @@ namespace Launcher
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GraphicsPage(Files::ConfigurationManager &cfg, Settings::Manager &engineSettings, QWidget *parent = 0);
|
GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSettings, QWidget *parent = 0);
|
||||||
|
|
||||||
void saveSettings();
|
void saveSettings();
|
||||||
bool loadSettings();
|
bool loadSettings();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
void rendererChanged(const QString &renderer);
|
||||||
void screenChanged(int screen);
|
void screenChanged(int screen);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
@ -31,17 +34,20 @@ namespace Launcher
|
||||||
void slotStandardToggled(bool checked);
|
void slotStandardToggled(bool checked);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Files::ConfigurationManager &mCfgMgr;
|
OgreInit::OgreInit mOgreInit;
|
||||||
Settings::Manager &mEngineSettings;
|
Ogre::Root *mOgre;
|
||||||
|
Ogre::RenderSystem *mSelectedRenderSystem;
|
||||||
|
Ogre::RenderSystem *mOpenGLRenderSystem;
|
||||||
|
Ogre::RenderSystem *mDirect3DRenderSystem;
|
||||||
|
|
||||||
|
Files::ConfigurationManager &mCfgMgr;
|
||||||
|
GraphicsSettings &mGraphicsSettings;
|
||||||
|
|
||||||
|
QStringList getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer);
|
||||||
QStringList getAvailableResolutions(int screen);
|
QStringList getAvailableResolutions(int screen);
|
||||||
QRect getMaximumResolution();
|
QRect getMaximumResolution();
|
||||||
|
|
||||||
/**
|
bool setupOgre();
|
||||||
* 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,29 +12,67 @@
|
||||||
#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
|
||||||
QDir dir(QCoreApplication::applicationDirPath());
|
QDir dir(QCoreApplication::applicationDirPath());
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
if (dir.dirName() == "MacOS") {
|
||||||
|
dir.cdUp();
|
||||||
|
dir.cdUp();
|
||||||
|
dir.cdUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
// force Qt to load only LOCAL plugins, don't touch system Qt installation
|
||||||
|
QDir pluginsPath(QCoreApplication::applicationDirPath());
|
||||||
|
pluginsPath.cdUp();
|
||||||
|
pluginsPath.cd("Plugins");
|
||||||
|
|
||||||
|
QStringList libraryPaths;
|
||||||
|
libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath();
|
||||||
|
app.setLibraryPaths(libraryPaths);
|
||||||
|
#endif
|
||||||
|
|
||||||
QDir::setCurrent(dir.absolutePath());
|
QDir::setCurrent(dir.absolutePath());
|
||||||
|
|
||||||
|
// Support non-latin characters
|
||||||
|
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
|
||||||
|
|
||||||
Launcher::MainDialog mainWin;
|
Launcher::MainDialog mainWin;
|
||||||
|
|
||||||
Launcher::FirstRunDialogResult result = mainWin.showFirstRunDialog();
|
Launcher::FirstRunDialogResult result = mainWin.showFirstRunDialog();
|
||||||
if (result == Launcher::FirstRunDialogResultFailure)
|
if (result == Launcher::FirstRunDialogResultFailure)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
// if (!mainWin.setup()) {
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
#include <components/version/version.hpp>
|
#include <components/version/version.hpp>
|
||||||
|
|
||||||
|
#include <QLabel>
|
||||||
#include <QDate>
|
#include <QDate>
|
||||||
|
#include <QTime>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
|
@ -10,6 +12,8 @@
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QCloseEvent>
|
#include <QCloseEvent>
|
||||||
#include <QTextCodec>
|
#include <QTextCodec>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QDir>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
|
@ -17,19 +21,9 @@
|
||||||
#include "graphicspage.hpp"
|
#include "graphicspage.hpp"
|
||||||
#include "datafilespage.hpp"
|
#include "datafilespage.hpp"
|
||||||
#include "settingspage.hpp"
|
#include "settingspage.hpp"
|
||||||
#include "advancedpage.hpp"
|
|
||||||
|
|
||||||
using namespace Process;
|
using namespace Process;
|
||||||
|
|
||||||
void cfgError(const QString& title, const QString& msg) {
|
|
||||||
QMessageBox msgBox;
|
|
||||||
msgBox.setWindowTitle(title);
|
|
||||||
msgBox.setIcon(QMessageBox::Critical);
|
|
||||||
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
||||||
msgBox.setText(msg);
|
|
||||||
msgBox.exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
Launcher::MainDialog::MainDialog(QWidget *parent)
|
Launcher::MainDialog::MainDialog(QWidget *parent)
|
||||||
: QMainWindow(parent), mGameSettings (mCfgMgr)
|
: QMainWindow(parent), mGameSettings (mCfgMgr)
|
||||||
{
|
{
|
||||||
|
@ -63,6 +57,26 @@ Launcher::MainDialog::MainDialog(QWidget *parent)
|
||||||
// Remove what's this? button
|
// Remove what's this? button
|
||||||
setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||||
|
|
||||||
|
// Add version information to bottom of the window
|
||||||
|
QString revision(OPENMW_VERSION_COMMITHASH);
|
||||||
|
QString tag(OPENMW_VERSION_TAGHASH);
|
||||||
|
|
||||||
|
versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
|
if (!revision.isEmpty() && !tag.isEmpty())
|
||||||
|
{
|
||||||
|
if (revision == tag) {
|
||||||
|
versionLabel->setText(tr("OpenMW %1 release").arg(OPENMW_VERSION));
|
||||||
|
} else {
|
||||||
|
versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the compile date and time
|
||||||
|
versionLabel->setToolTip(tr("Compiled on %1 %2").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(),
|
||||||
|
QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate),
|
||||||
|
QLocale(QLocale::C).toTime(QString(__TIME__).simplified(),
|
||||||
|
QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate)));
|
||||||
|
}
|
||||||
|
|
||||||
createIcons();
|
createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,23 +104,17 @@ 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);
|
|
||||||
advancedButton->setIcon(QIcon(":/images/preferences-advanced.png"));
|
|
||||||
advancedButton->setText(tr("Advanced"));
|
|
||||||
advancedButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
|
|
||||||
advancedButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
|
|
||||||
|
|
||||||
connect(iconWidget,
|
connect(iconWidget,
|
||||||
SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
|
SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
|
||||||
this, SLOT(changePage(QListWidgetItem*,QListWidgetItem*)));
|
this, SLOT(changePage(QListWidgetItem*,QListWidgetItem*)));
|
||||||
|
@ -117,9 +125,8 @@ void Launcher::MainDialog::createPages()
|
||||||
{
|
{
|
||||||
mPlayPage = new PlayPage(this);
|
mPlayPage = new PlayPage(this);
|
||||||
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
|
mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
|
||||||
mGraphicsPage = new GraphicsPage(mCfgMgr, mEngineSettings, this);
|
mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this);
|
||||||
mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
|
mSettingsPage = new SettingsPage(mCfgMgr, mGameSettings, mLauncherSettings, this);
|
||||||
mAdvancedPage = new AdvancedPage(mCfgMgr, mGameSettings, 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());
|
||||||
|
@ -130,7 +137,6 @@ void Launcher::MainDialog::createPages()
|
||||||
pagesWidget->addWidget(mDataFilesPage);
|
pagesWidget->addWidget(mDataFilesPage);
|
||||||
pagesWidget->addWidget(mGraphicsPage);
|
pagesWidget->addWidget(mGraphicsPage);
|
||||||
pagesWidget->addWidget(mSettingsPage);
|
pagesWidget->addWidget(mSettingsPage);
|
||||||
pagesWidget->addWidget(mAdvancedPage);
|
|
||||||
|
|
||||||
// Select the first page
|
// Select the first page
|
||||||
iconWidget->setCurrentItem(iconWidget->item(0), QItemSelectionModel::Select);
|
iconWidget->setCurrentItem(iconWidget->item(0), QItemSelectionModel::Select);
|
||||||
|
@ -139,8 +145,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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,31 +183,7 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!setup() || !setupGameData()) {
|
return setup() ? FirstRunDialogResultContinue : FirstRunDialogResultFailure;
|
||||||
return FirstRunDialogResultFailure;
|
|
||||||
}
|
|
||||||
return FirstRunDialogResultContinue;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::MainDialog::setVersionLabel()
|
|
||||||
{
|
|
||||||
// Add version information to bottom of the window
|
|
||||||
Version::Version v = Version::getOpenmwVersion(mGameSettings.value("resources").toUtf8().constData());
|
|
||||||
|
|
||||||
QString revision(QString::fromUtf8(v.mCommitHash.c_str()));
|
|
||||||
QString tag(QString::fromUtf8(v.mTagHash.c_str()));
|
|
||||||
|
|
||||||
versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
|
||||||
if (!v.mVersion.empty() && (revision.isEmpty() || revision == tag))
|
|
||||||
versionLabel->setText(tr("OpenMW %1 release").arg(QString::fromUtf8(v.mVersion.c_str())));
|
|
||||||
else
|
|
||||||
versionLabel->setText(tr("OpenMW development (%1)").arg(revision.left(10)));
|
|
||||||
|
|
||||||
// Add the compile date and time
|
|
||||||
versionLabel->setToolTip(tr("Compiled on %1 %2").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(),
|
|
||||||
QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate),
|
|
||||||
QLocale(QLocale::C).toTime(QString(__TIME__).simplified(),
|
|
||||||
QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Launcher::MainDialog::setup()
|
bool Launcher::MainDialog::setup()
|
||||||
|
@ -211,8 +191,6 @@ bool Launcher::MainDialog::setup()
|
||||||
if (!setupGameSettings())
|
if (!setupGameSettings())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
setVersionLabel();
|
|
||||||
|
|
||||||
mLauncherSettings.setContentList(mGameSettings);
|
mLauncherSettings.setContentList(mGameSettings);
|
||||||
|
|
||||||
if (!setupGraphicsSettings())
|
if (!setupGraphicsSettings())
|
||||||
|
@ -221,7 +199,7 @@ bool Launcher::MainDialog::setup()
|
||||||
// Now create the pages as they need the settings
|
// Now create the pages as they need the settings
|
||||||
createPages();
|
createPages();
|
||||||
|
|
||||||
// Call this so we can exit on SDL errors before mainwindow is shown
|
// Call this so we can exit on Ogre/SDL errors before mainwindow is shown
|
||||||
if (!mGraphicsPage->loadSettings())
|
if (!mGraphicsPage->loadSettings())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -252,9 +230,6 @@ bool Launcher::MainDialog::reloadSettings()
|
||||||
if (!mGraphicsPage->loadSettings())
|
if (!mGraphicsPage->loadSettings())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!mAdvancedPage->loadSettings())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,8 +245,6 @@ void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem
|
||||||
|
|
||||||
bool Launcher::MainDialog::setupLauncherSettings()
|
bool Launcher::MainDialog::setupLauncherSettings()
|
||||||
{
|
{
|
||||||
mLauncherSettings.clear();
|
|
||||||
|
|
||||||
mLauncherSettings.setMultiValueEnabled(true);
|
mLauncherSettings.setMultiValueEnabled(true);
|
||||||
|
|
||||||
QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str());
|
QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str());
|
||||||
|
@ -281,14 +254,18 @@ bool Launcher::MainDialog::setupLauncherSettings()
|
||||||
paths.append(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName));
|
paths.append(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName));
|
||||||
|
|
||||||
foreach (const QString &path, paths) {
|
foreach (const QString &path, paths) {
|
||||||
qDebug() << "Loading config file:" << path.toUtf8().constData();
|
qDebug() << "Loading config file:" << qPrintable(path);
|
||||||
QFile file(path);
|
QFile file(path);
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
cfgError(tr("Error opening OpenMW configuration file"),
|
QMessageBox msgBox;
|
||||||
tr("<br><b>Could not open %0 for reading</b><br><br> \
|
msgBox.setWindowTitle(tr("Error opening OpenMW configuration file"));
|
||||||
|
msgBox.setIcon(QMessageBox::Critical);
|
||||||
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msgBox.setText(tr("<br><b>Could not open %0 for reading</b><br><br> \
|
||||||
Please make sure you have the right permissions \
|
Please make sure you have the right permissions \
|
||||||
and try again.<br>").arg(file.fileName()));
|
and try again.<br>").arg(file.fileName()));
|
||||||
|
msgBox.exec();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QTextStream stream(&file);
|
QTextStream stream(&file);
|
||||||
|
@ -304,9 +281,6 @@ bool Launcher::MainDialog::setupLauncherSettings()
|
||||||
|
|
||||||
bool Launcher::MainDialog::setupGameSettings()
|
bool Launcher::MainDialog::setupGameSettings()
|
||||||
{
|
{
|
||||||
mGameSettings.clear();
|
|
||||||
|
|
||||||
QString localPath = QString::fromUtf8(mCfgMgr.getLocalPath().string().c_str());
|
|
||||||
QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str());
|
QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str());
|
||||||
QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str());
|
QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str());
|
||||||
|
|
||||||
|
@ -315,14 +289,18 @@ bool Launcher::MainDialog::setupGameSettings()
|
||||||
QString path = userPath + QLatin1String("openmw.cfg");
|
QString path = userPath + QLatin1String("openmw.cfg");
|
||||||
QFile file(path);
|
QFile file(path);
|
||||||
|
|
||||||
qDebug() << "Loading config file:" << path.toUtf8().constData();
|
qDebug() << "Loading config file:" << qPrintable(path);
|
||||||
|
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
cfgError(tr("Error opening OpenMW configuration file"),
|
QMessageBox msgBox;
|
||||||
tr("<br><b>Could not open %0 for reading</b><br><br> \
|
msgBox.setWindowTitle(tr("Error opening OpenMW configuration file"));
|
||||||
|
msgBox.setIcon(QMessageBox::Critical);
|
||||||
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msgBox.setText(tr("<br><b>Could not open %0 for reading</b><br><br> \
|
||||||
Please make sure you have the right permissions \
|
Please make sure you have the right permissions \
|
||||||
and try again.<br>").arg(file.fileName()));
|
and try again.<br>").arg(file.fileName()));
|
||||||
|
msgBox.exec();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QTextStream stream(&file);
|
QTextStream stream(&file);
|
||||||
|
@ -331,22 +309,26 @@ bool Launcher::MainDialog::setupGameSettings()
|
||||||
mGameSettings.readUserFile(stream);
|
mGameSettings.readUserFile(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now the rest - priority: user > local > global
|
// Now the rest
|
||||||
QStringList paths;
|
QStringList paths;
|
||||||
paths.append(globalPath + QString("openmw.cfg"));
|
|
||||||
paths.append(localPath + QString("openmw.cfg"));
|
|
||||||
paths.append(userPath + QString("openmw.cfg"));
|
paths.append(userPath + QString("openmw.cfg"));
|
||||||
|
paths.append(QString("openmw.cfg"));
|
||||||
|
paths.append(globalPath + QString("openmw.cfg"));
|
||||||
|
|
||||||
foreach (const QString &path2, paths) {
|
foreach (const QString &path, paths) {
|
||||||
qDebug() << "Loading config file:" << path2.toUtf8().constData();
|
qDebug() << "Loading config file:" << qPrintable(path);
|
||||||
|
|
||||||
file.setFileName(path2);
|
QFile file(path);
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
cfgError(tr("Error opening OpenMW configuration file"),
|
QMessageBox msgBox;
|
||||||
tr("<br><b>Could not open %0 for reading</b><br><br> \
|
msgBox.setWindowTitle(tr("Error opening OpenMW configuration file"));
|
||||||
|
msgBox.setIcon(QMessageBox::Critical);
|
||||||
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msgBox.setText(tr("<br><b>Could not open %0 for reading</b><br><br> \
|
||||||
Please make sure you have the right permissions \
|
Please make sure you have the right permissions \
|
||||||
and try again.<br>").arg(file.fileName()));
|
and try again.<br>").arg(file.fileName()));
|
||||||
|
msgBox.exec();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QTextStream stream(&file);
|
QTextStream stream(&file);
|
||||||
|
@ -357,21 +339,16 @@ bool Launcher::MainDialog::setupGameSettings()
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Launcher::MainDialog::setupGameData()
|
|
||||||
{
|
|
||||||
QStringList dataDirs;
|
QStringList dataDirs;
|
||||||
|
|
||||||
// Check if the paths actually contain data files
|
// Check if the paths actually contain data files
|
||||||
foreach (const QString path3, mGameSettings.getDataDirs()) {
|
foreach (const QString path, mGameSettings.getDataDirs()) {
|
||||||
QDir dir(path3);
|
QDir dir(path);
|
||||||
QStringList filters;
|
QStringList filters;
|
||||||
filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon";
|
filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon";
|
||||||
|
|
||||||
if (!dir.entryList(filters).isEmpty())
|
if (!dir.entryList(filters).isEmpty())
|
||||||
dataDirs.append(path3);
|
dataDirs.append(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dataDirs.isEmpty())
|
if (dataDirs.isEmpty())
|
||||||
|
@ -403,54 +380,53 @@ bool Launcher::MainDialog::setupGameData()
|
||||||
|
|
||||||
bool Launcher::MainDialog::setupGraphicsSettings()
|
bool Launcher::MainDialog::setupGraphicsSettings()
|
||||||
{
|
{
|
||||||
// This method is almost a copy of OMW::Engine::loadSettings(). They should definitely
|
mGraphicsSettings.setMultiValueEnabled(false);
|
||||||
// remain consistent, and possibly be merged into a shared component. At the very least
|
|
||||||
// the filenames should be in the CfgMgr component.
|
|
||||||
|
|
||||||
// Ensure to clear previous settings in case we had already loaded settings.
|
QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str());
|
||||||
mEngineSettings.clear();
|
QString globalPath = QString::fromUtf8(mCfgMgr.getGlobalPath().string().c_str());
|
||||||
|
|
||||||
// Create the settings manager and load default settings file
|
QFile localDefault(QString("settings-default.cfg"));
|
||||||
const std::string localDefault = (mCfgMgr.getLocalPath() / "settings-default.cfg").string();
|
QFile globalDefault(globalPath + QString("settings-default.cfg"));
|
||||||
const std::string globalDefault = (mCfgMgr.getGlobalPath() / "settings-default.cfg").string();
|
|
||||||
std::string defaultPath;
|
|
||||||
|
|
||||||
// Prefer the settings-default.cfg in the current directory.
|
if (!localDefault.exists() && !globalDefault.exists()) {
|
||||||
if (boost::filesystem::exists(localDefault))
|
QMessageBox msgBox;
|
||||||
defaultPath = localDefault;
|
msgBox.setWindowTitle(tr("Error reading OpenMW configuration file"));
|
||||||
else if (boost::filesystem::exists(globalDefault))
|
msgBox.setIcon(QMessageBox::Critical);
|
||||||
defaultPath = globalDefault;
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
// Something's very wrong if we can't find the file at all.
|
msgBox.setText(tr("<br><b>Could not find settings-default.cfg</b><br><br> \
|
||||||
else {
|
|
||||||
cfgError(tr("Error reading OpenMW configuration file"),
|
|
||||||
tr("<br><b>Could not find settings-default.cfg</b><br><br> \
|
|
||||||
The problem may be due to an incomplete installation of OpenMW.<br> \
|
The problem may be due to an incomplete installation of OpenMW.<br> \
|
||||||
Reinstalling OpenMW may resolve the problem."));
|
Reinstalling OpenMW may resolve the problem."));
|
||||||
|
msgBox.exec();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the default settings, report any parsing errors.
|
|
||||||
try {
|
QStringList paths;
|
||||||
mEngineSettings.loadDefault(defaultPath);
|
paths.append(globalPath + QString("settings-default.cfg"));
|
||||||
}
|
paths.append(QString("settings-default.cfg"));
|
||||||
catch (std::exception& e) {
|
paths.append(userPath + QString("settings.cfg"));
|
||||||
std::string msg = std::string("<br><b>Error reading settings-default.cfg</b><br><br>") + e.what();
|
|
||||||
cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str()));
|
foreach (const QString &path, paths) {
|
||||||
|
qDebug() << "Loading config file:" << qPrintable(path);
|
||||||
|
QFile file(path);
|
||||||
|
if (file.exists()) {
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
QMessageBox msgBox;
|
||||||
|
msgBox.setWindowTitle(tr("Error opening OpenMW configuration file"));
|
||||||
|
msgBox.setIcon(QMessageBox::Critical);
|
||||||
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msgBox.setText(tr("<br><b>Could not open %0 for reading</b><br><br> \
|
||||||
|
Please make sure you have the right permissions \
|
||||||
|
and try again.<br>").arg(file.fileName()));
|
||||||
|
msgBox.exec();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
QTextStream stream(&file);
|
||||||
|
stream.setCodec(QTextCodec::codecForName("UTF-8"));
|
||||||
|
|
||||||
// Load user settings if they exist
|
mGraphicsSettings.readFile(stream);
|
||||||
const std::string userPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string();
|
|
||||||
// User settings are not required to exist, so if they don't we're done.
|
|
||||||
if (!boost::filesystem::exists(userPath)) return true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
mEngineSettings.loadUser(userPath);
|
|
||||||
}
|
}
|
||||||
catch (std::exception& e) {
|
file.close();
|
||||||
std::string msg = std::string("<br><b>Error reading settings.cfg</b><br><br>") + e.what();
|
|
||||||
cfgError(tr("Error reading OpenMW configuration file"), tr(msg.c_str()));
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -493,17 +469,20 @@ bool Launcher::MainDialog::writeSettings()
|
||||||
mDataFilesPage->saveSettings();
|
mDataFilesPage->saveSettings();
|
||||||
mGraphicsPage->saveSettings();
|
mGraphicsPage->saveSettings();
|
||||||
mSettingsPage->saveSettings();
|
mSettingsPage->saveSettings();
|
||||||
mAdvancedPage->saveSettings();
|
|
||||||
|
|
||||||
QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str());
|
QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str());
|
||||||
QDir dir(userPath);
|
QDir dir(userPath);
|
||||||
|
|
||||||
if (!dir.exists()) {
|
if (!dir.exists()) {
|
||||||
if (!dir.mkpath(userPath)) {
|
if (!dir.mkpath(userPath)) {
|
||||||
cfgError(tr("Error creating OpenMW configuration directory"),
|
QMessageBox msgBox;
|
||||||
tr("<br><b>Could not create %0</b><br><br> \
|
msgBox.setWindowTitle(tr("Error creating OpenMW configuration directory"));
|
||||||
|
msgBox.setIcon(QMessageBox::Critical);
|
||||||
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msgBox.setText(tr("<br><b>Could not create %0</b><br><br> \
|
||||||
Please make sure you have the right permissions \
|
Please make sure you have the right permissions \
|
||||||
and try again.<br>").arg(userPath));
|
and try again.<br>").arg(userPath));
|
||||||
|
msgBox.exec();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -511,44 +490,63 @@ bool Launcher::MainDialog::writeSettings()
|
||||||
// Game settings
|
// Game settings
|
||||||
QFile file(userPath + QString("openmw.cfg"));
|
QFile file(userPath + QString("openmw.cfg"));
|
||||||
|
|
||||||
if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) {
|
if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) {
|
||||||
// File cannot be opened or created
|
// File cannot be opened or created
|
||||||
cfgError(tr("Error writing OpenMW configuration file"),
|
QMessageBox msgBox;
|
||||||
tr("<br><b>Could not open or create %0 for writing</b><br><br> \
|
msgBox.setWindowTitle(tr("Error writing OpenMW configuration file"));
|
||||||
|
msgBox.setIcon(QMessageBox::Critical);
|
||||||
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msgBox.setText(tr("<br><b>Could not open or create %0 for writing</b><br><br> \
|
||||||
Please make sure you have the right permissions \
|
Please make sure you have the right permissions \
|
||||||
and try again.<br>").arg(file.fileName()));
|
and try again.<br>").arg(file.fileName()));
|
||||||
|
msgBox.exec();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QTextStream stream(&file);
|
||||||
|
stream.setCodec(QTextCodec::codecForName("UTF-8"));
|
||||||
|
|
||||||
mGameSettings.writeFileWithComments(file);
|
mGameSettings.writeFile(stream);
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
// Graphics settings
|
// Graphics settings
|
||||||
const std::string settingsPath = (mCfgMgr.getUserConfigPath() / "settings.cfg").string();
|
file.setFileName(userPath + QString("settings.cfg"));
|
||||||
try {
|
|
||||||
mEngineSettings.saveUser(settingsPath);
|
if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) {
|
||||||
}
|
// File cannot be opened or created
|
||||||
catch (std::exception& e) {
|
QMessageBox msgBox;
|
||||||
std::string msg = "<br><b>Error writing settings.cfg</b><br><br>" +
|
msgBox.setWindowTitle(tr("Error writing OpenMW configuration file"));
|
||||||
settingsPath + "<br><br>" + e.what();
|
msgBox.setIcon(QMessageBox::Critical);
|
||||||
cfgError(tr("Error writing user settings file"), tr(msg.c_str()));
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msgBox.setText(tr("<br><b>Could not open or create %0 for writing</b><br><br> \
|
||||||
|
Please make sure you have the right permissions \
|
||||||
|
and try again.<br>").arg(file.fileName()));
|
||||||
|
msgBox.exec();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stream.setDevice(&file);
|
||||||
|
stream.setCodec(QTextCodec::codecForName("UTF-8"));
|
||||||
|
|
||||||
|
mGraphicsSettings.writeFile(stream);
|
||||||
|
file.close();
|
||||||
|
|
||||||
// Launcher settings
|
// Launcher settings
|
||||||
file.setFileName(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName));
|
file.setFileName(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName));
|
||||||
|
|
||||||
if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) {
|
if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) {
|
||||||
// File cannot be opened or created
|
// File cannot be opened or created
|
||||||
cfgError(tr("Error writing Launcher configuration file"),
|
QMessageBox msgBox;
|
||||||
tr("<br><b>Could not open or create %0 for writing</b><br><br> \
|
msgBox.setWindowTitle(tr("Error writing Launcher configuration file"));
|
||||||
|
msgBox.setIcon(QMessageBox::Critical);
|
||||||
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msgBox.setText(tr("<br><b>Could not open or create %0 for writing</b><br><br> \
|
||||||
Please make sure you have the right permissions \
|
Please make sure you have the right permissions \
|
||||||
and try again.<br>").arg(file.fileName()));
|
and try again.<br>").arg(file.fileName()));
|
||||||
|
msgBox.exec();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QTextStream stream(&file);
|
|
||||||
stream.setDevice(&file);
|
stream.setDevice(&file);
|
||||||
stream.setCodec(QTextCodec::codecForName("UTF-8"));
|
stream.setCodec(QTextCodec::codecForName("UTF-8"));
|
||||||
|
|
||||||
|
@ -599,6 +597,6 @@ void Launcher::MainDialog::play()
|
||||||
|
|
||||||
// Launch the game detached
|
// Launch the game detached
|
||||||
|
|
||||||
if (mGameInvoker->startProcess(QLatin1String("tes3mp-browser"), true))
|
if (mGameInvoker->startProcess(QLatin1String("openmw"), true))
|
||||||
return qApp->quit();
|
return qApp->quit();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
#include <components/config/gamesettings.hpp>
|
#include <components/config/gamesettings.hpp>
|
||||||
#include <components/config/launchersettings.hpp>
|
#include <components/config/launchersettings.hpp>
|
||||||
|
|
||||||
#include <components/settings/settings.hpp>
|
#include "settings/graphicssettings.hpp"
|
||||||
|
|
||||||
#include "ui_mainwindow.h"
|
#include "ui_mainwindow.h"
|
||||||
|
|
||||||
|
@ -30,7 +30,6 @@ namespace Launcher
|
||||||
class DataFilesPage;
|
class DataFilesPage;
|
||||||
class UnshieldThread;
|
class UnshieldThread;
|
||||||
class SettingsPage;
|
class SettingsPage;
|
||||||
class AdvancedPage;
|
|
||||||
|
|
||||||
enum FirstRunDialogResult
|
enum FirstRunDialogResult
|
||||||
{
|
{
|
||||||
|
@ -51,6 +50,7 @@ namespace Launcher
|
||||||
explicit MainDialog(QWidget *parent = 0);
|
explicit MainDialog(QWidget *parent = 0);
|
||||||
~MainDialog();
|
~MainDialog();
|
||||||
|
|
||||||
|
bool setup();
|
||||||
FirstRunDialogResult showFirstRunDialog();
|
FirstRunDialogResult showFirstRunDialog();
|
||||||
|
|
||||||
bool reloadSettings();
|
bool reloadSettings();
|
||||||
|
@ -65,17 +65,12 @@ namespace Launcher
|
||||||
void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
void wizardFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool setup();
|
|
||||||
|
|
||||||
void createIcons();
|
void createIcons();
|
||||||
void createPages();
|
void createPages();
|
||||||
|
|
||||||
bool setupLauncherSettings();
|
bool setupLauncherSettings();
|
||||||
bool setupGameSettings();
|
bool setupGameSettings();
|
||||||
bool setupGraphicsSettings();
|
bool setupGraphicsSettings();
|
||||||
bool setupGameData();
|
|
||||||
|
|
||||||
void setVersionLabel();
|
|
||||||
|
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
void saveSettings();
|
void saveSettings();
|
||||||
|
@ -89,7 +84,6 @@ namespace Launcher
|
||||||
GraphicsPage *mGraphicsPage;
|
GraphicsPage *mGraphicsPage;
|
||||||
DataFilesPage *mDataFilesPage;
|
DataFilesPage *mDataFilesPage;
|
||||||
SettingsPage *mSettingsPage;
|
SettingsPage *mSettingsPage;
|
||||||
AdvancedPage *mAdvancedPage;
|
|
||||||
|
|
||||||
Process::ProcessInvoker *mGameInvoker;
|
Process::ProcessInvoker *mGameInvoker;
|
||||||
Process::ProcessInvoker *mWizardInvoker;
|
Process::ProcessInvoker *mWizardInvoker;
|
||||||
|
@ -97,7 +91,7 @@ namespace Launcher
|
||||||
Files::ConfigurationManager mCfgMgr;
|
Files::ConfigurationManager mCfgMgr;
|
||||||
|
|
||||||
Config::GameSettings mGameSettings;
|
Config::GameSettings mGameSettings;
|
||||||
Settings::Manager mEngineSettings;
|
GraphicsSettings mGraphicsSettings;
|
||||||
Config::LauncherSettings mLauncherSettings;
|
Config::LauncherSettings mLauncherSettings;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,11 +2,20 @@
|
||||||
|
|
||||||
#include <QListView>
|
#include <QListView>
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
#include <QPlastiqueStyle>
|
||||||
|
#endif
|
||||||
|
|
||||||
Launcher::PlayPage::PlayPage(QWidget *parent) : QWidget(parent)
|
Launcher::PlayPage::PlayPage(QWidget *parent) : QWidget(parent)
|
||||||
{
|
{
|
||||||
setObjectName ("PlayPage");
|
setObjectName ("PlayPage");
|
||||||
setupUi(this);
|
setupUi(this);
|
||||||
|
|
||||||
|
// Hacks to get the stylesheet look properly
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
QPlastiqueStyle *style = new QPlastiqueStyle;
|
||||||
|
profilesComboBox->setStyle(style);
|
||||||
|
#endif
|
||||||
profilesComboBox->setView(new QListView());
|
profilesComboBox->setView(new QListView());
|
||||||
|
|
||||||
connect(profilesComboBox, SIGNAL(activated(int)), this, SIGNAL (signalProfileChanged(int)));
|
connect(profilesComboBox, SIGNAL(activated(int)), this, SIGNAL (signalProfileChanged(int)));
|
||||||
|
|
44
apps/launcher/settings/graphicssettings.cpp
Normal file
44
apps/launcher/settings/graphicssettings.cpp
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
#include "graphicssettings.hpp"
|
||||||
|
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QString>
|
||||||
|
#include <QRegExp>
|
||||||
|
#include <QMap>
|
||||||
|
|
||||||
|
Launcher::GraphicsSettings::GraphicsSettings()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Launcher::GraphicsSettings::~GraphicsSettings()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Launcher::GraphicsSettings::writeFile(QTextStream &stream)
|
||||||
|
{
|
||||||
|
QString sectionPrefix;
|
||||||
|
QRegExp sectionRe("([^/]+)/(.+)$");
|
||||||
|
QMap<QString, QString> settings = SettingsBase::getSettings();
|
||||||
|
|
||||||
|
QMapIterator<QString, QString> i(settings);
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
|
||||||
|
QString prefix;
|
||||||
|
QString key;
|
||||||
|
|
||||||
|
if (sectionRe.exactMatch(i.key())) {
|
||||||
|
prefix = sectionRe.cap(1);
|
||||||
|
key = sectionRe.cap(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sectionPrefix != prefix) {
|
||||||
|
sectionPrefix = prefix;
|
||||||
|
stream << "\n[" << prefix << "]\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
stream << key << " = " << i.value() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
18
apps/launcher/settings/graphicssettings.hpp
Normal file
18
apps/launcher/settings/graphicssettings.hpp
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef GRAPHICSSETTINGS_HPP
|
||||||
|
#define GRAPHICSSETTINGS_HPP
|
||||||
|
|
||||||
|
#include <components/config/settingsbase.hpp>
|
||||||
|
|
||||||
|
namespace Launcher
|
||||||
|
{
|
||||||
|
class GraphicsSettings : public Config::SettingsBase<QMap<QString, QString> >
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GraphicsSettings();
|
||||||
|
~GraphicsSettings();
|
||||||
|
|
||||||
|
bool writeFile(QTextStream &stream);
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif // GRAPHICSSETTINGS_HPP
|
|
@ -18,10 +18,10 @@ using namespace Process;
|
||||||
Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg,
|
Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg,
|
||||||
Config::GameSettings &gameSettings,
|
Config::GameSettings &gameSettings,
|
||||||
Config::LauncherSettings &launcherSettings, MainDialog *parent)
|
Config::LauncherSettings &launcherSettings, MainDialog *parent)
|
||||||
: QWidget(parent)
|
: mCfgMgr(cfg)
|
||||||
, mCfgMgr(cfg)
|
|
||||||
, mGameSettings(gameSettings)
|
, mGameSettings(gameSettings)
|
||||||
, mLauncherSettings(launcherSettings)
|
, mLauncherSettings(launcherSettings)
|
||||||
|
, QWidget(parent)
|
||||||
, mMain(parent)
|
, mMain(parent)
|
||||||
{
|
{
|
||||||
setupUi(this);
|
setupUi(this);
|
||||||
|
|
|
@ -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
|
|
|
@ -1,32 +0,0 @@
|
||||||
project(masterserver)
|
|
||||||
|
|
||||||
#set(CMAKE_CXX_STANDARD 14)
|
|
||||||
add_definitions(-std=gnu++14)
|
|
||||||
|
|
||||||
include_directories("./")
|
|
||||||
|
|
||||||
set(SOURCE_FILES main.cpp MasterServer.cpp MasterServer.hpp RestServer.cpp RestServer.hpp)
|
|
||||||
|
|
||||||
add_executable(masterserver ${SOURCE_FILES})
|
|
||||||
target_link_libraries(masterserver ${RakNet_LIBRARY} components)
|
|
||||||
|
|
||||||
option(BUILD_MASTER_TEST "build master server test program" OFF)
|
|
||||||
|
|
||||||
if(BUILD_MASTER_TEST)
|
|
||||||
add_executable(ServerTest ServerTest.cpp)
|
|
||||||
target_link_libraries(ServerTest ${RakNet_LIBRARY} components)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (UNIX)
|
|
||||||
# Fix for not visible pthreads functions for linker with glibc 2.15
|
|
||||||
if(NOT APPLE)
|
|
||||||
target_link_libraries(masterserver ${CMAKE_THREAD_LIBS_INIT})
|
|
||||||
if(BUILD_MASTER_TEST)
|
|
||||||
target_link_libraries(ServerTest ${CMAKE_THREAD_LIBS_INIT})
|
|
||||||
endif()
|
|
||||||
endif(NOT APPLE)
|
|
||||||
endif(UNIX)
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
target_link_libraries(masterserver wsock32)
|
|
||||||
endif(WIN32)
|
|
|
@ -1,236 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 21.04.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <RakPeerInterface.h>
|
|
||||||
#include <RakSleep.h>
|
|
||||||
#include <BitStream.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include "MasterServer.hpp"
|
|
||||||
|
|
||||||
#include <components/openmw-mp/Master/PacketMasterQuery.hpp>
|
|
||||||
#include <components/openmw-mp/Master/PacketMasterUpdate.hpp>
|
|
||||||
#include <components/openmw-mp/Master/PacketMasterAnnounce.hpp>
|
|
||||||
#include <components/openmw-mp/Version.hpp>
|
|
||||||
|
|
||||||
using namespace RakNet;
|
|
||||||
using namespace std;
|
|
||||||
using namespace mwmp;
|
|
||||||
using namespace chrono;
|
|
||||||
|
|
||||||
MasterServer::MasterServer(unsigned short maxConnections, unsigned short port)
|
|
||||||
{
|
|
||||||
peer = RakPeerInterface::GetInstance();
|
|
||||||
sockdescr = SocketDescriptor(port, 0);
|
|
||||||
peer->Startup(maxConnections, &sockdescr, 1, 1000);
|
|
||||||
|
|
||||||
peer->SetMaximumIncomingConnections(maxConnections);
|
|
||||||
peer->SetIncomingPassword(TES3MP_MASTERSERVER_PASSW, (int) strlen(TES3MP_MASTERSERVER_PASSW));
|
|
||||||
run = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
MasterServer::~MasterServer()
|
|
||||||
{
|
|
||||||
Stop(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
using namespace chrono;
|
|
||||||
|
|
||||||
void MasterServer::Thread()
|
|
||||||
{
|
|
||||||
unsigned char packetId = 0;
|
|
||||||
|
|
||||||
auto startTime = chrono::steady_clock::now();
|
|
||||||
|
|
||||||
BitStream send;
|
|
||||||
PacketMasterQuery pmq(peer);
|
|
||||||
pmq.SetSendStream(&send);
|
|
||||||
|
|
||||||
PacketMasterUpdate pmu(peer);
|
|
||||||
pmu.SetSendStream(&send);
|
|
||||||
|
|
||||||
PacketMasterAnnounce pma(peer);
|
|
||||||
pma.SetSendStream(&send);
|
|
||||||
|
|
||||||
while (run)
|
|
||||||
{
|
|
||||||
Packet *packet = peer->Receive();
|
|
||||||
|
|
||||||
auto now = steady_clock::now();
|
|
||||||
if (now - startTime >= 60s)
|
|
||||||
{
|
|
||||||
startTime = steady_clock::now();
|
|
||||||
for (auto it = servers.begin(); it != servers.end();)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (it->second.lastUpdate + 60s <= now)
|
|
||||||
servers.erase(it++);
|
|
||||||
else ++it;
|
|
||||||
}
|
|
||||||
for(auto id = pendingACKs.begin(); id != pendingACKs.end();)
|
|
||||||
{
|
|
||||||
if(now - id->second >= 30s)
|
|
||||||
{
|
|
||||||
cout << "timeout: " << peer->GetSystemAddressFromGuid(id->first).ToString() << endl;
|
|
||||||
peer->CloseConnection(id->first, true);
|
|
||||||
id = pendingACKs.erase(id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
++id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet == nullptr)
|
|
||||||
RakSleep(10);
|
|
||||||
else
|
|
||||||
for (; packet; peer->DeallocatePacket(packet), packet = peer->Receive())
|
|
||||||
{
|
|
||||||
BitStream data(packet->data, packet->length, false);
|
|
||||||
data.Read(packetId);
|
|
||||||
switch (packetId)
|
|
||||||
{
|
|
||||||
case ID_NEW_INCOMING_CONNECTION:
|
|
||||||
cout << "New incoming connection: " << packet->systemAddress.ToString() << endl;
|
|
||||||
break;
|
|
||||||
case ID_DISCONNECTION_NOTIFICATION:
|
|
||||||
cout << "Disconnected: " << packet->systemAddress.ToString() << endl;
|
|
||||||
break;
|
|
||||||
case ID_CONNECTION_LOST:
|
|
||||||
cout << "Connection lost: " << packet->systemAddress.ToString() << endl;
|
|
||||||
break;
|
|
||||||
case ID_MASTER_QUERY:
|
|
||||||
{
|
|
||||||
pmq.SetServers(reinterpret_cast<map<SystemAddress, QueryData> *>(&servers));
|
|
||||||
pmq.Send(packet->systemAddress);
|
|
||||||
pendingACKs[packet->guid] = steady_clock::now();
|
|
||||||
|
|
||||||
cout << "Sent info about all " << servers.size() << " servers to "
|
|
||||||
<< packet->systemAddress.ToString() << endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ID_MASTER_UPDATE:
|
|
||||||
{
|
|
||||||
SystemAddress addr;
|
|
||||||
data.Read(addr); // update 1 server
|
|
||||||
|
|
||||||
ServerIter it = servers.find(addr);
|
|
||||||
if (it != servers.end())
|
|
||||||
{
|
|
||||||
pair<SystemAddress, QueryData> pairPtr(it->first, static_cast<QueryData>(it->second));
|
|
||||||
pmu.SetServer(&pairPtr);
|
|
||||||
pmu.Send(packet->systemAddress);
|
|
||||||
pendingACKs[packet->guid] = steady_clock::now();
|
|
||||||
cout << "Sent info about " << addr.ToString() << " to " << packet->systemAddress.ToString()
|
|
||||||
<< endl;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ID_MASTER_ANNOUNCE:
|
|
||||||
{
|
|
||||||
ServerIter iter = servers.find(packet->systemAddress);
|
|
||||||
|
|
||||||
pma.SetReadStream(&data);
|
|
||||||
SServer server;
|
|
||||||
pma.SetServer(&server);
|
|
||||||
pma.Read();
|
|
||||||
|
|
||||||
auto keepAliveFunc = [&]() {
|
|
||||||
iter->second.lastUpdate = now;
|
|
||||||
pma.SetFunc(PacketMasterAnnounce::FUNCTION_KEEP);
|
|
||||||
pma.Send(packet->systemAddress);
|
|
||||||
pendingACKs[packet->guid] = steady_clock::now();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (iter != servers.end())
|
|
||||||
{
|
|
||||||
if (pma.GetFunc() == PacketMasterAnnounce::FUNCTION_DELETE)
|
|
||||||
{
|
|
||||||
servers.erase(iter);
|
|
||||||
cout << "Deleted";
|
|
||||||
pma.Send(packet->systemAddress);
|
|
||||||
pendingACKs[packet->guid] = steady_clock::now();
|
|
||||||
}
|
|
||||||
else if (pma.GetFunc() == PacketMasterAnnounce::FUNCTION_ANNOUNCE)
|
|
||||||
{
|
|
||||||
cout << "Updated";
|
|
||||||
iter->second = server;
|
|
||||||
keepAliveFunc();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cout << "Keeping alive";
|
|
||||||
keepAliveFunc();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (pma.GetFunc() == PacketMasterAnnounce::FUNCTION_ANNOUNCE)
|
|
||||||
{
|
|
||||||
cout << "Added";
|
|
||||||
iter = servers.insert({packet->systemAddress, server}).first;
|
|
||||||
keepAliveFunc();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cout << "Unknown";
|
|
||||||
pma.SetFunc(PacketMasterAnnounce::FUNCTION_DELETE);
|
|
||||||
pma.Send(packet->systemAddress);
|
|
||||||
pendingACKs[packet->guid] = steady_clock::now();
|
|
||||||
}
|
|
||||||
cout << " server " << packet->systemAddress.ToString() << endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ID_SND_RECEIPT_ACKED:
|
|
||||||
uint32_t num;
|
|
||||||
memcpy(&num, packet->data+1, 4);
|
|
||||||
cout << "Packet with id " << num << " was delivered." << endl;
|
|
||||||
pendingACKs.erase(packet->guid);
|
|
||||||
peer->CloseConnection(packet->systemAddress, true);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
cout << "Wrong packet. id " << (unsigned) packet->data[0] << " packet length " << packet->length << " from " << packet->systemAddress.ToString() << endl;
|
|
||||||
peer->CloseConnection(packet->systemAddress, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
peer->Shutdown(1000);
|
|
||||||
RakPeerInterface::DestroyInstance(peer);
|
|
||||||
cout << "Server thread stopped" << endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MasterServer::Start()
|
|
||||||
{
|
|
||||||
if (!run)
|
|
||||||
{
|
|
||||||
run = true;
|
|
||||||
tMasterThread = thread(&MasterServer::Thread, this);
|
|
||||||
cout << "Started" << endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MasterServer::Stop(bool wait)
|
|
||||||
{
|
|
||||||
if (run)
|
|
||||||
{
|
|
||||||
run = false;
|
|
||||||
if (wait && tMasterThread.joinable())
|
|
||||||
tMasterThread.join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MasterServer::isRunning()
|
|
||||||
{
|
|
||||||
return run;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MasterServer::Wait()
|
|
||||||
{
|
|
||||||
if (run)
|
|
||||||
{
|
|
||||||
if (tMasterThread.joinable())
|
|
||||||
tMasterThread.join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MasterServer::ServerMap *MasterServer::GetServers()
|
|
||||||
{
|
|
||||||
return &servers;
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 21.04.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef NEWMASTERPROTO_MASTERSERVER_HPP
|
|
||||||
#define NEWMASTERPROTO_MASTERSERVER_HPP
|
|
||||||
|
|
||||||
#include <thread>
|
|
||||||
#include <chrono>
|
|
||||||
#include <RakPeerInterface.h>
|
|
||||||
#include <components/openmw-mp/Master/MasterData.hpp>
|
|
||||||
|
|
||||||
class MasterServer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct Ban
|
|
||||||
{
|
|
||||||
RakNet::SystemAddress sa;
|
|
||||||
bool permanent;
|
|
||||||
struct Date
|
|
||||||
{
|
|
||||||
} date;
|
|
||||||
};
|
|
||||||
struct SServer : QueryData
|
|
||||||
{
|
|
||||||
std::chrono::steady_clock::time_point lastUpdate;
|
|
||||||
};
|
|
||||||
typedef std::map<RakNet::SystemAddress, SServer> ServerMap;
|
|
||||||
//typedef ServerMap::const_iterator ServerCIter;
|
|
||||||
typedef ServerMap::iterator ServerIter;
|
|
||||||
|
|
||||||
MasterServer(unsigned short maxConnections, unsigned short port);
|
|
||||||
~MasterServer();
|
|
||||||
|
|
||||||
void Start();
|
|
||||||
void Stop(bool wait = false);
|
|
||||||
bool isRunning();
|
|
||||||
void Wait();
|
|
||||||
|
|
||||||
ServerMap* GetServers();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void Thread();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::thread tMasterThread;
|
|
||||||
RakNet::RakPeerInterface* peer;
|
|
||||||
RakNet::SocketDescriptor sockdescr;
|
|
||||||
ServerMap servers;
|
|
||||||
bool run;
|
|
||||||
std::map<RakNet::RakNetGUID, std::chrono::steady_clock::time_point> pendingACKs;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //NEWMASTERPROTO_MASTERSERVER_HPP
|
|
|
@ -1,192 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 13.05.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "RestServer.hpp"
|
|
||||||
|
|
||||||
#include <boost/property_tree/ptree.hpp>
|
|
||||||
#include <boost/property_tree/json_parser.hpp>
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
using namespace chrono;
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
server.SetName(pt.get<string>("hostname").c_str());
|
|
||||||
server.SetGameMode(pt.get<string>("modname").c_str());
|
|
||||||
server.SetVersion(pt.get<string>("version").c_str());
|
|
||||||
server.SetPassword(pt.get<bool>("passw"));
|
|
||||||
//server.query_port = pt.get<unsigned short>("query_port");
|
|
||||||
server.SetPlayers(pt.get<unsigned>("players"));
|
|
||||||
server.SetMaxPlayers(pt.get<unsigned>("max_players"));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void queryToStringStream(stringstream &ss, string addr, MasterServer::SServer &query)
|
|
||||||
{
|
|
||||||
ss <<"\"" << addr << "\":{";
|
|
||||||
ss << "\"modname\": \"" << query.GetGameMode() << "\"" << ", ";
|
|
||||||
ss << "\"passw\": " << (query.GetPassword() ? "true" : "false") << ", ";
|
|
||||||
ss << "\"hostname\": \"" << query.GetName() << "\"" << ", ";
|
|
||||||
ss << "\"query_port\": " << 0 << ", ";
|
|
||||||
ss << "\"last_update\": " << duration_cast<seconds>(steady_clock::now() - query.lastUpdate).count() << ", ";
|
|
||||||
ss << "\"players\": " << query.GetPlayers() << ", ";
|
|
||||||
ss << "\"version\": \"" << query.GetVersion() << "\"" << ", ";
|
|
||||||
ss << "\"max_players\": " << query.GetMaxPlayers();
|
|
||||||
ss << "}";
|
|
||||||
}
|
|
||||||
|
|
||||||
RestServer::RestServer(unsigned short port, MasterServer::ServerMap *pMap) : serverMap(pMap)
|
|
||||||
{
|
|
||||||
httpServer.config.port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RestServer::start()
|
|
||||||
{
|
|
||||||
static const string ValidIpAddressRegex = "(?:[0-9]{1,3}\\.){3}[0-9]{1,3}";
|
|
||||||
static const string ValidPortRegex = "(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$";
|
|
||||||
static const string ServersRegex = "^/api/servers(?:/(" + ValidIpAddressRegex + "\\:" + ValidPortRegex + "))?";
|
|
||||||
|
|
||||||
httpServer.resource[ServersRegex]["GET"] = [this](auto response, auto request) {
|
|
||||||
if (request->path_match[1].length() > 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
stringstream ss;
|
|
||||||
ss << "{";
|
|
||||||
auto addr = request->path_match[1].str();
|
|
||||||
auto port = (unsigned short)stoi(&(addr[addr.find(':')+1]));
|
|
||||||
queryToStringStream(ss, "server", serverMap->at(RakNet::SystemAddress(addr.c_str(), port)));
|
|
||||||
ss << "}";
|
|
||||||
ResponseStr(*response, ss.str(), "application/json");
|
|
||||||
}
|
|
||||||
catch(out_of_range e)
|
|
||||||
{
|
|
||||||
*response << response400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
static string str;
|
|
||||||
|
|
||||||
//if (updatedCache)
|
|
||||||
{
|
|
||||||
stringstream ss;
|
|
||||||
ss << "{";
|
|
||||||
ss << "\"list servers\":{";
|
|
||||||
for (auto query = serverMap->begin(); query != serverMap->end(); query++)
|
|
||||||
{
|
|
||||||
queryToStringStream(ss, query->first.ToString(true, ':'), query->second);
|
|
||||||
if (next(query) != serverMap->end())
|
|
||||||
ss << ", ";
|
|
||||||
}
|
|
||||||
ss << "}}";
|
|
||||||
ResponseStr(*response, ss.str(), "application/json");
|
|
||||||
updatedCache = false;
|
|
||||||
}
|
|
||||||
*response << str;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Add query for < 0.6 servers
|
|
||||||
httpServer.resource[ServersRegex]["POST"] = [this](auto response, auto request) {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ptree pt;
|
|
||||||
read_json(request->content, pt);
|
|
||||||
|
|
||||||
MasterServer::SServer server;
|
|
||||||
ptreeToServer(pt, server);
|
|
||||||
|
|
||||||
unsigned short port = pt.get<unsigned short>("port");
|
|
||||||
server.lastUpdate = steady_clock::now();
|
|
||||||
serverMap->insert({RakNet::SystemAddress(request->remote_endpoint_address.c_str(), port), server});
|
|
||||||
updatedCache = true;
|
|
||||||
|
|
||||||
*response << response201;
|
|
||||||
}
|
|
||||||
catch (exception& e)
|
|
||||||
{
|
|
||||||
cout << e.what() << endl;
|
|
||||||
*response << response400;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Update query for < 0.6 servers
|
|
||||||
httpServer.resource[ServersRegex]["PUT"] = [this](auto response, auto request) {
|
|
||||||
auto addr = request->path_match[1].str();
|
|
||||||
auto port = (unsigned short)stoi(&(addr[addr.find(':')+1]));
|
|
||||||
|
|
||||||
auto query = serverMap->find(RakNet::SystemAddress(request->remote_endpoint_address.c_str(), port));
|
|
||||||
|
|
||||||
if (query == serverMap->end())
|
|
||||||
{
|
|
||||||
cout << request->remote_endpoint_address + ": Trying to update a non-existent server or without permissions." << endl;
|
|
||||||
*response << response400;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request->content.size() != 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ptree pt;
|
|
||||||
read_json(request->content, pt);
|
|
||||||
|
|
||||||
ptreeToServer(pt, query->second);
|
|
||||||
|
|
||||||
updatedCache = true;
|
|
||||||
}
|
|
||||||
catch(exception &e)
|
|
||||||
{
|
|
||||||
cout << e.what() << endl;
|
|
||||||
*response << response400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query->second.lastUpdate = steady_clock::now();
|
|
||||||
|
|
||||||
*response << response202;
|
|
||||||
};
|
|
||||||
|
|
||||||
httpServer.resource["/api/servers/info"]["GET"] = [this](auto response, auto /*request*/) {
|
|
||||||
stringstream ss;
|
|
||||||
ss << '{';
|
|
||||||
ss << "\"servers\": " << serverMap->size();
|
|
||||||
unsigned int players = 0;
|
|
||||||
for (auto s : *serverMap)
|
|
||||||
players += s.second.GetPlayers();
|
|
||||||
ss << ", \"players\": " << players;
|
|
||||||
ss << "}";
|
|
||||||
|
|
||||||
ResponseStr(*response, ss.str(), "application/json");
|
|
||||||
};
|
|
||||||
|
|
||||||
httpServer.default_resource["GET"]=[](auto response, auto /*request*/) {
|
|
||||||
*response << response400;
|
|
||||||
};
|
|
||||||
|
|
||||||
httpServer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RestServer::cacheUpdated()
|
|
||||||
{
|
|
||||||
updatedCache = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RestServer::stop()
|
|
||||||
{
|
|
||||||
httpServer.stop();
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 13.05.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef NEWRESTAPI_RESTSERVER_HPP
|
|
||||||
#define NEWRESTAPI_RESTSERVER_HPP
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include "MasterServer.hpp"
|
|
||||||
#include "SimpleWeb/http_server.hpp"
|
|
||||||
|
|
||||||
typedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;
|
|
||||||
|
|
||||||
class RestServer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
RestServer(unsigned short port, MasterServer::ServerMap *pMap);
|
|
||||||
void start();
|
|
||||||
void stop();
|
|
||||||
void cacheUpdated();
|
|
||||||
|
|
||||||
private:
|
|
||||||
HttpServer httpServer;
|
|
||||||
MasterServer::ServerMap *serverMap;
|
|
||||||
bool updatedCache = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //NEWRESTAPI_RESTSERVER_HPP
|
|
|
@ -1,186 +0,0 @@
|
||||||
//
|
|
||||||
// Created by koncord on 21.04.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <RakPeerInterface.h>
|
|
||||||
#include <RakSleep.h>
|
|
||||||
#include <BitStream.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include <Kbhit.h>
|
|
||||||
#include <Gets.h>
|
|
||||||
#include <components/openmw-mp/Master/MasterData.hpp>
|
|
||||||
#include <components/openmw-mp/Master/PacketMasterAnnounce.hpp>
|
|
||||||
#include <components/openmw-mp/Master/PacketMasterUpdate.hpp>
|
|
||||||
#include <components/openmw-mp/Master/PacketMasterQuery.hpp>
|
|
||||||
#include <components/openmw-mp/NetworkMessages.hpp>
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
using namespace RakNet;
|
|
||||||
using namespace mwmp;
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
cout << "Server test" << endl;
|
|
||||||
|
|
||||||
SystemAddress masterAddr("127.0.0.1", 25560);
|
|
||||||
|
|
||||||
RakPeerInterface *peer = RakNet::RakPeerInterface::GetInstance();
|
|
||||||
|
|
||||||
RakNet::SocketDescriptor sd(25565, 0);
|
|
||||||
peer->Startup(8, &sd, 1);
|
|
||||||
|
|
||||||
ConnectionAttemptResult result = peer->Connect(masterAddr.ToString(false), masterAddr.GetPort(), "pass",
|
|
||||||
(int)(strlen("pass")), 0, 0, 5, 500);
|
|
||||||
|
|
||||||
assert(result == RakNet::CONNECTION_ATTEMPT_STARTED);
|
|
||||||
|
|
||||||
char message[2048];
|
|
||||||
BitStream send;
|
|
||||||
|
|
||||||
PacketMasterQuery pmq(peer);
|
|
||||||
pmq.SetSendStream(&send);
|
|
||||||
|
|
||||||
PacketMasterAnnounce pma(peer);
|
|
||||||
pma.SetSendStream(&send);
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
RakSleep(30);
|
|
||||||
|
|
||||||
if (kbhit())
|
|
||||||
{
|
|
||||||
Gets(message, sizeof(message));
|
|
||||||
|
|
||||||
if (strcmp(message, "quit") == 0)
|
|
||||||
{
|
|
||||||
puts("Quitting.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (strcmp(message, "send") == 0)
|
|
||||||
{
|
|
||||||
puts("Sending data about server");
|
|
||||||
QueryData server;
|
|
||||||
server.SetName("Super Server");
|
|
||||||
server.SetPlayers(0);
|
|
||||||
server.SetMaxPlayers(0);
|
|
||||||
|
|
||||||
pma.SetServer(&server);
|
|
||||||
pma.SetFunc(PacketMasterAnnounce::FUNCTION_ANNOUNCE);
|
|
||||||
pma.Send(masterAddr);
|
|
||||||
}
|
|
||||||
else if (strcmp(message, "get") == 0)
|
|
||||||
{
|
|
||||||
puts("Request query info");
|
|
||||||
send.Reset();
|
|
||||||
send.Write((unsigned char) (ID_MASTER_QUERY));
|
|
||||||
peer->Send(&send, HIGH_PRIORITY, RELIABLE_ORDERED, CHANNEL_MASTER, masterAddr, false);
|
|
||||||
}
|
|
||||||
else if (strcmp(message, "getme") == 0)
|
|
||||||
{
|
|
||||||
send.Reset();
|
|
||||||
send.Write((unsigned char) (ID_MASTER_UPDATE));
|
|
||||||
send.Write(SystemAddress("127.0.0.1", 25565));
|
|
||||||
peer->Send(&send, HIGH_PRIORITY, RELIABLE_ORDERED, CHANNEL_MASTER, masterAddr, false);
|
|
||||||
}
|
|
||||||
else if (strcmp(message, "status") == 0)
|
|
||||||
{
|
|
||||||
cout << (peer->GetConnectionState(masterAddr) == IS_CONNECTED ? "Connected" : "Not connected") << endl;
|
|
||||||
}
|
|
||||||
else if (strcmp(message, "keep") == 0)
|
|
||||||
{
|
|
||||||
cout << "Sending keep alive" << endl;
|
|
||||||
pma.SetFunc(PacketMasterAnnounce::FUNCTION_KEEP);
|
|
||||||
pma.Send(masterAddr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (RakNet::Packet *packet = peer->Receive(); packet; peer->DeallocatePacket(packet), packet = peer->Receive())
|
|
||||||
{
|
|
||||||
BitStream data(packet->data, packet->length, false);
|
|
||||||
unsigned char packetID;
|
|
||||||
data.Read(packetID);
|
|
||||||
switch (packetID)
|
|
||||||
{
|
|
||||||
case ID_DISCONNECTION_NOTIFICATION:
|
|
||||||
// Connection lost normally
|
|
||||||
printf("ID_DISCONNECTION_NOTIFICATION\n");
|
|
||||||
break;
|
|
||||||
case ID_ALREADY_CONNECTED:
|
|
||||||
// Connection lost normally
|
|
||||||
printf("ID_ALREADY_CONNECTED with guid %lu\n", packet->guid.g);
|
|
||||||
break;
|
|
||||||
case ID_INCOMPATIBLE_PROTOCOL_VERSION:
|
|
||||||
printf("ID_INCOMPATIBLE_PROTOCOL_VERSION\n");
|
|
||||||
break;
|
|
||||||
case ID_REMOTE_DISCONNECTION_NOTIFICATION: // Server telling the clients of another client disconnecting gracefully. You can manually broadcast this in a peer to peer enviroment if you want.
|
|
||||||
printf("ID_REMOTE_DISCONNECTION_NOTIFICATION\n");
|
|
||||||
break;
|
|
||||||
case ID_REMOTE_CONNECTION_LOST: // Server telling the clients of another client disconnecting forcefully. You can manually broadcast this in a peer to peer enviroment if you want.
|
|
||||||
printf("ID_REMOTE_CONNECTION_LOST\n");
|
|
||||||
break;
|
|
||||||
case ID_REMOTE_NEW_INCOMING_CONNECTION: // Server telling the clients of another client connecting. You can manually broadcast this in a peer to peer enviroment if you want.
|
|
||||||
printf("ID_REMOTE_NEW_INCOMING_CONNECTION\n");
|
|
||||||
break;
|
|
||||||
case ID_CONNECTION_BANNED: // Banned from this server
|
|
||||||
printf("We are banned from this server.\n");
|
|
||||||
break;
|
|
||||||
case ID_CONNECTION_ATTEMPT_FAILED:
|
|
||||||
printf("Connection attempt failed\n");
|
|
||||||
break;
|
|
||||||
case ID_NO_FREE_INCOMING_CONNECTIONS:
|
|
||||||
// Sorry, the server is full. I don't do anything here but
|
|
||||||
// A real app should tell the user
|
|
||||||
printf("ID_NO_FREE_INCOMING_CONNECTIONS\n");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ID_INVALID_PASSWORD:
|
|
||||||
printf("ID_INVALID_PASSWORD\n");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ID_CONNECTION_LOST:
|
|
||||||
// Couldn't deliver a reliable packet - i.e. the other system was abnormally
|
|
||||||
// terminated
|
|
||||||
printf("ID_CONNECTION_LOST\n");
|
|
||||||
return 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ID_CONNECTION_REQUEST_ACCEPTED:
|
|
||||||
// This tells the client they have connected
|
|
||||||
printf("ID_CONNECTION_REQUEST_ACCEPTED to %s with GUID %s\n", packet->systemAddress.ToString(true),
|
|
||||||
packet->guid.ToString());
|
|
||||||
printf("My external address is %s\n", peer->GetExternalID(packet->systemAddress).ToString(true));
|
|
||||||
break;
|
|
||||||
case ID_MASTER_QUERY:
|
|
||||||
{
|
|
||||||
map<SystemAddress, QueryData> servers;
|
|
||||||
|
|
||||||
pmq.SetReadStream(&data);
|
|
||||||
pmq.SetServers(&servers);
|
|
||||||
pmq.Read();
|
|
||||||
|
|
||||||
cout << "Received query data about " << servers.size() << " servers" << endl;
|
|
||||||
|
|
||||||
for (auto serv : servers)
|
|
||||||
cout << serv.second.GetName() << endl;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ID_MASTER_UPDATE:
|
|
||||||
{
|
|
||||||
pair<SystemAddress, QueryData> serverPair;
|
|
||||||
PacketMasterUpdate pmu(peer);
|
|
||||||
pmu.SetReadStream(&data);
|
|
||||||
pmu.SetServer(&serverPair);
|
|
||||||
pmu.Read();
|
|
||||||
cout << "Received info about " << serverPair.first.ToString() << endl;
|
|
||||||
cout << serverPair.second.GetName() << endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
cout << "Wrong packet" << endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
peer->Shutdown(1000);
|
|
||||||
RakPeerInterface::DestroyInstance(peer);
|
|
||||||
}
|
|
|
@ -1,511 +0,0 @@
|
||||||
#ifndef BASE_SERVER_HPP
|
|
||||||
#define BASE_SERVER_HPP
|
|
||||||
|
|
||||||
#include <boost/asio.hpp>
|
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
|
||||||
#include <boost/functional/hash.hpp>
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <thread>
|
|
||||||
#include <functional>
|
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <regex>
|
|
||||||
|
|
||||||
#ifndef CASE_INSENSITIVE_EQUALS_AND_HASH
|
|
||||||
#define CASE_INSENSITIVE_EQUALS_AND_HASH
|
|
||||||
|
|
||||||
//Based on http://www.boost.org/doc/libs/1_60_0/doc/html/unordered/hash_equality.html
|
|
||||||
struct case_insensitive_equals
|
|
||||||
{
|
|
||||||
bool operator()(const std::string &key1, const std::string &key2) const
|
|
||||||
{
|
|
||||||
return boost::algorithm::iequals(key1, key2);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct case_insensitive_hash
|
|
||||||
{
|
|
||||||
size_t operator()(const std::string &key) const
|
|
||||||
{
|
|
||||||
std::size_t seed = 0;
|
|
||||||
for (auto &c: key)
|
|
||||||
boost::hash_combine(seed, std::tolower(c));
|
|
||||||
return seed;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace SimpleWeb
|
|
||||||
{
|
|
||||||
template<class socket_type>
|
|
||||||
class Server;
|
|
||||||
|
|
||||||
template<class socket_type>
|
|
||||||
class ServerBase
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual ~ServerBase()
|
|
||||||
{}
|
|
||||||
|
|
||||||
class Response : public std::ostream
|
|
||||||
{
|
|
||||||
friend class ServerBase<socket_type>;
|
|
||||||
|
|
||||||
boost::asio::streambuf streambuf;
|
|
||||||
|
|
||||||
std::shared_ptr<socket_type> socket;
|
|
||||||
|
|
||||||
Response(const std::shared_ptr<socket_type> &socket) : std::ostream(&streambuf), socket(socket)
|
|
||||||
{}
|
|
||||||
|
|
||||||
public:
|
|
||||||
size_t size()
|
|
||||||
{
|
|
||||||
return streambuf.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
/// without specifying the content length.
|
|
||||||
bool close_connection_after_response = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Content : public std::istream
|
|
||||||
{
|
|
||||||
friend class ServerBase<socket_type>;
|
|
||||||
|
|
||||||
public:
|
|
||||||
size_t size()
|
|
||||||
{
|
|
||||||
return streambuf.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string string()
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << rdbuf();
|
|
||||||
return ss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
boost::asio::streambuf &streambuf;
|
|
||||||
|
|
||||||
Content(boost::asio::streambuf &streambuf) : std::istream(&streambuf), streambuf(streambuf)
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Request
|
|
||||||
{
|
|
||||||
friend class ServerBase<socket_type>;
|
|
||||||
|
|
||||||
friend class Server<socket_type>;
|
|
||||||
|
|
||||||
public:
|
|
||||||
std::string method, path, http_version;
|
|
||||||
|
|
||||||
Content content;
|
|
||||||
|
|
||||||
std::unordered_multimap<std::string, std::string, case_insensitive_hash, case_insensitive_equals> header;
|
|
||||||
|
|
||||||
std::smatch path_match;
|
|
||||||
|
|
||||||
std::string remote_endpoint_address;
|
|
||||||
unsigned short remote_endpoint_port;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Request(const socket_type &socket) : content(streambuf)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Config
|
|
||||||
{
|
|
||||||
friend class ServerBase<socket_type>;
|
|
||||||
|
|
||||||
Config(unsigned short port) : port(port)
|
|
||||||
{}
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// Port number to use. Defaults to 80 for HTTP and 443 for HTTPS.
|
|
||||||
unsigned short port;
|
|
||||||
/// Number of threads that the server will use when start() is called. Defaults to 1 thread.
|
|
||||||
size_t thread_pool_size = 1;
|
|
||||||
/// Timeout on request handling. Defaults to 5 seconds.
|
|
||||||
size_t timeout_request = 5;
|
|
||||||
/// Timeout on content handling. Defaults to 300 seconds.
|
|
||||||
size_t timeout_content = 300;
|
|
||||||
/// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation.
|
|
||||||
/// If empty, the address will be any address.
|
|
||||||
std::string address;
|
|
||||||
/// Set to false to avoid binding the socket to an address that is already in use. Defaults to true.
|
|
||||||
bool reuse_address = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
///Set before calling start().
|
|
||||||
Config config;
|
|
||||||
|
|
||||||
private:
|
|
||||||
class regex_orderable : public std::regex
|
|
||||||
{
|
|
||||||
std::string str;
|
|
||||||
public:
|
|
||||||
regex_orderable(const char *regex_cstr) : std::regex(regex_cstr), str(regex_cstr)
|
|
||||||
{}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// Warning: do not add or remove resources after start() is called
|
|
||||||
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::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>::Request>,
|
|
||||||
const boost::system::error_code &)>
|
|
||||||
on_error;
|
|
||||||
|
|
||||||
std::function<void(std::shared_ptr<socket_type> socket,
|
|
||||||
std::shared_ptr<typename ServerBase<socket_type>::Request>)> on_upgrade;
|
|
||||||
|
|
||||||
virtual void start()
|
|
||||||
{
|
|
||||||
if (!io_service)
|
|
||||||
io_service = std::make_shared<boost::asio::io_service>();
|
|
||||||
|
|
||||||
if (io_service->stopped())
|
|
||||||
io_service->reset();
|
|
||||||
|
|
||||||
boost::asio::ip::tcp::endpoint endpoint;
|
|
||||||
if (config.address.size() > 0)
|
|
||||||
endpoint = boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(config.address),
|
|
||||||
config.port);
|
|
||||||
else
|
|
||||||
endpoint = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), config.port);
|
|
||||||
|
|
||||||
if (!acceptor)
|
|
||||||
acceptor = std::unique_ptr<boost::asio::ip::tcp::acceptor>(
|
|
||||||
new boost::asio::ip::tcp::acceptor(*io_service));
|
|
||||||
acceptor->open(endpoint.protocol());
|
|
||||||
acceptor->set_option(boost::asio::socket_base::reuse_address(config.reuse_address));
|
|
||||||
acceptor->bind(endpoint);
|
|
||||||
acceptor->listen();
|
|
||||||
|
|
||||||
accept();
|
|
||||||
|
|
||||||
//If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling
|
|
||||||
threads.clear();
|
|
||||||
for (size_t c = 1; c < config.thread_pool_size; c++)
|
|
||||||
{
|
|
||||||
threads.emplace_back([this]()
|
|
||||||
{
|
|
||||||
io_service->run();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//Main thread
|
|
||||||
if (config.thread_pool_size > 0)
|
|
||||||
io_service->run();
|
|
||||||
|
|
||||||
//Wait for the rest of the threads, if any, to finish as well
|
|
||||||
for (auto &t: threads)
|
|
||||||
{
|
|
||||||
t.join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop()
|
|
||||||
{
|
|
||||||
acceptor->close();
|
|
||||||
if (config.thread_pool_size > 0)
|
|
||||||
io_service->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
///Use this function if you need to recursively send parts of a longer message
|
|
||||||
void send(const std::shared_ptr<Response> &response,
|
|
||||||
const std::function<void(const boost::system::error_code &)> &callback = nullptr) const
|
|
||||||
{
|
|
||||||
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:
|
|
||||||
std::unique_ptr<boost::asio::ip::tcp::acceptor> acceptor;
|
|
||||||
std::vector<std::thread> threads;
|
|
||||||
|
|
||||||
ServerBase(unsigned short port) : config(port)
|
|
||||||
{}
|
|
||||||
|
|
||||||
virtual void accept()=0;
|
|
||||||
|
|
||||||
std::shared_ptr<boost::asio::deadline_timer>
|
|
||||||
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);
|
|
||||||
timer->expires_from_now(boost::posix_time::seconds(seconds));
|
|
||||||
timer->async_wait([socket](const boost::system::error_code &ec)
|
|
||||||
{
|
|
||||||
if (!ec)
|
|
||||||
{
|
|
||||||
boost::system::error_code ec;
|
|
||||||
socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
|
|
||||||
socket->lowest_layer().close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return timer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void read_request_and_content(const std::shared_ptr<socket_type> &socket)
|
|
||||||
{
|
|
||||||
//Create new streambuf (Request::streambuf) for async_read_until()
|
|
||||||
//shared_ptr is used to pass temporary objects to the asynchronous functions
|
|
||||||
std::shared_ptr<Request> request(new Request(*socket));
|
|
||||||
|
|
||||||
//Set timeout on the following boost::asio::async-read or write function
|
|
||||||
auto timer = this->get_timeout_timer(socket, config.timeout_request);
|
|
||||||
|
|
||||||
boost::asio::async_read_until(*socket, request->streambuf, "\r\n\r\n", [this, socket, request, timer]
|
|
||||||
(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;
|
|
||||||
|
|
||||||
//If content, read that as well
|
|
||||||
auto it = request->header.find("Content-Length");
|
|
||||||
if (it != request->header.end())
|
|
||||||
{
|
|
||||||
unsigned long long content_length;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
content_length = stoull(it->second);
|
|
||||||
}
|
|
||||||
catch (const std::exception &e)
|
|
||||||
{
|
|
||||||
if (on_error)
|
|
||||||
on_error(request, boost::system::error_code(
|
|
||||||
boost::system::errc::protocol_error,
|
|
||||||
boost::system::generic_category()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (content_length > num_additional_bytes)
|
|
||||||
{
|
|
||||||
//Set timeout on the following boost::asio::async-read or write function
|
|
||||||
auto timer = this->get_timeout_timer(socket,
|
|
||||||
config.timeout_content);
|
|
||||||
boost::asio::async_read(*socket, request->streambuf,
|
|
||||||
boost::asio::transfer_exactly(
|
|
||||||
content_length -
|
|
||||||
num_additional_bytes),
|
|
||||||
[this, socket, request, timer]
|
|
||||||
(const boost::system::error_code &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
|
|
||||||
this->find_resource(socket, request);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
this->find_resource(socket, request);
|
|
||||||
}
|
|
||||||
else if (on_error)
|
|
||||||
on_error(request, ec);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool parse_request(const std::shared_ptr<Request> &request) const
|
|
||||||
{
|
|
||||||
std::string line;
|
|
||||||
getline(request->content, line);
|
|
||||||
size_t method_end;
|
|
||||||
if ((method_end = line.find(' ')) != std::string::npos)
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
return false;
|
|
||||||
request->http_version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
|
|
||||||
getline(request->content, 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())
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Find path- and method-match, and call write_response
|
|
||||||
for (auto ®ex_method: resource)
|
|
||||||
{
|
|
||||||
auto it = regex_method.second.find(request->method);
|
|
||||||
if (it != regex_method.second.end())
|
|
||||||
{
|
|
||||||
std::smatch sm_res;
|
|
||||||
if (std::regex_match(request->path, sm_res, regex_method.first))
|
|
||||||
{
|
|
||||||
request->path_match = std::move(sm_res);
|
|
||||||
write_response(socket, request, it->second);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto it = default_resource.find(request->method);
|
|
||||||
if (it != default_resource.end())
|
|
||||||
{
|
|
||||||
write_response(socket, request, it->second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_response(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request,
|
|
||||||
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>,
|
|
||||||
std::shared_ptr<
|
|
||||||
typename ServerBase<socket_type>::Request>)> &resource_function)
|
|
||||||
{
|
|
||||||
//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);
|
|
||||||
this->send(response, [this, response, request, timer](
|
|
||||||
const boost::system::error_code &ec)
|
|
||||||
{
|
|
||||||
if (timer)
|
|
||||||
timer->cancel();
|
|
||||||
if (!ec)
|
|
||||||
{
|
|
||||||
if (response->close_connection_after_response)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto range = request->header.equal_range(
|
|
||||||
"Connection");
|
|
||||||
for (auto it = range.first; it != range.second; it++)
|
|
||||||
{
|
|
||||||
if (boost::iequals(it->second, "close"))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (boost::iequals(it->second, "keep-alive"))
|
|
||||||
{
|
|
||||||
this->read_request_and_content(
|
|
||||||
response->socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (request->http_version >= "1.1")
|
|
||||||
this->read_request_and_content(response->socket);
|
|
||||||
}
|
|
||||||
else if (on_error)
|
|
||||||
on_error(request, ec);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
resource_function(response, request);
|
|
||||||
}
|
|
||||||
catch (const std::exception &e)
|
|
||||||
{
|
|
||||||
if (on_error)
|
|
||||||
on_error(request, boost::system::error_code(boost::system::errc::operation_canceled,
|
|
||||||
boost::system::generic_category()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#endif //BASE_SERVER_HPP
|
|
|
@ -1,55 +0,0 @@
|
||||||
/*
|
|
||||||
* 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"
|
|
||||||
|
|
||||||
namespace SimpleWeb
|
|
||||||
{
|
|
||||||
|
|
||||||
template<class socket_type>
|
|
||||||
class Server : public ServerBase<socket_type> {};
|
|
||||||
|
|
||||||
typedef boost::asio::ip::tcp::socket HTTP;
|
|
||||||
|
|
||||||
template<>
|
|
||||||
class Server<HTTP> : public ServerBase<HTTP>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Server() : ServerBase<HTTP>::ServerBase(80)
|
|
||||||
{}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void accept()
|
|
||||||
{
|
|
||||||
//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)
|
|
||||||
{
|
|
||||||
//Immediately start accepting a new connection (if io_service hasn't been stopped)
|
|
||||||
if (ec != boost::asio::error::operation_aborted)
|
|
||||||
accept();
|
|
||||||
|
|
||||||
if (!ec)
|
|
||||||
{
|
|
||||||
boost::asio::ip::tcp::no_delay option(true);
|
|
||||||
socket->set_option(option);
|
|
||||||
|
|
||||||
this->read_request_and_content(socket);
|
|
||||||
}
|
|
||||||
else if (on_error)
|
|
||||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //SERVER_HTTP_HPP
|
|
|
@ -1,91 +0,0 @@
|
||||||
#ifndef HTTPS_SERVER_HPP
|
|
||||||
#define HTTPS_SERVER_HPP
|
|
||||||
|
|
||||||
#include "base_server.hpp"
|
|
||||||
#include <boost/asio/ssl.hpp>
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace SimpleWeb
|
|
||||||
{
|
|
||||||
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> HTTPS;
|
|
||||||
|
|
||||||
template<>
|
|
||||||
class Server<HTTPS> : public ServerBase<HTTPS>
|
|
||||||
{
|
|
||||||
std::string session_id_context;
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
context.load_verify_file(verify_file);
|
|
||||||
context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert |
|
|
||||||
boost::asio::ssl::verify_client_once);
|
|
||||||
set_session_id_context = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void start()
|
|
||||||
{
|
|
||||||
if (set_session_id_context)
|
|
||||||
{
|
|
||||||
// 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.append(config.address.rbegin(), config.address.rend());
|
|
||||||
SSL_CTX_set_session_id_context(context.native_handle(),
|
|
||||||
reinterpret_cast<const unsigned char *>(session_id_context.data()),
|
|
||||||
std::min<size_t>(session_id_context.size(),
|
|
||||||
SSL_MAX_SSL_SESSION_ID_LENGTH));
|
|
||||||
}
|
|
||||||
ServerBase::start();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
boost::asio::ssl::context context;
|
|
||||||
|
|
||||||
virtual void accept()
|
|
||||||
{
|
|
||||||
//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)
|
|
||||||
{
|
|
||||||
//Immediately start accepting a new connection (if io_service hasn't been stopped)
|
|
||||||
if (ec != boost::asio::error::operation_aborted)
|
|
||||||
accept();
|
|
||||||
|
|
||||||
|
|
||||||
if (!ec)
|
|
||||||
{
|
|
||||||
boost::asio::ip::tcp::no_delay option(true);
|
|
||||||
socket->lowest_layer().set_option(option);
|
|
||||||
|
|
||||||
//Set timeout on the following boost::asio::ssl::stream::async_handshake
|
|
||||||
auto timer = get_timeout_timer(socket, config.timeout_request);
|
|
||||||
socket->async_handshake(boost::asio::ssl::stream_base::server, [this, socket, timer]
|
|
||||||
(const boost::system::error_code &ec)
|
|
||||||
{
|
|
||||||
if (timer)
|
|
||||||
timer->cancel();
|
|
||||||
if (!ec)
|
|
||||||
read_request_and_content(socket);
|
|
||||||
else if (on_error)
|
|
||||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (on_error)
|
|
||||||
on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //HTTPS_SERVER_HPP
|
|
|
@ -1,37 +0,0 @@
|
||||||
#include <iostream>
|
|
||||||
#include <Kbhit.h>
|
|
||||||
#include <RakSleep.h>
|
|
||||||
#include "MasterServer.hpp"
|
|
||||||
#include "RestServer.hpp"
|
|
||||||
|
|
||||||
using namespace RakNet;
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
unique_ptr<RestServer> restServer;
|
|
||||||
unique_ptr<MasterServer> masterServer;
|
|
||||||
bool run = true;
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
masterServer.reset(new MasterServer(2000, 25560));
|
|
||||||
restServer.reset(new RestServer(8080, masterServer->GetServers()));
|
|
||||||
|
|
||||||
auto onExit = [](int /*sig*/){
|
|
||||||
restServer->stop();
|
|
||||||
masterServer->Stop(false);
|
|
||||||
masterServer->Wait();
|
|
||||||
run = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
signal(SIGINT, onExit);
|
|
||||||
signal(SIGTERM, onExit);
|
|
||||||
|
|
||||||
masterServer->Start();
|
|
||||||
|
|
||||||
thread server_thread([]() { restServer->start(); });
|
|
||||||
|
|
||||||
server_thread.join();
|
|
||||||
masterServer->Wait();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -9,26 +9,15 @@ set(MWINIIMPORT_HEADER
|
||||||
|
|
||||||
source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER})
|
source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER})
|
||||||
|
|
||||||
openmw_add_executable(openmw-iniimporter
|
add_executable(openmw-iniimporter
|
||||||
${MWINIIMPORT}
|
${MWINIIMPORT}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(openmw-iniimporter
|
target_link_libraries(openmw-iniimporter
|
||||||
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
${Boost_LIBRARIES}
|
||||||
${Boost_FILESYSTEM_LIBRARY}
|
|
||||||
components
|
components
|
||||||
)
|
)
|
||||||
|
|
||||||
if (WIN32)
|
|
||||||
target_link_libraries(openmw-iniimporter
|
|
||||||
${Boost_LOCALE_LIBRARY})
|
|
||||||
INSTALL(TARGETS openmw-iniimporter RUNTIME DESTINATION ".")
|
|
||||||
endif(WIN32)
|
|
||||||
|
|
||||||
if (MINGW)
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (BUILD_WITH_CODE_COVERAGE)
|
if (BUILD_WITH_CODE_COVERAGE)
|
||||||
add_definitions (--coverage)
|
add_definitions (--coverage)
|
||||||
target_link_libraries(openmw-iniimporter gcov)
|
target_link_libraries(openmw-iniimporter gcov)
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
#include "importer.hpp"
|
#include "importer.hpp"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <components/misc/stringops.hpp>
|
#include <components/misc/stringops.hpp>
|
||||||
#include <components/esm/esmreader.hpp>
|
|
||||||
|
|
||||||
|
#include <boost/version.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/filesystem/fstream.hpp>
|
#include <boost/filesystem/fstream.hpp>
|
||||||
#include <boost/version.hpp>
|
|
||||||
|
|
||||||
namespace bfs = boost::filesystem;
|
namespace bfs = boost::filesystem;
|
||||||
|
|
||||||
|
@ -17,6 +20,7 @@ MwIniImporter::MwIniImporter()
|
||||||
{
|
{
|
||||||
const char *map[][2] =
|
const char *map[][2] =
|
||||||
{
|
{
|
||||||
|
{ "fps", "General:Show FPS" },
|
||||||
{ "no-sound", "General:Disable Audio" },
|
{ "no-sound", "General:Disable Audio" },
|
||||||
{ 0, 0 }
|
{ 0, 0 }
|
||||||
};
|
};
|
||||||
|
@ -635,9 +639,6 @@ MwIniImporter::MwIniImporter()
|
||||||
"Blood:Texture Name 1",
|
"Blood:Texture Name 1",
|
||||||
"Blood:Texture Name 2",
|
"Blood:Texture Name 2",
|
||||||
|
|
||||||
// werewolf (Bloodmoon)
|
|
||||||
"General:Werewolf FOV",
|
|
||||||
|
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -654,6 +655,12 @@ void MwIniImporter::setVerbose(bool verbose) {
|
||||||
mVerbose = verbose;
|
mVerbose = verbose;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string MwIniImporter::numberToString(int n) {
|
||||||
|
std::stringstream str;
|
||||||
|
str << n;
|
||||||
|
return str.str();
|
||||||
|
}
|
||||||
|
|
||||||
MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::path& filename) const {
|
MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::path& filename) const {
|
||||||
std::cout << "load ini file: " << filename << std::endl;
|
std::cout << "load ini file: " << filename << std::endl;
|
||||||
|
|
||||||
|
@ -795,7 +802,7 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con
|
||||||
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++) {
|
||||||
archive = baseArchive;
|
archive = baseArchive;
|
||||||
archive.append(std::to_string(i));
|
archive.append(this->numberToString(i));
|
||||||
|
|
||||||
it = ini.find(archive);
|
it = ini.find(archive);
|
||||||
if(it == ini.end()) {
|
if(it == ini.end()) {
|
||||||
|
@ -814,109 +821,38 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con
|
||||||
// does not appears in the ini file
|
// does not appears in the ini file
|
||||||
cfg["fallback-archive"].push_back("Morrowind.bsa");
|
cfg["fallback-archive"].push_back("Morrowind.bsa");
|
||||||
|
|
||||||
for(std::vector<std::string>::const_iterator iter=archives.begin(); iter!=archives.end(); ++iter) {
|
for(std::vector<std::string>::const_iterator it=archives.begin(); it!=archives.end(); ++it) {
|
||||||
cfg["fallback-archive"].push_back(*iter);
|
cfg["fallback-archive"].push_back(*it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MwIniImporter::dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector<std::string>& result)
|
void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const {
|
||||||
{
|
std::vector<std::pair<std::time_t, std::string> > contentFiles;
|
||||||
auto iter = std::find_if(
|
|
||||||
source.begin(),
|
|
||||||
source.end(),
|
|
||||||
[&element](std::pair< std::string, std::vector<std::string> >& sourceElement)
|
|
||||||
{
|
|
||||||
return sourceElement.first == element;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (iter != source.end())
|
|
||||||
{
|
|
||||||
auto foundElement = std::move(*iter);
|
|
||||||
source.erase(iter);
|
|
||||||
for (auto name : foundElement.second)
|
|
||||||
{
|
|
||||||
MwIniImporter::dependencySortStep(name, source, result);
|
|
||||||
}
|
|
||||||
result.push_back(std::move(foundElement.first));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> MwIniImporter::dependencySort(MwIniImporter::dependencyList source)
|
|
||||||
{
|
|
||||||
std::vector<std::string> result;
|
|
||||||
while (!source.empty())
|
|
||||||
{
|
|
||||||
MwIniImporter::dependencySortStep(source.begin()->first, source, result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string>::iterator MwIniImporter::findString(std::vector<std::string>& source, const std::string& string)
|
|
||||||
{
|
|
||||||
return std::find_if(source.begin(), source.end(), [&string](const std::string& sourceString)
|
|
||||||
{
|
|
||||||
return Misc::StringUtils::ciEqual(sourceString, string);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void MwIniImporter::addPaths(std::vector<boost::filesystem::path>& output, std::vector<std::string> input) {
|
|
||||||
for (auto& path : input) {
|
|
||||||
if (path.front() == '"')
|
|
||||||
{
|
|
||||||
path.erase(path.begin());
|
|
||||||
path.erase(path.end() - 1);
|
|
||||||
}
|
|
||||||
output.emplace_back(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
std::vector<boost::filesystem::path> dataPaths;
|
// assume the Game Files are all in a "Data Files" directory under the directory holding Morrowind.ini
|
||||||
if (cfg.count("data"))
|
const boost::filesystem::path gameFilesDir(iniFilename.parent_path() /= "Data Files");
|
||||||
addPaths(dataPaths, cfg["data"]);
|
|
||||||
|
|
||||||
if (cfg.count("data-local"))
|
|
||||||
addPaths(dataPaths, cfg["data-local"]);
|
|
||||||
|
|
||||||
dataPaths.push_back(iniFilename.parent_path() /= "Data Files");
|
|
||||||
|
|
||||||
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++) {
|
||||||
{
|
gameFile = baseGameFile;
|
||||||
std::string gameFile = baseGameFile;
|
gameFile.append(this->numberToString(i));
|
||||||
gameFile.append(std::to_string(i));
|
|
||||||
|
|
||||||
it = ini.find(gameFile);
|
it = ini.find(gameFile);
|
||||||
if(it == ini.end())
|
if(it == ini.end()) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
for(std::vector<std::string>::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry)
|
for(std::vector<std::string>::const_iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) {
|
||||||
{
|
|
||||||
std::string filetype(entry->substr(entry->length()-3));
|
std::string filetype(entry->substr(entry->length()-3));
|
||||||
Misc::StringUtils::lowerCaseInPlace(filetype);
|
Misc::StringUtils::toLower(filetype);
|
||||||
|
|
||||||
if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0)
|
if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) {
|
||||||
{
|
boost::filesystem::path filepath(gameFilesDir);
|
||||||
bool found = false;
|
filepath /= *entry;
|
||||||
for (auto & dataPath : dataPaths)
|
contentFiles.push_back(std::make_pair(lastWriteTime(filepath, defaultTime), *entry));
|
||||||
{
|
|
||||||
boost::filesystem::path path = dataPath / *entry;
|
|
||||||
std::time_t time = lastWriteTime(path, defaultTime);
|
|
||||||
if (time != defaultTime)
|
|
||||||
{
|
|
||||||
contentFiles.push_back({time, path});
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found)
|
|
||||||
std::cout << "Warning: " << *entry << " not found, ignoring" << std::endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -924,46 +860,11 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, co
|
||||||
cfg.erase("content");
|
cfg.erase("content");
|
||||||
cfg.insert( std::make_pair("content", std::vector<std::string>() ) );
|
cfg.insert( std::make_pair("content", std::vector<std::string>() ) );
|
||||||
|
|
||||||
// sort by timestamp
|
// this will sort files by time order first, then alphabetical (maybe), I suspect non ASCII filenames will be stuffed.
|
||||||
sort(contentFiles.begin(), contentFiles.end());
|
sort(contentFiles.begin(), contentFiles.end());
|
||||||
|
for(std::vector<std::pair<std::time_t, std::string> >::const_iterator it=contentFiles.begin(); it!=contentFiles.end(); ++it) {
|
||||||
MwIniImporter::dependencyList unsortedFiles;
|
cfg["content"].push_back(it->second);
|
||||||
|
|
||||||
ESM::ESMReader reader;
|
|
||||||
reader.setEncoder(&encoder);
|
|
||||||
for (auto& file : contentFiles)
|
|
||||||
{
|
|
||||||
reader.open(file.second.string());
|
|
||||||
std::vector<std::string> dependencies;
|
|
||||||
for (auto& gameFile : reader.getGameFiles())
|
|
||||||
{
|
|
||||||
dependencies.push_back(gameFile.name);
|
|
||||||
}
|
}
|
||||||
unsortedFiles.emplace_back(boost::filesystem::path(reader.getName()).filename().string(), dependencies);
|
|
||||||
reader.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto sortedFiles = dependencySort(unsortedFiles);
|
|
||||||
|
|
||||||
// hard-coded dependency Morrowind - Tribunal - Bloodmoon
|
|
||||||
if(findString(sortedFiles, "Morrowind.esm") != sortedFiles.end())
|
|
||||||
{
|
|
||||||
auto tribunalIter = findString(sortedFiles, "Tribunal.esm");
|
|
||||||
auto bloodmoonIter = findString(sortedFiles, "Bloodmoon.esm");
|
|
||||||
|
|
||||||
if (bloodmoonIter != sortedFiles.end() && tribunalIter != sortedFiles.end())
|
|
||||||
{
|
|
||||||
size_t bloodmoonIndex = std::distance(sortedFiles.begin(), bloodmoonIter);
|
|
||||||
size_t tribunalIndex = std::distance(sortedFiles.begin(), tribunalIter);
|
|
||||||
if (bloodmoonIndex < tribunalIndex)
|
|
||||||
tribunalIndex++;
|
|
||||||
sortedFiles.insert(bloodmoonIter, *tribunalIter);
|
|
||||||
sortedFiles.erase(sortedFiles.begin() + tribunalIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& file : sortedFiles)
|
|
||||||
cfg["content"].push_back(file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MwIniImporter::writeToFile(std::ostream &out, const multistrmap &cfg) {
|
void MwIniImporter::writeToFile(std::ostream &out, const multistrmap &cfg) {
|
||||||
|
@ -994,13 +895,12 @@ std::time_t MwIniImporter::lastWriteTime(const boost::filesystem::path& filename
|
||||||
boost::filesystem::path resolved = filename;
|
boost::filesystem::path resolved = filename;
|
||||||
#endif
|
#endif
|
||||||
writeTime = boost::filesystem::last_write_time(resolved);
|
writeTime = boost::filesystem::last_write_time(resolved);
|
||||||
|
|
||||||
// print timestamp
|
|
||||||
const int size=1024;
|
|
||||||
char timeStrBuffer[size];
|
|
||||||
if (std::strftime(timeStrBuffer, size, "%x %X", localtime(&writeTime)) > 0)
|
|
||||||
std::cout << "content file: " << resolved << " timestamp = (" << writeTime <<
|
std::cout << "content file: " << resolved << " timestamp = (" << writeTime <<
|
||||||
") " << timeStrBuffer << std::endl;
|
") " << asctime(localtime(&writeTime)) << std::endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout << "content file: " << filename << " not found" << std::endl;
|
||||||
}
|
}
|
||||||
return writeTime;
|
return writeTime;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ class MwIniImporter {
|
||||||
public:
|
public:
|
||||||
typedef std::map<std::string, std::string> strmap;
|
typedef std::map<std::string, std::string> strmap;
|
||||||
typedef std::map<std::string, std::vector<std::string> > multistrmap;
|
typedef std::map<std::string, std::vector<std::string> > multistrmap;
|
||||||
typedef std::vector< std::pair< std::string, std::vector<std::string> > > dependencyList;
|
|
||||||
|
|
||||||
MwIniImporter();
|
MwIniImporter();
|
||||||
void setInputEncoding(const ToUTF8::FromType& encoding);
|
void setInputEncoding(const ToUTF8::FromType& encoding);
|
||||||
|
@ -28,14 +27,9 @@ class MwIniImporter {
|
||||||
void importArchives(multistrmap &cfg, const multistrmap &ini) const;
|
void importArchives(multistrmap &cfg, const multistrmap &ini) const;
|
||||||
static void writeToFile(std::ostream &out, const multistrmap &cfg);
|
static void writeToFile(std::ostream &out, const multistrmap &cfg);
|
||||||
|
|
||||||
static std::vector<std::string> dependencySort(MwIniImporter::dependencyList source);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void dependencySortStep(std::string& element, MwIniImporter::dependencyList& source, std::vector<std::string>& result);
|
|
||||||
static std::vector<std::string>::iterator findString(std::vector<std::string>& source, const std::string& string);
|
|
||||||
|
|
||||||
static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value);
|
static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value);
|
||||||
static void addPaths(std::vector<boost::filesystem::path>& output, std::vector<std::string> input);
|
static std::string numberToString(int n);
|
||||||
|
|
||||||
/// \return file's "last modified time", used in original MW to determine plug-in load order
|
/// \return file's "last modified time", used in original MW to determine plug-in load order
|
||||||
static std::time_t lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime);
|
static std::time_t lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime);
|
||||||
|
@ -46,4 +40,5 @@ class MwIniImporter {
|
||||||
ToUTF8::FromType mEncoding;
|
ToUTF8::FromType mEncoding;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
#include "importer.hpp"
|
#include "importer.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <boost/program_options.hpp>
|
#include <boost/program_options.hpp>
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
#include <boost/filesystem/fstream.hpp>
|
#include <boost/filesystem/fstream.hpp>
|
||||||
|
|
||||||
namespace bpo = boost::program_options;
|
namespace bpo = boost::program_options;
|
||||||
|
|
|
@ -1,165 +0,0 @@
|
||||||
///Program to test .nif files both on the FileSystem and in BSA archives.
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
#include <components/nif/niffile.hpp>
|
|
||||||
#include <components/files/constrainedfilestream.hpp>
|
|
||||||
#include <components/vfs/manager.hpp>
|
|
||||||
#include <components/vfs/bsaarchive.hpp>
|
|
||||||
#include <components/vfs/filesystemarchive.hpp>
|
|
||||||
|
|
||||||
#include <boost/program_options.hpp>
|
|
||||||
#include <boost/filesystem.hpp>
|
|
||||||
|
|
||||||
// Create local aliases for brevity
|
|
||||||
namespace bpo = boost::program_options;
|
|
||||||
namespace bfs = boost::filesystem;
|
|
||||||
|
|
||||||
///See if the file has the named extension
|
|
||||||
bool hasExtension(std::string filename, std::string extensionToFind)
|
|
||||||
{
|
|
||||||
std::string extension = filename.substr(filename.find_last_of(".")+1);
|
|
||||||
|
|
||||||
//Convert strings to lower case for comparison
|
|
||||||
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
|
||||||
std::transform(extensionToFind.begin(), extensionToFind.end(), extensionToFind.begin(), ::tolower);
|
|
||||||
|
|
||||||
if(extension == extensionToFind)
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
///See if the file has the "nif" extension.
|
|
||||||
bool isNIF(const std::string & filename)
|
|
||||||
{
|
|
||||||
return hasExtension(filename,"nif");
|
|
||||||
}
|
|
||||||
///See if the file has the "bsa" extension.
|
|
||||||
bool isBSA(const std::string & filename)
|
|
||||||
{
|
|
||||||
return hasExtension(filename,"bsa");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check all the nif files in a given VFS::Archive
|
|
||||||
/// \note Takes ownership!
|
|
||||||
/// \note Can not read a bsa file inside of a bsa file.
|
|
||||||
void readVFS(VFS::Archive* anArchive,std::string archivePath = "")
|
|
||||||
{
|
|
||||||
VFS::Manager myManager(true);
|
|
||||||
myManager.addArchive(anArchive);
|
|
||||||
myManager.buildIndex();
|
|
||||||
|
|
||||||
std::map<std::string, VFS::File*> files=myManager.getIndex();
|
|
||||||
for(std::map<std::string, VFS::File*>::const_iterator it=files.begin(); it!=files.end(); ++it)
|
|
||||||
{
|
|
||||||
std::string name = it->first;
|
|
||||||
|
|
||||||
try{
|
|
||||||
if(isNIF(name))
|
|
||||||
{
|
|
||||||
// std::cout << "Decoding: " << name << std::endl;
|
|
||||||
Nif::NIFFile temp_nif(myManager.get(name),archivePath+name);
|
|
||||||
}
|
|
||||||
else if(isBSA(name))
|
|
||||||
{
|
|
||||||
if(!archivePath.empty() && !isBSA(archivePath))
|
|
||||||
{
|
|
||||||
// std::cout << "Reading BSA File: " << name << std::endl;
|
|
||||||
readVFS(new VFS::BsaArchive(archivePath+name),archivePath+name+"/");
|
|
||||||
// std::cout << "Done with BSA File: " << name << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (std::exception& e)
|
|
||||||
{
|
|
||||||
std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> parseOptions (int argc, char** argv)
|
|
||||||
{
|
|
||||||
bpo::options_description desc("Ensure that OpenMW can use the provided NIF and BSA files\n\n"
|
|
||||||
"Usages:\n"
|
|
||||||
" niftool <nif files, BSA files, or directories>\n"
|
|
||||||
" Scan the file or directories for nif errors.\n\n"
|
|
||||||
"Allowed options");
|
|
||||||
desc.add_options()
|
|
||||||
("help,h", "print help message.")
|
|
||||||
("input-file", bpo::value< std::vector<std::string> >(), "input file")
|
|
||||||
;
|
|
||||||
|
|
||||||
//Default option if none provided
|
|
||||||
bpo::positional_options_description p;
|
|
||||||
p.add("input-file", -1);
|
|
||||||
|
|
||||||
bpo::variables_map variables;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv).
|
|
||||||
options(desc).positional(p).run();
|
|
||||||
bpo::store(valid_opts, variables);
|
|
||||||
}
|
|
||||||
catch(std::exception &e)
|
|
||||||
{
|
|
||||||
std::cout << "ERROR parsing arguments: " << e.what() << "\n\n"
|
|
||||||
<< desc << std::endl;
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
bpo::notify(variables);
|
|
||||||
if (variables.count ("help"))
|
|
||||||
{
|
|
||||||
std::cout << desc << std::endl;
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
if (variables.count("input-file"))
|
|
||||||
{
|
|
||||||
return variables["input-file"].as< std::vector<std::string> >();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "No input files or directories specified!" << std::endl;
|
|
||||||
std::cout << desc << std::endl;
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
std::vector<std::string> files = parseOptions (argc, argv);
|
|
||||||
|
|
||||||
// std::cout << "Reading Files" << std::endl;
|
|
||||||
for(std::vector<std::string>::const_iterator it=files.begin(); it!=files.end(); ++it)
|
|
||||||
{
|
|
||||||
std::string name = *it;
|
|
||||||
|
|
||||||
try{
|
|
||||||
if(isNIF(name))
|
|
||||||
{
|
|
||||||
//std::cout << "Decoding: " << name << std::endl;
|
|
||||||
Nif::NIFFile temp_nif(Files::openConstrainedFileStream(name.c_str()),name);
|
|
||||||
}
|
|
||||||
else if(isBSA(name))
|
|
||||||
{
|
|
||||||
// std::cout << "Reading BSA File: " << name << std::endl;
|
|
||||||
readVFS(new VFS::BsaArchive(name));
|
|
||||||
}
|
|
||||||
else if(bfs::is_directory(bfs::path(name)))
|
|
||||||
{
|
|
||||||
// std::cout << "Reading All Files in: " << name << std::endl;
|
|
||||||
readVFS(new VFS::FileSystemArchive(name),name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cerr << "ERROR: \"" << name << "\" is not a nif file, bsa file, or directory!" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (std::exception& e)
|
|
||||||
{
|
|
||||||
std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue