Merge pull request #360 from TES3MP/0.6.1-openmw-updates while resolving conflicts

0.6.2
David Cernat 6 years ago
commit cac2c6c1e8

1
.gitignore vendored

@ -26,6 +26,7 @@ Doxygen
.settings
.directory
.idea
cmake-build-*
files/windows/*.aps
cmake-build-*
## qt-creator

@ -24,7 +24,7 @@ addons:
- llvm-toolchain-precise-3.6
packages: [
# Dev
clang-3.6, libunshield-dev, libtinyxml-dev,
cmake, clang-3.6, libunshield-dev, libtinyxml-dev,
g++-6,
# Tests
libgtest-dev, google-mock,
@ -43,7 +43,7 @@ addons:
name: "TES3MP/openmw-tes3mp"
description: "<Your project description here>"
notification_email: stas5978@gmail.com
build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE"
build_command_prepend: "cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE -DBUILD_BSATOOL=FALSE -DBUILD_ESMTOOL=FALSE -DBUILD_MWINIIMPORTER=FALSE -DBUILD_LAUNCHER=FALSE"
build_command: "make -j3"
branch_pattern: coverity_scan
matrix:

@ -34,11 +34,13 @@ Programmers
Ben Shealy (bentsherman)
Bret Curtis (psi29a)
Britt Mathis (galdor557)
Capostrophic
cc9cii
Chris Boyce (slothlife)
Chris Robinson (KittyCat)
Cory F. Cohen (cfcohen)
Cris Mihalache (Mirceam)
crussell187
darkf
devnexen
Dieho
@ -86,6 +88,7 @@ Programmers
lazydev
Leon Krieg (lkrieg)
Leon Saunders (emoose)
Łukasz Gołębiewski (lukago)
logzero
lohikaarme
Lukasz Gromanowski (lgro)
@ -104,6 +107,7 @@ Programmers
Michael Papageorgiou (werdanith)
Michał Bień (Glorf)
Michał Moroz (dragonee)
Miloslav Číž (drummyfish)
Miroslav Puda (pakanek)
MiroslavR
Mitchell Schwitzer (schwitzerm)
@ -125,6 +129,7 @@ Programmers
Radu-Marius Popovici (rpopovici)
Rafael Moura (dhustkoder)
rdimesio
rexelion
riothamus
Rob Cutmore (rcutmore)
Robert MacGregor (Ragora)
@ -148,6 +153,7 @@ Programmers
Sylvain Thesnieres (Garvek)
t6
terrorfisch
thegriglat
Thomas Luppi (Digmaster)
Will Herrmann (Thunderforge)
Tom Mason (wheybags)

@ -1,3 +1,140 @@
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
------

@ -6,5 +6,5 @@ brew outdated cmake || brew upgrade cmake
brew outdated pkgconfig || brew upgrade pkgconfig
brew install $macos_qt_formula
curl https://downloads.openmw.org/osx/dependencies/openmw-deps-5e144e2.zip -o ~/openmw-deps.zip
curl https://downloads.openmw.org/osx/dependencies/openmw-deps-c40905f.zip -o ~/openmw-deps.zip
unzip ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null

@ -320,9 +320,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
"ffmpeg-3.2.4-dev-win${BITS}.zip"
# MyGUI
download "MyGUI 3.2.3-git" \
"http://www.lysator.liu.se/~ace/OpenMW/deps/MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}.7z" \
"MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}.7z"
download "MyGUI 3.2.2" \
"http://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" \
@ -330,9 +330,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
"OpenAL-Soft-1.17.2.zip"
# OSG
download "OpenSceneGraph 3.4.0-scrawl" \
"http://www.lysator.liu.se/~ace/OpenMW/deps/OSG-3.4.0-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" \
"OSG-3.4.0-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z"
download "OpenSceneGraph 3.4.1-scrawl" \
"http://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
@ -350,9 +350,9 @@ if [ -z $SKIP_DOWNLOAD ]; then
fi
# SDL2
download "SDL 2.0.4" \
"https://www.libsdl.org/release/SDL2-devel-2.0.4-VC.zip" \
"SDL2-2.0.4.zip"
download "SDL 2.0.7" \
"https://www.libsdl.org/release/SDL2-devel-2.0.7-VC.zip" \
"SDL2-2.0.7.zip"
fi
cd .. #/..
@ -474,20 +474,20 @@ cd $DEPS
echo
# MyGUI
printf "MyGUI 3.2.3-git... "
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 3" 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.3-git-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
mv "MyGUI-3.2.3-git-msvc${MSVC_YEAR}-win${BITS}" 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"
@ -527,20 +527,20 @@ cd $DEPS
echo
# OSG
printf "OSG 3.4.0-scrawl... "
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 0" 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.0-scrawl-msvc${MSVC_YEAR}-win${BITS}.7z" $STRIP
mv "OSG-3.4.0-scrawl-msvc${MSVC_YEAR}-win${BITS}" 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"
@ -556,8 +556,8 @@ printf "OSG 3.4.0-scrawl... "
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.0/osgdb_"{bmp,dds,jpeg,osg,png,tga}${SUFFIX}.dll
add_osg_dlls "$(pwd)/OSG/bin/osgPlugins-3.4.0/osgdb_serializers_osg"{,animation,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.
}
@ -632,18 +632,18 @@ cd $DEPS
echo
# SDL2
printf "SDL 2.0.4... "
printf "SDL 2.0.7... "
{
if [ -d SDL2-2.0.4 ]; then
if [ -d SDL2-2.0.7 ]; then
printf "Exists. "
elif [ -z $SKIP_EXTRACT ]; then
rm -rf SDL2-2.0.4
eval 7z x -y SDL2-2.0.4.zip $STRIP
rm -rf SDL2-2.0.7
eval 7z x -y SDL2-2.0.7.zip $STRIP
fi
export SDL2DIR="$(real_pwd)/SDL2-2.0.4"
export SDL2DIR="$(real_pwd)/SDL2-2.0.7"
add_runtime_dlls "$(pwd)/SDL2-2.0.4/lib/x${ARCHSUFFIX}/SDL2.dll"
add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll"
echo Done.
}
@ -717,10 +717,10 @@ if [ -z $CI ]; then
echo
echo "- OSG Plugin DLLs..."
mkdir -p $BUILD_CONFIG/osgPlugins-3.4.0
mkdir -p $BUILD_CONFIG/osgPlugins-3.4.1
for DLL in $OSG_PLUGINS; do
echo " $(basename $DLL)."
cp "$DLL" $BUILD_CONFIG/osgPlugins-3.4.0
cp "$DLL" $BUILD_CONFIG/osgPlugins-3.4.1
done
echo
@ -750,19 +750,4 @@ if [ -z $VERBOSE ]; then
fi
fi
if [ -z $CI ]; then
echo "- Copying Runtime Resources/Config Files"
echo " gamecontrollerdb.txt"
cp gamecontrollerdb.txt $BUILD_CONFIG/gamecontrollerdb.txt
echo " openmw.cfg"
cp openmw.cfg.install $BUILD_CONFIG/openmw.cfg
echo " openmw-cs.cfg"
cp openmw-cs.cfg $BUILD_CONFIG/openmw-cs.cfg
echo " settings-default.cfg"
cp settings-default.cfg $BUILD_CONFIG/settings-default.cfg
echo " resources/"
cp -r resources $BUILD_CONFIG/resources
echo
fi
exit $RET

@ -1,3 +1,35 @@
# Apps and tools
option(BUILD_OPENMW "build OpenMW" ON)
option(BUILD_OPENMW_MP "build OpenMW-MP" ON)
option(BUILD_MASTER "build tes3mp master server" OFF)
option(BUILD_BSATOOL "build BSA extractor" ON)
option(BUILD_ESMTOOL "build ESM inspector" ON)
option(BUILD_LAUNCHER "build Launcher" ON)
option(BUILD_BROWSER "build tes3mp Server Browser" ON)
option(BUILD_MWINIIMPORTER "build MWiniImporter" ON)
option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON)
option(BUILD_OPENCS "build OpenMW Construction Set" ON)
option(BUILD_WIZARD "build Installation Wizard" ON)
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
option(BUILD_NIFTEST "build nif file tester" OFF)
option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON)
option(BUILD_DOCS "build documentation." OFF )
if (NOT BUILD_LAUNCHER AND NOT BUILD_BROWSER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD)
set(USE_QT FALSE)
else()
set(USE_QT TRUE)
endif()
if (USE_QT)
set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)")
set_property(CACHE DESIRED_QT_VERSION PROPERTY STRINGS 4 5)
endif()
# set the minimum required version across the board
cmake_minimum_required(VERSION 3.1.0)
project(OpenMW)
# If the user doesn't supply a CMAKE_BUILD_TYPE via command line, choose one for them.
@ -25,7 +57,7 @@ endif()
message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 42)
set(OPENMW_VERSION_MINOR 43)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_VERSION_COMMITHASH "")
@ -59,24 +91,6 @@ option(QT_STATIC "Link static build of QT into the binaries" FALSE)
option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE)
# Apps and tools
option(BUILD_OPENMW "build OpenMW" ON)
option(BUILD_OPENMW_MP "build OpenMW-MP" ON)
option(BUILD_MASTER "build tes3mp master server" OFF)
option(BUILD_BSATOOL "build BSA extractor" ON)
option(BUILD_ESMTOOL "build ESM inspector" ON)
option(BUILD_LAUNCHER "build Launcher" ON)
option(BUILD_BROWSER "build tes3mp Server Browser" ON)
option(BUILD_MWINIIMPORTER "build MWiniImporter" ON)
option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON)
option(BUILD_OPENCS "build OpenMW Construction Set" ON)
option(BUILD_WIZARD "build Installation Wizard" ON)
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
option(BUILD_NIFTEST "build nif file tester" OFF)
option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON)
option(BUILD_DOCS "build documentation." OFF )
# what is necessary to build documentation
IF( BUILD_DOCS )
# Builds the documentation.
@ -126,16 +140,8 @@ endif()
find_package(RakNet REQUIRED)
include_directories(${RakNet_INCLUDES})
if (NOT BUILD_LAUNCHER AND NOT BUILD_BROWSER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD)
set(USE_QT FALSE)
else()
set(USE_QT TRUE)
endif()
# Dependencies
if (USE_QT)
set(DESIRED_QT_VERSION 5 CACHE STRING "The QT version OpenMW should use (4 or 5)")
set_property(CACHE DESIRED_QT_VERSION PROPERTY STRINGS 4 5)
message(STATUS "Using Qt${DESIRED_QT_VERSION}")
if (DESIRED_QT_VERSION MATCHES 4)
@ -150,17 +156,6 @@ if (USE_QT)
endif()
endif()
if (APPLE)
# OS X build process relies on this fix: https://github.com/Kitware/CMake/commit/3df5147043d83aa09acd5c9ce31d5c602efb99db
cmake_minimum_required(VERSION 3.1.0)
elseif (USE_QT AND DESIRED_QT_VERSION MATCHES 5)
# 2.8.11+ is required to make Qt5 happy and allow linking QtMain on Windows.
cmake_minimum_required(VERSION 2.8.11)
else()
# We probably support older versions than this.
cmake_minimum_required(VERSION 2.6)
endif()
IF(BUILD_OPENMW OR BUILD_OPENCS)
# Sound setup
find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE)
@ -323,33 +318,34 @@ endif (APPLE)
# Other files
configure_file(${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp-client-default.cfg
"${OpenMW_BINARY_DIR}/tes3mp-client-default.cfg")
configure_resource_file(${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp-client-default.cfg
"${OpenMW_BINARY_DIR}" "tes3mp-client-default.cfg")
configure_file(${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp-server-default.cfg
"${OpenMW_BINARY_DIR}/tes3mp-server-default.cfg")
configure_resource_file(${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp-server-default.cfg
"${OpenMW_BINARY_DIR}" "tes3mp-server-default.cfg")
configure_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg
"${OpenMW_BINARY_DIR}/settings-default.cfg")
configure_resource_file(${OpenMW_SOURCE_DIR}/files/settings-default.cfg
"${OpenMW_BINARY_DIR}" "settings-default.cfg")
if (NOT APPLE)
configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg.local
"${OpenMW_BINARY_DIR}/openmw.cfg")
configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg
"${OpenMW_BINARY_DIR}/openmw.cfg.install")
configure_resource_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg.local
"${OpenMW_BINARY_DIR}" "openmw.cfg")
configure_resource_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg
"${OpenMW_BINARY_DIR}" "openmw.cfg.install")
else ()
configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg
"${OpenMW_BINARY_DIR}/openmw.cfg")
endif ()
configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg
"${OpenMW_BINARY_DIR}/openmw-cs.cfg")
configure_resource_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.cfg
"${OpenMW_BINARY_DIR}" "openmw-cs.cfg")
configure_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters
"${OpenMW_BINARY_DIR}/resources/defaultfilters" COPYONLY)
# Needs the copy version because the configure version assumes the end of the file has been reached when a null character is reached and there are no CMake expressions to evaluate.
copy_resource_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters
"${OpenMW_BINARY_DIR}" "resources/defaultfilters")
configure_file(${OpenMW_SOURCE_DIR}/files/gamecontrollerdb.txt
"${OpenMW_BINARY_DIR}/gamecontrollerdb.txt")
configure_resource_file(${OpenMW_SOURCE_DIR}/files/gamecontrollerdb.txt
"${OpenMW_BINARY_DIR}" "gamecontrollerdb.txt")
if (NOT WIN32 AND NOT APPLE)
configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop
@ -363,6 +359,7 @@ if (NOT WIN32 AND NOT APPLE)
endif()
# CXX Compiler settings
set(CMAKE_CXX_STANDARD 11)
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wundef -Wno-unused-parameter -std=c++11 -pedantic -Wno-long-long")
add_definitions( -DBOOST_NO_CXX11_SCOPED_ENUMS=ON )
@ -398,13 +395,16 @@ IF(NOT WIN32 AND NOT APPLE)
# Install binaries
IF(BUILD_OPENMW)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" )
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/tes3mp" DESTINATION "${BINDIR}" )
ENDIF(BUILD_OPENMW)
IF(BUILD_OPENMW_MP)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/tes3mp-server" DESTINATION "${BINDIR}")
ENDIF(BUILD_OPENMW_MP)
IF(BUILD_LAUNCHER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-launcher" DESTINATION "${BINDIR}" )
ENDIF(BUILD_LAUNCHER)
IF(BUILD_BROWSER)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-browser" DESTINATION "${BINDIR}" )
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/tes3mp-browser" DESTINATION "${BINDIR}" )
ENDIF(BUILD_BROWSER)
IF(BUILD_BSATOOL)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" )
@ -432,7 +432,7 @@ IF(NOT WIN32 AND NOT APPLE)
#ENDIF(BUILD_MYGUI_PLUGIN)
# Install licenses
INSTALL(FILES "docs/license/DejaVu Font License.txt" DESTINATION "${LICDIR}" )
INSTALL(FILES "files/mygui/DejaVu Font License.txt" DESTINATION "${LICDIR}" )
# Install icon and desktop file
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw")
@ -452,10 +452,11 @@ IF(NOT WIN32 AND NOT APPLE)
INSTALL(FILES "${OpenMW_BINARY_DIR}/resources/version" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/gamecontrollerdb.txt" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-client-default" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-client.install" DESTINATION "${SYSCONFDIR}" RENAME "tes3mp-client.cfg" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-server-default" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw-mp")
INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-server.install" DESTINATION "${SYSCONFDIR}" RENAME "tes3mp-server.cfg" COMPONENT "openmw-mp")
INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-client-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw")
#INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-client.install" DESTINATION "${SYSCONFDIR}" RENAME "tes3mp-client.cfg" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-server-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw-mp")
#INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-server.install" DESTINATION "${SYSCONFDIR}" RENAME "tes3mp-server.cfg" COMPONENT "openmw-mp")
#They both do not exist
IF(BUILD_OPENCS)
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs")
@ -471,16 +472,20 @@ if(WIN32)
FILE(GLOB dll_files_release "${OpenMW_BINARY_DIR}/Release/*.dll")
INSTALL(FILES ${dll_files_debug} DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES ${dll_files_release} DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg")
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg" CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg" CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/LICENSE" DESTINATION "." RENAME "LICENSE.txt")
INSTALL(FILES
"${OpenMW_SOURCE_DIR}/Docs/license/GPL3.txt"
"${OpenMW_SOURCE_DIR}/Docs/license/DejaVu Font License.txt"
"${OpenMW_BINARY_DIR}/settings-default.cfg"
"${OpenMW_BINARY_DIR}/tes3mp-client-default.cfg"
"${OpenMW_BINARY_DIR}/gamecontrollerdb.txt"
"${OpenMW_SOURCE_DIR}/files/mygui/DejaVu Font License.txt"
DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/settings-default.cfg" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/settings-default.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/tes3mp-client-default.cfg" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/tes3mp-client-default.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/gamecontrollerdb.txt" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
if(BUILD_MYGUI_PLUGIN)
INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Debug/Plugin_MyGUI_OpenMW_Resources.dll" DESTINATION "." CONFIGURATIONS Debug)
@ -492,7 +497,9 @@ if(WIN32)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/platforms" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
ENDIF()
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".")
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Debug/resources" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/resources" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
FILE(GLOB plugin_dir_debug "${OpenMW_BINARY_DIR}/Debug/osgPlugins-*")
FILE(GLOB plugin_dir_release "${OpenMW_BINARY_DIR}/Release/osgPlugins-*")
INSTALL(DIRECTORY ${plugin_dir_debug} DESTINATION "." CONFIGURATIONS Debug)
@ -530,6 +537,7 @@ if(WIN32)
SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\www.openmw.org")
SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.openmw.org")
SET(CPACK_NSIS_INSTALLED_ICON_NAME "openmw-launcher.exe")
SET(CPACK_NSIS_MUI_FINISHPAGE_RUN "openmw-launcher.exe")
SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp.ico")
SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/tes3mp/tes3mp.ico")
SET(CPACK_PACKAGE_ICON "${OpenMW_SOURCE_DIR}\\\\files\\\\openmw.bmp")

@ -33,6 +33,7 @@ Furthermore, we advise to:
* 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.
Guidelines for original engine "fixes"
=================================

@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.

@ -3,13 +3,14 @@ TES3MP
[![Build Status](https://travis-ci.org/TES3MP/openmw-tes3mp.svg?branch=master)](https://travis-ci.org/TES3MP/openmw-tes3mp)
TES3MP is a project aiming to add multiplayer functionality to [OpenMW](https://github.com/OpenMW/openmw), a free and open source recreation of the popular Bethesda Softworks game "The Elder Scrolls III: Morrowind".
TES3MP is a project aiming to add multiplayer functionality to [OpenMW](https://github.com/OpenMW/openmw), a free and open source engine recreation of the popular Bethesda Softworks game "The Elder Scrolls III: Morrowind".
* Version: 0.6.2
* License: GPLv3 (see docs/license/GPL3.txt for more information)
* TES3MP version: 0.6.2
* OpenMW version: 0.43.0
* License: GPLv3 (see [LICENSE](https://github.com/TES3MP/openmw-tes3mp/blob/master/LICENSE) for more information)
Font Licenses:
* DejaVuLGCSansMono.ttf: custom (see docs/license/DejaVu Font License.txt for more information)
* DejaVuLGCSansMono.ttf: custom (see [files/mygui/DejaVu Font License.txt](https://github.com/TES3MP/openmw-tes3mp/blob/master/files/mygui/DejaVu%20Font%20License.txt) for more information)
Project Status
--------------
@ -18,24 +19,24 @@ Project Status
TES3MP is now playable in most respects. Player and NPC movement, animations, combat and spell casting are properly synchronized with small exceptions, as is picking up and dropping items in the world, using doors and levers, and adding and removing items from containers. Journal entries, faction stats and dialogue topics are also synchronized, allowing the majority of quests to work fine.
[Serverside Lua scripts](https://github.com/TES3MP/PluginExamples) are used to save and load the state of most of the aforementioned.
[Serverside Lua scripts](https://github.com/TES3MP/CoreScripts) are used to save and load the state of most of the aforementioned.
Contributing
--------------
Development has been relatively fast, but any contribution regarding [code](https://github.com/TES3MP/openmw-tes3mp/blob/master/CONTRIBUTING.md), documentation, bug hunting or video showcases is greatly appreciated.
Test sessions are often advertised in [our Steam group](https://steamcommunity.com/groups/mwmulti) or [our Discord server](https://discord.gg/H8zhhuk).
Test sessions are often advertised on [our Discord server](https://discord.gg/ECJk293) or in [our Steam group](https://steamcommunity.com/groups/mwmulti).
Feel free to contact the [team members](https://github.com/TES3MP/openmw-tes3mp/blob/master/tes3mp-credits.md) for any questions you might have.
Getting Started
---------------
* [Quickstart guide](https://github.com/TES3MP/openmw-tes3mp/wiki/Quickstart-guide)
* [Steam group](https://steamcommunity.com/groups/mwmulti) and its [detailed FAQ](http://steamcommunity.com/groups/mwmulti/discussions/1/353916184342480541/)
* [TES3MP section on OpenMW forums](https://forum.openmw.org/viewforum.php?f=44)
* [Steam group](https://steamcommunity.com/groups/mwmulti)
* [Subreddit](https://www.reddit.com/r/tes3mp)
* [Installation and build instructions](https://github.com/TES3MP/openmw-tes3mp/wiki/Installation-and-build-instructions)
* [Known issues and bug reports](https://github.com/TES3MP/openmw-tes3mp/issues)
Donations

@ -4,7 +4,7 @@ set(BSATOOL
source_group(apps\\bsatool FILES ${BSATOOL})
# Main executable
add_executable(bsatool
openmw_add_executable(bsatool
${BSATOOL}
)

@ -8,7 +8,7 @@ set(ESMTOOL
source_group(apps\\esmtool FILES ${ESMTOOL})
# Main executable
add_executable(esmtool
openmw_add_executable(esmtool
${ESMTOOL}
)

@ -16,6 +16,8 @@ set(ESSIMPORTER_FILES
importjour.cpp
importscri.cpp
importscpt.cpp
importproj.cpp
importsplm.cpp
importercontext.cpp
converter.cpp
convertacdt.cpp
@ -28,7 +30,7 @@ set(ESSIMPORTER_FILES
convertplayer.cpp
)
add_executable(openmw-essimporter
openmw_add_executable(openmw-essimporter
${ESSIMPORTER_FILES}
)

@ -1,6 +1,7 @@
#include "converter.hpp"
#include <stdexcept>
#include <algorithm>
#include <osgDB/WriteFile>
@ -53,6 +54,36 @@ namespace
return true;
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
@ -322,12 +353,9 @@ namespace ESSImport
}
else
{
std::stringstream stream;
stream << std::hex << cellref.mIndexedRefId.substr(cellref.mIndexedRefId.size()-8,8);
int refIndex;
stream >> refIndex;
splitIndexedRefId(cellref.mIndexedRefId, refIndex, out.mRefID);
out.mRefID = cellref.mIndexedRefId.substr(0,cellref.mIndexedRefId.size()-8);
std::string idLower = Misc::StringUtils::lowerCase(out.mRefID);
std::map<std::pair<int, std::string>, NPCC>::const_iterator npccIt = mContext->mNpcChanges.find(
@ -347,6 +375,10 @@ namespace ESSImport
convertNpcData(cellref, objstate.mNpcStats);
convertNPCC(npccIt->second, 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_);
objstate.save(esm);
continue;
@ -383,6 +415,10 @@ namespace ESSImport
convertACSC(cellref.mACSC, objstate.mCreatureStats);
convertCREC(crecIt->second, 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);
objstate.save(esm);
continue;
@ -413,4 +449,73 @@ 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;
}
}

@ -22,6 +22,7 @@
#include <components/esm/globalscript.hpp>
#include <components/esm/queststate.hpp>
#include <components/esm/stolenitems.hpp>
#include <components/esm/projectilestate.hpp>
#include "importcrec.hpp"
#include "importcntc.hpp"
@ -35,6 +36,8 @@
#include "importques.hpp"
#include "importjour.hpp"
#include "importscpt.hpp"
#include "importproj.h"
#include "importsplm.h"
#include "convertacdt.hpp"
#include "convertnpcc.hpp"
@ -593,6 +596,27 @@ private:
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

@ -271,6 +271,7 @@ namespace ESSImport
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 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;
converters[ESM::REC_GLOB] = std::shared_ptr<Converter>(new ConvertGlobal());
@ -303,12 +304,13 @@ namespace ESSImport
converters[ESM::REC_QUES] = std::shared_ptr<Converter>(new ConvertQUES());
converters[recJOUR ] = std::shared_ptr<Converter>(new ConvertJOUR());
converters[ESM::REC_SCPT] = std::shared_ptr<Converter>(new ConvertSCPT());
converters[ESM::REC_PROJ] = std::shared_ptr<Converter>(new ConvertPROJ());
converters[recSPLM] = std::shared_ptr<Converter>(new ConvertSPLM());
// TODO:
// - REGN (weather in certain regions?)
// - VFXM
// - SPLM (active spell effects)
// - PROJ (magic projectiles in air)
std::set<unsigned int> unknownRecords;
@ -420,6 +422,19 @@ namespace ESSImport
context.mPlayer.save(writer);
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);
context.mDialogueState.save(writer);
writer.endRecord(ESM::REC_DIAS);

@ -15,7 +15,7 @@
#include "importcrec.hpp"
#include "importcntc.hpp"
#include "importplayer.hpp"
#include "importsplm.h"
@ -48,14 +48,20 @@ namespace ESSImport
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>, int> mActorIdMap;
int mNextActorId;
std::map<std::string, ESM::Creature> mCreatures;
std::map<std::string, ESM::NPC> mNpcs;
std::vector<SPLM::ActiveSpell> mActiveSpells;
Context()
: mDay(0)
, mMonth(0)
, mYear(0)
, mHour(0.f)
, mNextActorId(0)
{
mPlayer.mAutoMove = 0;
ESM::CellId playerCellId;
@ -67,16 +73,23 @@ namespace ESSImport
= mPlayer.mLastKnownExteriorPosition[2]
= 0.0f;
mPlayer.mHasMark = 0;
mPlayer.mCurrentCrimeId = 0; // TODO
mPlayer.mCurrentCrimeId = -1; // TODO
mPlayer.mPaidCrimeId = -1;
mPlayer.mObject.blank();
mPlayer.mObject.mEnabled = true;
mPlayer.mObject.mRef.mRefID = "player"; // REFR.mRefID would be PlayerSaveGame
mPlayer.mObject.mCreatureStats.mActorId = generateActorId();
mGlobalMapState.mBounds.mMinX = 0;
mGlobalMapState.mBounds.mMaxX = 0;
mGlobalMapState.mBounds.mMinY = 0;
mGlobalMapState.mBounds.mMaxY = 0;
}
int generateActorId()
{
return mNextActorId++;
}
};
}

@ -0,0 +1,18 @@
#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);
}
}
}

@ -0,0 +1,47 @@
#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

@ -0,0 +1,43 @@
#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);
}
}
}

@ -0,0 +1,81 @@
#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

@ -78,7 +78,7 @@ if(NOT WIN32)
endif(NOT WIN32)
# Main executable
add_executable(openmw-launcher
openmw_add_executable(openmw-launcher
${GUI_TYPE}
${LAUNCHER}
${LAUNCHER_HEADER}

@ -9,7 +9,7 @@ set(MWINIIMPORT_HEADER
source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER})
add_executable(openmw-iniimporter
openmw_add_executable(openmw-iniimporter
${MWINIIMPORT}
)

@ -4,7 +4,7 @@ set(NIFTEST
source_group(components\\nif\\tests FILES ${NIFTEST})
# Main executable
add_executable(niftest
openmw_add_executable(niftest
${NIFTEST}
)

@ -18,7 +18,7 @@ opencs_hdrs_noqt (model/doc
opencs_units (model/world
idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel
idtable idtableproxymodel regionmap data commanddispatcher idtablebase resourcetable nestedtableproxymodel idtree infotableproxymodel landtexturetableproxymodel
)
@ -70,7 +70,7 @@ opencs_units (view/world
cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview
infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable
dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator
bodypartcreator
bodypartcreator landtexturecreator landcreator
)
opencs_units_noqt (view/world
@ -174,7 +174,7 @@ else()
set (OPENCS_OPENMW_CFG "")
endif(APPLE)
add_executable(openmw-cs
openmw_add_executable(openmw-cs
MACOSX_BUNDLE
${OPENCS_SRC}
${OPENCS_UI_HDR}
@ -252,7 +252,8 @@ endif()
if (WIN32)
target_link_libraries(openmw-cs ${Boost_LOCALE_LIBRARY})
INSTALL(TARGETS openmw-cs RUNTIME DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.cfg" DESTINATION ".")
INSTALL(FILES "${OpenMW_BINARY_DIR}/Debug/openmw-cs.cfg" DESTINATION "." CONFIGURATIONS Debug)
INSTALL(FILES "${OpenMW_BINARY_DIR}/Release/openmw-cs.cfg" DESTINATION "." CONFIGURATIONS Release;RelWithDebInfo;MinSizeRel)
endif()
if (MSVC)

@ -90,16 +90,16 @@ std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfi
boost::program_options::options_description desc("Syntax: openmw-cs <options>\nAllowed options");
desc.add_options()
("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken()->composing())
("data-local", boost::program_options::value<std::string>()->default_value(""))
("data", boost::program_options::value<Files::EscapePathContainer>()->default_value(Files::EscapePathContainer(), "data")->multitoken()->composing())
("data-local", boost::program_options::value<Files::EscapeHashString>()->default_value(""))
("fs-strict", boost::program_options::value<bool>()->implicit_value(true)->default_value(false))
("encoding", boost::program_options::value<std::string>()->default_value("win1252"))
("resources", boost::program_options::value<std::string>()->default_value("resources"))
("fallback-archive", boost::program_options::value<std::vector<std::string> >()->
default_value(std::vector<std::string>(), "fallback-archive")->multitoken())
("encoding", boost::program_options::value<Files::EscapeHashString>()->default_value("win1252"))
("resources", boost::program_options::value<Files::EscapeHashString>()->default_value("resources"))
("fallback-archive", boost::program_options::value<Files::EscapeStringVector>()->
default_value(Files::EscapeStringVector(), "fallback-archive")->multitoken())
("fallback", boost::program_options::value<FallbackMap>()->default_value(FallbackMap(), "")
->multitoken()->composing(), "fallback values")
("script-blacklist", boost::program_options::value<std::vector<std::string> >()->default_value(std::vector<std::string>(), "")
("script-blacklist", boost::program_options::value<Files::EscapeStringVector>()->default_value(Files::EscapeStringVector(), "")
->multitoken(), "exclude specified script from the verifier (if the use of the blacklist is enabled)")
("script-blacklist-use", boost::program_options::value<bool>()->implicit_value(true)
->default_value(true), "enable script blacklisting");
@ -109,25 +109,29 @@ std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfi
mCfgMgr.readConfiguration(variables, desc, quiet);
mDocumentManager.setEncoding (
ToUTF8::calculateEncoding (variables["encoding"].as<std::string>()));
ToUTF8::calculateEncoding (variables["encoding"].as<Files::EscapeHashString>().toStdString()));
mDocumentManager.setResourceDir (mResources = variables["resources"].as<std::string>());
mDocumentManager.setResourceDir (mResources = variables["resources"].as<Files::EscapeHashString>().toStdString());
mDocumentManager.setFallbackMap (variables["fallback"].as<FallbackMap>().mMap);
if (variables["script-blacklist-use"].as<bool>())
mDocumentManager.setBlacklistedScripts (
variables["script-blacklist"].as<std::vector<std::string> >());
variables["script-blacklist"].as<Files::EscapeStringVector>().toStdStringVector());
mFsStrict = variables["fs-strict"].as<bool>();
Files::PathContainer dataDirs, dataLocal;
if (!variables["data"].empty()) {
dataDirs = Files::PathContainer(variables["data"].as<Files::PathContainer>());
dataDirs = Files::PathContainer(Files::EscapePath::toPathContainer(variables["data"].as<Files::EscapePathContainer>()));
}
std::string local = variables["data-local"].as<std::string>();
if (!local.empty()) {
std::string local = variables["data-local"].as<Files::EscapeHashString>().toStdString();
if (!local.empty())
{
if (local.front() == '\"')
local = local.substr(1, local.length() - 2);
dataLocal.push_back(Files::PathContainer::value_type(local));
}
@ -157,7 +161,7 @@ std::pair<Files::PathContainer, std::vector<std::string> > CS::Editor::readConfi
mFileDialog.addFiles(path);
}
return std::make_pair (dataDirs, variables["fallback-archive"].as<std::vector<std::string> >());
return std::make_pair (dataDirs, variables["fallback-archive"].as<Files::EscapeStringVector>().toStdStringVector());
}
void CS::Editor::createGame()

@ -288,7 +288,7 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration,
if (mContentFiles.empty())
throw std::runtime_error ("Empty content file sequence");
if (!boost::filesystem::exists (mProjectPath))
if (mNew || !boost::filesystem::exists (mProjectPath))
{
boost::filesystem::path customFiltersPath (configuration.getUserDataPath());
customFiltersPath /= "defaultfilters";

@ -132,7 +132,7 @@ namespace CSMPrefs
if (mods && i == 0)
{
if (mods & Qt::ControlModifier)
result.append("Ctl+");
result.append("Ctrl+");
if (mods & Qt::ShiftModifier)
result.append("Shift+");
if (mods & Qt::AltModifier)
@ -196,7 +196,7 @@ namespace CSMPrefs
std::string name = value.substr(start, end - start);
if (name == "Ctl")
if (name == "Ctrl")
{
mods |= Qt::ControlModifier;
}

@ -169,18 +169,25 @@ void CSMPrefs::State::declare()
"list go to the first/last item");
declareCategory ("3D Scene Input");
declareDouble ("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0);
declareDouble ("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0);
declareSeparator();
declareDouble ("p-navi-free-sensitivity", "Free Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0);
declareBool ("p-navi-free-invert", "Invert Free Camera Mouse Input", false);
declareDouble ("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0);
declareBool ("p-navi-orbit-invert", "Invert Orbit Camera Mouse Input", false);
declareDouble ("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0);
declareDouble ("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0);
declareDouble ("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0);
declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28);
declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28);
declareDouble ("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0);
declareSeparator();
declareDouble ("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0);
declareBool ("p-navi-orbit-invert", "Invert Orbit Camera Mouse Input", false);
declareDouble ("navi-orbit-rot-speed", "Orbital Camera Rotational Speed", 3.14 / 4).setRange(0.001, 6.28);
declareDouble ("navi-orbit-speed-mult", "Orbital Camera Speed Multiplier (from Modifier)", 4).setRange(0.001, 1000.0);
declareBool ("navi-orbit-const-roll", "Keep camera roll constant for orbital camera", true);
declareSeparator();
declareBool ("context-select", "Context Sensitive Selection", false);
declareDouble ("drag-factor", "Mouse sensitivity during drag operations", 1.0).
setRange (0.001, 100.0);
@ -192,6 +199,14 @@ void CSMPrefs::State::declare()
setRange (0.001, 100.0);
declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1);
declareCategory ("Rendering");
declareInt ("camera-fov", "Camera FOV", 90).setRange(10, 170);
declareBool ("camera-ortho", "Orthographic projection for camera", false);
declareInt ("camera-ortho-size", "Orthographic projection size parameter", 100).
setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.").
setRange(10, 10000);
declareDouble ("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0,1);
declareCategory ("Tooltips");
declareBool ("scene", "Show Tooltips in 3D scenes", true);
declareBool ("scene-hide-basic", "Hide basic 3D scenes tooltips", false);
@ -240,6 +255,8 @@ void CSMPrefs::State::declare()
declareShortcut ("document-world-cells", "Open Cell List", QKeySequence());
declareShortcut ("document-world-referencables", "Open Object List", QKeySequence());
declareShortcut ("document-world-references", "Open Instance List", QKeySequence());
declareShortcut ("document-world-lands", "Open Lands List", QKeySequence());
declareShortcut ("document-world-landtextures", "Open Land Textures List", QKeySequence());
declareShortcut ("document-world-pathgrid", "Open Pathgrid List", QKeySequence());
declareShortcut ("document-world-regionmap", "Open Region Map", QKeySequence());
declareShortcut ("document-mechanics-globals", "Open Global List", QKeySequence());
@ -276,6 +293,7 @@ void CSMPrefs::State::declare()
declareShortcut ("table-edit", "Edit Record", QKeySequence());
declareShortcut ("table-add", "Add Row/Record", QKeySequence(Qt::ShiftModifier | Qt::Key_A));
declareShortcut ("table-clone", "Clone Record", QKeySequence(Qt::ShiftModifier | Qt::Key_D));
declareShortcut ("touch-record", "Touch Record", QKeySequence());
declareShortcut ("table-revert", "Revert Record", QKeySequence());
declareShortcut ("table-remove", "Remove Row/Record", QKeySequence(Qt::Key_Delete));
declareShortcut ("table-moveup", "Move Record Up", QKeySequence());

@ -38,9 +38,10 @@ CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::Fr
appendStage (new MergeRefIdsStage (mState));
appendStage (new MergeReferencesStage (mState));
appendStage (new MergeReferencesStage (mState));
appendStage (new ListLandTexturesMergeStage (mState));
appendStage (new MergeLandTexturesStage (mState));
appendStage (new PopulateLandTexturesMergeStage (mState));
appendStage (new MergeLandStage (mState));
appendStage (new FixLandsAndLandTexturesMergeStage (mState));
appendStage (new CleanupLandTexturesMergeStage (mState));
appendStage (new FinishMergedDocumentStage (mState, encoding));
}

@ -8,7 +8,9 @@
#include "mergestate.hpp"
#include "../doc/document.hpp"
#include "../world/commands.hpp"
#include "../world/data.hpp"
#include "../world/idtable.hpp"
CSMTools::StartMergeStage::StartMergeStage (MergeState& state)
@ -109,149 +111,105 @@ void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messa
}
CSMTools::ListLandTexturesMergeStage::ListLandTexturesMergeStage (MergeState& state)
: mState (state)
{}
CSMTools::PopulateLandTexturesMergeStage::PopulateLandTexturesMergeStage (MergeState& state)
: mState (state)
{
}
int CSMTools::ListLandTexturesMergeStage::setup()
int CSMTools::PopulateLandTexturesMergeStage::setup()
{
return mState.mSource.getData().getLand().getSize();
return mState.mSource.getData().getLandTextures().getSize();
}
void CSMTools::ListLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages)
void CSMTools::PopulateLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages)
{
const CSMWorld::Record<CSMWorld::Land>& record =
mState.mSource.getData().getLand().getRecord (stage);
const CSMWorld::Record<CSMWorld::LandTexture>& record =
mState.mSource.getData().getLandTextures().getRecord (stage);
if (!record.isDeleted())
{
const CSMWorld::Land& land = record.get();
// make sure record is loaded
land.loadData (ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML |
ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX);
if (const ESM::Land::LandData *data = land.getLandData (ESM::Land::DATA_VTEX))
{
// list texture indices
std::pair<uint16_t, int> key;
key.second = land.mPlugin;
for (int i=0; i<ESM::Land::LAND_NUM_TEXTURES; ++i)
{
key.first = data->mTextures[i];
mState.mTextureIndices[key] = -1;
}
}
mState.mTarget->getData().getLandTextures().appendRecord(record);
}
}
CSMTools::MergeLandTexturesStage::MergeLandTexturesStage (MergeState& state)
: mState (state), mNext (mState.mTextureIndices.end())
{}
int CSMTools::MergeLandTexturesStage::setup()
CSMTools::MergeLandStage::MergeLandStage (MergeState& state)
: mState (state)
{
// Should use the size of mState.mTextureIndices instead, but that is not available at this
// point. Unless there are any errors in the land and land texture records this will not
// make a difference.
return mState.mSource.getData().getLandTextures().getSize();
}
void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& messages)
int CSMTools::MergeLandStage::setup()
{
if (stage==0)
mNext = mState.mTextureIndices.begin();
return mState.mSource.getData().getLand().getSize();
}
bool found = false;
void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages)
{
const CSMWorld::Record<CSMWorld::Land>& record =
mState.mSource.getData().getLand().getRecord (stage);
do
if (!record.isDeleted())
{
if (mNext==mState.mTextureIndices.end())
return;
mNext->second = stage+1;
std::ostringstream stream;
stream << mNext->first.first-1 << "_" << mNext->first.second;
int index = mState.mSource.getData().getLandTextures().searchId (stream.str());
if (index!=-1)
{
CSMWorld::LandTexture texture =
mState.mSource.getData().getLandTextures().getRecord (index).get();
stream.clear();
stream << mNext->second-1 << "_0";
texture.mIndex = mNext->second-1;
texture.mId = stream.str();
CSMWorld::Record<CSMWorld::LandTexture> newRecord (
CSMWorld::RecordBase::State_ModifiedOnly, 0, &texture);
mState.mTarget->getData().getLandTextures().appendRecord (newRecord);
found = true;
}
++mNext;
mState.mTarget->getData().getLand().appendRecord (record);
}
while (!found);
}
CSMTools::MergeLandStage::MergeLandStage (MergeState& state) : mState (state) {}
CSMTools::FixLandsAndLandTexturesMergeStage::FixLandsAndLandTexturesMergeStage (MergeState& state)
: mState (state)
{
}
int CSMTools::MergeLandStage::setup()
int CSMTools::FixLandsAndLandTexturesMergeStage::setup()
{
// We will have no more than the source
return mState.mSource.getData().getLand().getSize();
}
void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages)
void CSMTools::FixLandsAndLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages)
{
const CSMWorld::Record<CSMWorld::Land>& record =
mState.mSource.getData().getLand().getRecord (stage);
if (!record.isDeleted())
if (stage < mState.mTarget->getData().getLand().getSize())
{
const CSMWorld::Land& land = record.get();
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&>(
*mState.mTarget->getData().getTableModel(CSMWorld::UniversalId::Type_Lands));
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&>(
*mState.mTarget->getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures));
land.loadData (ESM::Land::DATA_VCLR | ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML |
ESM::Land::DATA_VTEX);
std::string id = mState.mTarget->getData().getLand().getId(stage);
CSMWorld::Land newLand (land);
CSMWorld::TouchLandCommand cmd(landTable, ltexTable, id);
cmd.redo();
newLand.mPlugin = 0;
// Get rid of base data
const CSMWorld::Record<CSMWorld::Land>& oldRecord =
mState.mTarget->getData().getLand().getRecord (stage);
if (land.mDataTypes & ESM::Land::DATA_VTEX)
{
// adjust land texture references
if (ESM::Land::LandData *data = newLand.getLandData())
{
std::pair<uint16_t, int> key;
key.second = land.mPlugin;
CSMWorld::Record<CSMWorld::Land> newRecord(CSMWorld::RecordBase::State_ModifiedOnly,
nullptr, &oldRecord.get());
for (int i=0; i<ESM::Land::LAND_NUM_TEXTURES; ++i)
{
key.first = data->mTextures[i];
std::map<std::pair<uint16_t, int>, int>::const_iterator iter =
mState.mTextureIndices.find (key);
mState.mTarget->getData().getLand().setRecord(stage, newRecord);
}
}
if (iter!=mState.mTextureIndices.end())
data->mTextures[i] = iter->second;
else
data->mTextures[i] = 0;
}
}
}
CSMTools::CleanupLandTexturesMergeStage::CleanupLandTexturesMergeStage (MergeState& state)
: mState (state)
{
}
CSMWorld::Record<CSMWorld::Land> newRecord (
CSMWorld::RecordBase::State_ModifiedOnly, 0, &newLand);
int CSMTools::CleanupLandTexturesMergeStage::setup()
{
return 1;
}
mState.mTarget->getData().getLand().appendRecord (newRecord);
void CSMTools::CleanupLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages)
{
auto& landTextures = mState.mTarget->getData().getLandTextures();
for (int i = 0; i < landTextures.getSize(); )
{
if (!landTextures.getRecord(i).isModified())
landTextures.removeRows(i, 1);
else
++i;
}
}

@ -116,13 +116,14 @@ namespace CSMTools
///< Messages resulting from this stage will be appended to \a messages.
};
class ListLandTexturesMergeStage : public CSMDoc::Stage
/// Adds all land texture records that could potentially be referenced when merging
class PopulateLandTexturesMergeStage : public CSMDoc::Stage
{
MergeState& mState;
public:
ListLandTexturesMergeStage (MergeState& state);
PopulateLandTexturesMergeStage (MergeState& state);
virtual int setup();
///< \return number of steps
@ -131,14 +132,13 @@ namespace CSMTools
///< Messages resulting from this stage will be appended to \a messages.
};
class MergeLandTexturesStage : public CSMDoc::Stage
class MergeLandStage : public CSMDoc::Stage
{
MergeState& mState;
std::map<std::pair<uint16_t, int>, int>::iterator mNext;
public:
MergeLandTexturesStage (MergeState& state);
MergeLandStage (MergeState& state);
virtual int setup();
///< \return number of steps
@ -147,13 +147,32 @@ namespace CSMTools
///< Messages resulting from this stage will be appended to \a messages.
};
class MergeLandStage : public CSMDoc::Stage
/// During this stage, the complex process of combining LandTextures from
/// potentially multiple plugins is undertaken.
class FixLandsAndLandTexturesMergeStage : public CSMDoc::Stage
{
MergeState& mState;
public:
MergeLandStage (MergeState& state);
FixLandsAndLandTexturesMergeStage (MergeState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
/// Removes base LandTexture records. This gets rid of the base records previously
/// needed in FixLandsAndLandTexturesMergeStage.
class CleanupLandTexturesMergeStage : public CSMDoc::Stage
{
MergeState& mState;
public:
CleanupLandTexturesMergeStage (MergeState& state);
virtual int setup();
///< \return number of steps

@ -6,6 +6,7 @@
#include <algorithm>
#include <cctype>
#include <stdexcept>
#include <string>
#include <functional>
#include <QVariant>
@ -13,8 +14,9 @@
#include <components/misc/stringops.hpp>
#include "columnbase.hpp"
#include "collectionbase.hpp"
#include "land.hpp"
#include "landtexture.hpp"
namespace CSMWorld
{
@ -22,15 +24,14 @@ namespace CSMWorld
template<typename ESXRecordT>
struct IdAccessor
{
std::string& getId (ESXRecordT& record);
void setId(ESXRecordT& record, const std::string& id) const;
const std::string getId (const ESXRecordT& record) const;
};
template<typename ESXRecordT>
std::string& IdAccessor<ESXRecordT>::getId (ESXRecordT& record)
void IdAccessor<ESXRecordT>::setId(ESXRecordT& record, const std::string& id) const
{
return record.mId;
record.mId = id;
}
template<typename ESXRecordT>
@ -39,6 +40,39 @@ namespace CSMWorld
return record.mId;
}
template<>
inline void IdAccessor<Land>::setId (Land& record, const std::string& id) const
{
int x=0, y=0;
Land::parseUniqueRecordId(id, x, y);
record.mX = x;
record.mY = y;
}
template<>
inline void IdAccessor<LandTexture>::setId (LandTexture& record, const std::string& id) const
{
int plugin = 0;
int index = 0;
LandTexture::parseUniqueRecordId(id, plugin, index);
record.mPluginIndex = plugin;
record.mIndex = index;
}
template<>
inline const std::string IdAccessor<Land>::getId (const Land& record) const
{
return Land::createUniqueRecordId(record.mX, record.mY);
}
template<>
inline const std::string IdAccessor<LandTexture>::getId (const LandTexture& record) const
{
return LandTexture::createUniqueRecordId(record.mPluginIndex, record.mIndex);
}
/// \brief Single-type record collection
template<typename ESXRecordT, typename IdAccessorT = IdAccessor<ESXRecordT> >
class Collection : public CollectionBase
@ -69,6 +103,13 @@ namespace CSMWorld
///
/// \return Success?
int cloneRecordImp (const std::string& origin, const std::string& dest,
UniversalId::Type type);
///< Returns the index of the clone.
int touchRecordImp (const std::string& id);
///< Returns the index of the record on success, -1 on failure.
public:
Collection();
@ -108,6 +149,10 @@ namespace CSMWorld
const std::string& destination,
const UniversalId::Type type);
virtual bool touchRecord(const std::string& id);
///< Change the state of a record from base to modified, if it is not already.
/// \return True if the record was changed.
virtual int searchId (const std::string& id) const;
////< Search record with \a id.
/// \return index of record (if found) or -1 (not found)
@ -205,17 +250,72 @@ namespace CSMWorld
return true;
}
template<typename ESXRecordT, typename IdAccessorT>
int Collection<ESXRecordT, IdAccessorT>::cloneRecordImp(const std::string& origin,
const std::string& destination, UniversalId::Type type)
{
Record<ESXRecordT> copy;
copy.mModified = getRecord(origin).get();
copy.mState = RecordBase::State_ModifiedOnly;
IdAccessorT().setId(copy.get(), destination);
int index = getAppendIndex(destination, type);
insertRecord(copy, getAppendIndex(destination, type));
return index;
}
template<typename ESXRecordT, typename IdAccessorT>
int Collection<ESXRecordT, IdAccessorT>::touchRecordImp(const std::string& id)
{
int index = getIndex(id);
Record<ESXRecordT>& record = mRecords.at(index);
if (record.isDeleted())
{
throw std::runtime_error("attempt to touch deleted record");
}
if (!record.isModified())
{
record.setModified(record.get());
return index;
}
return -1;
}
template<typename ESXRecordT, typename IdAccessorT>
void Collection<ESXRecordT, IdAccessorT>::cloneRecord(const std::string& origin,
const std::string& destination,
const UniversalId::Type type)
const std::string& destination, const UniversalId::Type type)
{
Record<ESXRecordT> copy;
copy.mModified = getRecord(origin).get();
copy.mState = RecordBase::State_ModifiedOnly;
copy.get().mId = destination;
cloneRecordImp(origin, destination, type);
}
insertRecord(copy, getAppendIndex(destination, type));
template<>
inline void Collection<Land, IdAccessor<Land> >::cloneRecord(const std::string& origin,
const std::string& destination, const UniversalId::Type type)
{
int index = cloneRecordImp(origin, destination, type);
mRecords.at(index).get().mPlugin = 0;
}
template<typename ESXRecordT, typename IdAccessorT>
bool Collection<ESXRecordT, IdAccessorT>::touchRecord(const std::string& id)
{
return touchRecordImp(id) != -1;
}
template<>
inline bool Collection<Land, IdAccessor<Land> >::touchRecord(const std::string& id)
{
int index = touchRecordImp(id);
if (index >= 0)
{
mRecords.at(index).get().mPlugin = 0;
return true;
}
return false;
}
template<typename ESXRecordT, typename IdAccessorT>
@ -366,7 +466,7 @@ namespace CSMWorld
UniversalId::Type type)
{
ESXRecordT record;
IdAccessorT().getId (record) = id;
IdAccessorT().setId(record, id);
record.blank();
Record<ESXRecordT> record2;

@ -78,6 +78,8 @@ namespace CSMWorld
const std::string& destination,
const UniversalId::Type type) = 0;
virtual bool touchRecord(const std::string& id) = 0;
virtual const RecordBase& getRecord (const std::string& id) const = 0;
virtual const RecordBase& getRecord (int index) const = 0;

@ -1,28 +1,341 @@
#include "columnimp.hpp"
CSMWorld::BodyPartRaceColumn::BodyPartRaceColumn(const MeshTypeColumn<ESM::BodyPart> *meshType)
: mMeshType(meshType)
{}
#include <stdexcept>
#include <QVector>
QVariant CSMWorld::BodyPartRaceColumn::get(const Record<ESM::BodyPart> &record) const
namespace CSMWorld
{
if (mMeshType != NULL && mMeshType->get(record) == ESM::BodyPart::MT_Skin)
/* LandTextureNicknameColumn */
LandTextureNicknameColumn::LandTextureNicknameColumn()
: Column<LandTexture>(Columns::ColumnId_TextureNickname, ColumnBase::Display_String)
{
return QString::fromUtf8(record.get().mRace.c_str());
}
return QVariant(QVariant::UserType);
}
void CSMWorld::BodyPartRaceColumn::set(Record<ESM::BodyPart> &record, const QVariant &data)
{
ESM::BodyPart record2 = record.get();
QVariant LandTextureNicknameColumn::get(const Record<LandTexture>& record) const
{
return QString::fromUtf8(record.get().mId.c_str());
}
record2.mRace = data.toString().toUtf8().constData();
void LandTextureNicknameColumn::set(Record<LandTexture>& record, const QVariant& data)
{
LandTexture copy = record.get();
copy.mId = data.toString().toUtf8().constData();
record.setModified(copy);
}
record.setModified(record2);
}
bool LandTextureNicknameColumn::isEditable() const
{
return true;
}
bool CSMWorld::BodyPartRaceColumn::isEditable() const
{
return true;
/* LandTextureIndexColumn */
LandTextureIndexColumn::LandTextureIndexColumn()
: Column<LandTexture>(Columns::ColumnId_TextureIndex, ColumnBase::Display_Integer)
{
}
QVariant LandTextureIndexColumn::get(const Record<LandTexture>& record) const
{
return record.get().mIndex;
}
bool LandTextureIndexColumn::isEditable() const
{
return false;
}
/* LandPluginIndexColumn */
LandPluginIndexColumn::LandPluginIndexColumn()
: Column<Land>(Columns::ColumnId_PluginIndex, ColumnBase::Display_Integer, 0)
{
}
QVariant LandPluginIndexColumn::get(const Record<Land>& record) const
{
return record.get().mPlugin;
}
bool LandPluginIndexColumn::isEditable() const
{
return false;
}
/* LandTexturePluginIndexColumn */
LandTexturePluginIndexColumn::LandTexturePluginIndexColumn()
: Column<LandTexture>(Columns::ColumnId_PluginIndex, ColumnBase::Display_Integer, 0)
{
}
QVariant LandTexturePluginIndexColumn::get(const Record<LandTexture>& record) const
{
return record.get().mPluginIndex;
}
bool LandTexturePluginIndexColumn::isEditable() const
{
return false;
}
/* LandMapLodColumn */
LandMapLodColumn::LandMapLodColumn()
: Column<Land>(Columns::ColumnId_LandMapLodIndex, ColumnBase::Display_String, 0)
{
}
QVariant LandMapLodColumn::get(const Record<Land>& record) const
{
const int Size = Land::LAND_GLOBAL_MAP_LOD_SIZE;
const Land& land = record.get();
DataType values(Size, 0);
if (land.isDataLoaded(Land::DATA_WNAM))
{
for (int i = 0; i < Size; ++i)
values[i] = land.mWnam[i];
}
QVariant variant;
variant.setValue(values);
return variant;
}
void LandMapLodColumn::set(Record<Land>& record, const QVariant& data)
{
DataType values = data.value<DataType>();
if (values.size() != Land::LAND_GLOBAL_MAP_LOD_SIZE)
throw std::runtime_error("invalid land map LOD data");
Land copy = record.get();
copy.add(Land::DATA_WNAM);
for (int i = 0; i < values.size(); ++i)
{
copy.mWnam[i] = values[i];
}
record.setModified(copy);
}
bool LandMapLodColumn::isEditable() const
{
return true;
}
/* LandNormalsColumn */
LandNormalsColumn::LandNormalsColumn()
: Column<Land>(Columns::ColumnId_LandNormalsIndex, ColumnBase::Display_String, 0)
{
}
QVariant LandNormalsColumn::get(const Record<Land>& record) const
{
const int Size = Land::LAND_NUM_VERTS * 3;
const Land& land = record.get();
DataType values(Size, 0);
if (land.isDataLoaded(Land::DATA_VNML))
{
for (int i = 0; i < Size; ++i)
values[i] = land.getLandData()->mNormals[i];
}
QVariant variant;
variant.setValue(values);
return variant;
}
void LandNormalsColumn::set(Record<Land>& record, const QVariant& data)
{
DataType values = data.value<DataType>();
if (values.size() != Land::LAND_NUM_VERTS * 3)
throw std::runtime_error("invalid land normals data");
Land copy = record.get();
copy.add(Land::DATA_VNML);
for (int i = 0; i < values.size(); ++i)
{
copy.getLandData()->mNormals[i] = values[i];
}
record.setModified(copy);
}
bool LandNormalsColumn::isEditable() const
{
return true;
}
/* LandHeightsColumn */
LandHeightsColumn::LandHeightsColumn()
: Column<Land>(Columns::ColumnId_LandHeightsIndex, ColumnBase::Display_String, 0)
{
}
QVariant LandHeightsColumn::get(const Record<Land>& record) const
{
const int Size = Land::LAND_NUM_VERTS;
const Land& land = record.get();
DataType values(Size, 0);
if (land.isDataLoaded(Land::DATA_VHGT))
{
for (int i = 0; i < Size; ++i)
values[i] = land.getLandData()->mHeights[i];
}
QVariant variant;
variant.setValue(values);
return variant;
}
void LandHeightsColumn::set(Record<Land>& record, const QVariant& data)
{
DataType values = data.value<DataType>();
if (values.size() != Land::LAND_NUM_VERTS)
throw std::runtime_error("invalid land heights data");
Land copy = record.get();
copy.add(Land::DATA_VHGT);
for (int i = 0; i < values.size(); ++i)
{
copy.getLandData()->mHeights[i] = values[i];
}
record.setModified(copy);
}
bool LandHeightsColumn::isEditable() const
{
return true;
}
/* LandColoursColumn */
LandColoursColumn::LandColoursColumn()
: Column<Land>(Columns::ColumnId_LandColoursIndex, ColumnBase::Display_String, 0)
{
}
QVariant LandColoursColumn::get(const Record<Land>& record) const
{
const int Size = Land::LAND_NUM_VERTS * 3;
const Land& land = record.get();
DataType values(Size, 0);
if (land.isDataLoaded(Land::DATA_VCLR))
{
for (int i = 0; i < Size; ++i)
values[i] = land.getLandData()->mColours[i];
}
QVariant variant;
variant.setValue(values);
return variant;
}
void LandColoursColumn::set(Record<Land>& record, const QVariant& data)
{
DataType values = data.value<DataType>();
if (values.size() != Land::LAND_NUM_VERTS * 3)
throw std::runtime_error("invalid land colours data");
Land copy = record.get();
copy.add(Land::DATA_VCLR);
for (int i = 0; i < values.size(); ++i)
{
copy.getLandData()->mColours[i] = values[i];
}
record.setModified(copy);
}
bool LandColoursColumn::isEditable() const
{
return true;
}
/* LandTexturesColumn */
LandTexturesColumn::LandTexturesColumn()
: Column<Land>(Columns::ColumnId_LandTexturesIndex, ColumnBase::Display_String, 0)
{
}
QVariant LandTexturesColumn::get(const Record<Land>& record) const
{
const int Size = Land::LAND_NUM_TEXTURES;
const Land& land = record.get();
DataType values(Size, 0);
if (land.isDataLoaded(Land::DATA_VTEX))
{
for (int i = 0; i < Size; ++i)
values[i] = land.getLandData()->mTextures[i];
}
QVariant variant;
variant.setValue(values);
return variant;
}
void LandTexturesColumn::set(Record<Land>& record, const QVariant& data)
{
DataType values = data.value<DataType>();
if (values.size() != Land::LAND_NUM_TEXTURES)
throw std::runtime_error("invalid land textures data");
Land copy = record.get();
copy.add(Land::DATA_VTEX);
for (int i = 0; i < values.size(); ++i)
{
copy.getLandData()->mTextures[i] = values[i];
}
record.setModified(copy);
}
bool LandTexturesColumn::isEditable() const
{
return true;
}
/* BodyPartRaceColumn */
BodyPartRaceColumn::BodyPartRaceColumn(const MeshTypeColumn<ESM::BodyPart> *meshType)
: mMeshType(meshType)
{}
QVariant BodyPartRaceColumn::get(const Record<ESM::BodyPart> &record) const
{
if (mMeshType != NULL && mMeshType->get(record) == ESM::BodyPart::MT_Skin)
{
return QString::fromUtf8(record.get().mRace.c_str());
}
return QVariant(QVariant::UserType);
}
void BodyPartRaceColumn::set(Record<ESM::BodyPart> &record, const QVariant &data)
{
ESM::BodyPart record2 = record.get();
record2.mRace = data.toString().toUtf8().constData();
record.setModified(record2);
}
bool BodyPartRaceColumn::isEditable() const
{
return true;
}
}

@ -2,10 +2,12 @@
#define CSM_WOLRD_COLUMNIMP_H
#include <cassert>
#include <cstdint>
#include <sstream>
#include <stdexcept>
#include <QColor>
#include <QVector>
#include <components/esm/loadbody.hpp>
#include <components/esm/loadskil.hpp>
@ -15,6 +17,9 @@
#include "columns.hpp"
#include "info.hpp"
#include "land.hpp"
#include "landtexture.hpp"
namespace CSMWorld
{
/// \note Shares ID with VarValueColumn. A table can not have both.
@ -60,6 +65,20 @@ namespace CSMWorld
}
};
template<>
inline QVariant StringIdColumn<Land>::get(const Record<Land>& record) const
{
const Land& land = record.get();
return QString::fromUtf8(Land::createUniqueRecordId(land.mX, land.mY).c_str());
}
template<>
inline QVariant StringIdColumn<LandTexture>::get(const Record<LandTexture>& record) const
{
const LandTexture& ltex = record.get();
return QString::fromUtf8(LandTexture::createUniqueRecordId(ltex.mPluginIndex, ltex.mIndex).c_str());
}
template<typename ESXRecordT>
struct RecordStateColumn : public Column<ESXRecordT>
{
@ -673,32 +692,23 @@ namespace CSMWorld
}
};
/// \todo QColor is a GUI class and should not be in model. Need to think of an alternative
/// solution.
template<typename ESXRecordT>
struct MapColourColumn : public Column<ESXRecordT>
{
/// \todo Replace Display_Integer with something that displays the colour value more directly.
MapColourColumn()
: Column<ESXRecordT> (Columns::ColumnId_MapColour, ColumnBase::Display_Colour)
{}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
int colour = record.get().mMapColor;
return QColor (colour & 0xff, (colour>>8) & 0xff, (colour>>16) & 0xff);
return record.get().mMapColor;
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
QColor colour = data.value<QColor>();
record2.mMapColor = (colour.blue() << 16) | (colour.green() << 8) | colour.red();
record.setModified (record2);
ESXRecordT copy = record.get();
copy.mMapColor = data.toInt();
record.setModified (copy);
}
virtual bool isEditable() const
@ -1499,9 +1509,9 @@ namespace CSMWorld
template<typename ESXRecordT>
struct TopicColumn : public Column<ESXRecordT>
{
TopicColumn (bool journal)
TopicColumn (bool journal)
: Column<ESXRecordT> (journal ? Columns::ColumnId_Journal : Columns::ColumnId_Topic,
journal ? ColumnBase::Display_Journal : ColumnBase::Display_Topic)
journal ? ColumnBase::Display_Journal : ColumnBase::Display_Topic)
{}
virtual QVariant get (const Record<ESXRecordT>& record) const
@ -1755,7 +1765,7 @@ namespace CSMWorld
return true;
}
};
template<typename ESXRecordT>
struct GenderNpcColumn : public Column<ESXRecordT>
{
@ -2198,8 +2208,8 @@ namespace CSMWorld
struct EffectTextureColumn : public Column<ESXRecordT>
{
EffectTextureColumn (Columns::ColumnId columnId)
: Column<ESXRecordT> (columnId,
columnId == Columns::ColumnId_Particle ? ColumnBase::Display_Texture
: Column<ESXRecordT> (columnId,
columnId == Columns::ColumnId_Particle ? ColumnBase::Display_Texture
: ColumnBase::Display_Icon)
{
assert (this->mColumnId==Columns::ColumnId_Icon ||
@ -2417,7 +2427,95 @@ namespace CSMWorld
return true;
}
};
struct LandTextureNicknameColumn : public Column<LandTexture>
{
LandTextureNicknameColumn();
QVariant get(const Record<LandTexture>& record) const override;
void set(Record<LandTexture>& record, const QVariant& data) override;
bool isEditable() const override;
};
struct LandTextureIndexColumn : public Column<LandTexture>
{
LandTextureIndexColumn();
QVariant get(const Record<LandTexture>& record) const override;
bool isEditable() const override;
};
struct LandPluginIndexColumn : public Column<Land>
{
LandPluginIndexColumn();
QVariant get(const Record<Land>& record) const override;
bool isEditable() const override;
};
struct LandTexturePluginIndexColumn : public Column<LandTexture>
{
LandTexturePluginIndexColumn();
QVariant get(const Record<LandTexture>& record) const override;
bool isEditable() const override;
};
struct LandMapLodColumn : public Column<Land>
{
using DataType = QVector<signed char>;
LandMapLodColumn();
QVariant get(const Record<Land>& record) const override;
void set(Record<Land>& record, const QVariant& data) override;
bool isEditable() const override;
};
struct LandNormalsColumn : public Column<Land>
{
using DataType = QVector<signed char>;
LandNormalsColumn();
QVariant get(const Record<Land>& record) const override;
void set(Record<Land>& record, const QVariant& data) override;
bool isEditable() const override;
};
struct LandHeightsColumn : public Column<Land>
{
using DataType = QVector<float>;
LandHeightsColumn();
QVariant get(const Record<Land>& record) const override;
void set(Record<Land>& record, const QVariant& data) override;
bool isEditable() const override;
};
struct LandColoursColumn : public Column<Land>
{
using DataType = QVector<unsigned char>;
LandColoursColumn();
QVariant get(const Record<Land>& record) const override;
void set(Record<Land>& record, const QVariant& data) override;
bool isEditable() const override;
};
struct LandTexturesColumn : public Column<Land>
{
using DataType = QVector<uint16_t>;
LandTexturesColumn();
QVariant get(const Record<Land>& record) const override;
void set(Record<Land>& record, const QVariant& data) override;
bool isEditable() const override;
};
struct BodyPartRaceColumn : public RaceColumn<ESM::BodyPart>
{
const MeshTypeColumn<ESM::BodyPart> *mMeshType;
@ -2430,4 +2528,11 @@ namespace CSMWorld
};
}
// This is required to access the type as a QVariant.
Q_DECLARE_METATYPE(CSMWorld::LandMapLodColumn::DataType)
//Q_DECLARE_METATYPE(CSMWorld::LandNormalsColumn::DataType) // Same as LandMapLodColumn::DataType
Q_DECLARE_METATYPE(CSMWorld::LandHeightsColumn::DataType)
Q_DECLARE_METATYPE(CSMWorld::LandColoursColumn::DataType)
Q_DECLARE_METATYPE(CSMWorld::LandTexturesColumn::DataType)
#endif

@ -124,7 +124,7 @@ namespace CSMWorld
{ ColumnId_Portable, "Portable" },
{ ColumnId_NegativeLight, "Negative Light" },
{ ColumnId_EmitterType, "Emitter Type" },
{ ColumnId_Fire, "Fire" },
{ ColumnId_OffByDefault, "Off by default" },
{ ColumnId_IsKey, "Is Key" },
@ -330,6 +330,14 @@ namespace CSMWorld
{ ColumnId_WeatherChance, "Percent Chance" },
{ ColumnId_Text, "Text" },
{ ColumnId_TextureNickname, "Texture Nickname" },
{ ColumnId_PluginIndex, "Plugin Index" },
{ ColumnId_TextureIndex, "Texture Index" },
{ ColumnId_LandMapLodIndex, "Land map height LOD" },
{ ColumnId_LandNormalsIndex, "Land normals" },
{ ColumnId_LandHeightsIndex, "Land heights" },
{ ColumnId_LandColoursIndex, "Land colors" },
{ ColumnId_LandTexturesIndex, "Land textures" },
{ ColumnId_UseValue1, "Use value 1" },
{ ColumnId_UseValue2, "Use value 2" },

@ -329,6 +329,15 @@ namespace CSMWorld
ColumnId_Text = 297,
ColumnId_TextureNickname = 298,
ColumnId_PluginIndex = 299,
ColumnId_TextureIndex = 300,
ColumnId_LandMapLodIndex = 301,
ColumnId_LandNormalsIndex = 302,
ColumnId_LandHeightsIndex = 303,
ColumnId_LandColoursIndex = 304,
ColumnId_LandTexturesIndex = 305,
// Allocated to a separate value range, so we don't get a collision should we ever need
// to extend the number of use values.
ColumnId_UseValue1 = 0x10000,

@ -2,6 +2,7 @@
#include <cmath>
#include <sstream>
#include <unordered_set>
#include <components/misc/stringops.hpp>
@ -15,6 +16,175 @@
#include "nestedtablewrapper.hpp"
#include "pathgrid.hpp"
CSMWorld::TouchCommand::TouchCommand(IdTable& table, const std::string& id, QUndoCommand* parent)
: QUndoCommand(parent)
, mTable(table)
, mId(id)
, mOld(nullptr)
, mChanged(false)
{
setText(("Touch " + mId).c_str());
mOld.reset(mTable.getRecord(mId).clone());
}
void CSMWorld::TouchCommand::redo()
{
mChanged = mTable.touchRecord(mId);
}
void CSMWorld::TouchCommand::undo()
{
if (mChanged)
{
mTable.setRecord(mId, *mOld);
mChanged = false;
}
}
CSMWorld::ImportLandTexturesCommand::ImportLandTexturesCommand(IdTable& landTable,
IdTable& ltexTable, QUndoCommand* parent)
: QUndoCommand(parent)
, mLands(landTable)
, mLtexs(ltexTable)
, mOldState(0)
{
setText("Copy land textures to current plugin");
}
void CSMWorld::ImportLandTexturesCommand::redo()
{
int pluginColumn = mLands.findColumnIndex(Columns::ColumnId_PluginIndex);
int oldPlugin = mLands.data(mLands.getModelIndex(getOriginId(), pluginColumn)).toInt();
// Original data
int textureColumn = mLands.findColumnIndex(Columns::ColumnId_LandTexturesIndex);
mOld = mLands.data(mLands.getModelIndex(getOriginId(), textureColumn)).value<DataType>();
// Need to make a copy so the old values can be looked up
DataType copy(mOld);
// Perform touch/copy/etc...
onRedo();
// Find all indices used
std::unordered_set<int> texIndices;
for (int i = 0; i < mOld.size(); ++i)
{
// All indices are offset by 1 for a default texture
if (mOld[i] > 0)
texIndices.insert(mOld[i] - 1);
}
std::vector<std::string> oldTextures;
for (int index : texIndices)
{
oldTextures.push_back(LandTexture::createUniqueRecordId(oldPlugin, index));
}
// Import the textures, replace old values
LandTextureIdTable::ImportResults results = dynamic_cast<LandTextureIdTable&>(mLtexs).importTextures(oldTextures);
mCreatedTextures = std::move(results.createdRecords);
for (const auto& it : results.recordMapping)
{
int plugin = 0, newIndex = 0, oldIndex = 0;
LandTexture::parseUniqueRecordId(it.first, plugin, oldIndex);
LandTexture::parseUniqueRecordId(it.second, plugin, newIndex);
if (newIndex != oldIndex)
{
for (int i = 0; i < Land::LAND_NUM_TEXTURES; ++i)
{
// All indices are offset by 1 for a default texture
if (mOld[i] == oldIndex + 1)
copy[i] = newIndex + 1;
}
}
}
// Apply modification
int stateColumn = mLands.findColumnIndex(Columns::ColumnId_Modification);
mOldState = mLands.data(mLands.getModelIndex(getDestinationId(), stateColumn)).toInt();
QVariant variant;
variant.setValue(copy);
mLands.setData(mLands.getModelIndex(getDestinationId(), textureColumn), variant);
}
void CSMWorld::ImportLandTexturesCommand::undo()
{
// Restore to previous
int textureColumn = mLands.findColumnIndex(Columns::ColumnId_LandTexturesIndex);
QVariant variant;
variant.setValue(mOld);
mLands.setData(mLands.getModelIndex(getDestinationId(), textureColumn), variant);
int stateColumn = mLands.findColumnIndex(Columns::ColumnId_Modification);
mLands.setData(mLands.getModelIndex(getDestinationId(), stateColumn), mOldState);
// Undo copy/touch/etc...
onUndo();
for (const std::string& id : mCreatedTextures)
{
int row = mLtexs.getModelIndex(id, 0).row();
mLtexs.removeRows(row, 1);
}
mCreatedTextures.clear();
}
CSMWorld::CopyLandTexturesCommand::CopyLandTexturesCommand(IdTable& landTable, IdTable& ltexTable,
const std::string& origin, const std::string& dest, QUndoCommand* parent)
: ImportLandTexturesCommand(landTable, ltexTable, parent)
, mOriginId(origin)
, mDestId(dest)
{
}
const std::string& CSMWorld::CopyLandTexturesCommand::getOriginId() const
{
return mOriginId;
}
const std::string& CSMWorld::CopyLandTexturesCommand::getDestinationId() const
{
return mDestId;
}
CSMWorld::TouchLandCommand::TouchLandCommand(IdTable& landTable, IdTable& ltexTable,
const std::string& id, QUndoCommand* parent)
: ImportLandTexturesCommand(landTable, ltexTable, parent)
, mId(id)
, mOld(nullptr)
, mChanged(false)
{
setText(("Touch " + mId).c_str());
mOld.reset(mLands.getRecord(mId).clone());
}
const std::string& CSMWorld::TouchLandCommand::getOriginId() const
{
return mId;
}
const std::string& CSMWorld::TouchLandCommand::getDestinationId() const
{
return mId;
}
void CSMWorld::TouchLandCommand::onRedo()
{
mChanged = mLands.touchRecord(mId);
}
void CSMWorld::TouchLandCommand::onUndo()
{
if (mChanged)
{
mLands.setRecord(mId, *mOld);
mChanged = false;
}
}
CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index,
const QVariant& new_, QUndoCommand* parent)
: QUndoCommand (parent), mModel (&model), mIndex (index), mNew (new_), mHasRecordState(false), mOldRecordState(CSMWorld::RecordBase::State_BaseOnly)

@ -5,12 +5,14 @@
#include <string>
#include <map>
#include <memory>
#include <vector>
#include <QVariant>
#include <QUndoCommand>
#include <QModelIndex>
#include "columnimp.hpp"
#include "universalid.hpp"
#include "nestedtablewrapper.hpp"
@ -24,6 +26,106 @@ namespace CSMWorld
struct RecordBase;
struct NestedTableWrapperBase;
class TouchCommand : public QUndoCommand
{
public:
TouchCommand(IdTable& model, const std::string& id, QUndoCommand* parent=nullptr);
void redo() override;
void undo() override;
private:
IdTable& mTable;
std::string mId;
std::unique_ptr<RecordBase> mOld;
bool mChanged;
};
/// \brief Adds LandTexture records and modifies texture indices as needed.
///
/// LandTexture records are different from other types of records, because
/// they only effect the current plugin. Thus, when modifying or copying
/// a Land record, all of the LandTexture records referenced need to be
/// added to the current plugin. Since these newly added LandTextures could
/// have indices that conflict with pre-existing LandTextures in the current
/// plugin, the indices might have to be changed, both for the newly added
/// LandRecord and within the Land record.
class ImportLandTexturesCommand : public QUndoCommand
{
public:
ImportLandTexturesCommand(IdTable& landTable, IdTable& ltexTable,
QUndoCommand* parent);
void redo() override;
void undo() override;
protected:
using DataType = LandTexturesColumn::DataType;
virtual const std::string& getOriginId() const = 0;
virtual const std::string& getDestinationId() const = 0;
virtual void onRedo() = 0;
virtual void onUndo() = 0;
IdTable& mLands;
IdTable& mLtexs;
DataType mOld;
int mOldState;
std::vector<std::string> mCreatedTextures;
};
/// \brief This command is used to fix LandTexture records and texture
/// indices after cloning a Land. See ImportLandTexturesCommand for
/// details.
class CopyLandTexturesCommand : public ImportLandTexturesCommand
{
public:
CopyLandTexturesCommand(IdTable& landTable, IdTable& ltexTable, const std::string& origin,
const std::string& dest, QUndoCommand* parent = nullptr);
private:
const std::string& getOriginId() const override;
const std::string& getDestinationId() const override;
void onRedo() override {}
void onUndo() override {}
std::string mOriginId;
std::string mDestId;
};
/// \brief This command brings a land record into the current plugin, adding
/// LandTexture records and modifying texture indices as needed.
/// \note See ImportLandTextures for more details.
class TouchLandCommand : public ImportLandTexturesCommand
{
public:
TouchLandCommand(IdTable& landTable, IdTable& ltexTable,
const std::string& id, QUndoCommand* parent = nullptr);
private:
const std::string& getOriginId() const override;
const std::string& getDestinationId() const override;
void onRedo() override;
void onUndo() override;
std::string mId;
std::unique_ptr<RecordBase> mOld;
bool mChanged;
};
class ModifyCommand : public QUndoCommand
{
QAbstractItemModel *mModel;

@ -412,6 +412,24 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat
Columns::ColumnId_NegativeLight, ESM::MagicEffect::NegativeLight));
mMagicEffects.addColumn (new DescriptionColumn<ESM::MagicEffect>);
mLand.addColumn (new StringIdColumn<Land>);
mLand.addColumn (new RecordStateColumn<Land>);
mLand.addColumn (new FixedRecordTypeColumn<Land>(UniversalId::Type_Land));
mLand.addColumn (new LandPluginIndexColumn);
mLand.addColumn (new LandMapLodColumn);
mLand.addColumn (new LandNormalsColumn);
mLand.addColumn (new LandHeightsColumn);
mLand.addColumn (new LandColoursColumn);
mLand.addColumn (new LandTexturesColumn);
mLandTextures.addColumn (new StringIdColumn<LandTexture>(true));
mLandTextures.addColumn (new RecordStateColumn<LandTexture>);
mLandTextures.addColumn (new FixedRecordTypeColumn<LandTexture>(UniversalId::Type_LandTexture));
mLandTextures.addColumn (new LandTextureNicknameColumn);
mLandTextures.addColumn (new LandTexturePluginIndexColumn);
mLandTextures.addColumn (new LandTextureIndexColumn);
mLandTextures.addColumn (new TextureColumn<LandTexture>);
mPathgrids.addColumn (new StringIdColumn<Pathgrid>);
mPathgrids.addColumn (new RecordStateColumn<Pathgrid>);
mPathgrids.addColumn (new FixedRecordTypeColumn<Pathgrid> (UniversalId::Type_Pathgrid));
@ -531,6 +549,8 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat
addModel (new IdTable (&mBodyParts), UniversalId::Type_BodyPart);
addModel (new IdTable (&mSoundGens), UniversalId::Type_SoundGen);
addModel (new IdTable (&mMagicEffects), UniversalId::Type_MagicEffect);
addModel (new IdTable (&mLand, IdTable::Feature_AllowTouch), UniversalId::Type_Land);
addModel (new LandTextureIdTable (&mLandTextures), UniversalId::Type_LandTexture);
addModel (new IdTree (&mPathgrids, &mPathgrids), UniversalId::Type_Pathgrid);
addModel (new IdTable (&mStartScripts), UniversalId::Type_StartScript);
addModel (new IdTree (&mReferenceables, &mReferenceables, IdTable::Feature_Preview),
@ -993,19 +1013,7 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages)
case ESM::REC_LTEX: mLandTextures.load (*mReader, mBase); break;
case ESM::REC_LAND:
{
int index = mLand.load(*mReader, mBase);
// Load all land data for now. A future optimisation may only load non-base data
// if a suitable mechanism for avoiding race conditions can be established.
if (index!=-1/* && !mBase*/)
mLand.getRecord (index).get().loadData (
ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR |
ESM::Land::DATA_VTEX);
break;
}
case ESM::REC_LAND: mLand.load(*mReader, mBase); break;
case ESM::REC_CELL:
{

@ -4,6 +4,7 @@
#include <components/esm/esmreader.hpp>
#include "collection.hpp"
#include "land.hpp"
namespace CSMWorld
{
@ -39,6 +40,22 @@ namespace CSMWorld
record.load (reader, isDeleted);
}
template<>
inline void IdCollection<Land, IdAccessor<Land> >::loadRecord (Land& record,
ESM::ESMReader& reader, bool& isDeleted)
{
record.load (reader, isDeleted);
// Load all land data for now. A future optimisation may only load non-base data
// if a suitable mechanism for avoiding race conditions can be established.
int flags = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML |
ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX;
record.loadData (flags);
// Prevent data from being reloaded.
record.mContext.filename.clear();
}
template<typename ESXRecordT, typename IdAccessorT>
int IdCollection<ESXRecordT, IdAccessorT>::load (ESM::ESMReader& reader, bool base)
{

@ -1,11 +1,17 @@
#include "idtable.hpp"
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <limits>
#include <map>
#include <stdexcept>
#include <components/esm/cellid.hpp>
#include "collectionbase.hpp"
#include "columnbase.hpp"
#include "landtexture.hpp"
CSMWorld::IdTable::IdTable (CollectionBase *idCollection, unsigned int features)
: IdTableBase (features), mIdCollection (idCollection)
@ -179,6 +185,26 @@ void CSMWorld::IdTable::cloneRecord(const std::string& origin,
endInsertRows();
}
bool CSMWorld::IdTable::touchRecord(const std::string& id)
{
bool changed = mIdCollection->touchRecord(id);
int row = mIdCollection->getIndex(id);
int column = mIdCollection->searchColumnIndex(Columns::ColumnId_RecordType);
if (changed && column != -1)
{
QModelIndex modelIndex = index(row, column);
emit dataChanged(modelIndex, modelIndex);
}
return changed;
}
std::string CSMWorld::IdTable::getId(int row) const
{
return mIdCollection->getId(row);
}
///This method can return only indexes to the top level table cells
QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const
{
@ -281,3 +307,72 @@ CSMWorld::CollectionBase *CSMWorld::IdTable::idCollection() const
{
return mIdCollection;
}
CSMWorld::LandTextureIdTable::LandTextureIdTable(CollectionBase* idCollection, unsigned int features)
: IdTable(idCollection, features)
{
}
CSMWorld::LandTextureIdTable::ImportResults CSMWorld::LandTextureIdTable::importTextures(const std::vector<std::string>& ids)
{
ImportResults results;
// Map existing textures to ids
std::map<std::string, std::string> reverseLookupMap;
for (int i = 0; i < idCollection()->getSize(); ++i)
{
auto& record = static_cast<const Record<LandTexture>&>(idCollection()->getRecord(i));
std::string texture = record.get().mTexture;
std::transform(texture.begin(), texture.end(), texture.begin(), tolower);
if (record.isModified())
reverseLookupMap.emplace(texture, idCollection()->getId(i));
}
for (const std::string& id : ids)
{
int plugin, index;
LandTexture::parseUniqueRecordId(id, plugin, index);
int oldRow = idCollection()->searchId(id);
// If it does not exist or it is in the current plugin, it can be skipped.
if (oldRow < 0 || plugin == 0)
{
results.recordMapping.push_back(std::make_pair(id, id));
continue;
}
// Look for a pre-existing record
auto& record = static_cast<const Record<LandTexture>&>(idCollection()->getRecord(oldRow));
std::string texture = record.get().mTexture;
std::transform(texture.begin(), texture.end(), texture.begin(), tolower);
auto searchIt = reverseLookupMap.find(texture);
if (searchIt != reverseLookupMap.end())
{
results.recordMapping.push_back(std::make_pair(id, searchIt->second));
continue;
}
// Iterate until an unused index or found, or the index has completely wrapped around.
int startIndex = index;
do {
std::string newId = LandTexture::createUniqueRecordId(0, index);
int newRow = idCollection()->searchId(newId);
if (newRow < 0)
{
// Id not taken, clone it
cloneRecord(id, newId, UniversalId::Type_LandTexture);
results.createdRecords.push_back(newId);
results.recordMapping.push_back(std::make_pair(id, newId));
reverseLookupMap.emplace(texture, newId);
break;
}
const size_t MaxIndex = std::numeric_limits<uint16_t>::max() - 1;
index = (index + 1) % MaxIndex;
} while (index != startIndex);
}
return results;
}

@ -61,6 +61,11 @@ namespace CSMWorld
const std::string& destination,
UniversalId::Type type = UniversalId::Type_None);
bool touchRecord(const std::string& id);
///< Will change the record state to modified, if it is not already.
std::string getId(int row) const;
virtual QModelIndex getModelIndex (const std::string& id, int column) const;
void setRecord (const std::string& id, const RecordBase& record,
@ -93,6 +98,29 @@ namespace CSMWorld
virtual CollectionBase *idCollection() const;
};
/// An IdTable customized to handle the more unique needs of LandTextureId's which behave
/// differently from other records. The major difference is that base records cannot be
/// modified.
class LandTextureIdTable : public IdTable
{
public:
struct ImportResults
{
using StringPair = std::pair<std::string,std::string>;
/// The newly added records
std::vector<std::string> createdRecords;
/// The 1st string is the original id, the 2nd is the mapped id
std::vector<StringPair> recordMapping;
};
LandTextureIdTable(CollectionBase* idCollection, unsigned int features=0);
/// Finds and maps/recreates the specified ids.
ImportResults importTextures(const std::vector<std::string>& ids);
};
}
#endif

@ -32,7 +32,9 @@ namespace CSMWorld
Feature_Preview = 8,
/// Table can not be modified through ordinary means.
Feature_Constant = 16
Feature_Constant = 16,
Feature_AllowTouch = 32
};
private:
@ -61,7 +63,7 @@ namespace CSMWorld
virtual bool isDeleted (const std::string& id) const = 0;
virtual int getColumnId (int column) const = 0;
unsigned int getFeatures() const;
};
}

@ -1,15 +1,30 @@
#include "land.hpp"
#include <sstream>
#include <stdexcept>
namespace CSMWorld
{
void Land::load(ESM::ESMReader &esm, bool &isDeleted)
{
ESM::Land::load(esm, isDeleted);
}
std::string Land::createUniqueRecordId(int x, int y)
{
std::ostringstream stream;
stream << "#" << mX << " " << mY;
mId = stream.str();
stream << "#" << x << " " << y;
return stream.str();
}
void Land::parseUniqueRecordId(const std::string& id, int& x, int& y)
{
size_t mid = id.find(' ');
if (mid == std::string::npos || id[0] != '#')
throw std::runtime_error("Invalid Land ID");
x = std::stoi(id.substr(1, mid - 1));
y = std::stoi(id.substr(mid + 1));
}
}

@ -12,10 +12,11 @@ namespace CSMWorld
/// \todo Add worldspace support to the Land record.
struct Land : public ESM::Land
{
std::string mId;
/// Loads the metadata and ID
void load (ESM::ESMReader &esm, bool &isDeleted);
static std::string createUniqueRecordId(int x, int y);
static void parseUniqueRecordId(const std::string& id, int& x, int& y);
};
}

@ -1,5 +1,8 @@
#include "landtexture.hpp"
#include <sstream>
#include <stdexcept>
#include <components/esm/esmreader.hpp>
namespace CSMWorld
@ -11,4 +14,21 @@ namespace CSMWorld
mPluginIndex = esm.getIndex();
}
std::string LandTexture::createUniqueRecordId(int plugin, int index)
{
std::stringstream ss;
ss << 'L' << plugin << '#' << index;
return ss.str();
}
void LandTexture::parseUniqueRecordId(const std::string& id, int& plugin, int& index)
{
size_t middle = id.find('#');
if (middle == std::string::npos || id[0] != 'L')
throw std::runtime_error("Invalid LandTexture ID");
plugin = std::stoi(id.substr(1,middle-1));
index = std::stoi(id.substr(middle+1));
}
}

@ -13,6 +13,11 @@ namespace CSMWorld
int mPluginIndex;
void load (ESM::ESMReader &esm, bool &isDeleted);
/// Returns a string identifier that will be unique to any LandTexture.
static std::string createUniqueRecordId(int plugin, int index);
/// Deconstructs a unique string identifier into plugin and index.
static void parseUniqueRecordId(const std::string& id, int& plugin, int& index);
};
}

@ -0,0 +1,21 @@
#include "landtexturetableproxymodel.hpp"
#include "idtable.hpp"
namespace CSMWorld
{
LandTextureTableProxyModel::LandTextureTableProxyModel(QObject* parent)
: IdTableProxyModel(parent)
{
}
bool LandTextureTableProxyModel::filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const
{
int columnIndex = mSourceModel->findColumnIndex(Columns::ColumnId_Modification);
QModelIndex index = mSourceModel->index(sourceRow, columnIndex);
if (mSourceModel->data(index).toInt() != RecordBase::State_ModifiedOnly)
return false;
return IdTableProxyModel::filterAcceptsRow(sourceRow, sourceParent);
}
}

@ -0,0 +1,22 @@
#ifndef CSM_WORLD_LANDTEXTURETABLEPROXYMODEL_H
#define CSM_WORLD_LANDTEXTURETABLEPROXYMODEL_H
#include "idtableproxymodel.hpp"
namespace CSMWorld
{
/// \brief Removes base records from filtered results.
class LandTextureTableProxyModel : public IdTableProxyModel
{
Q_OBJECT
public:
LandTextureTableProxyModel(QObject* parent = nullptr);
protected:
bool filterAcceptsRow (int sourceRow, const QModelIndex& sourceParent) const override;
};
}
#endif

@ -813,6 +813,12 @@ void CSMWorld::RefIdCollection::cloneRecord(const std::string& origin,
mData.insertRecord(*newRecord, type, destination);
}
bool CSMWorld::RefIdCollection::touchRecord(const std::string& id)
{
throw std::runtime_error("RefIdCollection::touchRecord is unimplemented");
return false;
}
void CSMWorld::RefIdCollection::appendRecord (const RecordBase& record,
UniversalId::Type type)
{

@ -80,6 +80,8 @@ namespace CSMWorld
const std::string& destination,
const UniversalId::Type type);
virtual bool touchRecord(const std::string& id);
virtual void appendBlankRecord (const std::string& id, UniversalId::Type type);
///< \param type Will be ignored, unless the collection supports multiple record types

@ -53,6 +53,8 @@ namespace
{ CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", 0 },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SoundGens, "Sound Generators", 0 },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MagicEffects, "Magic Effects", 0 },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Lands", 0 },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_LandTextures, "LandTextures", 0 },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", 0 },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_StartScripts, "Start Scripts", 0 },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Meta Data Table", 0 },
@ -118,6 +120,8 @@ namespace
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_DebugProfile, "Debug Profile", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_SoundGen, "Sound Generator", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "LandTexture", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", 0 },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Meta Data", 0 },
@ -364,8 +368,8 @@ std::vector<CSMWorld::UniversalId::Type> CSMWorld::UniversalId::listTypes (int c
for (int i=0; sIndexArg[i].mName; ++i)
if (sIndexArg[i].mClass & classes)
list.push_back (sIndexArg[i].mType);
return list;
return list;
}
CSMWorld::UniversalId::Type CSMWorld::UniversalId::getParentType (Type type)

@ -126,6 +126,10 @@ namespace CSMWorld
Type_SoundGen,
Type_MagicEffects,
Type_MagicEffect,
Type_Lands,
Type_Land,
Type_LandTextures,
Type_LandTexture,
Type_Pathgrids,
Type_Pathgrid,
Type_StartScripts,

@ -172,6 +172,16 @@ void CSVDoc::View::setupWorldMenu()
setupShortcut("document-world-references", references);
world->addAction (references);
QAction *lands = new QAction (tr ("Lands"), this);
connect (lands, SIGNAL (triggered()), this, SLOT (addLandsSubView()));
setupShortcut("document-world-lands", lands);
world->addAction (lands);
QAction *landTextures = new QAction (tr ("Land Textures"), this);
connect (landTextures, SIGNAL (triggered()), this, SLOT (addLandTexturesSubView()));
setupShortcut("document-world-landtextures", landTextures);
world->addAction (landTextures);
QAction *grid = new QAction (tr ("Pathgrid"), this);
connect (grid, SIGNAL (triggered()), this, SLOT (addPathgridSubView()));
setupShortcut("document-world-pathgrid", grid);
@ -876,6 +886,16 @@ void CSVDoc::View::addRunLogSubView()
addSubView (CSMWorld::UniversalId::Type_RunLog);
}
void CSVDoc::View::addLandsSubView()
{
addSubView (CSMWorld::UniversalId::Type_Lands);
}
void CSVDoc::View::addLandTexturesSubView()
{
addSubView (CSMWorld::UniversalId::Type_LandTextures);
}
void CSVDoc::View::addPathgridSubView()
{
addSubView (CSMWorld::UniversalId::Type_Pathgrids);

@ -224,6 +224,10 @@ namespace CSVDoc
void addRunLogSubView();
void addLandsSubView();
void addLandTexturesSubView();
void addPathgridSubView();
void addStartScriptsSubView();

@ -24,7 +24,7 @@ void CSVPrefs::Dialogue::buildCategorySelector (QSplitter *main)
main->addWidget (list);
QFontMetrics metrics (QApplication::font());
QFontMetrics metrics (QApplication::font(list));
int maxWidth = 1;
@ -84,7 +84,6 @@ CSVPrefs::Dialogue::~Dialogue()
void CSVPrefs::Dialogue::closeEvent (QCloseEvent *event)
{
QMainWindow::closeEvent (event);
CSMPrefs::State::get().save();
}

@ -668,16 +668,25 @@ namespace CSVRender
mInitialized = true;
}
void OrbitCameraController::setConstRoll(bool enabled)
{
mConstRoll = enabled;
}
void OrbitCameraController::rotateHorizontal(double value)
{
osg::Vec3d eye, center, up;
getCamera()->getViewMatrixAsLookAt(eye, center, up);
osg::Vec3d absoluteUp = osg::Vec3(0,0,1);
osg::Quat rotation = osg::Quat(value, up);
osg::Quat rotation = osg::Quat(value, mConstRoll ? absoluteUp : up);
osg::Vec3d oldOffset = eye - mCenter;
osg::Vec3d newOffset = rotation * oldOffset;
if (mConstRoll)
up = rotation * up;
getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up);
}
@ -687,9 +696,14 @@ namespace CSVRender
getCamera()->getViewMatrixAsLookAt(eye, center, up);
osg::Vec3d forward = center - eye;
osg::Quat rotation = osg::Quat(value, up ^ forward);
osg::Vec3d axis = up ^ forward;
osg::Quat rotation = osg::Quat(value,axis);
osg::Vec3d oldOffset = eye - mCenter;
osg::Vec3d newOffset = rotation * oldOffset;
if (mConstRoll)
up = rotation * up;
getCamera()->setViewMatrixAsLookAt(mCenter + newOffset, mCenter, up);
}

@ -160,6 +160,8 @@ namespace CSVRender
/// \brief Flag controller to be re-initialized.
void reset();
void setConstRoll(bool enable);
private:
void initialize();
@ -181,6 +183,8 @@ namespace CSVRender
double mOrbitSpeed;
double mOrbitSpeedMult;
bool mConstRoll;
private slots:
void naviPrimary(bool active);

@ -26,6 +26,33 @@
#include "terrainstorage.hpp"
#include "object.hpp"
namespace CSVRender
{
class CellNodeContainer : public osg::Referenced
{
public:
CellNodeContainer(Cell* cell) : mCell(cell) {}
Cell* getCell(){ return mCell; }
private:
Cell* mCell;
};
class CellNodeCallback : public osg::NodeCallback
{
public:
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
CellNodeContainer* container = static_cast<CellNodeContainer*>(node->getUserData());
container->getCell()->updateLand();
}
};
}
bool CSVRender::Cell::removeObject (const std::string& id)
{
std::map<std::string, Object *>::iterator iter =
@ -75,10 +102,69 @@ bool CSVRender::Cell::addObjects (int start, int end)
return modified;
}
void CSVRender::Cell::updateLand()
{
if (!mUpdateLand || mLandDeleted)
return;
mUpdateLand = false;
// Cell is deleted
if (mDeleted)
{
unloadLand();
return;
}
// Setup land if available
const CSMWorld::IdCollection<CSMWorld::Land>& land = mData.getLand();
int landIndex = land.searchId(mId);
if (landIndex != -1 && !land.getRecord(mId).isDeleted())
{
const ESM::Land& esmLand = land.getRecord(mId).get();
if (esmLand.getLandData (ESM::Land::DATA_VHGT))
{
if (mTerrain)
{
mTerrain->unloadCell(mCoordinates.getX(), mCoordinates.getY());
mTerrain->clearAssociatedCaches();
}
else
{
mTerrain.reset(new Terrain::TerrainGrid(mCellNode, mCellNode,
mData.getResourceSystem().get(), new TerrainStorage(mData), Mask_Terrain));
}
mTerrain->loadCell(esmLand.mX, esmLand.mY);
if (!mCellBorder)
mCellBorder.reset(new CellBorder(mCellNode, mCoordinates));
mCellBorder->buildShape(esmLand);
return;
}
}
// No land data
mLandDeleted = true;
unloadLand();
}
void CSVRender::Cell::unloadLand()
{
if (mTerrain)
mTerrain->unloadCell(mCoordinates.getX(), mCoordinates.getY());
if (mCellBorder)
mCellBorder.reset();
}
CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id,
bool deleted)
: mData (data), mId (Misc::StringUtils::lowerCase (id)), mDeleted (deleted), mSubMode (0),
mSubModeElementMask (0)
mSubModeElementMask (0), mUpdateLand(true), mLandDeleted(false)
{
std::pair<CSMWorld::CellCoordinates, bool> result = CSMWorld::CellCoordinates::fromId (id);
@ -86,6 +172,8 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st
mCoordinates = result.first;
mCellNode = new osg::Group;
mCellNode->setUserData(new CellNodeContainer(this));
mCellNode->setUpdateCallback(new CellNodeCallback);
rootNode->addChild(mCellNode);
setCellMarker();
@ -99,22 +187,7 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st
addObjects (0, rows-1);
const CSMWorld::IdCollection<CSMWorld::Land>& land = mData.getLand();
int landIndex = land.searchId(mId);
if (landIndex != -1)
{
const ESM::Land& esmLand = land.getRecord(mId).get();
if (esmLand.getLandData (ESM::Land::DATA_VHGT))
{
mTerrain.reset(new Terrain::TerrainGrid(mCellNode, mCellNode, data.getResourceSystem().get(), new TerrainStorage(mData), Mask_Terrain));
mTerrain->loadCell(esmLand.mX,
esmLand.mY);
mCellBorder.reset(new CellBorder(mCellNode, mCoordinates));
mCellBorder->buildShape(esmLand);
}
}
updateLand();
mPathgrid.reset(new Pathgrid(mData, mCellNode, mId, mCoordinates));
mCellWater.reset(new CellWater(mData, mCellNode, mId, mCoordinates));
@ -285,6 +358,38 @@ void CSVRender::Cell::pathgridRemoved()
mPathgrid->removeGeometry();
}
void CSVRender::Cell::landDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
mUpdateLand = true;
}
void CSVRender::Cell::landAboutToBeRemoved (const QModelIndex& parent, int start, int end)
{
mLandDeleted = true;
unloadLand();
}
void CSVRender::Cell::landAdded (const QModelIndex& parent, int start, int end)
{
mUpdateLand = true;
mLandDeleted = false;
}
void CSVRender::Cell::landTextureChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
mUpdateLand = true;
}
void CSVRender::Cell::landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end)
{
mUpdateLand = true;
}
void CSVRender::Cell::landTextureAdded (const QModelIndex& parent, int start, int end)
{
mUpdateLand = true;
}
void CSVRender::Cell::reloadAssets()
{
for (std::map<std::string, Object *>::const_iterator iter (mObjects.begin());

@ -57,6 +57,7 @@ namespace CSVRender
bool mDeleted;
int mSubMode;
unsigned int mSubModeElementMask;
bool mUpdateLand, mLandDeleted;
/// Ignored if cell does not have an object with the given ID.
///
@ -72,6 +73,9 @@ namespace CSVRender
/// \return Have any objects been added?
bool addObjects (int start, int end);
void updateLand();
void unloadLand();
public:
enum Selection
@ -118,6 +122,18 @@ namespace CSVRender
void pathgridRemoved();
void landDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
void landAboutToBeRemoved (const QModelIndex& parent, int start, int end);
void landAdded (const QModelIndex& parent, int start, int end);
void landTextureChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight);
void landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end);
void landTextureAdded (const QModelIndex& parent, int start, int end);
void reloadAssets();
void setSelection (int elementMask, Selection mode);
@ -145,6 +161,8 @@ namespace CSVRender
/// Erase all overrides and restore the visual representation of the cell to its
/// true state.
void reset (unsigned int elementMask);
friend class CellNodeCallback;
};
}

@ -3,6 +3,7 @@
#include <stdexcept>
#include <iostream>
#include <osg/Depth>
#include <osg/Group>
#include <osg/PositionAttitudeTransform>
@ -21,6 +22,7 @@
#include "../../model/world/universalid.hpp"
#include "../../model/world/commandmacro.hpp"
#include "../../model/world/cellcoordinates.hpp"
#include "../../model/prefs/state.hpp"
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/lightutil.hpp>
@ -220,7 +222,7 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeMoveOrScaleMarker (int axis)
for (int i=0; i<2; ++i)
{
float length = i ? shaftLength : 0;
float length = i ? shaftLength : MarkerShaftWidth;
vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, -MarkerShaftWidth/2, length, axis));
vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, MarkerShaftWidth/2, length, axis));
@ -285,15 +287,15 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeMoveOrScaleMarker (int axis)
for (int i=0; i<8; ++i)
colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f,
axis==2 ? 1.0f : 0.2f, 1.0f));
axis==2 ? 1.0f : 0.2f, mMarkerTransparency));
for (int i=8; i<8+4+1; ++i)
colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.0f, axis==1 ? 1.0f : 0.0f,
axis==2 ? 1.0f : 0.0f, 1.0f));
axis==2 ? 1.0f : 0.0f, mMarkerTransparency));
geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX);
geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF);
setupCommonMarkerState(geometry);
osg::ref_ptr<osg::Geode> geode (new osg::Geode);
geode->addDrawable (geometry);
@ -305,11 +307,11 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
{
const float Pi = 3.14159265f;
const float InnerRadius = mBaseNode->getBound().radius();
const float InnerRadius = std::max(MarkerShaftBaseLength, mBaseNode->getBound().radius());
const float OuterRadius = InnerRadius + MarkerShaftWidth;
const float SegmentDistance = 100.f;
const size_t SegmentCount = std::min(64, std::max(8, (int)(OuterRadius * 2 * Pi / SegmentDistance)));
const size_t SegmentCount = std::min(64, std::max(24, (int)(OuterRadius * 2 * Pi / SegmentDistance)));
const size_t VerticesPerSegment = 4;
const size_t IndicesPerSegment = 24;
@ -334,6 +336,9 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
osg::ref_ptr<osg::DrawElementsUShort> primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES,
IndexCount);
// prevent some depth collision issues from overlaps
osg::Vec3f offset = getMarkerPosition(0, MarkerShaftWidth/4, 0, axis);
for (size_t i = 0; i < SegmentCount; ++i)
{
size_t index = i * VerticesPerSegment;
@ -344,13 +349,17 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
float outerX = OuterRadius * std::cos(i * Angle);
float outerY = OuterRadius * std::sin(i * Angle);
vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis);
vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis);
vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis);
vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis);
vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis) + offset;
}
colors->at(0) = osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f, axis==2 ? 1.0f : 0.2f, 1.0f);
colors->at(0) = osg::Vec4f (
axis==0 ? 1.0f : 0.2f,
axis==1 ? 1.0f : 0.2f,
axis==2 ? 1.0f : 0.2f,
mMarkerTransparency);
for (size_t i = 0; i < SegmentCount; ++i)
{
@ -374,7 +383,7 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
geometry->setColorArray(colors, osg::Array::BIND_OVERALL);
geometry->addPrimitiveSet(primitives);
geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF);
setupCommonMarkerState(geometry);
osg::ref_ptr<osg::Geode> geode = new osg::Geode();
geode->addDrawable (geometry);
@ -382,6 +391,15 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
return geode;
}
void CSVRender::Object::setupCommonMarkerState(osg::ref_ptr<osg::Geometry> geometry)
{
osg::ref_ptr<osg::StateSet> state = geometry->getOrCreateStateSet();
state->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
state->setMode(GL_BLEND, osg::StateAttribute::ON);
state->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
}
osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int axis)
{
switch (axis)
@ -399,7 +417,7 @@ osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int
CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode,
const std::string& id, bool referenceable, bool forceBaseToZero)
: mData (data), mBaseNode(0), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero),
mScaleOverride (1), mOverrideFlags (0), mSubMode (-1)
mScaleOverride (1), mOverrideFlags (0), mSubMode (-1), mMarkerTransparency(0.5f)
{
mRootNode = new osg::PositionAttitudeTransform;
@ -453,6 +471,7 @@ void CSVRender::Object::setSelected(bool selected)
else
mRootNode->addChild(mBaseNode);
mMarkerTransparency = CSMPrefs::get()["Rendering"]["object-marker-alpha"].toDouble();
updateMarker();
}
@ -629,6 +648,12 @@ void CSVRender::Object::setScale (float scale)
adjustTransform();
}
void CSVRender::Object::setMarkerTransparency(float value)
{
mMarkerTransparency = value;
updateMarker();
}
void CSVRender::Object::apply (CSMWorld::CommandMacro& commands)
{
const CSMWorld::RefCollection& collection = mData.getReferences();

@ -4,6 +4,7 @@
#include <string>
#include <osg/ref_ptr>
#include <osg/Geometry>
#include <osg/Referenced>
#include <components/esm/defs.hpp>
@ -96,6 +97,7 @@ namespace CSVRender
int mOverrideFlags;
osg::ref_ptr<osg::Node> mMarker[3];
int mSubMode;
float mMarkerTransparency;
/// Not implemented
Object (const Object&);
@ -121,6 +123,9 @@ namespace CSVRender
osg::ref_ptr<osg::Node> makeMoveOrScaleMarker (int axis);
osg::ref_ptr<osg::Node> makeRotateMarker (int axis);
/// Sets up a stateset with properties common to all marker types.
void setupCommonMarkerState(osg::ref_ptr<osg::Geometry> geometry);
osg::Vec3f getMarkerPosition (float x, float y, float z, int axis);
public:
@ -179,6 +184,8 @@ namespace CSVRender
/// Set override scale
void setScale (float scale);
void setMarkerTransparency(float value);
/// Apply override changes via command and end edit mode
void apply (CSMWorld::CommandMacro& commands);

@ -351,6 +351,72 @@ void CSVRender::PagedWorldspaceWidget::pathgridAdded(const QModelIndex& parent,
}
}
void CSVRender::PagedWorldspaceWidget::landDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
for (int r = topLeft.row(); r <= bottomRight.row(); ++r)
{
std::string id = mDocument.getData().getLand().getId(r);
auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id).first);
if (cellIt != mCells.end())
{
cellIt->second->landDataChanged(topLeft, bottomRight);
flagAsModified();
}
}
}
void CSVRender::PagedWorldspaceWidget::landAboutToBeRemoved (const QModelIndex& parent, int start, int end)
{
for (int r = start; r <= end; ++r)
{
std::string id = mDocument.getData().getLand().getId(r);
auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id).first);
if (cellIt != mCells.end())
{
cellIt->second->landAboutToBeRemoved(parent, start, end);
flagAsModified();
}
}
}
void CSVRender::PagedWorldspaceWidget::landAdded (const QModelIndex& parent, int start, int end)
{
for (int r = start; r <= end; ++r)
{
std::string id = mDocument.getData().getLand().getId(r);
auto cellIt = mCells.find(CSMWorld::CellCoordinates::fromId(id).first);
if (cellIt != mCells.end())
{
cellIt->second->landAdded(parent, start, end);
flagAsModified();
}
}
}
void CSVRender::PagedWorldspaceWidget::landTextureDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
for (auto cellIt : mCells)
cellIt.second->landTextureChanged(topLeft, bottomRight);
flagAsModified();
}
void CSVRender::PagedWorldspaceWidget::landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end)
{
for (auto cellIt : mCells)
cellIt.second->landTextureAboutToBeRemoved(parent, start, end);
flagAsModified();
}
void CSVRender::PagedWorldspaceWidget::landTextureAdded (const QModelIndex& parent, int start, int end)
{
for (auto cellIt : mCells)
cellIt.second->landTextureAdded(parent, start, end);
flagAsModified();
}
std::string CSVRender::PagedWorldspaceWidget::getStartupInstruction()
{
@ -475,6 +541,24 @@ CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc
connect (&document.getData(), SIGNAL (assetTablesChanged ()),
this, SLOT (assetTablesChanged ()));
QAbstractItemModel *lands = document.getData().getTableModel (CSMWorld::UniversalId::Type_Lands);
connect (lands, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
this, SLOT (landDataChanged (const QModelIndex&, const QModelIndex&)));
connect (lands, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)),
this, SLOT (landAboutToBeRemoved (const QModelIndex&, int, int)));
connect (lands, SIGNAL (rowsInserted (const QModelIndex&, int, int)),
this, SLOT (landAdded (const QModelIndex&, int, int)));
QAbstractItemModel *ltexs = document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures);
connect (ltexs, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
this, SLOT (landTextureDataChanged (const QModelIndex&, const QModelIndex&)));
connect (ltexs, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)),
this, SLOT (landTextureAboutToBeRemoved (const QModelIndex&, int, int)));
connect (ltexs, SIGNAL (rowsInserted (const QModelIndex&, int, int)),
this, SLOT (landTextureAdded (const QModelIndex&, int, int)));
// Shortcuts
CSMPrefs::Shortcut* loadCameraCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-cell", this);
connect(loadCameraCellShortcut, SIGNAL(activated()), this, SLOT(loadCameraCell()));

@ -155,6 +155,14 @@ namespace CSVRender
virtual void cellAdded (const QModelIndex& index, int start, int end);
virtual void landDataChanged (const QModelIndex& topLeft, const QModelIndex& botomRight);
virtual void landAboutToBeRemoved (const QModelIndex& parent, int start, int end);
virtual void landAdded (const QModelIndex& parent, int start, int end);
virtual void landTextureDataChanged (const QModelIndex& topLeft, const QModelIndex& botomRight);
virtual void landTextureAboutToBeRemoved (const QModelIndex& parent, int start, int end);
virtual void landTextureAdded (const QModelIndex& parent, int start, int end);
void assetTablesChanged ();
void loadCameraCell();

@ -56,6 +56,7 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f)
traits->vsync = false;
mView = new osgViewer::View;
updateCameraParameters( traits->width / static_cast<double>(traits->height) );
osg::ref_ptr<osgQt::GraphicsWindowQt> window = new osgQt::GraphicsWindowQt(traits.get());
QLayout* layout = new QHBoxLayout(this);
@ -66,7 +67,6 @@ RenderWidget::RenderWidget(QWidget *parent, Qt::WindowFlags f)
mView->getCamera()->setGraphicsContext(window);
mView->getCamera()->setClearColor( osg::Vec4(0.2, 0.2, 0.6, 1.0) );
mView->getCamera()->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) );
mView->getCamera()->setProjectionMatrixAsPerspective(30.0f, static_cast<double>(traits->width)/static_cast<double>(traits->height), 1.0f, 10000.0f );
SceneUtil::LightManager* lightMgr = new SceneUtil::LightManager;
lightMgr->setStartLight(1);
@ -188,6 +188,8 @@ SceneWidget::SceneWidget(std::shared_ptr<Resource::ResourceSystem> resourceSyste
mOrbitCamControl->setPickingMask(Mask_Reference | Mask_Terrain);
mOrbitCamControl->setConstRoll( CSMPrefs::get()["3D Scene Input"]["navi-orbit-const-roll"].isTrue() );
// we handle lighting manually
mView->setLightingMode(osgViewer::View::NO_LIGHT);
@ -370,6 +372,40 @@ void SceneWidget::settingChanged (const CSMPrefs::Setting *setting)
{
mOrbitCamControl->setOrbitSpeedMultiplier(setting->toDouble());
}
else if (*setting=="3D Scene Input/navi-orbit-const-roll")
{
mOrbitCamControl->setConstRoll(setting->isTrue());
}
else if (*setting=="Rendering/camera-fov" ||
*setting=="Rendering/camera-ortho" ||
*setting=="Rendering/camera-ortho-size")
{
updateCameraParameters();
}
}
void RenderWidget::updateCameraParameters(double overrideAspect)
{
const float nearDist = 1.0;
const float farDist = 1000.0;
if (CSMPrefs::get()["Rendering"]["camera-ortho"].isTrue())
{
const float size = CSMPrefs::get()["Rendering"]["camera-ortho-size"].toInt();
const float aspect = overrideAspect >= 0.0 ? overrideAspect : (width() / static_cast<double>(height()));
const float halfH = size * 10.0;
const float halfW = halfH * aspect;
mView->getCamera()->setProjectionMatrixAsOrtho(
-halfW, halfW, -halfH, halfH, nearDist, farDist);
}
else
{
mView->getCamera()->setProjectionMatrixAsPerspective(
CSMPrefs::get()["Rendering"]["camera-fov"].toInt(),
static_cast<double>(width())/static_cast<double>(height()),
nearDist, farDist);
}
}
void SceneWidget::selectNavigationMode (const std::string& mode)

@ -64,6 +64,8 @@ namespace CSVRender
osg::ref_ptr<osgViewer::View> mView;
osg::ref_ptr<osg::Group> mRootNode;
void updateCameraParameters(double overrideAspect = -1.0);
QTimer mTimer;
protected slots:

@ -1,5 +1,8 @@
#include "terrainstorage.hpp"
#include "../../model/world/land.hpp"
#include "../../model/world/landtexture.hpp"
namespace CSVRender
{
@ -11,12 +14,9 @@ namespace CSVRender
osg::ref_ptr<const ESMTerrain::LandObject> TerrainStorage::getLand(int cellX, int cellY)
{
std::ostringstream stream;
stream << "#" << cellX << " " << cellY;
// The cell isn't guaranteed to have Land. This is because the terrain implementation
// has to wrap the vertices of the last row and column to the next cell, which may be a nonexisting cell
int index = mData.getLand().searchId(stream.str());
int index = mData.getLand().searchId(CSMWorld::Land::createUniqueRecordId(cellX, cellY));
if (index == -1)
return NULL;
@ -26,16 +26,11 @@ namespace CSVRender
const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin)
{
int numRecords = mData.getLandTextures().getSize();
for (int i=0; i<numRecords; ++i)
{
const CSMWorld::LandTexture* ltex = &mData.getLandTextures().getRecord(i).get();
if (ltex->mIndex == index && ltex->mPluginIndex == plugin)
return ltex;
}
int row = mData.getLandTextures().searchId(CSMWorld::LandTexture::createUniqueRecordId(plugin, index));
if (row == -1)
return nullptr;
return NULL;
return &mData.getLandTextures().getRecord(row).get();
}
void TerrainStorage::getBounds(float &minX, float &maxX, float &minY, float &maxY)

@ -51,6 +51,7 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg
, mToolTipPos (-1, -1)
, mShowToolTips(false)
, mToolTipDelay(0)
, mInConstructor(true)
{
setAcceptDrops(true);
@ -114,6 +115,8 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg
CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this);
connect(abortShortcut, SIGNAL(activated()), this, SLOT(abortDrag()));
mInConstructor = false;
}
CSVRender::WorldspaceWidget::~WorldspaceWidget ()
@ -128,6 +131,17 @@ void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setti
mDragWheelFactor = setting->toDouble();
else if (*setting=="3D Scene Input/drag-shift-factor")
mDragShiftFactor = setting->toDouble();
else if (*setting=="Rendering/object-marker-alpha" && !mInConstructor)
{
float alpha = setting->toDouble();
// getSelection is virtual, thus this can not be called from the constructor
auto selection = getSelection(Mask_Reference);
for (osg::ref_ptr<TagBase> tag : selection)
{
if (auto objTag = dynamic_cast<ObjectTag*>(tag.get()))
objTag->mObject->setMarkerTransparency(alpha);
}
}
else if (*setting=="Tooltips/scene-delay")
mToolTipDelay = setting->toInt();
else if (*setting=="Tooltips/scene")

@ -65,6 +65,7 @@ namespace CSVRender
QPoint mToolTipPos;
bool mShowToolTips;
int mToolTipDelay;
bool mInConstructor;
public:

@ -5,29 +5,32 @@
#include "../widget/coloreditor.hpp"
CSVWorld::ColorDelegate::ColorDelegate(CSMWorld::CommandDispatcher *dispatcher,
CSMDoc::Document& document,
CSVWorld::ColorDelegate::ColorDelegate(CSMWorld::CommandDispatcher *dispatcher,
CSMDoc::Document& document,
QObject *parent)
: CommandDelegate(dispatcher, document, parent)
{}
void CSVWorld::ColorDelegate::paint(QPainter *painter,
void CSVWorld::ColorDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
int colorInt = index.data().toInt();
QColor color(colorInt & 0xff, (colorInt >> 8) & 0xff, (colorInt >> 16) & 0xff);
QRect coloredRect(option.rect.x() + qRound(option.rect.width() / 4.0),
option.rect.y() + qRound(option.rect.height() / 4.0),
option.rect.width() / 2,
option.rect.height() / 2);
painter->save();
painter->fillRect(coloredRect, index.data().value<QColor>());
painter->fillRect(coloredRect, color);
painter->setPen(Qt::black);
painter->drawRect(coloredRect);
painter->restore();
}
CSVWorld::CommandDelegate *CSVWorld::ColorDelegateFactory::makeDelegate(CSMWorld::CommandDispatcher *dispatcher,
CSMDoc::Document &document,
CSVWorld::CommandDelegate *CSVWorld::ColorDelegateFactory::makeDelegate(CSMWorld::CommandDispatcher *dispatcher,
CSMDoc::Document &document,
QObject *parent) const
{
return new ColorDelegate(dispatcher, document, parent);

@ -31,6 +31,9 @@ namespace CSVWorld
virtual void cloneMode(const std::string& originId,
const CSMWorld::UniversalId::Type type) = 0;
/// Touches a record, if the creator supports it.
virtual void touch(const std::vector<CSMWorld::UniversalId>& ids) = 0;
virtual void setEditLock (bool locked) = 0;
virtual void toggleWidgets(bool active = true) = 0;

@ -51,6 +51,11 @@ std::string CSVWorld::GenericCreator::getId() const
return mId->text().toUtf8().constData();
}
std::string CSVWorld::GenericCreator::getClonedId() const
{
return mClonedId;
}
std::string CSVWorld::GenericCreator::getIdValidatorResult() const
{
std::string errors;
@ -254,6 +259,22 @@ void CSVWorld::GenericCreator::cloneMode(const std::string& originId,
mClonedType = type;
}
void CSVWorld::GenericCreator::touch(const std::vector<CSMWorld::UniversalId>& ids)
{
// Combine multiple touch commands into one "macro" command
mUndoStack.beginMacro("Touch Records");
CSMWorld::IdTable& table = dynamic_cast<CSMWorld::IdTable&>(*mData.getTableModel(mListId));
for (const CSMWorld::UniversalId& uid : ids)
{
CSMWorld::TouchCommand* touchCmd = new CSMWorld::TouchCommand(table, uid.getId());
mUndoStack.push(touchCmd);
}
// Execute
mUndoStack.endMacro();
}
void CSVWorld::GenericCreator::toggleWidgets(bool active)
{
}

@ -64,6 +64,8 @@ namespace CSVWorld
virtual std::string getId() const;
std::string getClonedId() const;
virtual std::string getIdValidatorResult() const;
/// Allow subclasses to add additional data to \a command.
@ -103,6 +105,8 @@ namespace CSVWorld
virtual void cloneMode(const std::string& originId,
const CSMWorld::UniversalId::Type type);
virtual void touch(const std::vector<CSMWorld::UniversalId>& ids);
virtual std::string getErrors() const;
///< Return formatted error descriptions for the current state of the creator. if an empty
/// string is returned, there is no error.

@ -0,0 +1,120 @@
#include "landcreator.hpp"
#include <limits>
#include <QLabel>
#include <QSpinBox>
#include "../../model/world/commands.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/land.hpp"
namespace CSVWorld
{
LandCreator::LandCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id)
: GenericCreator(data, undoStack, id)
, mXLabel(nullptr)
, mYLabel(nullptr)
, mX(nullptr)
, mY(nullptr)
{
const int MaxInt = std::numeric_limits<int>::max();
const int MinInt = std::numeric_limits<int>::min();
setManualEditing(false);
mXLabel = new QLabel("X: ");
mX = new QSpinBox();
mX->setMinimum(MinInt);
mX->setMaximum(MaxInt);
insertBeforeButtons(mXLabel, false);
insertBeforeButtons(mX, true);
mYLabel = new QLabel("Y: ");
mY = new QSpinBox();
mY->setMinimum(MinInt);
mY->setMaximum(MaxInt);
insertBeforeButtons(mYLabel, false);
insertBeforeButtons(mY, true);
connect (mX, SIGNAL(valueChanged(int)), this, SLOT(coordChanged(int)));
connect (mY, SIGNAL(valueChanged(int)), this, SLOT(coordChanged(int)));
}
void LandCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type)
{
GenericCreator::cloneMode(originId, type);
int x = 0, y = 0;
CSMWorld::Land::parseUniqueRecordId(originId, x, y);
mX->setValue(x);
mY->setValue(y);
}
void LandCreator::touch(const std::vector<CSMWorld::UniversalId>& ids)
{
// Combine multiple touch commands into one "macro" command
getUndoStack().beginMacro("Touch records");
CSMWorld::IdTable& lands = dynamic_cast<CSMWorld::IdTable&>(*getData().getTableModel(CSMWorld::UniversalId::Type_Lands));
CSMWorld::IdTable& ltexs = dynamic_cast<CSMWorld::IdTable&>(*getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures));
for (const CSMWorld::UniversalId& uid : ids)
{
CSMWorld::TouchLandCommand* touchCmd = new CSMWorld::TouchLandCommand(lands, ltexs, uid.getId());
getUndoStack().push(touchCmd);
}
// Execute
getUndoStack().endMacro();
}
void LandCreator::focus()
{
mX->setFocus();
}
void LandCreator::reset()
{
GenericCreator::reset();
mX->setValue(0);
mY->setValue(0);
}
std::string LandCreator::getErrors() const
{
if (getData().getLand().searchId(getId()) >= 0)
return "A land with that name already exists.";
return "";
}
std::string LandCreator::getId() const
{
return CSMWorld::Land::createUniqueRecordId(mX->value(), mY->value());
}
void LandCreator::pushCommand(std::unique_ptr<CSMWorld::CreateCommand> command, const std::string& id)
{
if (mCloneMode)
{
CSMWorld::IdTable& lands = dynamic_cast<CSMWorld::IdTable&>(*getData().getTableModel(CSMWorld::UniversalId::Type_Lands));
CSMWorld::IdTable& ltexs = dynamic_cast<CSMWorld::IdTable&>(*getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures));
getUndoStack().beginMacro(("Clone " + id).c_str());
getUndoStack().push(command.release());
CSMWorld::CopyLandTexturesCommand* ltexCopy = new CSMWorld::CopyLandTexturesCommand(lands, ltexs, getClonedId(), getId());
getUndoStack().push(ltexCopy);
getUndoStack().endMacro();
}
else
getUndoStack().push (command.release());
}
void LandCreator::coordChanged(int value)
{
update();
}
}

@ -0,0 +1,47 @@
#ifndef CSV_WORLD_LANDCREATOR_H
#define CSV_WORLD_LANDCREATOR_H
#include "genericcreator.hpp"
class QLabel;
class QSpinBox;
namespace CSVWorld
{
class LandCreator : public GenericCreator
{
Q_OBJECT
QLabel* mXLabel;
QLabel* mYLabel;
QSpinBox* mX;
QSpinBox* mY;
public:
LandCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id);
void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override;
void touch(const std::vector<CSMWorld::UniversalId>& ids) override;
void focus() override;
void reset() override;
std::string getErrors() const override;
protected:
std::string getId() const override;
void pushCommand(std::unique_ptr<CSMWorld::CreateCommand> command,
const std::string& id) override;
private slots:
void coordChanged(int value);
};
}
#endif

@ -0,0 +1,101 @@
#include "landtexturecreator.hpp"
#include <cstdint>
#include <limits>
#include <QLabel>
#include <QLineEdit>
#include <QSpinBox>
#include "../../model/world/commands.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/landtexture.hpp"
namespace CSVWorld
{
LandTextureCreator::LandTextureCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id)
: GenericCreator(data, undoStack, id)
{
// One index is reserved for a default texture
const size_t MaxIndex = std::numeric_limits<uint16_t>::max() - 1;
setManualEditing(false);
QLabel* nameLabel = new QLabel("Name");
insertBeforeButtons(nameLabel, false);
mNameEdit = new QLineEdit(this);
insertBeforeButtons(mNameEdit, true);
QLabel* indexLabel = new QLabel("Index");
insertBeforeButtons(indexLabel, false);
mIndexBox = new QSpinBox(this);
mIndexBox->setMinimum(0);
mIndexBox->setMaximum(MaxIndex);
insertBeforeButtons(mIndexBox, true);
connect(mNameEdit, SIGNAL(textChanged(const QString&)), this, SLOT(nameChanged(const QString&)));
connect(mIndexBox, SIGNAL(valueChanged(int)), this, SLOT(indexChanged(int)));
}
void LandTextureCreator::cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type)
{
GenericCreator::cloneMode(originId, type);
CSMWorld::IdTable& table = dynamic_cast<CSMWorld::IdTable&>(*getData().getTableModel(getCollectionId()));
int column = table.findColumnIndex(CSMWorld::Columns::ColumnId_TextureNickname);
mNameEdit->setText((table.data(table.getModelIndex(originId, column)).toString()));
column = table.findColumnIndex(CSMWorld::Columns::ColumnId_TextureIndex);
mIndexBox->setValue((table.data(table.getModelIndex(originId, column)).toInt()));
}
void LandTextureCreator::focus()
{
mIndexBox->setFocus();
}
void LandTextureCreator::reset()
{
GenericCreator::reset();
mNameEdit->setText("");
mIndexBox->setValue(0);
}
std::string LandTextureCreator::getErrors() const
{
if (getData().getLandTextures().searchId(getId()) >= 0)
{
return "Index is already in use";
}
return "";
}
void LandTextureCreator::configureCreateCommand(CSMWorld::CreateCommand& command) const
{
GenericCreator::configureCreateCommand(command);
CSMWorld::IdTable& table = dynamic_cast<CSMWorld::IdTable&>(*getData().getTableModel(getCollectionId()));
int column = table.findColumnIndex(CSMWorld::Columns::ColumnId_TextureNickname);
command.addValue(column, mName.c_str());
}
std::string LandTextureCreator::getId() const
{
return CSMWorld::LandTexture::createUniqueRecordId(0, mIndexBox->value());
}
void LandTextureCreator::nameChanged(const QString& value)
{
mName = value.toUtf8().constData();
update();
}
void LandTextureCreator::indexChanged(int value)
{
update();
}
}

@ -0,0 +1,49 @@
#ifndef CSV_WORLD_LANDTEXTURECREATOR_H
#define CSV_WORLD_LANDTEXTURECREATOR_H
#include <string>
#include "genericcreator.hpp"
class QLineEdit;
class QSpinBox;
namespace CSVWorld
{
class LandTextureCreator : public GenericCreator
{
Q_OBJECT
public:
LandTextureCreator(CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id);
void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override;
void focus() override;
void reset() override;
std::string getErrors() const override;
protected:
void configureCreateCommand(CSMWorld::CreateCommand& command) const override;
std::string getId() const override;
private slots:
void nameChanged(const QString& val);
void indexChanged(int val);
private:
QLineEdit* mNameEdit;
QSpinBox* mIndexBox;
std::string mName;
};
}
#endif

@ -18,6 +18,8 @@
#include "pathgridcreator.hpp"
#include "previewsubview.hpp"
#include "bodypartcreator.hpp"
#include "landcreator.hpp"
#include "landtexturecreator.hpp"
void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager)
{
@ -81,6 +83,12 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager)
manager.add (CSMWorld::UniversalId::Type_Pathgrids,
new CSVDoc::SubViewFactoryWithCreator<TableSubView, PathgridCreatorFactory>);
manager.add (CSMWorld::UniversalId::Type_Lands,
new CSVDoc::SubViewFactoryWithCreator<TableSubView, CreatorFactory<LandCreator> >);
manager.add (CSMWorld::UniversalId::Type_LandTextures,
new CSVDoc::SubViewFactoryWithCreator<TableSubView, CreatorFactory<LandTextureCreator> >);
manager.add (CSMWorld::UniversalId::Type_Globals,
new CSVDoc::SubViewFactoryWithCreator<TableSubView, CreatorFactory<GlobalCreator> >);
@ -181,6 +189,12 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager)
manager.add (CSMWorld::UniversalId::Type_Pathgrid,
new CSVDoc::SubViewFactoryWithCreator<DialogueSubView, PathgridCreatorFactory> (false));
manager.add (CSMWorld::UniversalId::Type_Land,
new CSVDoc::SubViewFactoryWithCreator<DialogueSubView, CreatorFactory<LandCreator> >(false));
manager.add (CSMWorld::UniversalId::Type_LandTexture,
new CSVDoc::SubViewFactoryWithCreator<DialogueSubView, CreatorFactory<LandTextureCreator> >(false));
manager.add (CSMWorld::UniversalId::Type_DebugProfile,
new CSVDoc::SubViewFactoryWithCreator<DialogueSubView, CreatorFactory<GenericCreator, CSMWorld::Scope_Project | CSMWorld::Scope_Session> > (false));

@ -16,6 +16,7 @@
#include "../../model/world/idtableproxymodel.hpp"
#include "../../model/world/idtablebase.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/landtexturetableproxymodel.hpp"
#include "../../model/world/record.hpp"
#include "../../model/world/columns.hpp"
#include "../../model/world/commanddispatcher.hpp"
@ -60,6 +61,9 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event)
menu.addAction(mCloneAction);
}
if (mTouchAction)
menu.addAction (mTouchAction);
if (mCreateAction)
menu.addAction (mCreateAction);
@ -226,17 +230,22 @@ void CSVWorld::Table::mouseDoubleClickEvent (QMouseEvent *event)
CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
bool createAndDelete, bool sorting, CSMDoc::Document& document)
: DragRecordTable(document), mCreateAction (0),
mCloneAction(0), mRecordStatusDisplay (0), mJumpToAddedRecord(false), mUnselectAfterJump(false)
: DragRecordTable(document), mCreateAction (nullptr), mCloneAction(nullptr), mTouchAction(nullptr),
mRecordStatusDisplay (0), mJumpToAddedRecord(false), mUnselectAfterJump(false)
{
mModel = &dynamic_cast<CSMWorld::IdTableBase&> (*mDocument.getData().getTableModel (id));
bool isInfoTable = id.getType() == CSMWorld::UniversalId::Type_TopicInfos ||
id.getType() == CSMWorld::UniversalId::Type_JournalInfos;
bool isLtexTable = (id.getType() == CSMWorld::UniversalId::Type_LandTextures);
if (isInfoTable)
{
mProxyModel = new CSMWorld::InfoTableProxyModel(id.getType(), this);
}
else if (isLtexTable)
{
mProxyModel = new CSMWorld::LandTextureTableProxyModel (this);
}
else
{
mProxyModel = new CSMWorld::IdTableProxyModel (this);
@ -302,6 +311,15 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
cloneShortcut->associateAction(mCloneAction);
}
if (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_AllowTouch)
{
mTouchAction = new QAction(tr("Touch Record"), this);
connect(mTouchAction, SIGNAL(triggered()), this, SLOT(touchRecord()));
addAction(mTouchAction);
CSMPrefs::Shortcut* touchShortcut = new CSMPrefs::Shortcut("table-touch", this);
touchShortcut->associateAction(mTouchAction);
}
mRevertAction = new QAction (tr ("Revert Record"), this);
connect (mRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeRevert()));
addAction (mRevertAction);
@ -442,6 +460,22 @@ void CSVWorld::Table::cloneRecord()
}
}
void CSVWorld::Table::touchRecord()
{
if (!mEditLock && mModel->getFeatures() & CSMWorld::IdTableBase::Feature_AllowTouch)
{
std::vector<CSMWorld::UniversalId> touchIds;
QModelIndexList selectedRows = selectionModel()->selectedRows();
for (auto it = selectedRows.begin(); it != selectedRows.end(); ++it)
{
touchIds.push_back(getUniversalId(it->row()));
}
emit touchRequest(touchIds);
}
}
void CSVWorld::Table::moveUpRecord()
{
if (mEditLock || (mModel->getFeatures() & CSMWorld::IdTableBase::Feature_Constant))

@ -56,6 +56,7 @@ namespace CSVWorld
QAction *mEditAction;
QAction *mCreateAction;
QAction *mCloneAction;
QAction *mTouchAction;
QAction *mRevertAction;
QAction *mDeleteAction;
QAction *mMoveUpAction;
@ -115,6 +116,8 @@ namespace CSVWorld
void cloneRequest(const CSMWorld::UniversalId&);
void touchRequest(const std::vector<CSMWorld::UniversalId>& ids);
void closeRequest();
void extendedDeleteConfigRequest(const std::vector<std::string> &selectedIds);
@ -129,6 +132,8 @@ namespace CSVWorld
void cloneRecord();
void touchRecord();
void moveUpRecord();
void moveDownRecord();

@ -72,9 +72,9 @@ void CSVWorld::TableBottomBox::extendedConfigRequest(CSVWorld::ExtendedCommandCo
mExtendedConfigurator->setFocus();
}
CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory,
CSMDoc::Document& document,
const CSMWorld::UniversalId& id,
CSVWorld::TableBottomBox::TableBottomBox (const CreatorFactoryBase& creatorFactory,
CSMDoc::Document& document,
const CSMWorld::UniversalId& id,
QWidget *parent)
: QWidget (parent), mShowStatusBar (false), mEditMode(EditMode_None), mHasPosition(false), mRow(0), mColumn(0)
{
@ -249,6 +249,11 @@ void CSVWorld::TableBottomBox::cloneRequest(const std::string& id,
mCreator->focus();
}
void CSVWorld::TableBottomBox::touchRequest(const std::vector<CSMWorld::UniversalId>& ids)
{
mCreator->touch(ids);
}
void CSVWorld::TableBottomBox::extendedDeleteConfigRequest(const std::vector<std::string> &selectedIds)
{
extendedConfigRequest(ExtendedCommandConfigurator::Mode_Delete, selectedIds);

@ -102,6 +102,7 @@ namespace CSVWorld
void createRequest();
void cloneRequest(const std::string& id,
const CSMWorld::UniversalId::Type type);
void touchRequest(const std::vector<CSMWorld::UniversalId>&);
void extendedDeleteConfigRequest(const std::vector<std::string> &selectedIds);
void extendedRevertConfigRequest(const std::vector<std::string> &selectedIds);

@ -69,6 +69,9 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D
connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)),
mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)));
connect (mTable, SIGNAL(touchRequest(const std::vector<CSMWorld::UniversalId>&)),
mBottom, SLOT(touchRequest(const std::vector<CSMWorld::UniversalId>&)));
connect (mTable, SIGNAL(extendedDeleteConfigRequest(const std::vector<std::string> &)),
mBottom, SLOT(extendedDeleteConfigRequest(const std::vector<std::string> &)));
connect (mTable, SIGNAL(extendedRevertConfigRequest(const std::vector<std::string> &)),

@ -1,26 +1,29 @@
#include <RakPeerInterface.h>
#include <BitStream.h>
#include "Player.hpp"
#include "Networking.hpp"
#include "MasterClient.hpp"
#include <RakPeer.h>
#include <MessageIdentifiers.h>
#include <components/openmw-mp/Log.hpp>
#include <components/openmw-mp/NetworkMessages.hpp>
#include <apps/openmw-mp/Script/Script.hpp>
#include <iostream>
#include <components/files/configurationmanager.hpp>
#include <components/settings/settings.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/iostreams/concepts.hpp>
#include <boost/iostreams/stream_buffer.hpp>
#include <boost/filesystem/fstream.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/escape.hpp>
#include <components/settings/settings.hpp>
#include <components/version/version.hpp>
#include <components/openmw-mp/Log.hpp>
#include <components/openmw-mp/NetworkMessages.hpp>
#include <components/openmw-mp/Version.hpp>
#include "Utils.hpp"
#include <BitStream.h>
#include <MessageIdentifiers.h>
#include <RakPeer.h>
#include <RakPeerInterface.h>
#include "Player.hpp"
#include "Networking.hpp"
#include "MasterClient.hpp"
#include "Utils.hpp"
#include <components/version/version.hpp>
#include <components/files/escape.hpp>
#include <apps/openmw-mp/Script/Script.hpp>
#ifdef ENABLE_BREAKPAD
#include <handler/exception_handler.h>
@ -29,41 +32,6 @@
using namespace std;
using namespace mwmp;
void printVersion(string version, Version::Version ver, int protocol)
{
cout << "TES3:MP dedicated server " << version;
cout << " (";
#if defined(_WIN32)
cout << "Windows";
#elif defined(__linux)
cout << "Linux";
#elif defined(__APPLE__)
cout << "OS X";
#else
cout << "Unknown OS";
#endif
cout << " ";
#ifdef __x86_64__
cout << "64-bit";
#elif defined(__i386__) || defined(_M_I86)
cout << "32-bit";
#elif defined(__ARM_ARCH)
cout << "ARMv" << __ARM_ARCH << " ";
#ifdef __aarch64__
cout << "64-bit";
#else
cout << "32-bit";
#endif
#else
cout << "Unknown architecture";
#endif
cout << ")" << endl;
cout << "Protocol version: " << protocol << endl;
cout << "Commit hash: " << ver.mCommitHash.substr(0, 10) << endl;
cout << "------------------------------------------------------------" << endl;
}
#ifdef ENABLE_BREAKPAD
google_breakpad::ExceptionHandler *pHandler = 0;
#if defined(_WIN32)
@ -230,9 +198,7 @@ int main(int argc, char *argv[])
vector<string> plugins (Utils::split(mgr.getString("plugins", "Plugins"), ','));
printVersion(TES3MP_VERSION, version, TES3MP_PROTO_VERSION);
Utils::printVersion("TES3MP dedicated server", TES3MP_VERSION, version.mCommitHash, TES3MP_PROTO_VERSION);
setenv("AMXFILE", moddir.c_str(), 1);
setenv("MOD_DIR", moddir.c_str(), 1); // hack for lua

@ -42,7 +42,7 @@ add_openmw_dir (mwgui
itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview
tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog
recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview
draganddrop timeadvancer jailscreen itemchargeview
draganddrop timeadvancer jailscreen itemchargeview keyboardnavigation
)
add_openmw_dir (mwdialogue
@ -57,7 +57,8 @@ add_openmw_dir (mwscript
)
add_openmw_dir (mwsound
soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output loudness movieaudiofactory
soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output
loudness movieaudiofactory alext efx efx-presets
)
add_openmw_dir (mwworld
@ -131,7 +132,7 @@ add_openmw_dir (mwmp/processors/world BaseObjectProcessor ProcessorConsoleComman
# Main executable
if (NOT ANDROID)
add_executable(tes3mp
openmw_add_executable(tes3mp
${OPENMW_FILES}
${GAME} ${GAME_HEADER}
${APPLE_BUNDLE_RESOURCES}

@ -108,12 +108,14 @@ void OMW::Engine::executeLocalScripts()
}
}
void OMW::Engine::frame(float frametime)
bool OMW::Engine::frame(float frametime)
{
try
{
mStartTick = mViewer->getStartTick();
mEnvironment.setFrameDuration(frametime);
// update input
mEnvironment.getInputManager()->update(frametime, false);
@ -127,7 +129,7 @@ void OMW::Engine::frame(float frametime)
The game cannot be paused in multiplayer, so prevent that from happening even here
*/
//if (!mEnvironment.getInputManager()->isWindowVisible())
// return;
// return false;
/*
End of tes3mp change (major)
*/
@ -240,11 +242,6 @@ void OMW::Engine::frame(float frametime)
// update GUI
mEnvironment.getWindowManager()->onFrame(frametime);
if (mEnvironment.getStateManager()->getState()!=
MWBase::StateManager::State_NoGame)
{
mEnvironment.getWindowManager()->update();
}
unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber();
osg::Stats* stats = mViewer->getViewerStats();
@ -271,8 +268,9 @@ void OMW::Engine::frame(float frametime)
}
catch (const std::exception& e)
{
std::cerr << "Error in framelistener: " << e.what() << std::endl;
std::cerr << "Error in frame: " << e.what() << std::endl;
}
return true;
}
OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
@ -831,29 +829,9 @@ void OMW::Engine::go()
frameTimer.setStartTick();
dt = std::min(dt, 0.2);
bool guiActive = mEnvironment.getWindowManager()->isGuiMode();
/*
Start of tes3mp change (major)
Whether the GUI is active should have no relevance in multiplayer, so the guiActive
boolean is always set to false instead
*/
guiActive = false;
/*
End of tes3mp change (major)
*/
if (!guiActive)
simulationTime += dt;
mViewer->advance(simulationTime);
mEnvironment.setFrameDuration(dt);
frame(dt);
if (!mEnvironment.getInputManager()->isWindowVisible())
if (!frame(dt))
{
OpenThreads::Thread::microSleep(5000);
continue;
@ -866,6 +844,22 @@ void OMW::Engine::go()
mEnvironment.getWorld()->updateWindowManager();
mViewer->renderingTraversals();
bool guiActive = mEnvironment.getWindowManager()->isGuiMode();
/*
Start of tes3mp change (major)
Whether the GUI is active should have no relevance in multiplayer, so the guiActive
boolean is always set to false instead
*/
guiActive = false;
/*
End of tes3mp change (major)
*/
if (!guiActive)
simulationTime += dt;
}
mEnvironment.limitFrameRate(frameTimer.time_s());

@ -118,7 +118,7 @@ namespace OMW
void executeLocalScripts();
void frame (float dt);
bool frame (float dt);
/// Load settings from various files, returns the path to the user settings file
std::string loadSettings (Settings::Manager & settings);

@ -49,9 +49,11 @@ extern int is_debugger_attached(void);
/*
Start of tes3mp addition
Include the header of the logger added for multiplayer
Include additional headers for multiplayer purposes
*/
#include <components/openmw-mp/Log.hpp>
#include <components/openmw-mp/Utils.hpp>
#include <components/openmw-mp/Version.hpp>
/*
End of tes3mp addition
*/
@ -207,7 +209,26 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
cfgMgr.readConfiguration(variables, desc);
Version::Version v = Version::getOpenmwVersion(variables["resources"].as<Files::EscapeHashString>().toStdString());
std::cout << v.describe() << std::endl;
/*
Start of tes3mp addition
Print the multiplayer version first
*/
Utils::printVersion("TES3MP client", TES3MP_VERSION, v.mCommitHash, TES3MP_PROTO_VERSION);
/*
End of tes3mp addition
*/
/*
Start of tes3mp change (minor)
Because there is no need to print the commit hash again, only print OpenMW's version
*/
std::cout << "OpenMW version " << v.mVersion << std::endl;
/*
End of tes3mp change (minor)
*/
engine.setGrabMouse(!variables["no-grab"].as<bool>());
@ -224,6 +245,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
std::string local(variables["data-local"].as<Files::EscapeHashString>().toStdString());
if (!local.empty())
{
if (local.front() == '\"')
local = local.substr(1, local.length() - 2);
dataDirs.push_back(Files::PathContainer::value_type(local));
}

@ -2,6 +2,8 @@
#define GAME_MWBASE_DIALOGUEMANAGER_H
#include <string>
#include <vector>
#include <list>
#include <stdint.h>
@ -34,6 +36,12 @@ namespace MWBase
public:
class ResponseCallback
{
public:
virtual void addResponse(const std::string& title, const std::string& text) = 0;
};
DialogueManager() {}
virtual void clear() = 0;
@ -42,7 +50,7 @@ namespace MWBase
virtual bool isInChoice() const = 0;
virtual void startDialogue (const MWWorld::Ptr& actor) = 0;
virtual bool startDialogue (const MWWorld::Ptr& actor, ResponseCallback* callback) = 0;
virtual void addTopic (const std::string& topic) = 0;
@ -57,20 +65,24 @@ namespace MWBase
End of tes3mp addition
*/
virtual void askQuestion (const std::string& question,int choice) = 0;
virtual void addChoice (const std::string& text,int choice) = 0;
virtual const std::vector<std::pair<std::string, int> >& getChoices() = 0;
virtual bool isGoodbye() = 0;
virtual void goodbye() = 0;
virtual void say(const MWWorld::Ptr &actor, const std::string &topic) = 0;
//calbacks for the GUI
virtual void keywordSelected (const std::string& keyword) = 0;
virtual void keywordSelected (const std::string& keyword, ResponseCallback* callback) = 0;
virtual void goodbyeSelected() = 0;
virtual void questionAnswered (int answer) = 0;
virtual void questionAnswered (int answer, ResponseCallback* callback) = 0;
virtual std::list<std::string> getAvailableTopics() = 0;
virtual bool checkServiceRefused () = 0;
virtual bool checkServiceRefused (ResponseCallback* callback) = 0;
virtual void persuade (int type) = 0;
virtual void persuade (int type, ResponseCallback* callback) = 0;
virtual int getTemporaryDispositionChange () const = 0;
/// @note This change is temporary and gets discarded when dialogue ends.
@ -98,7 +110,7 @@ namespace MWBase
Declare this method here so it can be used from outside of MWDialogue::DialogueManager
*/
virtual void updateTopics() = 0;
virtual void updateActorKnownTopics() = 0;
/*
End of tes3mp addition
*/

@ -150,7 +150,7 @@ namespace MWBase
/// Utility to check if taking this item is illegal and calling commitCrime if so
/// @param container The container the item is in; may be empty for an item in the world
virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container,
int count) = 0;
int count, bool alarm = true) = 0;
/// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so
virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) = 0;
/// Attempt sleeping in a bed. If this is illegal, call commitCrime.
@ -201,6 +201,9 @@ namespace MWBase
virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& objects) = 0;
virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector<MWWorld::Ptr> &objects) = 0;
/// Check if there are actors in selected range
virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius) = 0;
///Returns the list of actors which are siding with the given actor in fights
/**ie AiFollow or AiEscort is active and the target is the actor **/
virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) = 0;
@ -235,6 +238,10 @@ namespace MWBase
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0;
virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const = 0;
/// Check if the target actor was detected by an observer
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0;
virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) = 0;
/// List the owners that the player has stolen this item from (the owner can be an NPC or a faction).
@ -244,7 +251,8 @@ namespace MWBase
/// Has the player stolen this item from the given owner?
virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid) = 0;
virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::ConstPtr& item, MWWorld::Ptr& victim) = 0;
virtual bool isBoundItem(const MWWorld::Ptr& item) = 0;
virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, MWWorld::Ptr& victim) = 0;
/// Turn actor into werewolf or normal form.
virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) = 0;
@ -256,7 +264,6 @@ namespace MWBase
virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0;
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0;
virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr) = 0;
virtual bool isRunning(const MWWorld::Ptr& ptr) = 0;
virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0;

@ -18,54 +18,59 @@ namespace MWSound
class Stream;
struct Sound_Decoder;
typedef std::shared_ptr<Sound_Decoder> DecoderPtr;
/* These must all fit together */
enum class PlayMode {
Normal = 0, /* non-looping, affected by environment */
Loop = 1<<0, /* Sound will continually loop until explicitly stopped */
NoEnv = 1<<1, /* Do not apply environment effects (eg, underwater filters) */
RemoveAtDistance = 1<<2, /* (3D only) If the listener gets further than 2000 units away
* from the sound source, the sound is removed.
* This is weird stuff but apparently how vanilla works for sounds
* played by the PlayLoopSound family of script functions. Perhaps
* we can make this cut off a more subtle fade later, but have to
* be careful to not change the overall volume of areas by too
* much. */
NoPlayerLocal = 1<<3, /* (3D only) Don't play the sound local to the listener even if the
* player is making it. */
LoopNoEnv = Loop | NoEnv,
LoopRemoveAtDistance = Loop | RemoveAtDistance
};
enum class Type {
Sfx = 1<<4, /* Normal SFX sound */
Voice = 1<<5, /* Voice sound */
Foot = 1<<6, /* Footstep sound */
Music = 1<<7, /* Music track */
Movie = 1<<8, /* Movie audio track */
Mask = Sfx | Voice | Foot | Music | Movie
};
// Used for creating a type mask for SoundManager::pauseSounds and resumeSounds
inline int operator~(Type a) { return ~static_cast<int>(a); }
inline int operator&(Type a, Type b) { return static_cast<int>(a) & static_cast<int>(b); }
inline int operator&(int a, Type b) { return a & static_cast<int>(b); }
inline int operator|(Type a, Type b) { return static_cast<int>(a) | static_cast<int>(b); }
}
namespace MWBase
{
typedef std::shared_ptr<MWSound::Sound> SoundPtr;
typedef std::shared_ptr<MWSound::Stream> SoundStreamPtr;
using Sound = MWSound::Sound;
using SoundStream = MWSound::Stream;
/// \brief Interface for sound manager (implemented in MWSound)
class SoundManager
{
public:
/* These must all fit together */
enum PlayMode {
Play_Normal = 0, /* non-looping, affected by environment */
Play_Loop = 1<<0, /* Sound will continually loop until explicitly stopped */
Play_NoEnv = 1<<1, /* Do not apply environment effects (eg, underwater filters) */
Play_RemoveAtDistance = 1<<2, /* (3D only) If the listener gets further than 2000 units away
from the sound source, the sound is removed.
This is weird stuff but apparently how vanilla works for sounds
played by the PlayLoopSound family of script functions. Perhaps we
can make this cut off a more subtle fade later, but have to
be careful to not change the overall volume of areas by too much. */
Play_NoPlayerLocal = 1<<3, /* (3D only) Don't play the sound local to the listener even if the
player is making it. */
Play_LoopNoEnv = Play_Loop | Play_NoEnv,
Play_LoopRemoveAtDistance = Play_Loop | Play_RemoveAtDistance
};
enum PlayType {
Play_TypeSfx = 1<<4, /* Normal SFX sound */
Play_TypeVoice = 1<<5, /* Voice sound */
Play_TypeFoot = 1<<6, /* Footstep sound */
Play_TypeMusic = 1<<7, /* Music track */
Play_TypeMovie = 1<<8, /* Movie audio track */
Play_TypeMask = Play_TypeSfx|Play_TypeVoice|Play_TypeFoot|Play_TypeMusic|Play_TypeMovie
};
private:
SoundManager (const SoundManager&);
///< not implemented
SoundManager& operator= (const SoundManager&);
///< not implemented
public:
protected:
using PlayMode = MWSound::PlayMode;
using Type = MWSound::Type;
public:
SoundManager() {}
virtual ~SoundManager() {}
virtual void processChangedSettings(const std::set< std::pair<std::string, std::string> >& settings) = 0;
@ -77,9 +82,6 @@ namespace MWBase
///< Play a soundifle
/// \param filename name of a sound file in "Music/" in the data directory.
virtual void startRandomTitle() = 0;
///< Starts a random track from the current playlist
virtual bool isMusicPlaying() = 0;
///< Returns true if music is playing
@ -106,34 +108,36 @@ namespace MWBase
/// and get an average loudness value (scale [0,1]) at the current time position.
/// If the actor is not saying anything, returns 0.
virtual SoundStreamPtr playTrack(const MWSound::DecoderPtr& decoder, PlayType type) = 0;
///< Play a 2D audio track, using a custom decoder
virtual SoundStream *playTrack(const MWSound::DecoderPtr& decoder, Type type) = 0;
///< Play a 2D audio track, using a custom decoder. The caller is expected to call
/// stopTrack with the returned handle when done.
virtual void stopTrack(SoundStreamPtr stream) = 0;
virtual void stopTrack(SoundStream *stream) = 0;
///< Stop the given audio track from playing
virtual double getTrackTimeDelay(SoundStreamPtr stream) = 0;
virtual double getTrackTimeDelay(SoundStream *stream) = 0;
///< Retives the time delay, in seconds, of the audio track (must be a sound
/// returned by \ref playTrack). Only intended to be called by the track
/// decoder's read method.
virtual SoundPtr playSound(const std::string& soundId, float volume, float pitch,
PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal,
float offset=0) = 0;
virtual Sound *playSound(const std::string& soundId, float volume, float pitch,
Type type=Type::Sfx, PlayMode mode=PlayMode::Normal,
float offset=0) = 0;
///< Play a sound, independently of 3D-position
///< @param offset Number of seconds into the sound to start playback.
virtual MWBase::SoundPtr playSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId,
float volume, float pitch, PlayType type=Play_TypeSfx,
PlayMode mode=Play_Normal, float offset=0) = 0;
virtual Sound *playSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId,
float volume, float pitch, Type type=Type::Sfx,
PlayMode mode=PlayMode::Normal, float offset=0) = 0;
///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless Play_NoTrack is specified.
///< @param offset Number of seconds into the sound to start playback.
virtual MWBase::SoundPtr playSound3D(const osg::Vec3f& initialPos, const std::string& soundId,
float volume, float pitch, PlayType type=Play_TypeSfx, PlayMode mode=Play_Normal, float offset=0) = 0;
virtual Sound *playSound3D(const osg::Vec3f& initialPos, const std::string& soundId,
float volume, float pitch, Type type=Type::Sfx,
PlayMode mode=PlayMode::Normal, float offset=0) = 0;
///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated using Sound::setPosition.
virtual void stopSound(SoundPtr sound) = 0;
virtual void stopSound(Sound *sound) = 0;
///< Stop the given sound from playing
virtual void stopSound3D(const MWWorld::ConstPtr &reference, const std::string& soundId) = 0;
@ -158,10 +162,10 @@ namespace MWBase
///< Is the given sound currently playing on the given object?
/// If you want to check if sound played with playSound is playing, use empty Ptr
virtual void pauseSounds(int types=Play_TypeMask) = 0;
virtual void pauseSounds(int types=static_cast<int>(Type::Mask)) = 0;
///< Pauses all currently playing sounds, including music.
virtual void resumeSounds(int types=Play_TypeMask) = 0;
virtual void resumeSounds(int types=static_cast<int>(Type::Mask)) = 0;
///< Resumes all previously paused sounds.
virtual void update(float duration) = 0;

@ -7,6 +7,8 @@
#include <map>
#include <set>
#include <MyGUI_KeyCode.h>
#include "../mwgui/mode.hpp"
namespace Loading
@ -100,23 +102,17 @@ namespace MWBase
virtual ~WindowManager() {}
/**
* Should be called each frame to update windows/gui elements.
* This could mean updating sizes of gui elements or opening
* new dialogs.
*/
virtual void update() = 0;
/// @note This method will block until the video finishes playing
/// (and will continually update the window while doing so)
virtual void playVideo(const std::string& name, bool allowSkipping) = 0;
virtual void setNewGame(bool newgame) = 0;
virtual void pushGuiMode (MWGui::GuiMode mode, const MWWorld::Ptr& arg) = 0;
virtual void pushGuiMode (MWGui::GuiMode mode) = 0;
virtual void popGuiMode() = 0;
virtual void popGuiMode(bool noSound=false) = 0;
virtual void removeGuiMode (MWGui::GuiMode mode) = 0;
virtual void removeGuiMode (MWGui::GuiMode mode, bool noSound=false) = 0;
///< can be anywhere in the stack
virtual void goToJail(int days) = 0;
@ -144,7 +140,6 @@ namespace MWBase
virtual bool isAllowed (MWGui::GuiWindow wnd) const = 0;
/// \todo investigate, if we really need to expose every single lousy UI element to the outside world
virtual MWGui::DialogueWindow* getDialogueWindow() = 0;
virtual MWGui::InventoryWindow* getInventoryWindow() = 0;
virtual MWGui::CountDialog* getCountDialog() = 0;
virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0;
@ -195,6 +190,7 @@ namespace MWBase
virtual void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) = 0;
virtual void setCursorVisible(bool visible) = 0;
virtual void setCursorActive(bool active) = 0;
virtual void getMousePosition(int &x, int &y) = 0;
virtual void getMousePosition(float &x, float &y) = 0;
virtual void setDragDrop(bool dragDrop) = 0;
@ -259,7 +255,7 @@ namespace MWBase
virtual void showCrosshair(bool show) = 0;
virtual bool getSubtitlesEnabled() = 0;
virtual bool toggleGui() = 0;
virtual bool toggleHud() = 0;
virtual void disallowMouse() = 0;
virtual void allowMouse() = 0;
@ -333,21 +329,6 @@ namespace MWBase
virtual bool getPlayerSleeping() = 0;
virtual void wakeUpPlayer() = 0;
virtual void showCompanionWindow(MWWorld::Ptr actor) = 0;
virtual void startSpellMaking(MWWorld::Ptr actor) = 0;
virtual void startEnchanting(MWWorld::Ptr actor) = 0;
virtual void startRecharge(MWWorld::Ptr soulgem) = 0;
virtual void startSelfEnchanting(MWWorld::Ptr soulgem) = 0;
virtual void startTraining(MWWorld::Ptr actor) = 0;
virtual void startRepair(MWWorld::Ptr actor) = 0;
virtual void startRepairItem(MWWorld::Ptr item) = 0;
virtual void startTravel(const MWWorld::Ptr& actor) = 0;
virtual void startSpellBuying(const MWWorld::Ptr& actor) = 0;
virtual void startTrade(const MWWorld::Ptr& actor) = 0;
virtual void openContainer(const MWWorld::Ptr& container, bool loot) = 0;
virtual void showBook(const MWWorld::Ptr& item, bool showTakeButton) = 0;
virtual void showScroll(const MWWorld::Ptr& item, bool showTakeButton) = 0;
virtual void showSoulgemDialog (MWWorld::Ptr item) = 0;
virtual void changePointer (const std::string& name) = 0;
@ -389,11 +370,11 @@ namespace MWBase
virtual void pinWindow (MWGui::GuiWindow window) = 0;
/// Fade the screen in, over \a time seconds
virtual void fadeScreenIn(const float time, bool clearQueue=true) = 0;
virtual void fadeScreenIn(const float time, bool clearQueue=true, float delay=0.f) = 0;
/// Fade the screen out to black, over \a time seconds
virtual void fadeScreenOut(const float time, bool clearQueue=true) = 0;
virtual void fadeScreenOut(const float time, bool clearQueue=true, float delay=0.f) = 0;
/// Fade the screen to a specified percentage of black, over \a time seconds
virtual void fadeScreenTo(const int percent, const float time, bool clearQueue=true) = 0;
virtual void fadeScreenTo(const int percent, const float time, bool clearQueue=true, float delay=0.f) = 0;
/// Darken the screen to a specified percentage
virtual void setBlindness(const int percent) = 0;
@ -411,7 +392,7 @@ namespace MWBase
// In WindowManager for now since there isn't a VFS singleton
virtual std::string correctIconPath(const std::string& path) = 0;
virtual std::string correctBookartPath(const std::string& path, int width, int height) = 0;
virtual std::string correctBookartPath(const std::string& path, int width, int height, bool* exists = nullptr) = 0;
virtual std::string correctTexturePath(const std::string& path) = 0;
virtual bool textureExists(const std::string& path) = 0;
@ -419,6 +400,8 @@ namespace MWBase
virtual void writeFog(MWWorld::CellStore* cell) = 0;
virtual const MWGui::TextColours& getTextColours() = 0;
virtual bool injectKeyPress(MyGUI::KeyCode key, unsigned int text) = 0;
};
}

@ -522,8 +522,7 @@ namespace MWBase
virtual void castSpell (const MWWorld::Ptr& actor) = 0;
virtual void launchMagicBolt (const std::string& spellId, bool stack, const ESM::EffectList& effects,
const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) = 0;
virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0;
virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile,
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) = 0;

@ -181,9 +181,7 @@ namespace MWClass
if(isTrapped)
{
ptr.getCellRef().setTrap("");
MWBase::Environment::get().getSoundManager()->playSound3D(ptr,
"Disarm Trap", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx,
MWBase::SoundManager::Play_Normal);
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Disarm Trap", 1.0f, 1.0f);
isTrapped = false;
/*

@ -567,11 +567,11 @@ namespace MWClass
// by default user can loot friendly actors during death animation
if (canLoot && !stats.getAiSequence().isInCombat())
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr));
// otherwise wait until death animation
if(stats.isDeathAnimationFinished())
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr));
// death animation is not finished, do nothing
return std::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction(""));
@ -580,6 +580,9 @@ namespace MWClass
if(stats.getAiSequence().isInCombat())
return std::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction(""));
if(stats.getKnockedDown())
return std::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction(""));
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
}

@ -175,9 +175,7 @@ namespace MWClass
if(isTrapped)
{
ptr.getCellRef().setTrap("");
MWBase::Environment::get().getSoundManager()->playSound3D(ptr,
"Disarm Trap", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx,
MWBase::SoundManager::Play_Normal);
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Disarm Trap", 1.0f, 1.0f);
isTrapped = false;
/*

@ -49,8 +49,8 @@ namespace MWClass
if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault))
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0,
MWBase::SoundManager::Play_TypeSfx,
MWBase::SoundManager::Play_Loop);
MWSound::Type::Sfx,
MWSound::PlayMode::Loop);
}
bool Light::useAnim() const

@ -1008,11 +1008,11 @@ namespace MWClass
// by default user can loot friendly actors during death animation
if (canLoot && !stats.getAiSequence().isInCombat())
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr));
// otherwise wait until death animation
if(stats.isDeathAnimationFinished())
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr));
// death animation is not finished, do nothing
return std::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction(""));
@ -1265,6 +1265,15 @@ namespace MWClass
else
{
ratings[i] = it->getClass().getEffectiveArmorRating(*it, ptr);
// Take in account armor condition
const bool hasHealth = it->getClass().hasItemHealth(*it);
if (hasHealth)
{
int armorHealth = it->getClass().getItemHealth(*it);
int armorMaxHealth = it->getClass().getItemMaxHealth(*it);
ratings[i] *= (float(armorHealth) / armorMaxHealth);
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save