From ae5d25ac58ad7aeeea5fc1b02ea170904cd758a1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 13 Feb 2014 14:23:27 +0100 Subject: [PATCH 001/114] FindSDL.cmake is unused --- cmake/FindSDL.cmake | 177 -------------------------------------------- 1 file changed, 177 deletions(-) delete mode 100644 cmake/FindSDL.cmake diff --git a/cmake/FindSDL.cmake b/cmake/FindSDL.cmake deleted file mode 100644 index 0dc02f5b6..000000000 --- a/cmake/FindSDL.cmake +++ /dev/null @@ -1,177 +0,0 @@ -# Locate SDL library -# This module defines -# SDL_LIBRARY, the name of the library to link against -# SDL_FOUND, if false, do not try to link to SDL -# SDL_INCLUDE_DIR, where to find SDL.h -# -# This module responds to the the flag: -# SDL_BUILDING_LIBRARY -# If this is defined, then no SDL_main will be linked in because -# only applications need main(). -# Otherwise, it is assumed you are building an application and this -# module will attempt to locate and set the the proper link flags -# as part of the returned SDL_LIBRARY variable. -# -# Don't forget to include SDLmain.h and SDLmain.m your project for the -# OS X framework based version. (Other versions link to -lSDLmain which -# this module will try to find on your behalf.) Also for OS X, this -# module will automatically add the -framework Cocoa on your behalf. -# -# -# Additional Note: If you see an empty SDL_LIBRARY_TEMP in your configuration -# and no SDL_LIBRARY, it means CMake did not find your SDL library -# (SDL.dll, libsdl.so, SDL.framework, etc). -# Set SDL_LIBRARY_TEMP to point to your SDL library, and configure again. -# Similarly, if you see an empty SDLMAIN_LIBRARY, you should set this value -# as appropriate. These values are used to generate the final SDL_LIBRARY -# variable, but when these values are unset, SDL_LIBRARY does not get created. -# -# -# $SDLDIR is an environment variable that would -# correspond to the ./configure --prefix=$SDLDIR -# used in building SDL. -# l.e.galup 9-20-02 -# -# Modified by Eric Wing. -# Added code to assist with automated building by using environmental variables -# and providing a more controlled/consistent search behavior. -# Added new modifications to recognize OS X frameworks and -# additional Unix paths (FreeBSD, etc). -# Also corrected the header search path to follow "proper" SDL guidelines. -# Added a search for SDLmain which is needed by some platforms. -# Added a search for threads which is needed by some platforms. -# Added needed compile switches for MinGW. -# -# On OSX, this will prefer the Framework version (if found) over others. -# People will have to manually change the cache values of -# SDL_LIBRARY to override this selection or set the CMake environment -# CMAKE_INCLUDE_PATH to modify the search paths. -# -# Note that the header path has changed from SDL/SDL.h to just SDL.h -# This needed to change because "proper" SDL convention -# is #include "SDL.h", not . This is done for portability -# reasons because not all systems place things in SDL/ (see FreeBSD). - -#============================================================================= -# Copyright 2003-2009 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -FIND_PATH(SDL_INCLUDE_DIR SDL.h - HINTS - $ENV{SDLDIR} - PATH_SUFFIXES include/SDL include - PATHS - ~/Library/Frameworks - /Library/Frameworks - /usr/local/include/SDL12 - /usr/local/include/SDL11 # FreeBSD ports - /usr/include/SDL12 - /usr/include/SDL11 - /sw # Fink - /opt/local # DarwinPorts - /opt/csw # Blastwave - /opt -) -#MESSAGE("SDL_INCLUDE_DIR is ${SDL_INCLUDE_DIR}") - -# SDL-1.1 is the name used by FreeBSD ports... -# don't confuse it for the version number. -FIND_LIBRARY(SDL_LIBRARY_TEMP - NAMES SDL SDL-1.1 - HINTS - $ENV{SDLDIR} - PATH_SUFFIXES lib64 lib - PATHS - /sw - /opt/local - /opt/csw - /opt -) - -#MESSAGE("SDL_LIBRARY_TEMP is ${SDL_LIBRARY_TEMP}") - -IF(NOT SDL_BUILDING_LIBRARY) - IF(NOT ${SDL_INCLUDE_DIR} MATCHES ".framework") - # Non-OS X framework versions expect you to also dynamically link to - # SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms - # seem to provide SDLmain for compatibility even though they don't - # necessarily need it. - FIND_LIBRARY(SDLMAIN_LIBRARY - NAMES SDLmain SDLmain-1.1 - HINTS - $ENV{SDLDIR} - PATH_SUFFIXES lib64 lib - PATHS - /sw - /opt/local - /opt/csw - /opt - ) - ENDIF(NOT ${SDL_INCLUDE_DIR} MATCHES ".framework") -ENDIF(NOT SDL_BUILDING_LIBRARY) - -# SDL may require threads on your system. -# The Apple build may not need an explicit flag because one of the -# frameworks may already provide it. -# But for non-OSX systems, I will use the CMake Threads package. -IF(NOT APPLE) - FIND_PACKAGE(Threads) -ENDIF(NOT APPLE) - -# MinGW needs an additional library, mwindows -# It's total link flags should look like -lmingw32 -lSDLmain -lSDL -lmwindows -# (Actually on second look, I think it only needs one of the m* libraries.) -IF(MINGW) - SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") -ENDIF(MINGW) - -SET(SDL_FOUND "NO") -IF(SDL_LIBRARY_TEMP) - # For SDLmain - IF(NOT SDL_BUILDING_LIBRARY) - IF(SDLMAIN_LIBRARY) - SET(SDL_LIBRARY_TEMP ${SDLMAIN_LIBRARY} ${SDL_LIBRARY_TEMP}) - ENDIF(SDLMAIN_LIBRARY) - ENDIF(NOT SDL_BUILDING_LIBRARY) - - # For OS X, SDL uses Cocoa as a backend so it must link to Cocoa. - # CMake doesn't display the -framework Cocoa string in the UI even - # though it actually is there if I modify a pre-used variable. - # I think it has something to do with the CACHE STRING. - # So I use a temporary variable until the end so I can set the - # "real" variable in one-shot. - IF(APPLE) - SET(SDL_LIBRARY_TEMP ${SDL_LIBRARY_TEMP} "-framework Cocoa") - ENDIF(APPLE) - - # For threads, as mentioned Apple doesn't need this. - # In fact, there seems to be a problem if I used the Threads package - # and try using this line, so I'm just skipping it entirely for OS X. - IF(NOT APPLE) - SET(SDL_LIBRARY_TEMP ${SDL_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) - ENDIF(NOT APPLE) - - # For MinGW library - IF(MINGW) - SET(SDL_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL_LIBRARY_TEMP}) - ENDIF(MINGW) - - # Set the final string here so the GUI reflects the final state. - SET(SDL_LIBRARY ${SDL_LIBRARY_TEMP} CACHE STRING "Where the SDL Library can be found") - # Set the temp variable to INTERNAL so it is not seen in the CMake GUI - SET(SDL_LIBRARY_TEMP "${SDL_LIBRARY_TEMP}" CACHE INTERNAL "") - - SET(SDL_FOUND "YES") -ENDIF(SDL_LIBRARY_TEMP) - -#MESSAGE("SDL_LIBRARY is ${SDL_LIBRARY}") - From 90f6cda4cc4781c014352f79230a97a35a5d4a4f Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 13 Feb 2014 14:33:30 +0100 Subject: [PATCH 002/114] Moved includes to appropriate place --- apps/openmw/mwrender/terrainstorage.hpp | 3 +++ components/terrain/storage.hpp | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 5c2035952..f2c4ecae3 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -1,6 +1,9 @@ #ifndef MWRENDER_TERRAINSTORAGE_H #define MWRENDER_TERRAINSTORAGE_H +#include +#include + #include namespace MWRender diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index 021e01c7e..9864e9825 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -1,9 +1,6 @@ #ifndef COMPONENTS_TERRAIN_STORAGE_H #define COMPONENTS_TERRAIN_STORAGE_H -#include -#include - #include #include From 439018e7067015975fcca25772838d65730521d5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 13 Feb 2014 15:08:40 +0100 Subject: [PATCH 003/114] Get rid of useless return values --- apps/openmw/mwinput/inputmanagerimp.cpp | 21 ++++++------------- apps/openmw/mwinput/inputmanagerimp.hpp | 10 ++++----- extern/oics/ICSInputControlSystem.h | 18 ++++++++-------- .../oics/ICSInputControlSystem_joystick.cpp | 19 +++++------------ .../oics/ICSInputControlSystem_keyboard.cpp | 10 +++------ extern/oics/ICSInputControlSystem_mouse.cpp | 12 +++-------- extern/sdl4ogre/events.h | 18 ++++++++-------- 7 files changed, 40 insertions(+), 68 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 66d93469c..8bc6facea 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -452,7 +452,7 @@ namespace MWInput mInputBinder->adjustMouseRegion(width, height); } - bool InputManager::keyPressed( const SDL_KeyboardEvent &arg ) + void InputManager::keyPressed( const SDL_KeyboardEvent &arg ) { // Cut, copy & paste MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getKeyFocusWidget(); @@ -498,7 +498,6 @@ namespace MWInput if (kc != OIS::KC_UNASSIGNED) MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Enum(kc), 0); - return true; } void InputManager::textInput(const SDL_TextInputEvent &arg) @@ -509,23 +508,21 @@ namespace MWInput MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::None, *it); } - bool InputManager::keyReleased(const SDL_KeyboardEvent &arg ) + void InputManager::keyReleased(const SDL_KeyboardEvent &arg ) { mInputBinder->keyReleased (arg); OIS::KeyCode kc = mInputManager->sdl2OISKeyCode(arg.keysym.sym); MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::Enum(kc)); - - return true; } - bool InputManager::mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ) + void InputManager::mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ) { mInputBinder->mousePressed (arg, id); if (id != SDL_BUTTON_LEFT && id != SDL_BUTTON_RIGHT) - return true; // MyGUI has no use for these events + return; // MyGUI has no use for these events MyGUI::InputManager::getInstance().injectMousePress(mMouseX, mMouseY, sdlButtonToMyGUI(id)); if (MyGUI::InputManager::getInstance ().getMouseFocusWidget () != 0) @@ -536,20 +533,16 @@ namespace MWInput MWBase::Environment::get().getSoundManager ()->playSound ("Menu Click", 1.f, 1.f); } } - - return true; } - bool InputManager::mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ) + void InputManager::mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ) { mInputBinder->mouseReleased (arg, id); MyGUI::InputManager::getInstance().injectMouseRelease(mMouseX, mMouseY, sdlButtonToMyGUI(id)); - - return true; } - bool InputManager::mouseMoved(const SFO::MouseMotionEvent &arg ) + void InputManager::mouseMoved(const SFO::MouseMotionEvent &arg ) { mInputBinder->mouseMoved (arg); @@ -597,8 +590,6 @@ namespace MWInput MWBase::Environment::get().getWorld()->setCameraDistance(arg.zrel, true, true); } } - - return true; } void InputManager::windowFocusChange(bool have_focus) diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index d41b4c3f3..bd3f4954b 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -86,13 +86,13 @@ namespace MWInput virtual void resetToDefaultBindings(); public: - virtual bool keyPressed(const SDL_KeyboardEvent &arg ); - virtual bool keyReleased( const SDL_KeyboardEvent &arg ); + virtual void keyPressed(const SDL_KeyboardEvent &arg ); + virtual void keyReleased( const SDL_KeyboardEvent &arg ); virtual void textInput (const SDL_TextInputEvent &arg); - virtual bool mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ); - virtual bool mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ); - virtual bool mouseMoved( const SFO::MouseMotionEvent &arg ); + virtual void mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ); + virtual void mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ); + virtual void mouseMoved( const SFO::MouseMotionEvent &arg ); virtual void windowVisibilityChange( bool visible ); virtual void windowFocusChange( bool have_focus ); diff --git a/extern/oics/ICSInputControlSystem.h b/extern/oics/ICSInputControlSystem.h index 907cba5fc..a83ae539e 100644 --- a/extern/oics/ICSInputControlSystem.h +++ b/extern/oics/ICSInputControlSystem.h @@ -102,19 +102,19 @@ namespace ICS JoystickIDList& getJoystickIdList(){ return mJoystickIDList; }; // MouseListener - bool mouseMoved(const SFO::MouseMotionEvent &evt); - bool mousePressed(const SDL_MouseButtonEvent &evt, Uint8); - bool mouseReleased(const SDL_MouseButtonEvent &evt, Uint8); + void mouseMoved(const SFO::MouseMotionEvent &evt); + void mousePressed(const SDL_MouseButtonEvent &evt, Uint8); + void mouseReleased(const SDL_MouseButtonEvent &evt, Uint8); // KeyListener - bool keyPressed(const SDL_KeyboardEvent &evt); - bool keyReleased(const SDL_KeyboardEvent &evt); + void keyPressed(const SDL_KeyboardEvent &evt); + void keyReleased(const SDL_KeyboardEvent &evt); // JoyStickListener - bool buttonPressed(const SDL_JoyButtonEvent &evt, int button); - bool buttonReleased(const SDL_JoyButtonEvent &evt, int button); - bool axisMoved(const SDL_JoyAxisEvent &evt, int axis); - bool povMoved(const SDL_JoyHatEvent &evt, int index); + void buttonPressed(const SDL_JoyButtonEvent &evt, int button); + void buttonReleased(const SDL_JoyButtonEvent &evt, int button); + void axisMoved(const SDL_JoyAxisEvent &evt, int axis); + void povMoved(const SDL_JoyHatEvent &evt, int index); //TODO: does this have an SDL equivalent? //bool sliderMoved(const OIS::JoyStickEvent &evt, int index); diff --git a/extern/oics/ICSInputControlSystem_joystick.cpp b/extern/oics/ICSInputControlSystem_joystick.cpp index 8e501d501..21adc9f74 100644 --- a/extern/oics/ICSInputControlSystem_joystick.cpp +++ b/extern/oics/ICSInputControlSystem_joystick.cpp @@ -318,7 +318,7 @@ namespace ICS } // joyStick listeners - bool InputControlSystem::buttonPressed(const SDL_JoyButtonEvent &evt, int button) + void InputControlSystem::buttonPressed(const SDL_JoyButtonEvent &evt, int button) { if(mActive) { @@ -354,11 +354,9 @@ namespace ICS mDetectingBindingControl, evt.which, button, mDetectingBindingDirection); } } - - return true; } - bool InputControlSystem::buttonReleased(const SDL_JoyButtonEvent &evt, int button) + void InputControlSystem::buttonReleased(const SDL_JoyButtonEvent &evt, int button) { if(mActive) { @@ -371,10 +369,9 @@ namespace ICS } } } - return true; } - bool InputControlSystem::axisMoved(const SDL_JoyAxisEvent &evt, int axis) + void InputControlSystem::axisMoved(const SDL_JoyAxisEvent &evt, int axis) { if(mActive) { @@ -417,12 +414,10 @@ namespace ICS } } } - - return true; } //Here be dragons, apparently - bool InputControlSystem::povMoved(const SDL_JoyHatEvent &evt, int index) + void InputControlSystem::povMoved(const SDL_JoyHatEvent &evt, int index) { if(mActive) { @@ -542,13 +537,11 @@ namespace ICS } } } - - return true; } //TODO: does this have an SDL equivalent? /* - bool InputControlSystem::sliderMoved(const OIS::JoyStickEvent &evt, int index) + void InputControlSystem::sliderMoved(const OIS::JoyStickEvent &evt, int index) { if(mActive) { @@ -590,8 +583,6 @@ namespace ICS } } } - - return true; } */ diff --git a/extern/oics/ICSInputControlSystem_keyboard.cpp b/extern/oics/ICSInputControlSystem_keyboard.cpp index 01d68f784..0a9a34d63 100644 --- a/extern/oics/ICSInputControlSystem_keyboard.cpp +++ b/extern/oics/ICSInputControlSystem_keyboard.cpp @@ -85,7 +85,7 @@ namespace ICS return SDLK_UNKNOWN; } - bool InputControlSystem::keyPressed(const SDL_KeyboardEvent &evt) + void InputControlSystem::keyPressed(const SDL_KeyboardEvent &evt) { if(mActive) { @@ -118,11 +118,9 @@ namespace ICS mDetectingBindingControl, evt.keysym.sym, mDetectingBindingDirection); } } - - return true; - } + } - bool InputControlSystem::keyReleased(const SDL_KeyboardEvent &evt) + void InputControlSystem::keyReleased(const SDL_KeyboardEvent &evt) { if(mActive) { @@ -132,8 +130,6 @@ namespace ICS it->second.control->setChangingDirection(Control::STOP); } } - - return true; } void DetectingBindingListener::keyBindingDetected(InputControlSystem* ICS, Control* control diff --git a/extern/oics/ICSInputControlSystem_mouse.cpp b/extern/oics/ICSInputControlSystem_mouse.cpp index 52eb894ed..be18ebbc0 100644 --- a/extern/oics/ICSInputControlSystem_mouse.cpp +++ b/extern/oics/ICSInputControlSystem_mouse.cpp @@ -219,7 +219,7 @@ namespace ICS } // mouse Listeners - bool InputControlSystem::mouseMoved(const SFO::MouseMotionEvent& evt) + void InputControlSystem::mouseMoved(const SFO::MouseMotionEvent& evt) { if(mActive) { @@ -304,11 +304,9 @@ namespace ICS } } } - - return true; } - bool InputControlSystem::mousePressed(const SDL_MouseButtonEvent &evt, Uint8 btn) + void InputControlSystem::mousePressed(const SDL_MouseButtonEvent &evt, Uint8 btn) { if(mActive) { @@ -341,11 +339,9 @@ namespace ICS mDetectingBindingControl, btn, mDetectingBindingDirection); } } - - return true; } - bool InputControlSystem::mouseReleased(const SDL_MouseButtonEvent &evt, Uint8 btn) + void InputControlSystem::mouseReleased(const SDL_MouseButtonEvent &evt, Uint8 btn) { if(mActive) { @@ -355,8 +351,6 @@ namespace ICS it->second.control->setChangingDirection(Control::STOP); } } - - return true; } // mouse auto bindings diff --git a/extern/sdl4ogre/events.h b/extern/sdl4ogre/events.h index 48adb4545..0fb4d6f06 100644 --- a/extern/sdl4ogre/events.h +++ b/extern/sdl4ogre/events.h @@ -26,9 +26,9 @@ class MouseListener { public: virtual ~MouseListener() {} - virtual bool mouseMoved( const MouseMotionEvent &arg ) = 0; - virtual bool mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ) = 0; - virtual bool mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ) = 0; + virtual void mouseMoved( const MouseMotionEvent &arg ) = 0; + virtual void mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ) = 0; + virtual void mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ) = 0; }; class KeyListener @@ -36,8 +36,8 @@ class KeyListener public: virtual ~KeyListener() {} virtual void textInput (const SDL_TextInputEvent& arg) {} - virtual bool keyPressed(const SDL_KeyboardEvent &arg) = 0; - virtual bool keyReleased(const SDL_KeyboardEvent &arg) = 0; + virtual void keyPressed(const SDL_KeyboardEvent &arg) = 0; + virtual void keyReleased(const SDL_KeyboardEvent &arg) = 0; }; class JoyListener @@ -45,18 +45,18 @@ class JoyListener public: virtual ~JoyListener() {} /** @remarks Joystick button down event */ - virtual bool buttonPressed( const SDL_JoyButtonEvent &evt, int button ) = 0; + virtual void buttonPressed( const SDL_JoyButtonEvent &evt, int button ) = 0; /** @remarks Joystick button up event */ - virtual bool buttonReleased( const SDL_JoyButtonEvent &evt, int button ) = 0; + virtual void buttonReleased( const SDL_JoyButtonEvent &evt, int button ) = 0; /** @remarks Joystick axis moved event */ - virtual bool axisMoved( const SDL_JoyAxisEvent &arg, int axis ) = 0; + virtual void axisMoved( const SDL_JoyAxisEvent &arg, int axis ) = 0; //-- Not so common control events, so are not required --// //! Joystick Event, and povID - virtual bool povMoved( const SDL_JoyHatEvent &arg, int index) {return true;} + virtual void povMoved( const SDL_JoyHatEvent &arg, int index) {} }; class WindowListener From bc376e6649b9d0f6e9f1da01b3410c2b5b9127db Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 13 Feb 2014 17:41:24 +0100 Subject: [PATCH 004/114] Closes #888: Treat "Bip 01" as animation root if existing --- apps/openmw/mwrender/animation.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 44bba90d0..b3aa0cd85 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -285,6 +285,17 @@ void Animation::addAnimSource(const std::string &model) } } + if (grp == 0 && dstval->getNode()->getName() == "Bip01") + { + mNonAccumRoot = dstval->getNode(); + mAccumRoot = mNonAccumRoot->getParent(); + if(!mAccumRoot) + { + std::cerr<< "Non-Accum root for "< Date: Sun, 16 Feb 2014 13:07:32 +0100 Subject: [PATCH 005/114] Terrain: change world bounds from AABB to 4 floats --- apps/openmw/mwrender/terrainstorage.cpp | 6 ++--- apps/openmw/mwrender/terrainstorage.hpp | 2 +- components/terrain/storage.hpp | 2 +- components/terrain/world.cpp | 31 ++++++++++++++----------- components/terrain/world.hpp | 2 +- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 197572db9..750441f6a 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -16,9 +16,9 @@ namespace MWRender { - Ogre::AxisAlignedBox TerrainStorage::getBounds() + void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) { - int minX = 0, minY = 0, maxX = 0, maxY = 0; + minX = 0, minY = 0, maxX = 0, maxY = 0; const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -39,8 +39,6 @@ namespace MWRender // since grid coords are at cell origin, we need to add 1 cell maxX += 1; maxY += 1; - - return Ogre::AxisAlignedBox(minX, minY, 0, maxX, maxY, 0); } ESM::Land* TerrainStorage::getLand(int cellX, int cellY) diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index f2c4ecae3..2ef014aaf 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -17,7 +17,7 @@ namespace MWRender public: /// Get bounds of the whole terrain in cell units - virtual Ogre::AxisAlignedBox getBounds(); + virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); /// Get the minimum and maximum heights of a terrain chunk. /// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree. diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index 9864e9825..d8cdab9ec 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -24,7 +24,7 @@ namespace Terrain public: /// Get bounds of the whole terrain in cell units - virtual Ogre::AxisAlignedBox getBounds() = 0; + virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; /// Get the minimum and maximum heights of a terrain chunk. /// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree. diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index f4070393d..4273f227d 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -62,6 +62,10 @@ namespace Terrain , mShaders(shaders) , mVisible(true) , mLoadingListener(loadingListener) + , mMaxX(0) + , mMinX(0) + , mMaxY(0) + , mMinY(0) { loadingListener->setLabel("Creating terrain"); loadingListener->indicateProgress(); @@ -76,20 +80,21 @@ namespace Terrain mCompositeMapRenderTarget->setAutoUpdated(false); mCompositeMapRenderTarget->addViewport(compositeMapCam); - mBounds = storage->getBounds(); + storage->getBounds(mMinX, mMaxX, mMinY, mMaxY); - int origSizeX = mBounds.getSize().x; - int origSizeY = mBounds.getSize().y; + int origSizeX = mMaxX-mMinX; + int origSizeY = mMaxY-mMinY; // Dividing a quad tree only works well for powers of two, so round up to the nearest one int size = nextPowerOfTwo(std::max(origSizeX, origSizeY)); // Adjust the center according to the new size - Ogre::Vector3 center = mBounds.getCenter() + Ogre::Vector3((size-origSizeX)/2.f, (size-origSizeY)/2.f, 0); + float centerX = (mMinX+mMaxX)/2.f + (size-origSizeX)/2.f; + float centerY = (mMinY+mMaxY)/2.f + (size-origSizeY)/2.f; mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(center.x, center.y), NULL); + mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL); buildQuadTree(mRootNode); loadingListener->indicateProgress(); mRootNode->initAabb(); @@ -122,10 +127,10 @@ namespace Terrain return; } - if (node->getCenter().x - halfSize > mBounds.getMaximum().x - || node->getCenter().x + halfSize < mBounds.getMinimum().x - || node->getCenter().y - halfSize > mBounds.getMaximum().y - || node->getCenter().y + halfSize < mBounds.getMinimum().y ) + if (node->getCenter().x - halfSize > mMaxX + || node->getCenter().x + halfSize < mMinX + || node->getCenter().y - halfSize > mMaxY + || node->getCenter().y + halfSize < mMinY ) // Out of bounds of the actual terrain - this will happen because // we rounded the size up to the next power of two { @@ -162,10 +167,10 @@ namespace Terrain Ogre::AxisAlignedBox World::getWorldBoundingBox (const Ogre::Vector2& center) { - if (center.x > mBounds.getMaximum().x - || center.x < mBounds.getMinimum().x - || center.y > mBounds.getMaximum().y - || center.y < mBounds.getMinimum().y) + if (center.x > mMaxX + || center.x < mMinX + || center.y > mMaxY + || center.y < mMinY) return Ogre::AxisAlignedBox::BOX_NULL; QuadTreeNode* node = findNode(center, mRootNode); Ogre::AxisAlignedBox box = node->getBoundingBox(); diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index b8c1b0a7d..bf733b889 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -105,7 +105,7 @@ namespace Terrain Ogre::SceneManager* mCompositeMapSceneMgr; /// Bounds in cell units - Ogre::AxisAlignedBox mBounds; + float mMinX, mMaxX, mMinY, mMaxY; /// Minimum size of a terrain batch along one side (in cell units) float mMinBatchSize; From ebc67a82cf7fda16943353ac834cfeff6ed4e6ca Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 16 Feb 2014 13:12:31 +0100 Subject: [PATCH 006/114] Don't list unnamed quests in the quest book --- apps/openmw/mwgui/journalviewmodel.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index a0d67b025..c6bd6d15d 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -206,9 +206,9 @@ struct JournalViewModelImpl : JournalViewModel const MWDialogue::Quest& quest = i->second; // Unfortunately Morrowind.esm has no quest names, since the quest book was added with tribunal. - if (quest.getName().empty()) - visitor (reinterpret_cast (&i->second), toUtf8Span (i->first)); - else + // Note that even with Tribunal, some quests still don't have quest names. I'm assuming those are not supposed + // to appear in the quest book. + if (!quest.getName().empty()) visitor (reinterpret_cast (&i->second), toUtf8Span (quest.getName())); } } From d25b3ad9cb969967cb7e87b37651668815871130 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 16 Feb 2014 18:48:03 +0100 Subject: [PATCH 007/114] Fix AiCombat for creatures with weapons --- apps/openmw/mwmechanics/aicombat.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 3653587f8..6a49f2f5e 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -13,7 +13,7 @@ #include "../mwbase/dialoguemanager.hpp" -#include "npcstats.hpp" +#include "creaturestats.hpp" #include "steering.hpp" #include "movement.hpp" #include "character.hpp" // fixme: for getActiveWeapon @@ -138,11 +138,11 @@ namespace MWMechanics { MWMechanics::DrawState_ state = actor.getClass().getCreatureStats(actor).getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - actor.getClass().getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + actor.getClass().getCreatureStats(actor).setDrawState(MWMechanics::DrawState_Weapon); //Get weapon speed and range MWWorld::ContainerStoreIterator weaponSlot = - MWMechanics::getActiveWeapon(cls.getNpcStats(actor), cls.getInventoryStore(actor), &weaptype); + MWMechanics::getActiveWeapon(cls.getCreatureStats(actor), cls.getInventoryStore(actor), &weaptype); if (weaptype == WeapType_HandToHand) { const MWWorld::Store &gmst = From 85b72409481622c1f29e779f61f61f2f0a9bc64a Mon Sep 17 00:00:00 2001 From: pvdk Date: Wed, 19 Feb 2014 13:43:14 +0100 Subject: [PATCH 008/114] Made the git version retrieval more reliable --- CMakeLists.txt | 54 ++++++++++++++++----------- cmake/GetGitRevisionDescription.cmake | 21 ++++++++--- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a9d1f2017..5451fd131 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,27 +13,39 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) include(OpenMWMacros) # Version - -include(GetGitRevisionDescription) - -get_git_tag_revision(TAGHASH --tags --max-count=1 "HEAD...") -get_git_head_revision(REFSPEC COMMITHASH) -git_describe(VERSION --tags ${TAGHASH}) - -string(REGEX MATCH "^openmw-[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" MATCH "${VERSION}") -if (MATCH) - string(REGEX REPLACE "^openmw-([0-9]+)\\..*" "\\1" OPENMW_VERSION_MAJOR "${VERSION}") - string(REGEX REPLACE "^openmw-[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_MINOR "${VERSION}") - string(REGEX REPLACE "^openmw-[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_RELEASE "${VERSION}") - - set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") - set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") - set(OPENMW_VERSION_TAGHASH "${TAGHASH}") - - message(STATUS "Configuring OpenMW ${OPENMW_VERSION}...") -else (MATCH) - message(FATAL_ERROR "Failed to get valid version information from Git") -endif (MATCH) +set(OPENMW_VERSION_MAJOR 0) +set(OPENMW_VERSION_MINOR 29) +set(OPENMW_VERSION_RELEASE 0) + +set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") + +if(EXISTS ${PROJECT_SOURCE_DIR}/.git) + find_package(Git) + + if(GIT_FOUND) + include(GetGitRevisionDescription) + get_git_tag_revision(TAGHASH --tags --max-count=1) + get_git_head_revision(REFSPEC COMMITHASH) + git_describe(VERSION --tags ${TAGHASH}) + + string(REGEX MATCH "^openmw-[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" MATCH "${VERSION}") + if(MATCH) + string(REGEX REPLACE "^openmw-([0-9]+)\\..*" "\\1" OPENMW_VERSION_MAJOR "${VERSION}") + string(REGEX REPLACE "^openmw-[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_MINOR "${VERSION}") + string(REGEX REPLACE "^openmw-[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_RELEASE "${VERSION}") + + set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") + set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") + set(OPENMW_VERSION_TAGHASH "${TAGHASH}") + + message(STATUS "Configuring OpenMW ${OPENMW_VERSION}...") + else(MATCH) + message(WARNING "Failed to get valid version information from Git") + endif(MATCH) + else(GIT_FOUND) + message(WARNING "Git executable not found") + endif(GIT_FOUND) +endif(EXISTS ${PROJECT_SOURCE_DIR}/.git) # doxygen main page diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake index fecd1654d..216eeb9fb 100644 --- a/cmake/GetGitRevisionDescription.cmake +++ b/cmake/GetGitRevisionDescription.cmake @@ -85,10 +85,6 @@ function(get_git_head_revision _refspecvar _hashvar) endfunction() function(git_describe _var) - if(NOT GIT_FOUND) - find_package(Git QUIET) - endif() - #get_git_head_revision(refspec hash) if(NOT GIT_FOUND) @@ -124,6 +120,20 @@ function(git_describe _var) out OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + execute_process(COMMAND + "${GIT_EXECUTABLE}" + describe + "--always" + WORKING_DIRECTORY + "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() + if(NOT res EQUAL 0) set(out "${out}-${res}-NOTFOUND") endif() @@ -133,7 +143,8 @@ endfunction() function(get_git_tag_revision _var) if(NOT GIT_FOUND) - find_package(Git QUIET) + set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) + return() endif() execute_process(COMMAND From b43325119a72009868901f96bea19bca5359fc5e Mon Sep 17 00:00:00 2001 From: pvdk Date: Wed, 19 Feb 2014 14:19:08 +0100 Subject: [PATCH 009/114] Some changes to the version retrieval: ignore shallow clones --- CMakeLists.txt | 66 +++++++++++++++------------ apps/launcher/maindialog.cpp | 23 ++++++---- cmake/GetGitRevisionDescription.cmake | 14 ------ 3 files changed, 50 insertions(+), 53 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5451fd131..788616811 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,47 +6,55 @@ if (APPLE) set(APP_BUNDLE_DIR "${OpenMW_BINARY_DIR}/${APP_BUNDLE_NAME}") endif (APPLE) -# Macros - set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) -include(OpenMWMacros) - # Version +message(STATUS "Configuring OpenMW...") + set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 29) set(OPENMW_VERSION_RELEASE 0) +set(OPENMW_VERSION_COMMITHASH "") +set(OPENMW_VERSION_TAGHASH "") + set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") if(EXISTS ${PROJECT_SOURCE_DIR}/.git) - find_package(Git) - - if(GIT_FOUND) - include(GetGitRevisionDescription) - get_git_tag_revision(TAGHASH --tags --max-count=1) - get_git_head_revision(REFSPEC COMMITHASH) - git_describe(VERSION --tags ${TAGHASH}) - - string(REGEX MATCH "^openmw-[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" MATCH "${VERSION}") - if(MATCH) - string(REGEX REPLACE "^openmw-([0-9]+)\\..*" "\\1" OPENMW_VERSION_MAJOR "${VERSION}") - string(REGEX REPLACE "^openmw-[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_MINOR "${VERSION}") - string(REGEX REPLACE "^openmw-[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_RELEASE "${VERSION}") - - set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") - set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") - set(OPENMW_VERSION_TAGHASH "${TAGHASH}") - - message(STATUS "Configuring OpenMW ${OPENMW_VERSION}...") - else(MATCH) - message(WARNING "Failed to get valid version information from Git") - endif(MATCH) - else(GIT_FOUND) - message(WARNING "Git executable not found") - endif(GIT_FOUND) + if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow) + find_package(Git) + + if(GIT_FOUND) + include(GetGitRevisionDescription) + get_git_tag_revision(TAGHASH --tags --max-count=1) + get_git_head_revision(REFSPEC COMMITHASH) + git_describe(VERSION --tags ${TAGHASH}) + + string(REGEX MATCH "^openmw-[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" MATCH "${VERSION}") + if(MATCH) + string(REGEX REPLACE "^openmw-([0-9]+)\\..*" "\\1" OPENMW_VERSION_MAJOR "${VERSION}") + string(REGEX REPLACE "^openmw-[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_MINOR "${VERSION}") + string(REGEX REPLACE "^openmw-[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_RELEASE "${VERSION}") + + set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") + set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") + set(OPENMW_VERSION_TAGHASH "${TAGHASH}") + + message(STATUS "OpenMW version ${OPENMW_VERSION}") + else(MATCH) + message(WARNING "Failed to get valid version information from Git") + endif(MATCH) + else(GIT_FOUND) + message(WARNING "Git executable not found") + endif(GIT_FOUND) + else(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow) + message(STATUS "Shallow Git clone detected, not attempting to retrieve version info") + endif(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/shallow) endif(EXISTS ${PROJECT_SOURCE_DIR}/.git) +# Macros +include(OpenMWMacros) + # doxygen main page configure_file ("${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp") diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 56b3186ff..5cf8f8a89 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -76,17 +76,20 @@ Launcher::MainDialog::MainDialog(QWidget *parent) QString revision(OPENMW_VERSION_COMMITHASH); QString tag(OPENMW_VERSION_TAGHASH); - if (revision == tag) { - versionLabel->setText(tr("OpenMW %0 release").arg(OPENMW_VERSION)); - } else { - versionLabel->setText(tr("OpenMW development (%0)").arg(revision.left(10))); - } + if (!revision.isEmpty() && !tag.isEmpty()) + { + if (revision == tag) { + versionLabel->setText(tr("OpenMW %0 release").arg(OPENMW_VERSION)); + } else { + versionLabel->setText(tr("OpenMW development (%0)").arg(revision.left(10))); + } - // Add the compile date and time - versionLabel->setToolTip(tr("Compiled on %0 %1").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), - QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate), - QLocale(QLocale::C).toTime(QString(__TIME__).simplified(), - QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate))); + // Add the compile date and time + versionLabel->setToolTip(tr("Compiled on %0 %1").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), + QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate), + QLocale(QLocale::C).toTime(QString(__TIME__).simplified(), + QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate))); + } createIcons(); } diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake index 216eeb9fb..56ff1d545 100644 --- a/cmake/GetGitRevisionDescription.cmake +++ b/cmake/GetGitRevisionDescription.cmake @@ -120,20 +120,6 @@ function(git_describe _var) out OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT res EQUAL 0) - execute_process(COMMAND - "${GIT_EXECUTABLE}" - describe - "--always" - WORKING_DIRECTORY - "${CMAKE_SOURCE_DIR}" - RESULT_VARIABLE - res - OUTPUT_VARIABLE - out - OUTPUT_STRIP_TRAILING_WHITESPACE) - endif() - if(NOT res EQUAL 0) set(out "${out}-${res}-NOTFOUND") endif() From 56d537e23ddd48eca6b9d7c44472f94ab6e616be Mon Sep 17 00:00:00 2001 From: pvdk Date: Wed, 19 Feb 2014 14:37:27 +0100 Subject: [PATCH 010/114] Added some sanity checking: compare git version with manual version --- CMakeLists.txt | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 788616811..ae9ec8ac0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 29) +set(OPENMW_VERSION_MINOR 28) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") @@ -32,13 +32,22 @@ if(EXISTS ${PROJECT_SOURCE_DIR}/.git) string(REGEX MATCH "^openmw-[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" MATCH "${VERSION}") if(MATCH) - string(REGEX REPLACE "^openmw-([0-9]+)\\..*" "\\1" OPENMW_VERSION_MAJOR "${VERSION}") - string(REGEX REPLACE "^openmw-[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_MINOR "${VERSION}") - string(REGEX REPLACE "^openmw-[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_RELEASE "${VERSION}") - - set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") - set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") - set(OPENMW_VERSION_TAGHASH "${TAGHASH}") + string(REGEX REPLACE "^openmw-([0-9]+)\\..*" "\\1" GIT_VERSION_MAJOR "${VERSION}") + string(REGEX REPLACE "^openmw-[0-9]+\\.([0-9]+).*" "\\1" GIT_VERSION_MINOR "${VERSION}") + string(REGEX REPLACE "^openmw-[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" GIT_VERSION_RELEASE "${VERSION}") + + set(GIT_VERSION "${GIT_VERSION_MAJOR}.${GIT_VERSION_MINOR}.${GIT_VERSION_RELEASE}") + + if(NOT ${OPENMW_VERSION} STREQUAL ${GIT_VERSION}) + message(FATAL_ERROR "Silly Zini forgot to update the version again...") + else(NOT ${OPENMW_VERSION} STREQUAL ${GIT_VERSION}) + set(OPENMW_VERSION_MAJOR ${GIT_VERSION_MAJOR}) + set(OPENMW_VERSION_MINOR ${GIT_VERSION_MINOR}) + set(OPENMW_VERSION_RELEASE ${GIT_VERSION_RELEASE}) + + set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") + set(OPENMW_VERSION_TAGHASH "${TAGHASH}") + endif(NOT ${OPENMW_VERSION} STREQUAL ${GIT_VERSION}) message(STATUS "OpenMW version ${OPENMW_VERSION}") else(MATCH) From c0631187c6836b490cbe6268ed37da50ff11de63 Mon Sep 17 00:00:00 2001 From: pvdk Date: Wed, 19 Feb 2014 14:48:33 +0100 Subject: [PATCH 011/114] Removed git tag retrieval for Travis --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 39a02de63..5d0326a07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ branches: - /openmw-.*$/ before_install: - pwd - - git fetch --tags - echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" - echo "yes" | sudo apt-add-repository ppa:openmw/openmw - sudo apt-get update -qq From 736644de05555d9e5b9771a63f35ba28e0b228ff Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 20 Feb 2014 12:31:40 +0100 Subject: [PATCH 012/114] Revert "#1041 in progress: decode first sample batch right in OpenAL_SoundStream::play()" This reverts commit 51fb9f65ea5086433fa0d1db12197b133a9b27fd. --- apps/openmw/mwsound/openal_output.cpp | 45 ++++++++++++++------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 58566225b..7563ad015 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -172,6 +172,7 @@ class OpenAL_SoundStream : public Sound DecoderPtr mDecoder; volatile bool mIsFinished; + volatile bool mIsInitialBatchEnqueued; void updateAll(bool local); @@ -264,7 +265,7 @@ private: OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, float basevol, float pitch, int flags) : Sound(Ogre::Vector3(0.0f), 1.0f, basevol, pitch, 1.0f, 1000.0f, flags) - , mOutput(output), mSource(src), mSamplesQueued(0), mDecoder(decoder), mIsFinished(true) + , mOutput(output), mSource(src), mSamplesQueued(0), mDecoder(decoder), mIsFinished(true), mIsInitialBatchEnqueued(false) { throwALerror(); @@ -315,26 +316,8 @@ void OpenAL_SoundStream::play() alSourcei(mSource, AL_BUFFER, 0); throwALerror(); mSamplesQueued = 0; - - std::vector data(mBufferSize); - - bool finished = false; - for(ALuint i = 0;i < sNumBuffers && !finished;i++) - { - size_t got = mDecoder->read(&data[0], data.size()); - finished = (got < data.size()); - if(got > 0) - { - ALuint bufid = mBuffers[i]; - alBufferData(bufid, mFormat, &data[0], got, mSampleRate); - alSourceQueueBuffers(mSource, 1, &bufid); - throwALerror(); - mSamplesQueued += getBufferSampleCount(bufid); - } - } - - mIsFinished = finished; - alSourcePlay(mSource); + mIsFinished = false; + mIsInitialBatchEnqueued = false; mOutput.mStreamThread->add(this); } @@ -342,6 +325,7 @@ void OpenAL_SoundStream::stop() { mOutput.mStreamThread->remove(this); mIsFinished = true; + mIsInitialBatchEnqueued = false; alSourceStop(mSource); alSourcei(mSource, AL_BUFFER, 0); @@ -454,6 +438,24 @@ bool OpenAL_SoundStream::process() } while(processed > 0); throwALerror(); } + else if (!mIsInitialBatchEnqueued) { // nothing enqueued yet + std::vector data(mBufferSize); + + for(ALuint i = 0;i < sNumBuffers && !finished;i++) + { + size_t got = mDecoder->read(&data[0], data.size()); + finished = (got < data.size()); + if(got > 0) + { + ALuint bufid = mBuffers[i]; + alBufferData(bufid, mFormat, &data[0], got, mSampleRate); + alSourceQueueBuffers(mSource, 1, &bufid); + throwALerror(); + mSamplesQueued += getBufferSampleCount(bufid); + } + } + mIsInitialBatchEnqueued = true; + } if(state != AL_PLAYING && state != AL_PAUSED) { @@ -471,6 +473,7 @@ bool OpenAL_SoundStream::process() std::cout<< "Error updating stream \""<getName()<<"\"" < Date: Thu, 20 Feb 2014 12:35:57 +0100 Subject: [PATCH 013/114] fix broken esm writer header code --- components/esm/esmwriter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index f38591b7b..a29b876e7 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -80,8 +80,8 @@ namespace ESM rec.name = name; rec.position = mStream->tellp(); rec.size = 0; - writeT(0); // Size goes here - writeT(0); // Unused header? + writeT(0); // Size goes here + writeT(0); // Unused header? writeT(flags); mRecords.push_back(rec); From ee7364a8a32d70bb1d0e16df02debf742fd9720f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 20 Feb 2014 12:43:49 +0100 Subject: [PATCH 014/114] updated changelog --- readme.txt | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/readme.txt b/readme.txt index 045b18b8b..07e89affa 100644 --- a/readme.txt +++ b/readme.txt @@ -97,6 +97,72 @@ Allowed options: CHANGELOG +0.29.0 + +Bug #556: Video soundtrack not played when music volume is set to zero +Bug #848: Wrong amount of footsteps playing in 1st person +Bug #888: Ascended Sleepers have movement issues +Bug #892: Explicit references are allowed on all script functions +Bug #999: Graphic Herbalism (mod): sometimes doesn't activate properly +Bug #1009: Lake Fjalding AI related slowdown. +Bug #1041: Music playback issues on OS X >= 10.9 +Bug #1043: No message box when advancing skill "Speechcraft" while in dialog window +Bug #1060: Some message boxes are cut off at the bottom +Bug #1062: Bittercup script does not work ('end' variable) +Bug #1074: Inventory paperdoll obscures armour rating +Bug #1077: Message after killing an essential NPC disappears too fast +Bug #1078: "Clutterbane" shows empty charge bar +Bug #1083: UndoWerewolf fails +Bug #1088: Better Clothes Bloodmoon Plus 1.5 by Spirited Treasure pants are not rendered +Bug #1090: Start scripts fail when going to a non-predefined cell +Bug #1091: Crash: Assertion `!q.isNaN() && "Invalid orientation supplied as parameter"' failed. +Bug #1093: Weapons of aggressive NPCs are invisible after you exit and re-enter interior +Bug #1105: Magicka is depleted when using uncastable spells +Bug #1106: Creatures should be able to run +Bug #1107: TR cliffs have way too huge collision boxes in OpenMW +Bug #1109: Cleaning True Light and Darkness with Tes3cmd makes Addamasartus , Zenarbael and Yasamsi flooded. +Bug #1114: Bad output for desktop-file-validate on openmw.desktop (and opencs.desktop) +Bug #1115: Memory leak when spying on Fargoth +Bug #1137: Script execution fails (drenSlaveOwners script) +Bug #1143: Mehra Milo quest (vivec informants) is broken +Bug #1145: Issues with moving gold between inventory and containers +Bug #1146: Issues with picking up stacks of gold +Bug #1147: Dwemer Crossbows are held incorrectly +Bug #1158: Armor rating should always stay below inventory mannequin +Bug #1159: Quick keys can be set during character generation +Bug #1160: Crash on equip lockpick when +Bug #1167: Editor: Referenceables are not correctly loaded when dealing with more than one content file +Feature #30: Loading/Saving (still missing a few parts) +Feature #101: AI Package: Activate +Feature #103: AI Package: Follow, FollowCell +Feature #138: Editor: Drag & Drop +Feature #428: Player death +Feature #505: Editor: Record Cloning +Feature #701: Levelled creatures +Feature #708: Improved Local Variable handling +Feature #709: Editor: Script verifier +Feature #764: Missing journal backend features +Feature #777: Creature weapons/shields +Feature #789: Editor: Referenceable record verifier +Feature #924: Load/Save GUI (still missing loading screen and progress bars) +Feature #946: Knockdown +Feature #947: Decrease fatigue when running, swimming and attacking +Feature #956: Melee Combat: Blocking +Feature #957: Area magic +Feature #960: Combat/AI combat for creatures +Feature #962: Combat-Related AI instructions +Feature #1075: Damage/Restore skill/attribute magic effects +Feature #1076: Soultrap magic effect +Feature #1081: Disease contraction +Feature #1086: Blood particles +Feature #1092: Interrupt resting +Feature #1101: Inventory equip scripts +Feature #1116: Version/Build number in Launcher window +Feature #1119: Resistance/weakness to normal weapons magic effect +Feature #1123: Slow Fall magic effect +Feature #1130: Auto-calculate spells +Feature #1164: Editor: Case-insensitive sorting in tables + 0.28.0 Bug #399: Inventory changes are not visible immediately From c5b2c154f8b3dc69afbefcd2b40cc53577c4d913 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 20 Feb 2014 12:49:56 +0100 Subject: [PATCH 015/114] another esm writer fix --- components/esm/esmwriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index a29b876e7..ff7bd765d 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -105,7 +105,7 @@ namespace ESM rec.name = name; rec.position = mStream->tellp(); rec.size = 0; - writeT(0); // Size goes here + writeT(0); // Size goes here mRecords.push_back(rec); assert(mRecords.back().size == 0); From c3f350e3fb1b7936ad1d78bc79c04054d5489dac Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 22 Feb 2014 12:06:50 +1100 Subject: [PATCH 016/114] Allow MinGW64 compilation in Windows/msys --- .gitignore | 1 + apps/openmw/mwrender/videoplayer.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- components/files/configurationmanager.hpp | 2 +- extern/sdl4ogre/sdlwindowhelper.cpp | 4 ++++ libs/platform/string.h | 2 +- 6 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 3975c4521..ca58fc006 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ Doxygen ## ides/editors *~ +*.bak *.kdev4 *.swp *.swo diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index adf20dc63..4209fb978 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -385,7 +385,7 @@ class MovieAudioDecoder : public MWSound::Sound_Decoder } void open(const std::string&) -#ifdef _WIN32 +#ifdef _MSC_VER { fail(std::string("Invalid call to ")+__FUNCSIG__); } #else { fail(std::string("Invalid call to ")+__PRETTY_FUNCTION__); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index be6c0b338..21780fd5c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1,5 +1,5 @@ #include "worldimp.hpp" -#ifdef _WIN32 +#if defined(_WIN32) && !defined(__MINGW32__) #include #elif defined HAVE_UNORDERED_MAP #include diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 35144fe04..9f31a7f2d 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -1,7 +1,7 @@ #ifndef COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP -#ifdef _WIN32 +#if defined(_WIN32) && !defined(__MINGW32__) #include #elif defined HAVE_UNORDERED_MAP #include diff --git a/extern/sdl4ogre/sdlwindowhelper.cpp b/extern/sdl4ogre/sdlwindowhelper.cpp index f819043cf..2a54a8636 100644 --- a/extern/sdl4ogre/sdlwindowhelper.cpp +++ b/extern/sdl4ogre/sdlwindowhelper.cpp @@ -31,7 +31,11 @@ SDLWindowHelper::SDLWindowHelper (SDL_Window* window, int w, int h, #ifdef WIN32 case SDL_SYSWM_WINDOWS: // Windows code +#ifdef __MINGW64__ + winHandle = Ogre::StringConverter::toString((DWORD_PTR)wmInfo.info.win.window); +#else winHandle = Ogre::StringConverter::toString((unsigned long)wmInfo.info.win.window); +#endif /* __MINGW64__ */ break; #elif __MACOSX__ case SDL_SYSWM_COCOA: diff --git a/libs/platform/string.h b/libs/platform/string.h index 5368d757c..7f0876587 100644 --- a/libs/platform/string.h +++ b/libs/platform/string.h @@ -9,7 +9,7 @@ #include #if (defined(__APPLE__) && __MAC_OS_X_VERSION_MIN_REQUIRED < 1070) || defined(__MINGW32__) // need our own implementation of strnlen -#ifdef __MINGW32__ +#ifdef __MINGW32__ && !__MINGW64__ static size_t strnlen(const char *s, size_t n) { const char *p = (const char *)memchr(s, 0, n); From c241405d91bfa7dff7cd7149c7b81e1989b345be Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 22 Feb 2014 12:15:20 +1100 Subject: [PATCH 017/114] Fix Windows save & load. --- apps/openmw/engine.cpp | 5 ++++- apps/openmw/mwstate/statemanagerimp.cpp | 2 +- components/esm/esmwriter.cpp | 8 ++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index e80bd954e..09757786b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -339,8 +340,10 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) void OMW::Engine::prepareEngine (Settings::Manager & settings) { + boost::filesystem::path saves(mCfgMgr.getUserDataPath() / "saves"); + mEnvironment.setStateManager ( - new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0))); + new MWState::StateManager (saves.make_preferred(), mContentFiles.at (0))); Nif::NIFFile::CacheLock cachelock; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index ba0e1d056..265069dc4 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -176,7 +176,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot else slot = mCharacterManager.getCurrentCharacter()->updateSlot (slot, profile); - std::ofstream stream (slot->mPath.string().c_str()); + std::ofstream stream (slot->mPath.string().c_str(), std::ios::binary); ESM::ESMWriter writer; diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index f38591b7b..91f123eb7 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -80,8 +80,8 @@ namespace ESM rec.name = name; rec.position = mStream->tellp(); rec.size = 0; - writeT(0); // Size goes here - writeT(0); // Unused header? + writeT(0); // Size goes here + writeT(0); // Unused header? writeT(flags); mRecords.push_back(rec); @@ -105,7 +105,7 @@ namespace ESM rec.name = name; rec.position = mStream->tellp(); rec.size = 0; - writeT(0); // Size goes here + writeT(0); // Size goes here mRecords.push_back(rec); assert(mRecords.back().size == 0); @@ -120,7 +120,7 @@ namespace ESM mStream->seekp(rec.position); mCounting = false; - write (reinterpret_cast (&rec.size), sizeof(int)); + write (reinterpret_cast (&rec.size), sizeof(uint32_t)); mCounting = true; mStream->seekp(0, std::ios::end); From 29a33364cc4f72fc86b3cbb3b6617bf13983388c Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 22 Feb 2014 17:21:19 +1100 Subject: [PATCH 018/114] Another attempt at fixing MinGW64 --- libs/platform/string.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/platform/string.h b/libs/platform/string.h index 7f0876587..be2e976b2 100644 --- a/libs/platform/string.h +++ b/libs/platform/string.h @@ -9,7 +9,7 @@ #include #if (defined(__APPLE__) && __MAC_OS_X_VERSION_MIN_REQUIRED < 1070) || defined(__MINGW32__) // need our own implementation of strnlen -#ifdef __MINGW32__ && !__MINGW64__ +#if defined(__MINGW32__) && !defined(__MINGW64__) static size_t strnlen(const char *s, size_t n) { const char *p = (const char *)memchr(s, 0, n); From 2ca28f40bef740aa489d31e6bcada2ab8f4682ac Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 22 Feb 2014 12:55:14 +0100 Subject: [PATCH 019/114] minor addition to change log --- readme.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.txt b/readme.txt index 07e89affa..a23cd1077 100644 --- a/readme.txt +++ b/readme.txt @@ -100,6 +100,7 @@ CHANGELOG 0.29.0 Bug #556: Video soundtrack not played when music volume is set to zero +Bug #829: OpenMW uses up all available vram, when playing for extended time Bug #848: Wrong amount of footsteps playing in 1st person Bug #888: Ascended Sleepers have movement issues Bug #892: Explicit references are allowed on all script functions From ac606a865c5fa2ffbf1dfe521ae0bf5f1384a786 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 22 Feb 2014 23:22:23 +1100 Subject: [PATCH 020/114] Back out unnecessary change. --- apps/openmw/engine.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 09757786b..e80bd954e 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -2,7 +2,6 @@ #include -#include #include #include @@ -340,10 +339,8 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) void OMW::Engine::prepareEngine (Settings::Manager & settings) { - boost::filesystem::path saves(mCfgMgr.getUserDataPath() / "saves"); - mEnvironment.setStateManager ( - new MWState::StateManager (saves.make_preferred(), mContentFiles.at (0))); + new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0))); Nif::NIFFile::CacheLock cachelock; From fcfc8fcccb90a9cd7429eeb7209bc447ea59879d Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 22 Feb 2014 23:45:13 +1100 Subject: [PATCH 021/114] Revert "Allow MinGW64 compilation in Windows/msys" This reverts commit c3f350e3fb1b7936ad1d78bc79c04054d5489dac. Conflicts: libs/platform/string.h --- .gitignore | 1 - apps/openmw/mwrender/videoplayer.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- components/files/configurationmanager.hpp | 2 +- extern/sdl4ogre/sdlwindowhelper.cpp | 4 ---- libs/platform/string.h | 2 +- 6 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index ca58fc006..3975c4521 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ Doxygen ## ides/editors *~ -*.bak *.kdev4 *.swp *.swo diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index 4209fb978..adf20dc63 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -385,7 +385,7 @@ class MovieAudioDecoder : public MWSound::Sound_Decoder } void open(const std::string&) -#ifdef _MSC_VER +#ifdef _WIN32 { fail(std::string("Invalid call to ")+__FUNCSIG__); } #else { fail(std::string("Invalid call to ")+__PRETTY_FUNCTION__); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 21780fd5c..be6c0b338 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1,5 +1,5 @@ #include "worldimp.hpp" -#if defined(_WIN32) && !defined(__MINGW32__) +#ifdef _WIN32 #include #elif defined HAVE_UNORDERED_MAP #include diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 9f31a7f2d..35144fe04 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -1,7 +1,7 @@ #ifndef COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP #define COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP -#if defined(_WIN32) && !defined(__MINGW32__) +#ifdef _WIN32 #include #elif defined HAVE_UNORDERED_MAP #include diff --git a/extern/sdl4ogre/sdlwindowhelper.cpp b/extern/sdl4ogre/sdlwindowhelper.cpp index 2a54a8636..f819043cf 100644 --- a/extern/sdl4ogre/sdlwindowhelper.cpp +++ b/extern/sdl4ogre/sdlwindowhelper.cpp @@ -31,11 +31,7 @@ SDLWindowHelper::SDLWindowHelper (SDL_Window* window, int w, int h, #ifdef WIN32 case SDL_SYSWM_WINDOWS: // Windows code -#ifdef __MINGW64__ - winHandle = Ogre::StringConverter::toString((DWORD_PTR)wmInfo.info.win.window); -#else winHandle = Ogre::StringConverter::toString((unsigned long)wmInfo.info.win.window); -#endif /* __MINGW64__ */ break; #elif __MACOSX__ case SDL_SYSWM_COCOA: diff --git a/libs/platform/string.h b/libs/platform/string.h index be2e976b2..5368d757c 100644 --- a/libs/platform/string.h +++ b/libs/platform/string.h @@ -9,7 +9,7 @@ #include #if (defined(__APPLE__) && __MAC_OS_X_VERSION_MIN_REQUIRED < 1070) || defined(__MINGW32__) // need our own implementation of strnlen -#if defined(__MINGW32__) && !defined(__MINGW64__) +#ifdef __MINGW32__ static size_t strnlen(const char *s, size_t n) { const char *p = (const char *)memchr(s, 0, n); From d780364842974a201876610eb58093fa659e9cc2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 22 Feb 2014 17:31:44 +0100 Subject: [PATCH 022/114] fixed record size type --- components/esm/esmwriter.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index 94f0a1004..33650e678 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -17,7 +17,7 @@ class ESMWriter { std::string name; std::streampos position; - size_t size; + uint32_t size; }; public: From 1ab5948f196618c76e839834d0c047f9a34941d2 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 23 Feb 2014 19:11:37 +1100 Subject: [PATCH 023/114] merge upstream changes --- apps/opencs/model/world/columnbase.hpp | 254 +-- apps/opencs/model/world/tablemimedata.cpp | 890 ++++----- apps/opencs/model/world/tablemimedata.hpp | 124 +- apps/opencs/view/filter/editwidget.cpp | 406 ++-- apps/opencs/view/filter/editwidget.hpp | 114 +- apps/opencs/view/filter/filterbox.cpp | 100 +- apps/opencs/view/filter/filterbox.hpp | 90 +- apps/opencs/view/filter/recordfilterbox.cpp | 64 +- apps/opencs/view/filter/recordfilterbox.hpp | 74 +- apps/opencs/view/world/table.cpp | 1090 +++++------ apps/opencs/view/world/table.hpp | 254 +-- apps/opencs/view/world/tablesubview.cpp | 262 +-- apps/opencs/view/world/tablesubview.hpp | 126 +- apps/openmw/mwstate/statemanagerimp.cpp | 704 +++---- components/esm/esmwriter.cpp | 406 ++-- components/esm/esmwriter.hpp | 228 +-- readme.txt | 1902 +++++++++---------- 17 files changed, 3544 insertions(+), 3544 deletions(-) diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index e04333608..4ce45ffe8 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -1,127 +1,127 @@ -#ifndef CSM_WOLRD_COLUMNBASE_H -#define CSM_WOLRD_COLUMNBASE_H - -#include - -#include -#include - -#include "record.hpp" - -namespace CSMWorld -{ - struct ColumnBase - { - enum Roles - { - Role_Flags = Qt::UserRole, - Role_Display = Qt::UserRole+1 - }; - - enum Flags - { - Flag_Table = 1, // column should be displayed in table view - Flag_Dialogue = 2 // column should be displayed in dialogue view - }; - - enum Display - { - Display_None, //Do not use - Display_String, - - //CONCRETE TYPES STARTS HERE - Display_Skill, - Display_Class, - Display_Faction, - Display_Race, - Display_Sound, - Display_Region, - Display_Birthsign, - Display_Spell, - Display_Cell, - Display_Referenceable, - Display_Activator, - Display_Potion, - Display_Apparatus, - Display_Armor, - Display_Book, - Display_Clothing, - Display_Container, - Display_Creature, - Display_Door, - Display_Ingredient, - Display_CreatureLevelledList, - Display_ItemLevelledList, - Display_Light, - Display_Lockpick, - Display_Miscellaneous, - Display_Npc, - Display_Probe, - Display_Repair, - Display_Static, - Display_Weapon, - Display_Reference, - Display_Filter, - Display_Topic, - Display_Journal, - Display_TopicInfo, - Display_JournalInfo, - Display_Scene, - //CONCRETE TYPES ENDS HERE - - Display_Integer, - Display_Float, - Display_Var, - Display_GmstVarType, - Display_GlobalVarType, - Display_Specialisation, - Display_Attribute, - Display_Boolean, - Display_SpellType, - Display_Script, - Display_ApparatusType, - Display_ArmorType, - Display_ClothingType, - Display_CreatureType, - Display_WeaponType, - Display_RecordState, - Display_RefRecordType, - Display_DialogueType, - Display_QuestStatusType, - Display_Gender - }; - - int mColumnId; - int mFlags; - Display mDisplayType; - - ColumnBase (int columnId, Display displayType, int flag); - - virtual ~ColumnBase(); - - virtual bool isEditable() const = 0; - - virtual bool isUserEditable() const; - ///< Can this column be edited directly by the user? - - virtual std::string getTitle() const; - }; - - template - struct Column : public ColumnBase - { - int mFlags; - - Column (int columnId, Display displayType, int flags = Flag_Table | Flag_Dialogue) - : ColumnBase (columnId, displayType, flags) {} - - virtual QVariant get (const Record& record) const = 0; - - virtual void set (Record& record, const QVariant& data) - { - throw std::logic_error ("Column " + getTitle() + " is not editable"); - } - }; -} - -#endif +#ifndef CSM_WOLRD_COLUMNBASE_H +#define CSM_WOLRD_COLUMNBASE_H + +#include + +#include +#include + +#include "record.hpp" + +namespace CSMWorld +{ + struct ColumnBase + { + enum Roles + { + Role_Flags = Qt::UserRole, + Role_Display = Qt::UserRole+1 + }; + + enum Flags + { + Flag_Table = 1, // column should be displayed in table view + Flag_Dialogue = 2 // column should be displayed in dialogue view + }; + + enum Display + { + Display_None, //Do not use + Display_String, + + //CONCRETE TYPES STARTS HERE + Display_Skill, + Display_Class, + Display_Faction, + Display_Race, + Display_Sound, + Display_Region, + Display_Birthsign, + Display_Spell, + Display_Cell, + Display_Referenceable, + Display_Activator, + Display_Potion, + Display_Apparatus, + Display_Armor, + Display_Book, + Display_Clothing, + Display_Container, + Display_Creature, + Display_Door, + Display_Ingredient, + Display_CreatureLevelledList, + Display_ItemLevelledList, + Display_Light, + Display_Lockpick, + Display_Miscellaneous, + Display_Npc, + Display_Probe, + Display_Repair, + Display_Static, + Display_Weapon, + Display_Reference, + Display_Filter, + Display_Topic, + Display_Journal, + Display_TopicInfo, + Display_JournalInfo, + Display_Scene, + //CONCRETE TYPES ENDS HERE + + Display_Integer, + Display_Float, + Display_Var, + Display_GmstVarType, + Display_GlobalVarType, + Display_Specialisation, + Display_Attribute, + Display_Boolean, + Display_SpellType, + Display_Script, + Display_ApparatusType, + Display_ArmorType, + Display_ClothingType, + Display_CreatureType, + Display_WeaponType, + Display_RecordState, + Display_RefRecordType, + Display_DialogueType, + Display_QuestStatusType, + Display_Gender + }; + + int mColumnId; + int mFlags; + Display mDisplayType; + + ColumnBase (int columnId, Display displayType, int flag); + + virtual ~ColumnBase(); + + virtual bool isEditable() const = 0; + + virtual bool isUserEditable() const; + ///< Can this column be edited directly by the user? + + virtual std::string getTitle() const; + }; + + template + struct Column : public ColumnBase + { + int mFlags; + + Column (int columnId, Display displayType, int flags = Flag_Table | Flag_Dialogue) + : ColumnBase (columnId, displayType, flags) {} + + virtual QVariant get (const Record& record) const = 0; + + virtual void set (Record& record, const QVariant& data) + { + throw std::logic_error ("Column " + getTitle() + " is not editable"); + } + }; +} + +#endif diff --git a/apps/opencs/model/world/tablemimedata.cpp b/apps/opencs/model/world/tablemimedata.cpp index b56c9c8c2..f5e1acfb4 100644 --- a/apps/opencs/model/world/tablemimedata.cpp +++ b/apps/opencs/model/world/tablemimedata.cpp @@ -1,446 +1,446 @@ -#include "tablemimedata.hpp" -#include - -#include "universalid.hpp" -#include "columnbase.hpp" - -CSMWorld::TableMimeData::TableMimeData (UniversalId id, const CSMDoc::Document& document) : -mDocument(document) -{ - mUniversalId.push_back (id); - mObjectsFormats << QString::fromStdString ("tabledata/" + id.getTypeName()); -} - -CSMWorld::TableMimeData::TableMimeData (std::vector< CSMWorld::UniversalId >& id, const CSMDoc::Document& document) : - mUniversalId (id), mDocument(document) -{ - for (std::vector::iterator it (mUniversalId.begin()); it != mUniversalId.end(); ++it) - { - mObjectsFormats << QString::fromStdString ("tabledata/" + it->getTypeName()); - } -} - -QStringList CSMWorld::TableMimeData::formats() const -{ - return mObjectsFormats; -} - -CSMWorld::TableMimeData::~TableMimeData() -{ -} - -std::string CSMWorld::TableMimeData::getIcon() const -{ - if (mUniversalId.empty()) - { - throw ("TableMimeData holds no UniversalId"); - } - - std::string tmpIcon; - bool firstIteration = true; - - for (unsigned i = 0; i < mUniversalId.size(); ++i) - { - if (firstIteration) - { - firstIteration = false; - tmpIcon = mUniversalId[i].getIcon(); - continue; - } - - if (tmpIcon != mUniversalId[i].getIcon()) - { - return ":/multitype.png"; //icon stolen from gnome - } - - tmpIcon = mUniversalId[i].getIcon(); - } - - return mUniversalId.begin()->getIcon(); //All objects are of the same type; -} - -std::vector< CSMWorld::UniversalId > CSMWorld::TableMimeData::getData() const -{ - return mUniversalId; -} - - -bool CSMWorld::TableMimeData::holdsType (CSMWorld::UniversalId::Type type) const -{ - for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) - { - if (it->getType() == type) - { - return true; - } - } - - return false; -} - -bool CSMWorld::TableMimeData::holdsType (CSMWorld::ColumnBase::Display type) const -{ - for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) - { - if (it->getType() == convertEnums (type)) - { - return true; - } - } - - return false; -} - -CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::UniversalId::Type type) const -{ - for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) - { - if (it->getType() == type) - { - return *it; - } - } - - throw ("TableMimeData object does not hold object of the seeked type"); -} - -CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::ColumnBase::Display type) const -{ - for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) - { - if (it->getType() == convertEnums (type)) - { - return *it; - } - } - - throw ("TableMimeData object does not hold object of the seeked type"); -} - -bool CSMWorld::TableMimeData::fromDocument (const CSMDoc::Document& document) const -{ - return &document == &mDocument; -} - -CSMWorld::UniversalId::Type CSMWorld::TableMimeData::convertEnums (CSMWorld::ColumnBase::Display type) -{ - switch (type) - { - case CSMWorld::ColumnBase::Display_Race: - return CSMWorld::UniversalId::Type_Race; - - - case CSMWorld::ColumnBase::Display_Skill: - return CSMWorld::UniversalId::Type_Skill; - - - case CSMWorld::ColumnBase::Display_Class: - return CSMWorld::UniversalId::Type_Class; - - - case CSMWorld::ColumnBase::Display_Faction: - return CSMWorld::UniversalId::Type_Faction; - - - case CSMWorld::ColumnBase::Display_Sound: - return CSMWorld::UniversalId::Type_Sound; - - - case CSMWorld::ColumnBase::Display_Region: - return CSMWorld::UniversalId::Type_Region; - - - case CSMWorld::ColumnBase::Display_Birthsign: - return CSMWorld::UniversalId::Type_Birthsign; - - - case CSMWorld::ColumnBase::Display_Spell: - return CSMWorld::UniversalId::Type_Spell; - - - case CSMWorld::ColumnBase::Display_Cell: - return CSMWorld::UniversalId::Type_Cell; - - - case CSMWorld::ColumnBase::Display_Referenceable: - return CSMWorld::UniversalId::Type_Referenceable; - - - case CSMWorld::ColumnBase::Display_Activator: - return CSMWorld::UniversalId::Type_Activator; - - - case CSMWorld::ColumnBase::Display_Potion: - return CSMWorld::UniversalId::Type_Potion; - - - case CSMWorld::ColumnBase::Display_Apparatus: - return CSMWorld::UniversalId::Type_Apparatus; - - - case CSMWorld::ColumnBase::Display_Armor: - return CSMWorld::UniversalId::Type_Armor; - - - case CSMWorld::ColumnBase::Display_Book: - return CSMWorld::UniversalId::Type_Book; - - - case CSMWorld::ColumnBase::Display_Clothing: - return CSMWorld::UniversalId::Type_Clothing; - - - case CSMWorld::ColumnBase::Display_Container: - return CSMWorld::UniversalId::Type_Container; - - - case CSMWorld::ColumnBase::Display_Creature: - return CSMWorld::UniversalId::Type_Creature; - - - case CSMWorld::ColumnBase::Display_Door: - return CSMWorld::UniversalId::Type_Door; - - - case CSMWorld::ColumnBase::Display_Ingredient: - return CSMWorld::UniversalId::Type_Ingredient; - - - case CSMWorld::ColumnBase::Display_CreatureLevelledList: - return CSMWorld::UniversalId::Type_CreatureLevelledList; - - - case CSMWorld::ColumnBase::Display_ItemLevelledList: - return CSMWorld::UniversalId::Type_ItemLevelledList; - - - case CSMWorld::ColumnBase::Display_Light: - return CSMWorld::UniversalId::Type_Light; - - - case CSMWorld::ColumnBase::Display_Lockpick: - return CSMWorld::UniversalId::Type_Lockpick; - - - case CSMWorld::ColumnBase::Display_Miscellaneous: - return CSMWorld::UniversalId::Type_Miscellaneous; - - - case CSMWorld::ColumnBase::Display_Npc: - return CSMWorld::UniversalId::Type_Npc; - - - case CSMWorld::ColumnBase::Display_Probe: - return CSMWorld::UniversalId::Type_Probe; - - - case CSMWorld::ColumnBase::Display_Repair: - return CSMWorld::UniversalId::Type_Repair; - - - case CSMWorld::ColumnBase::Display_Static: - return CSMWorld::UniversalId::Type_Static; - - - case CSMWorld::ColumnBase::Display_Weapon: - return CSMWorld::UniversalId::Type_Weapon; - - - case CSMWorld::ColumnBase::Display_Reference: - return CSMWorld::UniversalId::Type_Reference; - - - case CSMWorld::ColumnBase::Display_Filter: - return CSMWorld::UniversalId::Type_Filter; - - - case CSMWorld::ColumnBase::Display_Topic: - return CSMWorld::UniversalId::Type_Topic; - - - case CSMWorld::ColumnBase::Display_Journal: - return CSMWorld::UniversalId::Type_Journal; - - - case CSMWorld::ColumnBase::Display_TopicInfo: - return CSMWorld::UniversalId::Type_TopicInfo; - - - case CSMWorld::ColumnBase::Display_JournalInfo: - return CSMWorld::UniversalId::Type_JournalInfo; - - - case CSMWorld::ColumnBase::Display_Scene: - return CSMWorld::UniversalId::Type_Scene; - - - case CSMWorld::ColumnBase::Display_Script: - return CSMWorld::UniversalId::Type_Script; - - - default: - return CSMWorld::UniversalId::Type_None; - - } -} - -CSMWorld::ColumnBase::Display CSMWorld::TableMimeData::convertEnums (CSMWorld::UniversalId::Type type) -{ - switch (type) - { - case CSMWorld::UniversalId::Type_Race: - return CSMWorld::ColumnBase::Display_Race; - - - case CSMWorld::UniversalId::Type_Skill: - return CSMWorld::ColumnBase::Display_Skill; - - - case CSMWorld::UniversalId::Type_Class: - return CSMWorld::ColumnBase::Display_Class; - - - case CSMWorld::UniversalId::Type_Faction: - return CSMWorld::ColumnBase::Display_Faction; - - - case CSMWorld::UniversalId::Type_Sound: - return CSMWorld::ColumnBase::Display_Sound; - - - case CSMWorld::UniversalId::Type_Region: - return CSMWorld::ColumnBase::Display_Region; - - - case CSMWorld::UniversalId::Type_Birthsign: - return CSMWorld::ColumnBase::Display_Birthsign; - - - case CSMWorld::UniversalId::Type_Spell: - return CSMWorld::ColumnBase::Display_Spell; - - - case CSMWorld::UniversalId::Type_Cell: - return CSMWorld::ColumnBase::Display_Cell; - - - case CSMWorld::UniversalId::Type_Referenceable: - return CSMWorld::ColumnBase::Display_Referenceable; - - - case CSMWorld::UniversalId::Type_Activator: - return CSMWorld::ColumnBase::Display_Activator; - - - case CSMWorld::UniversalId::Type_Potion: - return CSMWorld::ColumnBase::Display_Potion; - - - case CSMWorld::UniversalId::Type_Apparatus: - return CSMWorld::ColumnBase::Display_Apparatus; - - - case CSMWorld::UniversalId::Type_Armor: - return CSMWorld::ColumnBase::Display_Armor; - - - case CSMWorld::UniversalId::Type_Book: - return CSMWorld::ColumnBase::Display_Book; - - - case CSMWorld::UniversalId::Type_Clothing: - return CSMWorld::ColumnBase::Display_Clothing; - - - case CSMWorld::UniversalId::Type_Container: - return CSMWorld::ColumnBase::Display_Container; - - - case CSMWorld::UniversalId::Type_Creature: - return CSMWorld::ColumnBase::Display_Creature; - - - case CSMWorld::UniversalId::Type_Door: - return CSMWorld::ColumnBase::Display_Door; - - - case CSMWorld::UniversalId::Type_Ingredient: - return CSMWorld::ColumnBase::Display_Ingredient; - - - case CSMWorld::UniversalId::Type_CreatureLevelledList: - return CSMWorld::ColumnBase::Display_CreatureLevelledList; - - - case CSMWorld::UniversalId::Type_ItemLevelledList: - return CSMWorld::ColumnBase::Display_ItemLevelledList; - - - case CSMWorld::UniversalId::Type_Light: - return CSMWorld::ColumnBase::Display_Light; - - - case CSMWorld::UniversalId::Type_Lockpick: - return CSMWorld::ColumnBase::Display_Lockpick; - - - case CSMWorld::UniversalId::Type_Miscellaneous: - return CSMWorld::ColumnBase::Display_Miscellaneous; - - - case CSMWorld::UniversalId::Type_Npc: - return CSMWorld::ColumnBase::Display_Npc; - - - case CSMWorld::UniversalId::Type_Probe: - return CSMWorld::ColumnBase::Display_Probe; - - - case CSMWorld::UniversalId::Type_Repair: - return CSMWorld::ColumnBase::Display_Repair; - - - case CSMWorld::UniversalId::Type_Static: - return CSMWorld::ColumnBase::Display_Static; - - - case CSMWorld::UniversalId::Type_Weapon: - return CSMWorld::ColumnBase::Display_Weapon; - - - case CSMWorld::UniversalId::Type_Reference: - return CSMWorld::ColumnBase::Display_Reference; - - - case CSMWorld::UniversalId::Type_Filter: - return CSMWorld::ColumnBase::Display_Filter; - - - case CSMWorld::UniversalId::Type_Topic: - return CSMWorld::ColumnBase::Display_Topic; - - - case CSMWorld::UniversalId::Type_Journal: - return CSMWorld::ColumnBase::Display_Journal; - - - case CSMWorld::UniversalId::Type_TopicInfo: - return CSMWorld::ColumnBase::Display_TopicInfo; - - - case CSMWorld::UniversalId::Type_JournalInfo: - return CSMWorld::ColumnBase::Display_JournalInfo; - - - case CSMWorld::UniversalId::Type_Scene: - return CSMWorld::ColumnBase::Display_Scene; - - - case CSMWorld::UniversalId::Type_Script: - return CSMWorld::ColumnBase::Display_Script; - - - default: - return CSMWorld::ColumnBase::Display_None; - } +#include "tablemimedata.hpp" +#include + +#include "universalid.hpp" +#include "columnbase.hpp" + +CSMWorld::TableMimeData::TableMimeData (UniversalId id, const CSMDoc::Document& document) : +mDocument(document) +{ + mUniversalId.push_back (id); + mObjectsFormats << QString::fromStdString ("tabledata/" + id.getTypeName()); +} + +CSMWorld::TableMimeData::TableMimeData (std::vector< CSMWorld::UniversalId >& id, const CSMDoc::Document& document) : + mUniversalId (id), mDocument(document) +{ + for (std::vector::iterator it (mUniversalId.begin()); it != mUniversalId.end(); ++it) + { + mObjectsFormats << QString::fromStdString ("tabledata/" + it->getTypeName()); + } +} + +QStringList CSMWorld::TableMimeData::formats() const +{ + return mObjectsFormats; +} + +CSMWorld::TableMimeData::~TableMimeData() +{ +} + +std::string CSMWorld::TableMimeData::getIcon() const +{ + if (mUniversalId.empty()) + { + throw ("TableMimeData holds no UniversalId"); + } + + std::string tmpIcon; + bool firstIteration = true; + + for (unsigned i = 0; i < mUniversalId.size(); ++i) + { + if (firstIteration) + { + firstIteration = false; + tmpIcon = mUniversalId[i].getIcon(); + continue; + } + + if (tmpIcon != mUniversalId[i].getIcon()) + { + return ":/multitype.png"; //icon stolen from gnome + } + + tmpIcon = mUniversalId[i].getIcon(); + } + + return mUniversalId.begin()->getIcon(); //All objects are of the same type; +} + +std::vector< CSMWorld::UniversalId > CSMWorld::TableMimeData::getData() const +{ + return mUniversalId; +} + + +bool CSMWorld::TableMimeData::holdsType (CSMWorld::UniversalId::Type type) const +{ + for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) + { + if (it->getType() == type) + { + return true; + } + } + + return false; +} + +bool CSMWorld::TableMimeData::holdsType (CSMWorld::ColumnBase::Display type) const +{ + for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) + { + if (it->getType() == convertEnums (type)) + { + return true; + } + } + + return false; +} + +CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::UniversalId::Type type) const +{ + for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) + { + if (it->getType() == type) + { + return *it; + } + } + + throw ("TableMimeData object does not hold object of the seeked type"); +} + +CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::ColumnBase::Display type) const +{ + for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) + { + if (it->getType() == convertEnums (type)) + { + return *it; + } + } + + throw ("TableMimeData object does not hold object of the seeked type"); +} + +bool CSMWorld::TableMimeData::fromDocument (const CSMDoc::Document& document) const +{ + return &document == &mDocument; +} + +CSMWorld::UniversalId::Type CSMWorld::TableMimeData::convertEnums (CSMWorld::ColumnBase::Display type) +{ + switch (type) + { + case CSMWorld::ColumnBase::Display_Race: + return CSMWorld::UniversalId::Type_Race; + + + case CSMWorld::ColumnBase::Display_Skill: + return CSMWorld::UniversalId::Type_Skill; + + + case CSMWorld::ColumnBase::Display_Class: + return CSMWorld::UniversalId::Type_Class; + + + case CSMWorld::ColumnBase::Display_Faction: + return CSMWorld::UniversalId::Type_Faction; + + + case CSMWorld::ColumnBase::Display_Sound: + return CSMWorld::UniversalId::Type_Sound; + + + case CSMWorld::ColumnBase::Display_Region: + return CSMWorld::UniversalId::Type_Region; + + + case CSMWorld::ColumnBase::Display_Birthsign: + return CSMWorld::UniversalId::Type_Birthsign; + + + case CSMWorld::ColumnBase::Display_Spell: + return CSMWorld::UniversalId::Type_Spell; + + + case CSMWorld::ColumnBase::Display_Cell: + return CSMWorld::UniversalId::Type_Cell; + + + case CSMWorld::ColumnBase::Display_Referenceable: + return CSMWorld::UniversalId::Type_Referenceable; + + + case CSMWorld::ColumnBase::Display_Activator: + return CSMWorld::UniversalId::Type_Activator; + + + case CSMWorld::ColumnBase::Display_Potion: + return CSMWorld::UniversalId::Type_Potion; + + + case CSMWorld::ColumnBase::Display_Apparatus: + return CSMWorld::UniversalId::Type_Apparatus; + + + case CSMWorld::ColumnBase::Display_Armor: + return CSMWorld::UniversalId::Type_Armor; + + + case CSMWorld::ColumnBase::Display_Book: + return CSMWorld::UniversalId::Type_Book; + + + case CSMWorld::ColumnBase::Display_Clothing: + return CSMWorld::UniversalId::Type_Clothing; + + + case CSMWorld::ColumnBase::Display_Container: + return CSMWorld::UniversalId::Type_Container; + + + case CSMWorld::ColumnBase::Display_Creature: + return CSMWorld::UniversalId::Type_Creature; + + + case CSMWorld::ColumnBase::Display_Door: + return CSMWorld::UniversalId::Type_Door; + + + case CSMWorld::ColumnBase::Display_Ingredient: + return CSMWorld::UniversalId::Type_Ingredient; + + + case CSMWorld::ColumnBase::Display_CreatureLevelledList: + return CSMWorld::UniversalId::Type_CreatureLevelledList; + + + case CSMWorld::ColumnBase::Display_ItemLevelledList: + return CSMWorld::UniversalId::Type_ItemLevelledList; + + + case CSMWorld::ColumnBase::Display_Light: + return CSMWorld::UniversalId::Type_Light; + + + case CSMWorld::ColumnBase::Display_Lockpick: + return CSMWorld::UniversalId::Type_Lockpick; + + + case CSMWorld::ColumnBase::Display_Miscellaneous: + return CSMWorld::UniversalId::Type_Miscellaneous; + + + case CSMWorld::ColumnBase::Display_Npc: + return CSMWorld::UniversalId::Type_Npc; + + + case CSMWorld::ColumnBase::Display_Probe: + return CSMWorld::UniversalId::Type_Probe; + + + case CSMWorld::ColumnBase::Display_Repair: + return CSMWorld::UniversalId::Type_Repair; + + + case CSMWorld::ColumnBase::Display_Static: + return CSMWorld::UniversalId::Type_Static; + + + case CSMWorld::ColumnBase::Display_Weapon: + return CSMWorld::UniversalId::Type_Weapon; + + + case CSMWorld::ColumnBase::Display_Reference: + return CSMWorld::UniversalId::Type_Reference; + + + case CSMWorld::ColumnBase::Display_Filter: + return CSMWorld::UniversalId::Type_Filter; + + + case CSMWorld::ColumnBase::Display_Topic: + return CSMWorld::UniversalId::Type_Topic; + + + case CSMWorld::ColumnBase::Display_Journal: + return CSMWorld::UniversalId::Type_Journal; + + + case CSMWorld::ColumnBase::Display_TopicInfo: + return CSMWorld::UniversalId::Type_TopicInfo; + + + case CSMWorld::ColumnBase::Display_JournalInfo: + return CSMWorld::UniversalId::Type_JournalInfo; + + + case CSMWorld::ColumnBase::Display_Scene: + return CSMWorld::UniversalId::Type_Scene; + + + case CSMWorld::ColumnBase::Display_Script: + return CSMWorld::UniversalId::Type_Script; + + + default: + return CSMWorld::UniversalId::Type_None; + + } +} + +CSMWorld::ColumnBase::Display CSMWorld::TableMimeData::convertEnums (CSMWorld::UniversalId::Type type) +{ + switch (type) + { + case CSMWorld::UniversalId::Type_Race: + return CSMWorld::ColumnBase::Display_Race; + + + case CSMWorld::UniversalId::Type_Skill: + return CSMWorld::ColumnBase::Display_Skill; + + + case CSMWorld::UniversalId::Type_Class: + return CSMWorld::ColumnBase::Display_Class; + + + case CSMWorld::UniversalId::Type_Faction: + return CSMWorld::ColumnBase::Display_Faction; + + + case CSMWorld::UniversalId::Type_Sound: + return CSMWorld::ColumnBase::Display_Sound; + + + case CSMWorld::UniversalId::Type_Region: + return CSMWorld::ColumnBase::Display_Region; + + + case CSMWorld::UniversalId::Type_Birthsign: + return CSMWorld::ColumnBase::Display_Birthsign; + + + case CSMWorld::UniversalId::Type_Spell: + return CSMWorld::ColumnBase::Display_Spell; + + + case CSMWorld::UniversalId::Type_Cell: + return CSMWorld::ColumnBase::Display_Cell; + + + case CSMWorld::UniversalId::Type_Referenceable: + return CSMWorld::ColumnBase::Display_Referenceable; + + + case CSMWorld::UniversalId::Type_Activator: + return CSMWorld::ColumnBase::Display_Activator; + + + case CSMWorld::UniversalId::Type_Potion: + return CSMWorld::ColumnBase::Display_Potion; + + + case CSMWorld::UniversalId::Type_Apparatus: + return CSMWorld::ColumnBase::Display_Apparatus; + + + case CSMWorld::UniversalId::Type_Armor: + return CSMWorld::ColumnBase::Display_Armor; + + + case CSMWorld::UniversalId::Type_Book: + return CSMWorld::ColumnBase::Display_Book; + + + case CSMWorld::UniversalId::Type_Clothing: + return CSMWorld::ColumnBase::Display_Clothing; + + + case CSMWorld::UniversalId::Type_Container: + return CSMWorld::ColumnBase::Display_Container; + + + case CSMWorld::UniversalId::Type_Creature: + return CSMWorld::ColumnBase::Display_Creature; + + + case CSMWorld::UniversalId::Type_Door: + return CSMWorld::ColumnBase::Display_Door; + + + case CSMWorld::UniversalId::Type_Ingredient: + return CSMWorld::ColumnBase::Display_Ingredient; + + + case CSMWorld::UniversalId::Type_CreatureLevelledList: + return CSMWorld::ColumnBase::Display_CreatureLevelledList; + + + case CSMWorld::UniversalId::Type_ItemLevelledList: + return CSMWorld::ColumnBase::Display_ItemLevelledList; + + + case CSMWorld::UniversalId::Type_Light: + return CSMWorld::ColumnBase::Display_Light; + + + case CSMWorld::UniversalId::Type_Lockpick: + return CSMWorld::ColumnBase::Display_Lockpick; + + + case CSMWorld::UniversalId::Type_Miscellaneous: + return CSMWorld::ColumnBase::Display_Miscellaneous; + + + case CSMWorld::UniversalId::Type_Npc: + return CSMWorld::ColumnBase::Display_Npc; + + + case CSMWorld::UniversalId::Type_Probe: + return CSMWorld::ColumnBase::Display_Probe; + + + case CSMWorld::UniversalId::Type_Repair: + return CSMWorld::ColumnBase::Display_Repair; + + + case CSMWorld::UniversalId::Type_Static: + return CSMWorld::ColumnBase::Display_Static; + + + case CSMWorld::UniversalId::Type_Weapon: + return CSMWorld::ColumnBase::Display_Weapon; + + + case CSMWorld::UniversalId::Type_Reference: + return CSMWorld::ColumnBase::Display_Reference; + + + case CSMWorld::UniversalId::Type_Filter: + return CSMWorld::ColumnBase::Display_Filter; + + + case CSMWorld::UniversalId::Type_Topic: + return CSMWorld::ColumnBase::Display_Topic; + + + case CSMWorld::UniversalId::Type_Journal: + return CSMWorld::ColumnBase::Display_Journal; + + + case CSMWorld::UniversalId::Type_TopicInfo: + return CSMWorld::ColumnBase::Display_TopicInfo; + + + case CSMWorld::UniversalId::Type_JournalInfo: + return CSMWorld::ColumnBase::Display_JournalInfo; + + + case CSMWorld::UniversalId::Type_Scene: + return CSMWorld::ColumnBase::Display_Scene; + + + case CSMWorld::UniversalId::Type_Script: + return CSMWorld::ColumnBase::Display_Script; + + + default: + return CSMWorld::ColumnBase::Display_None; + } } \ No newline at end of file diff --git a/apps/opencs/model/world/tablemimedata.hpp b/apps/opencs/model/world/tablemimedata.hpp index 7687f3555..0b0143abd 100644 --- a/apps/opencs/model/world/tablemimedata.hpp +++ b/apps/opencs/model/world/tablemimedata.hpp @@ -1,63 +1,63 @@ - -#ifndef TABLEMIMEDATA_H -#define TABLEMIMEDATA_H - -#include - -#include -#include - -#include "universalid.hpp" -#include "columnbase.hpp" - -namespace CSMDoc -{ - class Document; -} - -namespace CSMWorld -{ - -/// \brief Subclass of QmimeData, augmented to contain and transport UniversalIds. -/// -/// This class provides way to construct mimedata object holding the universalid copy -/// Universalid is used in the majority of the tables to store type, id, argument types. -/// This way universalid grants a way to retrive record from the concrete table. -/// Please note, that tablemimedata object can hold multiple universalIds in the vector. - - class TableMimeData : public QMimeData - { - public: - TableMimeData(UniversalId id, const CSMDoc::Document& document); - - TableMimeData(std::vector& id, const CSMDoc::Document& document); - - ~TableMimeData(); - - virtual QStringList formats() const; - - std::string getIcon() const; - - std::vector getData() const; - - bool holdsType(UniversalId::Type type) const; - - bool holdsType(CSMWorld::ColumnBase::Display type) const; - - bool fromDocument(const CSMDoc::Document& document) const; - - UniversalId returnMatching(UniversalId::Type type) const; - - UniversalId returnMatching(CSMWorld::ColumnBase::Display type) const; - - static CSMWorld::UniversalId::Type convertEnums(CSMWorld::ColumnBase::Display type); - static CSMWorld::ColumnBase::Display convertEnums(CSMWorld::UniversalId::Type type); - - private: - std::vector mUniversalId; - QStringList mObjectsFormats; - const CSMDoc::Document& mDocument; - - }; -} + +#ifndef TABLEMIMEDATA_H +#define TABLEMIMEDATA_H + +#include + +#include +#include + +#include "universalid.hpp" +#include "columnbase.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + +/// \brief Subclass of QmimeData, augmented to contain and transport UniversalIds. +/// +/// This class provides way to construct mimedata object holding the universalid copy +/// Universalid is used in the majority of the tables to store type, id, argument types. +/// This way universalid grants a way to retrive record from the concrete table. +/// Please note, that tablemimedata object can hold multiple universalIds in the vector. + + class TableMimeData : public QMimeData + { + public: + TableMimeData(UniversalId id, const CSMDoc::Document& document); + + TableMimeData(std::vector& id, const CSMDoc::Document& document); + + ~TableMimeData(); + + virtual QStringList formats() const; + + std::string getIcon() const; + + std::vector getData() const; + + bool holdsType(UniversalId::Type type) const; + + bool holdsType(CSMWorld::ColumnBase::Display type) const; + + bool fromDocument(const CSMDoc::Document& document) const; + + UniversalId returnMatching(UniversalId::Type type) const; + + UniversalId returnMatching(CSMWorld::ColumnBase::Display type) const; + + static CSMWorld::UniversalId::Type convertEnums(CSMWorld::ColumnBase::Display type); + static CSMWorld::ColumnBase::Display convertEnums(CSMWorld::UniversalId::Type type); + + private: + std::vector mUniversalId; + QStringList mObjectsFormats; + const CSMDoc::Document& mDocument; + + }; +} #endif // TABLEMIMEDATA_H \ No newline at end of file diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index cc1578bdd..841919a9e 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -1,203 +1,203 @@ - -#include "editwidget.hpp" - -#include -#include -#include - -#include "../../model/world/data.hpp" - -CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) -: QLineEdit (parent), mParser (data) -{ - mPalette = palette(); - connect (this, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); - - QAbstractItemModel *model = data.getTableModel (CSMWorld::UniversalId::Type_Filters); - - connect (model, SIGNAL (dataChanged (const QModelIndex &, const QModelIndex&)), - this, SLOT (filterDataChanged (const QModelIndex &, const QModelIndex&)), - Qt::QueuedConnection); - connect (model, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), - this, SLOT (filterRowsRemoved (const QModelIndex&, int, int)), - Qt::QueuedConnection); - connect (model, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (filterRowsInserted (const QModelIndex&, int, int)), - Qt::QueuedConnection); -} - -void CSVFilter::EditWidget::textChanged (const QString& text) -{ - if (mParser.parse (text.toUtf8().constData())) - { - setPalette (mPalette); - emit filterChanged (mParser.getFilter()); - } - else - { - QPalette palette (mPalette); - palette.setColor (QPalette::Text, Qt::red); - setPalette (palette); - - /// \todo improve error reporting; mark only the faulty part - } -} - -void CSVFilter::EditWidget::filterDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) -{ - textChanged (text()); -} - -void CSVFilter::EditWidget::filterRowsRemoved (const QModelIndex& parent, int start, int end) -{ - textChanged (text()); -} - -void CSVFilter::EditWidget::filterRowsInserted (const QModelIndex& parent, int start, int end) -{ - textChanged (text()); -} - -void CSVFilter::EditWidget::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource, - Qt::DropAction action) -{ - const unsigned count = filterSource.size(); - bool multipleElements = false; - - switch (count) //setting multipleElements; - { - case 0: //empty - return; //nothing to do here - - case 1: //only single - multipleElements = false; - break; - - default: - multipleElements = true; - break; - } - - Qt::KeyboardModifiers key = QApplication::keyboardModifiers(); - QString oldContent (text()); - - bool replaceMode = false; - std::string orAnd; - - switch (key) //setting replaceMode and string used to glue expressions - { - case Qt::ShiftModifier: - orAnd = "!or("; - replaceMode = false; - break; - - case Qt::ControlModifier: - orAnd = "!and("; - replaceMode = false; - break; - - default: - replaceMode = true; - break; - } - - if (oldContent.isEmpty() || !oldContent.contains (QRegExp ("^!.*$", Qt::CaseInsensitive))) //if line edit is empty or it does not contain one shot filter go into replace mode - { - replaceMode = true; - } - - if (!replaceMode) - { - oldContent.remove ('!'); - } - - std::stringstream ss; - - if (multipleElements) - { - if (replaceMode) - { - ss<<"!or("; - } else { - ss << orAnd << oldContent.toStdString() << ','; - } - - for (unsigned i = 0; i < count; ++i) - { - ss<4) - { - clear(); - insert (QString::fromUtf8(ss.str().c_str())); - } -} - -std::string CSVFilter::EditWidget::generateFilter (std::pair< std::string, std::vector< std::string > >& seekedString) const -{ - const unsigned columns = seekedString.second.size(); - - bool multipleColumns = false; - switch (columns) - { - case 0: //empty - return ""; //no column to filter - - case 1: //one column to look for - multipleColumns = false; - break; - - default: - multipleColumns = true; - break; - } - - std::stringstream ss; - if (multipleColumns) - { - ss<<"or("; - for (unsigned i = 0; i < columns; ++i) - { - ss<<"string("<<'"'< +#include +#include + +#include "../../model/world/data.hpp" + +CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) +: QLineEdit (parent), mParser (data) +{ + mPalette = palette(); + connect (this, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); + + QAbstractItemModel *model = data.getTableModel (CSMWorld::UniversalId::Type_Filters); + + connect (model, SIGNAL (dataChanged (const QModelIndex &, const QModelIndex&)), + this, SLOT (filterDataChanged (const QModelIndex &, const QModelIndex&)), + Qt::QueuedConnection); + connect (model, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), + this, SLOT (filterRowsRemoved (const QModelIndex&, int, int)), + Qt::QueuedConnection); + connect (model, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (filterRowsInserted (const QModelIndex&, int, int)), + Qt::QueuedConnection); +} + +void CSVFilter::EditWidget::textChanged (const QString& text) +{ + if (mParser.parse (text.toUtf8().constData())) + { + setPalette (mPalette); + emit filterChanged (mParser.getFilter()); + } + else + { + QPalette palette (mPalette); + palette.setColor (QPalette::Text, Qt::red); + setPalette (palette); + + /// \todo improve error reporting; mark only the faulty part + } +} + +void CSVFilter::EditWidget::filterDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + textChanged (text()); +} + +void CSVFilter::EditWidget::filterRowsRemoved (const QModelIndex& parent, int start, int end) +{ + textChanged (text()); +} + +void CSVFilter::EditWidget::filterRowsInserted (const QModelIndex& parent, int start, int end) +{ + textChanged (text()); +} + +void CSVFilter::EditWidget::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource, + Qt::DropAction action) +{ + const unsigned count = filterSource.size(); + bool multipleElements = false; + + switch (count) //setting multipleElements; + { + case 0: //empty + return; //nothing to do here + + case 1: //only single + multipleElements = false; + break; + + default: + multipleElements = true; + break; + } + + Qt::KeyboardModifiers key = QApplication::keyboardModifiers(); + QString oldContent (text()); + + bool replaceMode = false; + std::string orAnd; + + switch (key) //setting replaceMode and string used to glue expressions + { + case Qt::ShiftModifier: + orAnd = "!or("; + replaceMode = false; + break; + + case Qt::ControlModifier: + orAnd = "!and("; + replaceMode = false; + break; + + default: + replaceMode = true; + break; + } + + if (oldContent.isEmpty() || !oldContent.contains (QRegExp ("^!.*$", Qt::CaseInsensitive))) //if line edit is empty or it does not contain one shot filter go into replace mode + { + replaceMode = true; + } + + if (!replaceMode) + { + oldContent.remove ('!'); + } + + std::stringstream ss; + + if (multipleElements) + { + if (replaceMode) + { + ss<<"!or("; + } else { + ss << orAnd << oldContent.toStdString() << ','; + } + + for (unsigned i = 0; i < count; ++i) + { + ss<4) + { + clear(); + insert (QString::fromUtf8(ss.str().c_str())); + } +} + +std::string CSVFilter::EditWidget::generateFilter (std::pair< std::string, std::vector< std::string > >& seekedString) const +{ + const unsigned columns = seekedString.second.size(); + + bool multipleColumns = false; + switch (columns) + { + case 0: //empty + return ""; //no column to filter + + case 1: //one column to look for + multipleColumns = false; + break; + + default: + multipleColumns = true; + break; + } + + std::stringstream ss; + if (multipleColumns) + { + ss<<"or("; + for (unsigned i = 0; i < columns; ++i) + { + ss<<"string("<<'"'< - -#include -#include -#include - -#include "../../model/filter/parser.hpp" -#include "../../model/filter/node.hpp" - -class QModelIndex; - -namespace CSMWorld -{ - class Data; -} - -namespace CSVFilter -{ - class EditWidget : public QLineEdit - { - Q_OBJECT - - CSMFilter::Parser mParser; - QPalette mPalette; - - public: - - EditWidget (CSMWorld::Data& data, QWidget *parent = 0); - - signals: - - void filterChanged (boost::shared_ptr filter); - - private: - std::string generateFilter(std::pair >& seekedString) const; - - private slots: - - void textChanged (const QString& text); - - void filterDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - - void filterRowsRemoved (const QModelIndex& parent, int start, int end); - - void filterRowsInserted (const QModelIndex& parent, int start, int end); - - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - - void useFilterRequest(const std::string& idOfFilter); - }; -} - -#endif +#ifndef CSV_FILTER_EDITWIDGET_H +#define CSV_FILTER_EDITWIDGET_H + +#include + +#include +#include +#include + +#include "../../model/filter/parser.hpp" +#include "../../model/filter/node.hpp" + +class QModelIndex; + +namespace CSMWorld +{ + class Data; +} + +namespace CSVFilter +{ + class EditWidget : public QLineEdit + { + Q_OBJECT + + CSMFilter::Parser mParser; + QPalette mPalette; + + public: + + EditWidget (CSMWorld::Data& data, QWidget *parent = 0); + + signals: + + void filterChanged (boost::shared_ptr filter); + + private: + std::string generateFilter(std::pair >& seekedString) const; + + private slots: + + void textChanged (const QString& text); + + void filterDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void filterRowsRemoved (const QModelIndex& parent, int start, int end); + + void filterRowsInserted (const QModelIndex& parent, int start, int end); + + void createFilterRequest(std::vector > >& filterSource, + Qt::DropAction action); + + void useFilterRequest(const std::string& idOfFilter); + }; +} + +#endif diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp index a33288025..4954efefb 100644 --- a/apps/opencs/view/filter/filterbox.cpp +++ b/apps/opencs/view/filter/filterbox.cpp @@ -1,50 +1,50 @@ - -#include "filterbox.hpp" - -#include -#include - -#include "recordfilterbox.hpp" - -#include - -CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent) -: QWidget (parent) -{ - QHBoxLayout *layout = new QHBoxLayout (this); - - layout->setContentsMargins (0, 0, 0, 0); - - RecordFilterBox *recordFilterBox = new RecordFilterBox (data, this); - - layout->addWidget (recordFilterBox); - - setLayout (layout); - - connect (recordFilterBox, - SIGNAL (filterChanged (boost::shared_ptr)), - this, SIGNAL (recordFilterChanged (boost::shared_ptr))); - - connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - recordFilterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); - - connect(this, SIGNAL(useFilterRequest(const std::string&)), recordFilterBox, SIGNAL(useFilterRequest(const std::string&))); - setAcceptDrops(true); -} - -void CSVFilter::FilterBox::dropEvent (QDropEvent* event) -{ - std::vector data = dynamic_cast (event->mimeData())->getData(); - - emit recordDropped(data, event->proposedAction()); -} - -void CSVFilter::FilterBox::dragEnterEvent (QDragEnterEvent* event) -{ - event->acceptProposedAction(); -} - -void CSVFilter::FilterBox::dragMoveEvent (QDragMoveEvent* event) -{ - event->accept(); -} + +#include "filterbox.hpp" + +#include +#include + +#include "recordfilterbox.hpp" + +#include + +CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent) +: QWidget (parent) +{ + QHBoxLayout *layout = new QHBoxLayout (this); + + layout->setContentsMargins (0, 0, 0, 0); + + RecordFilterBox *recordFilterBox = new RecordFilterBox (data, this); + + layout->addWidget (recordFilterBox); + + setLayout (layout); + + connect (recordFilterBox, + SIGNAL (filterChanged (boost::shared_ptr)), + this, SIGNAL (recordFilterChanged (boost::shared_ptr))); + + connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), + recordFilterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); + + connect(this, SIGNAL(useFilterRequest(const std::string&)), recordFilterBox, SIGNAL(useFilterRequest(const std::string&))); + setAcceptDrops(true); +} + +void CSVFilter::FilterBox::dropEvent (QDropEvent* event) +{ + std::vector data = dynamic_cast (event->mimeData())->getData(); + + emit recordDropped(data, event->proposedAction()); +} + +void CSVFilter::FilterBox::dragEnterEvent (QDragEnterEvent* event) +{ + event->acceptProposedAction(); +} + +void CSVFilter::FilterBox::dragMoveEvent (QDragMoveEvent* event) +{ + event->accept(); +} diff --git a/apps/opencs/view/filter/filterbox.hpp b/apps/opencs/view/filter/filterbox.hpp index 3817d5e70..263e89e73 100644 --- a/apps/opencs/view/filter/filterbox.hpp +++ b/apps/opencs/view/filter/filterbox.hpp @@ -1,45 +1,45 @@ -#ifndef CSV_FILTER_FILTERBOX_H -#define CSV_FILTER_FILTERBOX_H - -#include - -#include -#include - -#include "../../model/filter/node.hpp" -#include "../../model/world/universalid.hpp" - -namespace CSMWorld -{ - class Data; -} - -namespace CSVFilter -{ - class FilterBox : public QWidget - { - Q_OBJECT - - void dragEnterEvent (QDragEnterEvent* event); - - void dropEvent (QDropEvent* event); - - void dragMoveEvent(QDragMoveEvent *event); - - public: - - FilterBox (CSMWorld::Data& data, QWidget *parent = 0); - - signals: - - void recordFilterChanged (boost::shared_ptr filter); - void recordDropped (std::vector& types, Qt::DropAction action); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - void useFilterRequest(const std::string& idOfFilter); - }; - -} - -#endif - +#ifndef CSV_FILTER_FILTERBOX_H +#define CSV_FILTER_FILTERBOX_H + +#include + +#include +#include + +#include "../../model/filter/node.hpp" +#include "../../model/world/universalid.hpp" + +namespace CSMWorld +{ + class Data; +} + +namespace CSVFilter +{ + class FilterBox : public QWidget + { + Q_OBJECT + + void dragEnterEvent (QDragEnterEvent* event); + + void dropEvent (QDropEvent* event); + + void dragMoveEvent(QDragMoveEvent *event); + + public: + + FilterBox (CSMWorld::Data& data, QWidget *parent = 0); + + signals: + + void recordFilterChanged (boost::shared_ptr filter); + void recordDropped (std::vector& types, Qt::DropAction action); + void createFilterRequest(std::vector > >& filterSource, + Qt::DropAction action); + void useFilterRequest(const std::string& idOfFilter); + }; + +} + +#endif + diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp index 2a1a1407f..59c083bd8 100644 --- a/apps/opencs/view/filter/recordfilterbox.cpp +++ b/apps/opencs/view/filter/recordfilterbox.cpp @@ -1,32 +1,32 @@ - -#include "recordfilterbox.hpp" - -#include -#include - -#include "editwidget.hpp" - -CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *parent) -: QWidget (parent) -{ - QHBoxLayout *layout = new QHBoxLayout (this); - - layout->setContentsMargins (0, 0, 0, 0); - - layout->addWidget (new QLabel ("Record Filter", this)); - - EditWidget *editWidget = new EditWidget (data, this); - - layout->addWidget (editWidget); - - setLayout (layout); - - connect ( - editWidget, SIGNAL (filterChanged (boost::shared_ptr)), - this, SIGNAL (filterChanged (boost::shared_ptr))); - - connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - editWidget, SLOT(createFilterRequest(std::vector > >&, Qt::DropAction))); - - connect(this, SIGNAL(useFilterRequest(const std::string&)), editWidget, SLOT(useFilterRequest(const std::string&))); -} + +#include "recordfilterbox.hpp" + +#include +#include + +#include "editwidget.hpp" + +CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *parent) +: QWidget (parent) +{ + QHBoxLayout *layout = new QHBoxLayout (this); + + layout->setContentsMargins (0, 0, 0, 0); + + layout->addWidget (new QLabel ("Record Filter", this)); + + EditWidget *editWidget = new EditWidget (data, this); + + layout->addWidget (editWidget); + + setLayout (layout); + + connect ( + editWidget, SIGNAL (filterChanged (boost::shared_ptr)), + this, SIGNAL (filterChanged (boost::shared_ptr))); + + connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), + editWidget, SLOT(createFilterRequest(std::vector > >&, Qt::DropAction))); + + connect(this, SIGNAL(useFilterRequest(const std::string&)), editWidget, SLOT(useFilterRequest(const std::string&))); +} diff --git a/apps/opencs/view/filter/recordfilterbox.hpp b/apps/opencs/view/filter/recordfilterbox.hpp index 3638dc6c3..fd384a32b 100644 --- a/apps/opencs/view/filter/recordfilterbox.hpp +++ b/apps/opencs/view/filter/recordfilterbox.hpp @@ -1,38 +1,38 @@ -#ifndef CSV_FILTER_RECORDFILTERBOX_H -#define CSV_FILTER_RECORDFILTERBOX_H - -#include - -#include -#include - -#include - -#include "../../model/filter/node.hpp" - -namespace CSMWorld -{ - class Data; -} - -namespace CSVFilter -{ - class RecordFilterBox : public QWidget - { - Q_OBJECT - - public: - - RecordFilterBox (CSMWorld::Data& data, QWidget *parent = 0); - - signals: - - void filterChanged (boost::shared_ptr filter); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - void useFilterRequest(const std::string& idOfFilter); - }; - -} - +#ifndef CSV_FILTER_RECORDFILTERBOX_H +#define CSV_FILTER_RECORDFILTERBOX_H + +#include + +#include +#include + +#include + +#include "../../model/filter/node.hpp" + +namespace CSMWorld +{ + class Data; +} + +namespace CSVFilter +{ + class RecordFilterBox : public QWidget + { + Q_OBJECT + + public: + + RecordFilterBox (CSMWorld::Data& data, QWidget *parent = 0); + + signals: + + void filterChanged (boost::shared_ptr filter); + void createFilterRequest(std::vector > >& filterSource, + Qt::DropAction action); + void useFilterRequest(const std::string& idOfFilter); + }; + +} + #endif \ No newline at end of file diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index edf3bc6de..bc67c68b5 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -1,546 +1,546 @@ - -#include "table.hpp" - -#include - -#include -#include -#include -#include -#include -#include - -#include "../../model/world/data.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/idtableproxymodel.hpp" -#include "../../model/world/idtable.hpp" -#include "../../model/world/record.hpp" -#include "../../model/world/columns.hpp" -#include "../../model/world/tablemimedata.hpp" -#include "../../model/world/tablemimedata.hpp" - -#include "recordstatusdelegate.hpp" -#include "util.hpp" - -void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) -{ - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - QMenu menu (this); - - /// \todo add menu items for select all and clear selection - - if (!mEditLock) - { - if (selectedRows.size()==1) - { - menu.addAction (mEditAction); - if (mCreateAction) - menu.addAction(mCloneAction); - } - - if (mCreateAction) - menu.addAction (mCreateAction); - - /// \todo Reverting temporarily disabled on tables that support reordering, because - /// revert logic currently can not handle reordering. - if (mModel->getReordering()==CSMWorld::IdTable::Reordering_None) - if (listRevertableSelectedIds().size()>0) - menu.addAction (mRevertAction); - - if (listDeletableSelectedIds().size()>0) - menu.addAction (mDeleteAction); - - if (mModel->getReordering()==CSMWorld::IdTable::Reordering_WithinTopic) - { - /// \todo allow reordering of multiple rows - if (selectedRows.size()==1) - { - int row =selectedRows.begin()->row(); - - int column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Topic); - - if (column==-1) - column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Journal); - - if (column!=-1) - { - if (row>0 && mProxyModel->data (mProxyModel->index (row, column))== - mProxyModel->data (mProxyModel->index (row-1, column))) - { - menu.addAction (mMoveUpAction); - } - - if (rowrowCount()-1 && mProxyModel->data (mProxyModel->index (row, column))== - mProxyModel->data (mProxyModel->index (row+1, column))) - { - menu.addAction (mMoveDownAction); - } - } - } - } - } - - menu.exec (event->globalPos()); -} - -std::vector CSVWorld::Table::listRevertableSelectedIds() const -{ - std::vector revertableIds; - - if (mProxyModel->columnCount()>0) - { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) - { - QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); - - CSMWorld::RecordBase::State state = - static_cast ( - mModel->data (mModel->index (index.row(), 1)).toInt()); - - if (state!=CSMWorld::RecordBase::State_BaseOnly) - { - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - - std::string id = mModel->data (mModel->index (index.row(), columnIndex)). - toString().toUtf8().constData(); - - revertableIds.push_back (id); - } - } - } - - return revertableIds; -} - -std::vector CSVWorld::Table::listDeletableSelectedIds() const -{ - std::vector deletableIds; - - if (mProxyModel->columnCount()>0) - { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) - { - QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); - - // check record state - CSMWorld::RecordBase::State state = - static_cast ( - mModel->data (mModel->index (index.row(), 1)).toInt()); - - if (state==CSMWorld::RecordBase::State_Deleted) - continue; - - // check other columns (only relevant for a subset of the tables) - int dialogueTypeIndex = - mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_DialogueType); - - if (dialogueTypeIndex!=-1) - { - int type = mModel->data (mModel->index (index.row(), dialogueTypeIndex)).toInt(); - - if (type!=ESM::Dialogue::Topic && type!=ESM::Dialogue::Journal) - continue; - } - - // add the id to the collection - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - - std::string id = mModel->data (mModel->index (index.row(), columnIndex)). - toString().toUtf8().constData(); - - deletableIds.push_back (id); - } - } - - return deletableIds; -} - -CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, - bool createAndDelete, bool sorting, const CSMDoc::Document& document) - : mUndoStack (undoStack), mCreateAction (0), mCloneAction(0), mEditLock (false), mRecordStatusDisplay (0), mDocument(document) -{ - mModel = &dynamic_cast (*data.getTableModel (id)); - - mProxyModel = new CSMWorld::IdTableProxyModel (this); - mProxyModel->setSourceModel (mModel); - - setModel (mProxyModel); - horizontalHeader()->setResizeMode (QHeaderView::Interactive); - verticalHeader()->hide(); - setSortingEnabled (sorting); - setSelectionBehavior (QAbstractItemView::SelectRows); - setSelectionMode (QAbstractItemView::ExtendedSelection); - - int columns = mModel->columnCount(); - - for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); - - if (flags & CSMWorld::ColumnBase::Flag_Table) - { - CSMWorld::ColumnBase::Display display = static_cast ( - mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - - CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate (display, - undoStack, this); - - mDelegates.push_back (delegate); - setItemDelegateForColumn (i, delegate); - } - else - hideColumn (i); - } - - mEditAction = new QAction (tr ("Edit Record"), this); - connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord())); - addAction (mEditAction); - - if (createAndDelete) - { - mCreateAction = new QAction (tr ("Add Record"), this); - connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest())); - addAction (mCreateAction); - - mCloneAction = new QAction (tr ("Clone Record"), this); - connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord())); - addAction(mCloneAction); - } - - mRevertAction = new QAction (tr ("Revert Record"), this); - connect (mRevertAction, SIGNAL (triggered()), this, SLOT (revertRecord())); - addAction (mRevertAction); - - mDeleteAction = new QAction (tr ("Delete Record"), this); - connect (mDeleteAction, SIGNAL (triggered()), this, SLOT (deleteRecord())); - addAction (mDeleteAction); - - mMoveUpAction = new QAction (tr ("Move Up"), this); - connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord())); - addAction (mMoveUpAction); - - mMoveDownAction = new QAction (tr ("Move Down"), this); - connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); - addAction (mMoveDownAction); - - connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (tableSizeUpdate())); - - /// \note This signal could instead be connected to a slot that filters out changes not affecting - /// the records status column (for permanence reasons) - connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (tableSizeUpdate())); - - connect (selectionModel(), SIGNAL (selectionChanged (const QItemSelection&, const QItemSelection&)), - this, SLOT (selectionSizeUpdate ())); - - setAcceptDrops(true); -} - -void CSVWorld::Table::setEditLock (bool locked) -{ - for (std::vector::iterator iter (mDelegates.begin()); iter!=mDelegates.end(); ++iter) - (*iter)->setEditLock (locked); - - mEditLock = locked; -} - -CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const -{ - return CSMWorld::UniversalId ( - static_cast (mProxyModel->data (mProxyModel->index (row, 2)).toInt()), - mProxyModel->data (mProxyModel->index (row, 0)).toString().toStdString()); -} - -void CSVWorld::Table::revertRecord() -{ - if (!mEditLock) - { - std::vector revertableIds = listRevertableSelectedIds(); - - if (revertableIds.size()>0) - { - if (revertableIds.size()>1) - mUndoStack.beginMacro (tr ("Revert multiple records")); - - for (std::vector::const_iterator iter (revertableIds.begin()); iter!=revertableIds.end(); ++iter) - mUndoStack.push (new CSMWorld::RevertCommand (*mModel, *iter)); - - if (revertableIds.size()>1) - mUndoStack.endMacro(); - } - } -} - -void CSVWorld::Table::deleteRecord() -{ - if (!mEditLock) - { - std::vector deletableIds = listDeletableSelectedIds(); - - if (deletableIds.size()>0) - { - if (deletableIds.size()>1) - mUndoStack.beginMacro (tr ("Delete multiple records")); - - for (std::vector::const_iterator iter (deletableIds.begin()); iter!=deletableIds.end(); ++iter) - mUndoStack.push (new CSMWorld::DeleteCommand (*mModel, *iter)); - - if (deletableIds.size()>1) - mUndoStack.endMacro(); - } - } -} - -void CSVWorld::Table::editRecord() -{ - if (!mEditLock) - { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - if (selectedRows.size()==1) - emit editRequest (selectedRows.begin()->row()); - } -} - -void CSVWorld::Table::cloneRecord() -{ - if (!mEditLock) - { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - const CSMWorld::UniversalId& toClone = getUniversalId(selectedRows.begin()->row()); - if (selectedRows.size()==1 && !mModel->getRecord(toClone.getId()).isDeleted()) - { - emit cloneRequest (toClone); - } - } -} - -void CSVWorld::Table::moveUpRecord() -{ - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - if (selectedRows.size()==1) - { - int row2 =selectedRows.begin()->row(); - - if (row2>0) - { - int row = row2-1; - - row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); - row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); - - if (row2<=row) - throw std::runtime_error ("Inconsistent row order"); - - std::vector newOrder (row2-row+1); - newOrder[0] = row2-row; - newOrder[row2-row] = 0; - for (int i=1; iselectedRows(); - - if (selectedRows.size()==1) - { - int row =selectedRows.begin()->row(); - - if (rowrowCount()-1) - { - int row2 = row+1; - - row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); - row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); - - if (row2<=row) - throw std::runtime_error ("Inconsistent row order"); - - std::vector newOrder (row2-row+1); - newOrder[0] = row2-row; - newOrder[row2-row] = 0; - for (int i=1; icolumnCount(); - - for (int i=0; i (*delegate). - updateEditorSetting (settingName, settingValue)) - emit dataChanged (mModel->index (0, i), mModel->index (mModel->rowCount()-1, i)); -} - -void CSVWorld::Table::tableSizeUpdate() -{ - int size = 0; - int deleted = 0; - int modified = 0; - - if (mProxyModel->columnCount()>0) - { - int rows = mProxyModel->rowCount(); - - for (int i=0; imapToSource (mProxyModel->index (i, 0)); - - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); - int state = mModel->data (mModel->index (index.row(), columnIndex)).toInt(); - - switch (state) - { - case CSMWorld::RecordBase::State_BaseOnly: ++size; break; - case CSMWorld::RecordBase::State_Modified: ++size; ++modified; break; - case CSMWorld::RecordBase::State_ModifiedOnly: ++size; ++modified; break; - case CSMWorld::RecordBase:: State_Deleted: ++deleted; ++modified; break; - } - } - } - - tableSizeChanged (size, deleted, modified); -} - -void CSVWorld::Table::selectionSizeUpdate() -{ - selectionSizeChanged (selectionModel()->selectedRows().size()); -} - -void CSVWorld::Table::requestFocus (const std::string& id) -{ - QModelIndex index = mProxyModel->getModelIndex (id, 0); - - if (index.isValid()) - scrollTo (index, QAbstractItemView::PositionAtTop); -} - -void CSVWorld::Table::recordFilterChanged (boost::shared_ptr filter) -{ - mProxyModel->setFilter (filter); -} - -void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event) -{ - if (event->buttons() & Qt::LeftButton) - { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - if (selectedRows.size() == 0) - { - return; - } - - QDrag* drag = new QDrag (this); - CSMWorld::TableMimeData* mime = NULL; - - if (selectedRows.size() == 1) - { - mime = new CSMWorld::TableMimeData (getUniversalId (selectedRows.begin()->row()), mDocument); - } - else - { - std::vector idToDrag; - - foreach (QModelIndex it, selectedRows) //I had a dream. Dream where you could use C++11 in OpenMW. - { - idToDrag.push_back (getUniversalId (it.row())); - } - - mime = new CSMWorld::TableMimeData (idToDrag, mDocument); - } - - drag->setMimeData (mime); - drag->setPixmap (QString::fromStdString (mime->getIcon())); - - Qt::DropActions action = Qt::IgnoreAction; - switch (QApplication::keyboardModifiers()) - { - case Qt::ControlModifier: - action = Qt::CopyAction; - break; - - case Qt::ShiftModifier: - action = Qt::MoveAction; - break; - } - - drag->exec(action); - } - -} - -void CSVWorld::Table::dragEnterEvent(QDragEnterEvent *event) -{ - event->acceptProposedAction(); -} - -void CSVWorld::Table::dropEvent(QDropEvent *event) -{ - QModelIndex index = indexAt (event->pos()); - - if (!index.isValid()) - { - return; - } - - const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); - if (mime->fromDocument (mDocument)) - { - CSMWorld::ColumnBase::Display display = static_cast - (mModel->headerData (index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - - if (mime->holdsType (display)) - { - CSMWorld::UniversalId record (mime->returnMatching (display)); - - std::auto_ptr command (new CSMWorld::ModifyCommand - (*mProxyModel, index, QVariant (QString::fromUtf8 (record.getId().c_str())))); - - mUndoStack.push (command.release()); - } - } //TODO handle drops from different document -} - -void CSVWorld::Table::dragMoveEvent(QDragMoveEvent *event) -{ - event->accept(); -} - -std::vector CSVWorld::Table::getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const -{ - const int count = mModel->columnCount(); - - std::vector titles; - for (int i = 0; i < count; ++i) - { - CSMWorld::ColumnBase::Display columndisplay = static_cast - (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - - if (display == columndisplay) - { - titles.push_back(mModel->headerData (i, Qt::Horizontal).toString().toStdString()); - } - } - return titles; + +#include "table.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include "../../model/world/data.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/idtableproxymodel.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/record.hpp" +#include "../../model/world/columns.hpp" +#include "../../model/world/tablemimedata.hpp" +#include "../../model/world/tablemimedata.hpp" + +#include "recordstatusdelegate.hpp" +#include "util.hpp" + +void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + QMenu menu (this); + + /// \todo add menu items for select all and clear selection + + if (!mEditLock) + { + if (selectedRows.size()==1) + { + menu.addAction (mEditAction); + if (mCreateAction) + menu.addAction(mCloneAction); + } + + if (mCreateAction) + menu.addAction (mCreateAction); + + /// \todo Reverting temporarily disabled on tables that support reordering, because + /// revert logic currently can not handle reordering. + if (mModel->getReordering()==CSMWorld::IdTable::Reordering_None) + if (listRevertableSelectedIds().size()>0) + menu.addAction (mRevertAction); + + if (listDeletableSelectedIds().size()>0) + menu.addAction (mDeleteAction); + + if (mModel->getReordering()==CSMWorld::IdTable::Reordering_WithinTopic) + { + /// \todo allow reordering of multiple rows + if (selectedRows.size()==1) + { + int row =selectedRows.begin()->row(); + + int column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Topic); + + if (column==-1) + column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Journal); + + if (column!=-1) + { + if (row>0 && mProxyModel->data (mProxyModel->index (row, column))== + mProxyModel->data (mProxyModel->index (row-1, column))) + { + menu.addAction (mMoveUpAction); + } + + if (rowrowCount()-1 && mProxyModel->data (mProxyModel->index (row, column))== + mProxyModel->data (mProxyModel->index (row+1, column))) + { + menu.addAction (mMoveDownAction); + } + } + } + } + } + + menu.exec (event->globalPos()); +} + +std::vector CSVWorld::Table::listRevertableSelectedIds() const +{ + std::vector revertableIds; + + if (mProxyModel->columnCount()>0) + { + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + ++iter) + { + QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); + + CSMWorld::RecordBase::State state = + static_cast ( + mModel->data (mModel->index (index.row(), 1)).toInt()); + + if (state!=CSMWorld::RecordBase::State_BaseOnly) + { + int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + std::string id = mModel->data (mModel->index (index.row(), columnIndex)). + toString().toUtf8().constData(); + + revertableIds.push_back (id); + } + } + } + + return revertableIds; +} + +std::vector CSVWorld::Table::listDeletableSelectedIds() const +{ + std::vector deletableIds; + + if (mProxyModel->columnCount()>0) + { + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + ++iter) + { + QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); + + // check record state + CSMWorld::RecordBase::State state = + static_cast ( + mModel->data (mModel->index (index.row(), 1)).toInt()); + + if (state==CSMWorld::RecordBase::State_Deleted) + continue; + + // check other columns (only relevant for a subset of the tables) + int dialogueTypeIndex = + mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_DialogueType); + + if (dialogueTypeIndex!=-1) + { + int type = mModel->data (mModel->index (index.row(), dialogueTypeIndex)).toInt(); + + if (type!=ESM::Dialogue::Topic && type!=ESM::Dialogue::Journal) + continue; + } + + // add the id to the collection + int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + std::string id = mModel->data (mModel->index (index.row(), columnIndex)). + toString().toUtf8().constData(); + + deletableIds.push_back (id); + } + } + + return deletableIds; +} + +CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, + bool createAndDelete, bool sorting, const CSMDoc::Document& document) + : mUndoStack (undoStack), mCreateAction (0), mCloneAction(0), mEditLock (false), mRecordStatusDisplay (0), mDocument(document) +{ + mModel = &dynamic_cast (*data.getTableModel (id)); + + mProxyModel = new CSMWorld::IdTableProxyModel (this); + mProxyModel->setSourceModel (mModel); + + setModel (mProxyModel); + horizontalHeader()->setResizeMode (QHeaderView::Interactive); + verticalHeader()->hide(); + setSortingEnabled (sorting); + setSelectionBehavior (QAbstractItemView::SelectRows); + setSelectionMode (QAbstractItemView::ExtendedSelection); + + int columns = mModel->columnCount(); + + for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); + + if (flags & CSMWorld::ColumnBase::Flag_Table) + { + CSMWorld::ColumnBase::Display display = static_cast ( + mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + + CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate (display, + undoStack, this); + + mDelegates.push_back (delegate); + setItemDelegateForColumn (i, delegate); + } + else + hideColumn (i); + } + + mEditAction = new QAction (tr ("Edit Record"), this); + connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord())); + addAction (mEditAction); + + if (createAndDelete) + { + mCreateAction = new QAction (tr ("Add Record"), this); + connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest())); + addAction (mCreateAction); + + mCloneAction = new QAction (tr ("Clone Record"), this); + connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord())); + addAction(mCloneAction); + } + + mRevertAction = new QAction (tr ("Revert Record"), this); + connect (mRevertAction, SIGNAL (triggered()), this, SLOT (revertRecord())); + addAction (mRevertAction); + + mDeleteAction = new QAction (tr ("Delete Record"), this); + connect (mDeleteAction, SIGNAL (triggered()), this, SLOT (deleteRecord())); + addAction (mDeleteAction); + + mMoveUpAction = new QAction (tr ("Move Up"), this); + connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord())); + addAction (mMoveUpAction); + + mMoveDownAction = new QAction (tr ("Move Down"), this); + connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); + addAction (mMoveDownAction); + + connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (tableSizeUpdate())); + + /// \note This signal could instead be connected to a slot that filters out changes not affecting + /// the records status column (for permanence reasons) + connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (tableSizeUpdate())); + + connect (selectionModel(), SIGNAL (selectionChanged (const QItemSelection&, const QItemSelection&)), + this, SLOT (selectionSizeUpdate ())); + + setAcceptDrops(true); +} + +void CSVWorld::Table::setEditLock (bool locked) +{ + for (std::vector::iterator iter (mDelegates.begin()); iter!=mDelegates.end(); ++iter) + (*iter)->setEditLock (locked); + + mEditLock = locked; +} + +CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const +{ + return CSMWorld::UniversalId ( + static_cast (mProxyModel->data (mProxyModel->index (row, 2)).toInt()), + mProxyModel->data (mProxyModel->index (row, 0)).toString().toStdString()); +} + +void CSVWorld::Table::revertRecord() +{ + if (!mEditLock) + { + std::vector revertableIds = listRevertableSelectedIds(); + + if (revertableIds.size()>0) + { + if (revertableIds.size()>1) + mUndoStack.beginMacro (tr ("Revert multiple records")); + + for (std::vector::const_iterator iter (revertableIds.begin()); iter!=revertableIds.end(); ++iter) + mUndoStack.push (new CSMWorld::RevertCommand (*mModel, *iter)); + + if (revertableIds.size()>1) + mUndoStack.endMacro(); + } + } +} + +void CSVWorld::Table::deleteRecord() +{ + if (!mEditLock) + { + std::vector deletableIds = listDeletableSelectedIds(); + + if (deletableIds.size()>0) + { + if (deletableIds.size()>1) + mUndoStack.beginMacro (tr ("Delete multiple records")); + + for (std::vector::const_iterator iter (deletableIds.begin()); iter!=deletableIds.end(); ++iter) + mUndoStack.push (new CSMWorld::DeleteCommand (*mModel, *iter)); + + if (deletableIds.size()>1) + mUndoStack.endMacro(); + } + } +} + +void CSVWorld::Table::editRecord() +{ + if (!mEditLock) + { + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + if (selectedRows.size()==1) + emit editRequest (selectedRows.begin()->row()); + } +} + +void CSVWorld::Table::cloneRecord() +{ + if (!mEditLock) + { + QModelIndexList selectedRows = selectionModel()->selectedRows(); + const CSMWorld::UniversalId& toClone = getUniversalId(selectedRows.begin()->row()); + if (selectedRows.size()==1 && !mModel->getRecord(toClone.getId()).isDeleted()) + { + emit cloneRequest (toClone); + } + } +} + +void CSVWorld::Table::moveUpRecord() +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + if (selectedRows.size()==1) + { + int row2 =selectedRows.begin()->row(); + + if (row2>0) + { + int row = row2-1; + + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); + + if (row2<=row) + throw std::runtime_error ("Inconsistent row order"); + + std::vector newOrder (row2-row+1); + newOrder[0] = row2-row; + newOrder[row2-row] = 0; + for (int i=1; iselectedRows(); + + if (selectedRows.size()==1) + { + int row =selectedRows.begin()->row(); + + if (rowrowCount()-1) + { + int row2 = row+1; + + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); + + if (row2<=row) + throw std::runtime_error ("Inconsistent row order"); + + std::vector newOrder (row2-row+1); + newOrder[0] = row2-row; + newOrder[row2-row] = 0; + for (int i=1; icolumnCount(); + + for (int i=0; i (*delegate). + updateEditorSetting (settingName, settingValue)) + emit dataChanged (mModel->index (0, i), mModel->index (mModel->rowCount()-1, i)); +} + +void CSVWorld::Table::tableSizeUpdate() +{ + int size = 0; + int deleted = 0; + int modified = 0; + + if (mProxyModel->columnCount()>0) + { + int rows = mProxyModel->rowCount(); + + for (int i=0; imapToSource (mProxyModel->index (i, 0)); + + int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + int state = mModel->data (mModel->index (index.row(), columnIndex)).toInt(); + + switch (state) + { + case CSMWorld::RecordBase::State_BaseOnly: ++size; break; + case CSMWorld::RecordBase::State_Modified: ++size; ++modified; break; + case CSMWorld::RecordBase::State_ModifiedOnly: ++size; ++modified; break; + case CSMWorld::RecordBase:: State_Deleted: ++deleted; ++modified; break; + } + } + } + + tableSizeChanged (size, deleted, modified); +} + +void CSVWorld::Table::selectionSizeUpdate() +{ + selectionSizeChanged (selectionModel()->selectedRows().size()); +} + +void CSVWorld::Table::requestFocus (const std::string& id) +{ + QModelIndex index = mProxyModel->getModelIndex (id, 0); + + if (index.isValid()) + scrollTo (index, QAbstractItemView::PositionAtTop); +} + +void CSVWorld::Table::recordFilterChanged (boost::shared_ptr filter) +{ + mProxyModel->setFilter (filter); +} + +void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event) +{ + if (event->buttons() & Qt::LeftButton) + { + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + if (selectedRows.size() == 0) + { + return; + } + + QDrag* drag = new QDrag (this); + CSMWorld::TableMimeData* mime = NULL; + + if (selectedRows.size() == 1) + { + mime = new CSMWorld::TableMimeData (getUniversalId (selectedRows.begin()->row()), mDocument); + } + else + { + std::vector idToDrag; + + foreach (QModelIndex it, selectedRows) //I had a dream. Dream where you could use C++11 in OpenMW. + { + idToDrag.push_back (getUniversalId (it.row())); + } + + mime = new CSMWorld::TableMimeData (idToDrag, mDocument); + } + + drag->setMimeData (mime); + drag->setPixmap (QString::fromStdString (mime->getIcon())); + + Qt::DropActions action = Qt::IgnoreAction; + switch (QApplication::keyboardModifiers()) + { + case Qt::ControlModifier: + action = Qt::CopyAction; + break; + + case Qt::ShiftModifier: + action = Qt::MoveAction; + break; + } + + drag->exec(action); + } + +} + +void CSVWorld::Table::dragEnterEvent(QDragEnterEvent *event) +{ + event->acceptProposedAction(); +} + +void CSVWorld::Table::dropEvent(QDropEvent *event) +{ + QModelIndex index = indexAt (event->pos()); + + if (!index.isValid()) + { + return; + } + + const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (mime->fromDocument (mDocument)) + { + CSMWorld::ColumnBase::Display display = static_cast + (mModel->headerData (index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + + if (mime->holdsType (display)) + { + CSMWorld::UniversalId record (mime->returnMatching (display)); + + std::auto_ptr command (new CSMWorld::ModifyCommand + (*mProxyModel, index, QVariant (QString::fromUtf8 (record.getId().c_str())))); + + mUndoStack.push (command.release()); + } + } //TODO handle drops from different document +} + +void CSVWorld::Table::dragMoveEvent(QDragMoveEvent *event) +{ + event->accept(); +} + +std::vector CSVWorld::Table::getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const +{ + const int count = mModel->columnCount(); + + std::vector titles; + for (int i = 0; i < count; ++i) + { + CSMWorld::ColumnBase::Display columndisplay = static_cast + (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + + if (display == columndisplay) + { + titles.push_back(mModel->headerData (i, Qt::Horizontal).toString().toStdString()); + } + } + return titles; } \ No newline at end of file diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 615a31b4d..04733a53e 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -1,127 +1,127 @@ -#ifndef CSV_WORLD_TABLE_H -#define CSV_WORLD_TABLE_H - -#include -#include - -#include -#include - -#include "../../model/filter/node.hpp" -#include "../../model/world/columnbase.hpp" - -namespace CSMDoc { - class Document; -} - -class QUndoStack; -class QAction; - -namespace CSMWorld -{ - class Data; - class UniversalId; - class IdTableProxyModel; - class IdTable; -} - -namespace CSVWorld -{ - class CommandDelegate; - - ///< Table widget - class Table : public QTableView - { - Q_OBJECT - - std::vector mDelegates; - QUndoStack& mUndoStack; - QAction *mEditAction; - QAction *mCreateAction; - QAction *mCloneAction; - QAction *mRevertAction; - QAction *mDeleteAction; - QAction *mMoveUpAction; - QAction *mMoveDownAction; - CSMWorld::IdTableProxyModel *mProxyModel; - CSMWorld::IdTable *mModel; - bool mEditLock; - int mRecordStatusDisplay; - - /// \brief This variable is used exclusivly for checking if dropEvents came from the same document. Most likely you - /// should NOT use it for anything else. - const CSMDoc::Document& mDocument; - - private: - - void contextMenuEvent (QContextMenuEvent *event); - - std::vector listRevertableSelectedIds() const; - - std::vector listDeletableSelectedIds() const; - - void mouseMoveEvent(QMouseEvent *event); - - void dragEnterEvent(QDragEnterEvent *event); - - void dragMoveEvent(QDragMoveEvent *event); - - void dropEvent(QDropEvent *event); - - public: - - Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete, - bool sorting, const CSMDoc::Document& document); - - ///< \param createAndDelete Allow creation and deletion of records. - /// \param sorting Allow changing order of rows in the view via column headers. - - void setEditLock (bool locked); - - CSMWorld::UniversalId getUniversalId (int row) const; - - void updateEditorSetting (const QString &settingName, const QString &settingValue); - - std::vector getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const; - - signals: - - void editRequest (int row); - - void selectionSizeChanged (int size); - - void tableSizeChanged (int size, int deleted, int modified); - ///< \param size Number of not deleted records - /// \param deleted Number of deleted records - /// \param modified Number of added and modified records - - void createRequest(); - void cloneRequest(const CSMWorld::UniversalId&); - - private slots: - - void revertRecord(); - - void deleteRecord(); - - void editRecord(); - - void cloneRecord(); - - void moveUpRecord(); - - void moveDownRecord(); - - public slots: - - void tableSizeUpdate(); - - void selectionSizeUpdate(); - - void requestFocus (const std::string& id); - - void recordFilterChanged (boost::shared_ptr filter); - }; -} - -#endif +#ifndef CSV_WORLD_TABLE_H +#define CSV_WORLD_TABLE_H + +#include +#include + +#include +#include + +#include "../../model/filter/node.hpp" +#include "../../model/world/columnbase.hpp" + +namespace CSMDoc { + class Document; +} + +class QUndoStack; +class QAction; + +namespace CSMWorld +{ + class Data; + class UniversalId; + class IdTableProxyModel; + class IdTable; +} + +namespace CSVWorld +{ + class CommandDelegate; + + ///< Table widget + class Table : public QTableView + { + Q_OBJECT + + std::vector mDelegates; + QUndoStack& mUndoStack; + QAction *mEditAction; + QAction *mCreateAction; + QAction *mCloneAction; + QAction *mRevertAction; + QAction *mDeleteAction; + QAction *mMoveUpAction; + QAction *mMoveDownAction; + CSMWorld::IdTableProxyModel *mProxyModel; + CSMWorld::IdTable *mModel; + bool mEditLock; + int mRecordStatusDisplay; + + /// \brief This variable is used exclusivly for checking if dropEvents came from the same document. Most likely you + /// should NOT use it for anything else. + const CSMDoc::Document& mDocument; + + private: + + void contextMenuEvent (QContextMenuEvent *event); + + std::vector listRevertableSelectedIds() const; + + std::vector listDeletableSelectedIds() const; + + void mouseMoveEvent(QMouseEvent *event); + + void dragEnterEvent(QDragEnterEvent *event); + + void dragMoveEvent(QDragMoveEvent *event); + + void dropEvent(QDropEvent *event); + + public: + + Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete, + bool sorting, const CSMDoc::Document& document); + + ///< \param createAndDelete Allow creation and deletion of records. + /// \param sorting Allow changing order of rows in the view via column headers. + + void setEditLock (bool locked); + + CSMWorld::UniversalId getUniversalId (int row) const; + + void updateEditorSetting (const QString &settingName, const QString &settingValue); + + std::vector getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const; + + signals: + + void editRequest (int row); + + void selectionSizeChanged (int size); + + void tableSizeChanged (int size, int deleted, int modified); + ///< \param size Number of not deleted records + /// \param deleted Number of deleted records + /// \param modified Number of added and modified records + + void createRequest(); + void cloneRequest(const CSMWorld::UniversalId&); + + private slots: + + void revertRecord(); + + void deleteRecord(); + + void editRecord(); + + void cloneRecord(); + + void moveUpRecord(); + + void moveDownRecord(); + + public slots: + + void tableSizeUpdate(); + + void selectionSizeUpdate(); + + void requestFocus (const std::string& id); + + void recordFilterChanged (boost::shared_ptr filter); + }; +} + +#endif diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index e330d4775..782ccfd24 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -1,132 +1,132 @@ - -#include "tablesubview.hpp" - -#include -#include - -#include "../../model/doc/document.hpp" -#include "../../model/world/tablemimedata.hpp" - -#include "../filter/filterbox.hpp" -#include "table.hpp" -#include "tablebottombox.hpp" -#include "creator.hpp" - -CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory, bool sorting) -: SubView (id) -{ - QVBoxLayout *layout = new QVBoxLayout; - - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - - layout->addWidget (mBottom = - new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this), 0); - - layout->insertWidget (0, mTable = - new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete(), sorting, document), 2); - - CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); - - layout->insertWidget (0, filterBox); - - QWidget *widget = new QWidget; - - widget->setLayout (layout); - - setWidget (widget); - - connect (mTable, SIGNAL (editRequest (int)), this, SLOT (editRequest (int))); - - connect (mTable, SIGNAL (selectionSizeChanged (int)), - mBottom, SLOT (selectionSizeChanged (int))); - connect (mTable, SIGNAL (tableSizeChanged (int, int, int)), - mBottom, SLOT (tableSizeChanged (int, int, int))); - - mTable->tableSizeUpdate(); - mTable->selectionSizeUpdate(); - mTable->viewport()->installEventFilter(this); - mBottom->installEventFilter(this); - filterBox->installEventFilter(this); - - if (mBottom->canCreateAndDelete()) - { - connect (mTable, SIGNAL (createRequest()), mBottom, SLOT (createRequest())); - - connect (mTable, SIGNAL (cloneRequest(const CSMWorld::UniversalId&)), this, - SLOT(cloneRequest(const CSMWorld::UniversalId&))); - - connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)), - mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type))); - } - connect (mBottom, SIGNAL (requestFocus (const std::string&)), - mTable, SLOT (requestFocus (const std::string&))); - - connect (filterBox, - SIGNAL (recordFilterChanged (boost::shared_ptr)), - mTable, SLOT (recordFilterChanged (boost::shared_ptr))); - - connect(filterBox, SIGNAL(recordDropped(std::vector&, Qt::DropAction)), - this, SLOT(createFilterRequest(std::vector&, Qt::DropAction))); - - connect(this, SIGNAL(useFilterRequest(const std::string&)), filterBox, SIGNAL(useFilterRequest(const std::string&))); - - connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - filterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); -} - -void CSVWorld::TableSubView::setEditLock (bool locked) -{ - mTable->setEditLock (locked); - mBottom->setEditLock (locked); -} - -void CSVWorld::TableSubView::editRequest (int row) -{ - focusId (mTable->getUniversalId (row)); -} - -void CSVWorld::TableSubView::updateEditorSetting(const QString &settingName, const QString &settingValue) -{ - mTable->updateEditorSetting(settingName, settingValue); -} - -void CSVWorld::TableSubView::setStatusBar (bool show) -{ - mBottom->setStatusBar (show); -} - -void CSVWorld::TableSubView::cloneRequest(const CSMWorld::UniversalId& toClone) -{ - emit cloneRequest(toClone.getId(), toClone.getType()); -} - -void CSVWorld::TableSubView::createFilterRequest (std::vector< CSMWorld::UniversalId>& types, Qt::DropAction action) -{ - std::vector > > filterSource; - - for (std::vector::iterator it = types.begin(); it != types.end(); ++it) - { - std::pair > pair( //splited long line - std::make_pair(it->getId(), mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(it->getType())))); - - filterSource.push_back(pair); - } - emit createFilterRequest(filterSource, action); -} - -bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) -{ - if (event->type() == QEvent::Drop) - { - QDropEvent* drop = dynamic_cast(event); - const CSMWorld::TableMimeData* data = dynamic_cast(drop->mimeData()); - bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter); - if (handled) - { - emit useFilterRequest(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); - } - return handled; - } - return false; + +#include "tablesubview.hpp" + +#include +#include + +#include "../../model/doc/document.hpp" +#include "../../model/world/tablemimedata.hpp" + +#include "../filter/filterbox.hpp" +#include "table.hpp" +#include "tablebottombox.hpp" +#include "creator.hpp" + +CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, + const CreatorFactoryBase& creatorFactory, bool sorting) +: SubView (id) +{ + QVBoxLayout *layout = new QVBoxLayout; + + layout->setContentsMargins (QMargins (0, 0, 0, 0)); + + layout->addWidget (mBottom = + new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this), 0); + + layout->insertWidget (0, mTable = + new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete(), sorting, document), 2); + + CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); + + layout->insertWidget (0, filterBox); + + QWidget *widget = new QWidget; + + widget->setLayout (layout); + + setWidget (widget); + + connect (mTable, SIGNAL (editRequest (int)), this, SLOT (editRequest (int))); + + connect (mTable, SIGNAL (selectionSizeChanged (int)), + mBottom, SLOT (selectionSizeChanged (int))); + connect (mTable, SIGNAL (tableSizeChanged (int, int, int)), + mBottom, SLOT (tableSizeChanged (int, int, int))); + + mTable->tableSizeUpdate(); + mTable->selectionSizeUpdate(); + mTable->viewport()->installEventFilter(this); + mBottom->installEventFilter(this); + filterBox->installEventFilter(this); + + if (mBottom->canCreateAndDelete()) + { + connect (mTable, SIGNAL (createRequest()), mBottom, SLOT (createRequest())); + + connect (mTable, SIGNAL (cloneRequest(const CSMWorld::UniversalId&)), this, + SLOT(cloneRequest(const CSMWorld::UniversalId&))); + + connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)), + mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type))); + } + connect (mBottom, SIGNAL (requestFocus (const std::string&)), + mTable, SLOT (requestFocus (const std::string&))); + + connect (filterBox, + SIGNAL (recordFilterChanged (boost::shared_ptr)), + mTable, SLOT (recordFilterChanged (boost::shared_ptr))); + + connect(filterBox, SIGNAL(recordDropped(std::vector&, Qt::DropAction)), + this, SLOT(createFilterRequest(std::vector&, Qt::DropAction))); + + connect(this, SIGNAL(useFilterRequest(const std::string&)), filterBox, SIGNAL(useFilterRequest(const std::string&))); + + connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), + filterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); +} + +void CSVWorld::TableSubView::setEditLock (bool locked) +{ + mTable->setEditLock (locked); + mBottom->setEditLock (locked); +} + +void CSVWorld::TableSubView::editRequest (int row) +{ + focusId (mTable->getUniversalId (row)); +} + +void CSVWorld::TableSubView::updateEditorSetting(const QString &settingName, const QString &settingValue) +{ + mTable->updateEditorSetting(settingName, settingValue); +} + +void CSVWorld::TableSubView::setStatusBar (bool show) +{ + mBottom->setStatusBar (show); +} + +void CSVWorld::TableSubView::cloneRequest(const CSMWorld::UniversalId& toClone) +{ + emit cloneRequest(toClone.getId(), toClone.getType()); +} + +void CSVWorld::TableSubView::createFilterRequest (std::vector< CSMWorld::UniversalId>& types, Qt::DropAction action) +{ + std::vector > > filterSource; + + for (std::vector::iterator it = types.begin(); it != types.end(); ++it) + { + std::pair > pair( //splited long line + std::make_pair(it->getId(), mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(it->getType())))); + + filterSource.push_back(pair); + } + emit createFilterRequest(filterSource, action); +} + +bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) +{ + if (event->type() == QEvent::Drop) + { + QDropEvent* drop = dynamic_cast(event); + const CSMWorld::TableMimeData* data = dynamic_cast(drop->mimeData()); + bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter); + if (handled) + { + emit useFilterRequest(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); + } + return handled; + } + return false; } \ No newline at end of file diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index 1f67e0262..4e578b180 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -1,63 +1,63 @@ -#ifndef CSV_WORLD_TABLESUBVIEW_H -#define CSV_WORLD_TABLESUBVIEW_H - -#include "../doc/subview.hpp" - -#include - -class QModelIndex; - -namespace CSMWorld -{ - class IdTable; -} - -namespace CSMDoc -{ - class Document; -} - -namespace CSVWorld -{ - class Table; - class TableBottomBox; - class CreatorFactoryBase; - - class TableSubView : public CSVDoc::SubView - { - Q_OBJECT - - Table *mTable; - TableBottomBox *mBottom; - - public: - - TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory, bool sorting); - - virtual void setEditLock (bool locked); - - virtual void updateEditorSetting (const QString& key, const QString& value); - - virtual void setStatusBar (bool show); - - protected: - bool eventFilter(QObject* object, QEvent *event); - - signals: - void cloneRequest(const std::string&, - const CSMWorld::UniversalId::Type); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - void useFilterRequest(const std::string& idOfFilter); - - private slots: - - void editRequest (int row); - void cloneRequest (const CSMWorld::UniversalId& toClone); - void createFilterRequest(std::vector< CSMWorld::UniversalId >& types, - Qt::DropAction action); - }; -} - -#endif +#ifndef CSV_WORLD_TABLESUBVIEW_H +#define CSV_WORLD_TABLESUBVIEW_H + +#include "../doc/subview.hpp" + +#include + +class QModelIndex; + +namespace CSMWorld +{ + class IdTable; +} + +namespace CSMDoc +{ + class Document; +} + +namespace CSVWorld +{ + class Table; + class TableBottomBox; + class CreatorFactoryBase; + + class TableSubView : public CSVDoc::SubView + { + Q_OBJECT + + Table *mTable; + TableBottomBox *mBottom; + + public: + + TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, + const CreatorFactoryBase& creatorFactory, bool sorting); + + virtual void setEditLock (bool locked); + + virtual void updateEditorSetting (const QString& key, const QString& value); + + virtual void setStatusBar (bool show); + + protected: + bool eventFilter(QObject* object, QEvent *event); + + signals: + void cloneRequest(const std::string&, + const CSMWorld::UniversalId::Type); + void createFilterRequest(std::vector > >& filterSource, + Qt::DropAction action); + void useFilterRequest(const std::string& idOfFilter); + + private slots: + + void editRequest (int row); + void cloneRequest (const CSMWorld::UniversalId& toClone); + void createFilterRequest(std::vector< CSMWorld::UniversalId >& types, + Qt::DropAction action); + }; +} + +#endif diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 265069dc4..f9b5b4d04 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -1,352 +1,352 @@ - -#include "statemanagerimp.hpp" - -#include -#include -#include -#include - -#include - -#include - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/journal.hpp" -#include "../mwbase/dialoguemanager.hpp" -#include "../mwbase/windowmanager.hpp" -#include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/scriptmanager.hpp" -#include "../mwbase/soundmanager.hpp" - -#include "../mwworld/player.hpp" -#include "../mwworld/class.hpp" - -#include "../mwmechanics/npcstats.hpp" - -#include "../mwscript/globalscripts.hpp" - -void MWState::StateManager::cleanup (bool force) -{ - if (mState!=State_NoGame || force) - { - MWBase::Environment::get().getSoundManager()->clear(); - MWBase::Environment::get().getDialogueManager()->clear(); - MWBase::Environment::get().getJournal()->clear(); - MWBase::Environment::get().getScriptManager()->getGlobalScripts().clear(); - MWBase::Environment::get().getWorld()->clear(); - MWBase::Environment::get().getWindowManager()->clear(); - - mState = State_NoGame; - mCharacterManager.clearCurrentCharacter(); - mTimePlayed = 0; - } -} - -std::map MWState::StateManager::buildContentFileIndexMap (const ESM::ESMReader& reader) - const -{ - const std::vector& current = - MWBase::Environment::get().getWorld()->getContentFiles(); - - const std::vector& prev = reader.getGameFiles(); - - std::map map; - - for (int iPrev = 0; iPrev (prev.size()); ++iPrev) - { - std::string id = Misc::StringUtils::lowerCase (prev[iPrev].name); - - for (int iCurrent = 0; iCurrent (current.size()); ++iCurrent) - if (id==Misc::StringUtils::lowerCase (current[iCurrent])) - { - map.insert (std::make_pair (iPrev, iCurrent)); - break; - } - } - - return map; -} - -MWState::StateManager::StateManager (const boost::filesystem::path& saves, const std::string& game) -: mQuitRequest (false), mAskLoadRecent(false), mState (State_NoGame), mCharacterManager (saves, game), mTimePlayed (0) -{ - -} - -void MWState::StateManager::requestQuit() -{ - mQuitRequest = true; -} - -bool MWState::StateManager::hasQuitRequest() const -{ - return mQuitRequest; -} - -void MWState::StateManager::askLoadRecent() -{ - if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu) - return; - - if( !mAskLoadRecent ) - { - if(getCurrentCharacter()->begin() == getCurrentCharacter()->end() )//no saves - { - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); - } - else - { - MWState::Slot lastSave = *getCurrentCharacter()->begin(); - std::vector buttons; - buttons.push_back("#{sYes}"); - buttons.push_back("#{sNo}"); - std::string tag("%s"); - std::string message = MWBase::Environment::get().getWindowManager()->getGameSettingString("sLoadLastSaveMsg", tag); - size_t pos = message.find(tag); - message.replace(pos, tag.length(), lastSave.mProfile.mDescription); - MWBase::Environment::get().getWindowManager()->messageBox(message, buttons); - mAskLoadRecent = true; - } - } -} - -MWState::StateManager::State MWState::StateManager::getState() const -{ - return mState; -} - -void MWState::StateManager::newGame (bool bypass) -{ - cleanup(); - - if (!bypass) - { - MWBase::Environment::get().getWorld()->startNewGame(); - MWBase::Environment::get().getWindowManager()->setNewGame (true); - } - else - MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1); - - MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); - - mState = State_Running; -} - -void MWState::StateManager::endGame() -{ - mState = State_Ended; - MWBase::Environment::get().getWorld()->useDeathCamera(); -} - -void MWState::StateManager::saveGame (const std::string& description, const Slot *slot) -{ - ESM::SavedGame profile; - - MWBase::World& world = *MWBase::Environment::get().getWorld(); - - MWWorld::Ptr player = world.getPlayer().getPlayer(); - - profile.mContentFiles = world.getContentFiles(); - - profile.mPlayerName = player.getClass().getName (player); - profile.mPlayerLevel = player.getClass().getNpcStats (player).getLevel(); - profile.mPlayerClass = player.get()->mBase->mClass; - - profile.mPlayerCell = world.getCellName(); - - profile.mInGameTime.mGameHour = world.getTimeStamp().getHour(); - profile.mInGameTime.mDay = world.getDay(); - profile.mInGameTime.mMonth = world.getMonth(); - profile.mInGameTime.mYear = world.getYear(); - profile.mTimePlayed = mTimePlayed; - profile.mDescription = description; - - int screenshotW = 259*2, screenshotH = 133*2; // *2 to get some nice antialiasing - Ogre::Image screenshot; - world.screenshot(screenshot, screenshotW, screenshotH); - Ogre::DataStreamPtr encoded = screenshot.encode("jpg"); - profile.mScreenshot.resize(encoded->size()); - encoded->read(&profile.mScreenshot[0], encoded->size()); - - if (!slot) - slot = mCharacterManager.getCurrentCharacter()->createSlot (profile); - else - slot = mCharacterManager.getCurrentCharacter()->updateSlot (slot, profile); - - std::ofstream stream (slot->mPath.string().c_str(), std::ios::binary); - - ESM::ESMWriter writer; - - const std::vector& current = - MWBase::Environment::get().getWorld()->getContentFiles(); - - for (std::vector::const_iterator iter (current.begin()); iter!=current.end(); - ++iter) - writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0 - - writer.setFormat (ESM::Header::CurrentFormat); - writer.setRecordCount ( - 1 // saved game header - +MWBase::Environment::get().getJournal()->countSavedGameRecords() - +MWBase::Environment::get().getWorld()->countSavedGameRecords() - +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() - +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() - +1 // global map - ); - - writer.save (stream); - - writer.startRecord (ESM::REC_SAVE); - slot->mProfile.save (writer); - writer.endRecord (ESM::REC_SAVE); - - MWBase::Environment::get().getJournal()->write (writer); - MWBase::Environment::get().getDialogueManager()->write (writer); - MWBase::Environment::get().getWorld()->write (writer); - MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer); - MWBase::Environment::get().getWindowManager()->write(writer); - - writer.close(); - - Settings::Manager::setString ("character", "Saves", - slot->mPath.parent_path().filename().string()); -} - -void MWState::StateManager::loadGame (const Character *character, const Slot *slot) -{ - try - { - cleanup(); - - mTimePlayed = slot->mProfile.mTimePlayed; - - ESM::ESMReader reader; - reader.open (slot->mPath.string()); - - std::map contentFileMap = buildContentFileIndexMap (reader); - - while (reader.hasMoreRecs()) - { - ESM::NAME n = reader.getRecName(); - reader.getRecHeader(); - - switch (n.val) - { - case ESM::REC_SAVE: - - // don't need to read that here - reader.skipRecord(); - break; - - case ESM::REC_JOUR: - case ESM::REC_QUES: - - MWBase::Environment::get().getJournal()->readRecord (reader, n.val); - break; - - case ESM::REC_DIAS: - - MWBase::Environment::get().getDialogueManager()->readRecord (reader, n.val); - break; - - case ESM::REC_ALCH: - case ESM::REC_ARMO: - case ESM::REC_BOOK: - case ESM::REC_CLAS: - case ESM::REC_CLOT: - case ESM::REC_ENCH: - case ESM::REC_NPC_: - case ESM::REC_SPEL: - case ESM::REC_WEAP: - case ESM::REC_GLOB: - case ESM::REC_PLAY: - case ESM::REC_CSTA: - - MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap); - break; - - case ESM::REC_GSCR: - - MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.val); - break; - - case ESM::REC_GMAP: - - MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val); - break; - - default: - - // ignore invalid records - /// \todo log error - reader.skipRecord(); - } - } - - mCharacterManager.setCurrentCharacter(character); - - mState = State_Running; - - Settings::Manager::setString ("character", "Saves", - slot->mPath.parent_path().filename().string()); - - MWBase::Environment::get().getWindowManager()->setNewGame(false); - MWBase::Environment::get().getWorld()->setupPlayer(); - MWBase::Environment::get().getWorld()->renderPlayer(); - MWBase::Environment::get().getWindowManager()->updatePlayer(); - MWBase::Environment::get().getMechanicsManager()->playerLoaded(); - - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - - ESM::CellId cellId = ptr.getCell()->mCell->getCellId(); - - MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition()); - } - catch (const std::exception& e) - { - std::cerr << "failed to load saved game: " << e.what() << std::endl; - cleanup (true); - } -} - -MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) -{ - return mCharacterManager.getCurrentCharacter (create); -} - -MWState::StateManager::CharacterIterator MWState::StateManager::characterBegin() -{ - return mCharacterManager.begin(); -} - -MWState::StateManager::CharacterIterator MWState::StateManager::characterEnd() -{ - return mCharacterManager.end(); -} - -void MWState::StateManager::update (float duration) -{ - mTimePlayed += duration; - - // Note: It would be nicer to trigger this from InputManager, i.e. the very beginning of the frame update. - if (mAskLoadRecent) - { - int iButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); - if(iButton==0) - { - mAskLoadRecent = false; - //Load last saved game for current character - MWState::Character *curCharacter = getCurrentCharacter(); - MWState::Slot lastSave = *curCharacter->begin(); - loadGame(curCharacter, &lastSave); - } - else if(iButton==1) - { - mAskLoadRecent = false; - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); - } - } -} + +#include "statemanagerimp.hpp" + +#include +#include +#include +#include + +#include + +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/journal.hpp" +#include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/scriptmanager.hpp" +#include "../mwbase/soundmanager.hpp" + +#include "../mwworld/player.hpp" +#include "../mwworld/class.hpp" + +#include "../mwmechanics/npcstats.hpp" + +#include "../mwscript/globalscripts.hpp" + +void MWState::StateManager::cleanup (bool force) +{ + if (mState!=State_NoGame || force) + { + MWBase::Environment::get().getSoundManager()->clear(); + MWBase::Environment::get().getDialogueManager()->clear(); + MWBase::Environment::get().getJournal()->clear(); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().clear(); + MWBase::Environment::get().getWorld()->clear(); + MWBase::Environment::get().getWindowManager()->clear(); + + mState = State_NoGame; + mCharacterManager.clearCurrentCharacter(); + mTimePlayed = 0; + } +} + +std::map MWState::StateManager::buildContentFileIndexMap (const ESM::ESMReader& reader) + const +{ + const std::vector& current = + MWBase::Environment::get().getWorld()->getContentFiles(); + + const std::vector& prev = reader.getGameFiles(); + + std::map map; + + for (int iPrev = 0; iPrev (prev.size()); ++iPrev) + { + std::string id = Misc::StringUtils::lowerCase (prev[iPrev].name); + + for (int iCurrent = 0; iCurrent (current.size()); ++iCurrent) + if (id==Misc::StringUtils::lowerCase (current[iCurrent])) + { + map.insert (std::make_pair (iPrev, iCurrent)); + break; + } + } + + return map; +} + +MWState::StateManager::StateManager (const boost::filesystem::path& saves, const std::string& game) +: mQuitRequest (false), mAskLoadRecent(false), mState (State_NoGame), mCharacterManager (saves, game), mTimePlayed (0) +{ + +} + +void MWState::StateManager::requestQuit() +{ + mQuitRequest = true; +} + +bool MWState::StateManager::hasQuitRequest() const +{ + return mQuitRequest; +} + +void MWState::StateManager::askLoadRecent() +{ + if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu) + return; + + if( !mAskLoadRecent ) + { + if(getCurrentCharacter()->begin() == getCurrentCharacter()->end() )//no saves + { + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + } + else + { + MWState::Slot lastSave = *getCurrentCharacter()->begin(); + std::vector buttons; + buttons.push_back("#{sYes}"); + buttons.push_back("#{sNo}"); + std::string tag("%s"); + std::string message = MWBase::Environment::get().getWindowManager()->getGameSettingString("sLoadLastSaveMsg", tag); + size_t pos = message.find(tag); + message.replace(pos, tag.length(), lastSave.mProfile.mDescription); + MWBase::Environment::get().getWindowManager()->messageBox(message, buttons); + mAskLoadRecent = true; + } + } +} + +MWState::StateManager::State MWState::StateManager::getState() const +{ + return mState; +} + +void MWState::StateManager::newGame (bool bypass) +{ + cleanup(); + + if (!bypass) + { + MWBase::Environment::get().getWorld()->startNewGame(); + MWBase::Environment::get().getWindowManager()->setNewGame (true); + } + else + MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1); + + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); + + mState = State_Running; +} + +void MWState::StateManager::endGame() +{ + mState = State_Ended; + MWBase::Environment::get().getWorld()->useDeathCamera(); +} + +void MWState::StateManager::saveGame (const std::string& description, const Slot *slot) +{ + ESM::SavedGame profile; + + MWBase::World& world = *MWBase::Environment::get().getWorld(); + + MWWorld::Ptr player = world.getPlayer().getPlayer(); + + profile.mContentFiles = world.getContentFiles(); + + profile.mPlayerName = player.getClass().getName (player); + profile.mPlayerLevel = player.getClass().getNpcStats (player).getLevel(); + profile.mPlayerClass = player.get()->mBase->mClass; + + profile.mPlayerCell = world.getCellName(); + + profile.mInGameTime.mGameHour = world.getTimeStamp().getHour(); + profile.mInGameTime.mDay = world.getDay(); + profile.mInGameTime.mMonth = world.getMonth(); + profile.mInGameTime.mYear = world.getYear(); + profile.mTimePlayed = mTimePlayed; + profile.mDescription = description; + + int screenshotW = 259*2, screenshotH = 133*2; // *2 to get some nice antialiasing + Ogre::Image screenshot; + world.screenshot(screenshot, screenshotW, screenshotH); + Ogre::DataStreamPtr encoded = screenshot.encode("jpg"); + profile.mScreenshot.resize(encoded->size()); + encoded->read(&profile.mScreenshot[0], encoded->size()); + + if (!slot) + slot = mCharacterManager.getCurrentCharacter()->createSlot (profile); + else + slot = mCharacterManager.getCurrentCharacter()->updateSlot (slot, profile); + + std::ofstream stream (slot->mPath.string().c_str(), std::ios::binary); + + ESM::ESMWriter writer; + + const std::vector& current = + MWBase::Environment::get().getWorld()->getContentFiles(); + + for (std::vector::const_iterator iter (current.begin()); iter!=current.end(); + ++iter) + writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0 + + writer.setFormat (ESM::Header::CurrentFormat); + writer.setRecordCount ( + 1 // saved game header + +MWBase::Environment::get().getJournal()->countSavedGameRecords() + +MWBase::Environment::get().getWorld()->countSavedGameRecords() + +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() + +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() + +1 // global map + ); + + writer.save (stream); + + writer.startRecord (ESM::REC_SAVE); + slot->mProfile.save (writer); + writer.endRecord (ESM::REC_SAVE); + + MWBase::Environment::get().getJournal()->write (writer); + MWBase::Environment::get().getDialogueManager()->write (writer); + MWBase::Environment::get().getWorld()->write (writer); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer); + MWBase::Environment::get().getWindowManager()->write(writer); + + writer.close(); + + Settings::Manager::setString ("character", "Saves", + slot->mPath.parent_path().filename().string()); +} + +void MWState::StateManager::loadGame (const Character *character, const Slot *slot) +{ + try + { + cleanup(); + + mTimePlayed = slot->mProfile.mTimePlayed; + + ESM::ESMReader reader; + reader.open (slot->mPath.string()); + + std::map contentFileMap = buildContentFileIndexMap (reader); + + while (reader.hasMoreRecs()) + { + ESM::NAME n = reader.getRecName(); + reader.getRecHeader(); + + switch (n.val) + { + case ESM::REC_SAVE: + + // don't need to read that here + reader.skipRecord(); + break; + + case ESM::REC_JOUR: + case ESM::REC_QUES: + + MWBase::Environment::get().getJournal()->readRecord (reader, n.val); + break; + + case ESM::REC_DIAS: + + MWBase::Environment::get().getDialogueManager()->readRecord (reader, n.val); + break; + + case ESM::REC_ALCH: + case ESM::REC_ARMO: + case ESM::REC_BOOK: + case ESM::REC_CLAS: + case ESM::REC_CLOT: + case ESM::REC_ENCH: + case ESM::REC_NPC_: + case ESM::REC_SPEL: + case ESM::REC_WEAP: + case ESM::REC_GLOB: + case ESM::REC_PLAY: + case ESM::REC_CSTA: + + MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap); + break; + + case ESM::REC_GSCR: + + MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.val); + break; + + case ESM::REC_GMAP: + + MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val); + break; + + default: + + // ignore invalid records + /// \todo log error + reader.skipRecord(); + } + } + + mCharacterManager.setCurrentCharacter(character); + + mState = State_Running; + + Settings::Manager::setString ("character", "Saves", + slot->mPath.parent_path().filename().string()); + + MWBase::Environment::get().getWindowManager()->setNewGame(false); + MWBase::Environment::get().getWorld()->setupPlayer(); + MWBase::Environment::get().getWorld()->renderPlayer(); + MWBase::Environment::get().getWindowManager()->updatePlayer(); + MWBase::Environment::get().getMechanicsManager()->playerLoaded(); + + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + + ESM::CellId cellId = ptr.getCell()->mCell->getCellId(); + + MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition()); + } + catch (const std::exception& e) + { + std::cerr << "failed to load saved game: " << e.what() << std::endl; + cleanup (true); + } +} + +MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) +{ + return mCharacterManager.getCurrentCharacter (create); +} + +MWState::StateManager::CharacterIterator MWState::StateManager::characterBegin() +{ + return mCharacterManager.begin(); +} + +MWState::StateManager::CharacterIterator MWState::StateManager::characterEnd() +{ + return mCharacterManager.end(); +} + +void MWState::StateManager::update (float duration) +{ + mTimePlayed += duration; + + // Note: It would be nicer to trigger this from InputManager, i.e. the very beginning of the frame update. + if (mAskLoadRecent) + { + int iButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); + if(iButton==0) + { + mAskLoadRecent = false; + //Load last saved game for current character + MWState::Character *curCharacter = getCurrentCharacter(); + MWState::Slot lastSave = *curCharacter->begin(); + loadGame(curCharacter, &lastSave); + } + else if(iButton==1) + { + mAskLoadRecent = false; + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + } + } +} diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index 91f123eb7..b7207486b 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -1,203 +1,203 @@ -#include "esmwriter.hpp" - -#include -#include -#include - -namespace ESM -{ - ESMWriter::ESMWriter() : mEncoder (0), mRecordCount (0), mCounting (true) {} - - unsigned int ESMWriter::getVersion() const - { - return mHeader.mData.version; - } - - void ESMWriter::setVersion(unsigned int ver) - { - mHeader.mData.version = ver; - } - - void ESMWriter::setAuthor(const std::string& auth) - { - mHeader.mData.author.assign (auth); - } - - void ESMWriter::setDescription(const std::string& desc) - { - mHeader.mData.desc.assign (desc); - } - - void ESMWriter::setRecordCount (int count) - { - mHeader.mData.records = count; - } - - void ESMWriter::setFormat (int format) - { - mHeader.mFormat = format; - } - - void ESMWriter::clearMaster() - { - mHeader.mMaster.clear(); - } - - void ESMWriter::addMaster(const std::string& name, uint64_t size) - { - Header::MasterData d; - d.name = name; - d.size = size; - mHeader.mMaster.push_back(d); - } - - void ESMWriter::save(std::ostream& file) - { - mRecordCount = 0; - mRecords.clear(); - mCounting = true; - mStream = &file; - - startRecord("TES3", 0); - - mHeader.save (*this); - - endRecord("TES3"); - } - - void ESMWriter::close() - { - if (!mRecords.empty()) - throw std::runtime_error ("Unclosed record remaining"); - } - - void ESMWriter::startRecord(const std::string& name, uint32_t flags) - { - mRecordCount++; - - writeName(name); - RecordData rec; - rec.name = name; - rec.position = mStream->tellp(); - rec.size = 0; - writeT(0); // Size goes here - writeT(0); // Unused header? - writeT(flags); - mRecords.push_back(rec); - - assert(mRecords.back().size == 0); - } - - void ESMWriter::startRecord (uint32_t name, uint32_t flags) - { - std::string type; - for (int i=0; i<4; ++i) - /// \todo make endianess agnostic - type += reinterpret_cast (&name)[i]; - - startRecord (type, flags); - } - - void ESMWriter::startSubRecord(const std::string& name) - { - writeName(name); - RecordData rec; - rec.name = name; - rec.position = mStream->tellp(); - rec.size = 0; - writeT(0); // Size goes here - mRecords.push_back(rec); - - assert(mRecords.back().size == 0); - } - - void ESMWriter::endRecord(const std::string& name) - { - RecordData rec = mRecords.back(); - assert(rec.name == name); - mRecords.pop_back(); - - mStream->seekp(rec.position); - - mCounting = false; - write (reinterpret_cast (&rec.size), sizeof(uint32_t)); - mCounting = true; - - mStream->seekp(0, std::ios::end); - - } - - void ESMWriter::endRecord (uint32_t name) - { - std::string type; - for (int i=0; i<4; ++i) - /// \todo make endianess agnostic - type += reinterpret_cast (&name)[i]; - - endRecord (type); - } - - void ESMWriter::writeHNString(const std::string& name, const std::string& data) - { - startSubRecord(name); - writeHString(data); - endRecord(name); - } - - void ESMWriter::writeHNString(const std::string& name, const std::string& data, size_t size) - { - assert(data.size() <= size); - startSubRecord(name); - writeHString(data); - - if (data.size() < size) - { - for (size_t i = data.size(); i < size; ++i) - write("\0",1); - } - - endRecord(name); - } - - void ESMWriter::writeHString(const std::string& data) - { - if (data.size() == 0) - write("\0", 1); - else - { - // Convert to UTF8 and return - std::string string = mEncoder ? mEncoder->getLegacyEnc(data) : data; - - write(string.c_str(), string.size()); - } - } - - void ESMWriter::writeHCString(const std::string& data) - { - writeHString(data); - if (data.size() > 0 && data[data.size()-1] != '\0') - write("\0", 1); - } - - void ESMWriter::writeName(const std::string& name) - { - assert((name.size() == 4 && name[3] != '\0')); - write(name.c_str(), name.size()); - } - - void ESMWriter::write(const char* data, size_t size) - { - if (mCounting && !mRecords.empty()) - { - for (std::list::iterator it = mRecords.begin(); it != mRecords.end(); ++it) - it->size += size; - } - - mStream->write(data, size); - } - - void ESMWriter::setEncoder(ToUTF8::Utf8Encoder* encoder) - { - mEncoder = encoder; - } -} +#include "esmwriter.hpp" + +#include +#include +#include + +namespace ESM +{ + ESMWriter::ESMWriter() : mEncoder (0), mRecordCount (0), mCounting (true) {} + + unsigned int ESMWriter::getVersion() const + { + return mHeader.mData.version; + } + + void ESMWriter::setVersion(unsigned int ver) + { + mHeader.mData.version = ver; + } + + void ESMWriter::setAuthor(const std::string& auth) + { + mHeader.mData.author.assign (auth); + } + + void ESMWriter::setDescription(const std::string& desc) + { + mHeader.mData.desc.assign (desc); + } + + void ESMWriter::setRecordCount (int count) + { + mHeader.mData.records = count; + } + + void ESMWriter::setFormat (int format) + { + mHeader.mFormat = format; + } + + void ESMWriter::clearMaster() + { + mHeader.mMaster.clear(); + } + + void ESMWriter::addMaster(const std::string& name, uint64_t size) + { + Header::MasterData d; + d.name = name; + d.size = size; + mHeader.mMaster.push_back(d); + } + + void ESMWriter::save(std::ostream& file) + { + mRecordCount = 0; + mRecords.clear(); + mCounting = true; + mStream = &file; + + startRecord("TES3", 0); + + mHeader.save (*this); + + endRecord("TES3"); + } + + void ESMWriter::close() + { + if (!mRecords.empty()) + throw std::runtime_error ("Unclosed record remaining"); + } + + void ESMWriter::startRecord(const std::string& name, uint32_t flags) + { + mRecordCount++; + + writeName(name); + RecordData rec; + rec.name = name; + rec.position = mStream->tellp(); + rec.size = 0; + writeT(0); // Size goes here + writeT(0); // Unused header? + writeT(flags); + mRecords.push_back(rec); + + assert(mRecords.back().size == 0); + } + + void ESMWriter::startRecord (uint32_t name, uint32_t flags) + { + std::string type; + for (int i=0; i<4; ++i) + /// \todo make endianess agnostic + type += reinterpret_cast (&name)[i]; + + startRecord (type, flags); + } + + void ESMWriter::startSubRecord(const std::string& name) + { + writeName(name); + RecordData rec; + rec.name = name; + rec.position = mStream->tellp(); + rec.size = 0; + writeT(0); // Size goes here + mRecords.push_back(rec); + + assert(mRecords.back().size == 0); + } + + void ESMWriter::endRecord(const std::string& name) + { + RecordData rec = mRecords.back(); + assert(rec.name == name); + mRecords.pop_back(); + + mStream->seekp(rec.position); + + mCounting = false; + write (reinterpret_cast (&rec.size), sizeof(uint32_t)); + mCounting = true; + + mStream->seekp(0, std::ios::end); + + } + + void ESMWriter::endRecord (uint32_t name) + { + std::string type; + for (int i=0; i<4; ++i) + /// \todo make endianess agnostic + type += reinterpret_cast (&name)[i]; + + endRecord (type); + } + + void ESMWriter::writeHNString(const std::string& name, const std::string& data) + { + startSubRecord(name); + writeHString(data); + endRecord(name); + } + + void ESMWriter::writeHNString(const std::string& name, const std::string& data, size_t size) + { + assert(data.size() <= size); + startSubRecord(name); + writeHString(data); + + if (data.size() < size) + { + for (size_t i = data.size(); i < size; ++i) + write("\0",1); + } + + endRecord(name); + } + + void ESMWriter::writeHString(const std::string& data) + { + if (data.size() == 0) + write("\0", 1); + else + { + // Convert to UTF8 and return + std::string string = mEncoder ? mEncoder->getLegacyEnc(data) : data; + + write(string.c_str(), string.size()); + } + } + + void ESMWriter::writeHCString(const std::string& data) + { + writeHString(data); + if (data.size() > 0 && data[data.size()-1] != '\0') + write("\0", 1); + } + + void ESMWriter::writeName(const std::string& name) + { + assert((name.size() == 4 && name[3] != '\0')); + write(name.c_str(), name.size()); + } + + void ESMWriter::write(const char* data, size_t size) + { + if (mCounting && !mRecords.empty()) + { + for (std::list::iterator it = mRecords.begin(); it != mRecords.end(); ++it) + it->size += size; + } + + mStream->write(data, size); + } + + void ESMWriter::setEncoder(ToUTF8::Utf8Encoder* encoder) + { + mEncoder = encoder; + } +} diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index 33650e678..304876dbf 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -1,114 +1,114 @@ -#ifndef OPENMW_ESM_WRITER_H -#define OPENMW_ESM_WRITER_H - -#include -#include - -#include - -#include "esmcommon.hpp" -#include "loadtes3.hpp" - -namespace ESM { - -class ESMWriter -{ - struct RecordData - { - std::string name; - std::streampos position; - uint32_t size; - }; - - public: - - ESMWriter(); - - unsigned int getVersion() const; - void setVersion(unsigned int ver = 0x3fa66666); - void setEncoder(ToUTF8::Utf8Encoder *encoding); - void setAuthor(const std::string& author); - void setDescription(const std::string& desc); - void setRecordCount (int count); - void setFormat (int format); - - void clearMaster(); - - void addMaster(const std::string& name, uint64_t size); - - void save(std::ostream& file); - ///< Start saving a file by writing the TES3 header. - - void close(); - ///< \note Does not close the stream. - - void writeHNString(const std::string& name, const std::string& data); - void writeHNString(const std::string& name, const std::string& data, size_t size); - void writeHNCString(const std::string& name, const std::string& data) - { - startSubRecord(name); - writeHCString(data); - endRecord(name); - } - void writeHNOString(const std::string& name, const std::string& data) - { - if (!data.empty()) - writeHNString(name, data); - } - void writeHNOCString(const std::string& name, const std::string& data) - { - if (!data.empty()) - writeHNCString(name, data); - } - - template - void writeHNT(const std::string& name, const T& data) - { - startSubRecord(name); - writeT(data); - endRecord(name); - } - - template - void writeHNT(const std::string& name, const T& data, int size) - { - startSubRecord(name); - writeT(data, size); - endRecord(name); - } - - template - void writeT(const T& data) - { - write((char*)&data, sizeof(T)); - } - - template - void writeT(const T& data, size_t size) - { - write((char*)&data, size); - } - - void startRecord(const std::string& name, uint32_t flags = 0); - void startRecord(uint32_t name, uint32_t flags = 0); - void startSubRecord(const std::string& name); - void endRecord(const std::string& name); - void endRecord(uint32_t name); - void writeHString(const std::string& data); - void writeHCString(const std::string& data); - void writeName(const std::string& data); - void write(const char* data, size_t size); - - private: - std::list mRecords; - std::ostream* mStream; - std::streampos mHeaderPos; - ToUTF8::Utf8Encoder* mEncoder; - int mRecordCount; - bool mCounting; - - Header mHeader; - }; -} - -#endif +#ifndef OPENMW_ESM_WRITER_H +#define OPENMW_ESM_WRITER_H + +#include +#include + +#include + +#include "esmcommon.hpp" +#include "loadtes3.hpp" + +namespace ESM { + +class ESMWriter +{ + struct RecordData + { + std::string name; + std::streampos position; + uint32_t size; + }; + + public: + + ESMWriter(); + + unsigned int getVersion() const; + void setVersion(unsigned int ver = 0x3fa66666); + void setEncoder(ToUTF8::Utf8Encoder *encoding); + void setAuthor(const std::string& author); + void setDescription(const std::string& desc); + void setRecordCount (int count); + void setFormat (int format); + + void clearMaster(); + + void addMaster(const std::string& name, uint64_t size); + + void save(std::ostream& file); + ///< Start saving a file by writing the TES3 header. + + void close(); + ///< \note Does not close the stream. + + void writeHNString(const std::string& name, const std::string& data); + void writeHNString(const std::string& name, const std::string& data, size_t size); + void writeHNCString(const std::string& name, const std::string& data) + { + startSubRecord(name); + writeHCString(data); + endRecord(name); + } + void writeHNOString(const std::string& name, const std::string& data) + { + if (!data.empty()) + writeHNString(name, data); + } + void writeHNOCString(const std::string& name, const std::string& data) + { + if (!data.empty()) + writeHNCString(name, data); + } + + template + void writeHNT(const std::string& name, const T& data) + { + startSubRecord(name); + writeT(data); + endRecord(name); + } + + template + void writeHNT(const std::string& name, const T& data, int size) + { + startSubRecord(name); + writeT(data, size); + endRecord(name); + } + + template + void writeT(const T& data) + { + write((char*)&data, sizeof(T)); + } + + template + void writeT(const T& data, size_t size) + { + write((char*)&data, size); + } + + void startRecord(const std::string& name, uint32_t flags = 0); + void startRecord(uint32_t name, uint32_t flags = 0); + void startSubRecord(const std::string& name); + void endRecord(const std::string& name); + void endRecord(uint32_t name); + void writeHString(const std::string& data); + void writeHCString(const std::string& data); + void writeName(const std::string& data); + void write(const char* data, size_t size); + + private: + std::list mRecords; + std::ostream* mStream; + std::streampos mHeaderPos; + ToUTF8::Utf8Encoder* mEncoder; + int mRecordCount; + bool mCounting; + + Header mHeader; + }; +} + +#endif diff --git a/readme.txt b/readme.txt index a23cd1077..4cc9c6234 100644 --- a/readme.txt +++ b/readme.txt @@ -1,951 +1,951 @@ -OpenMW: A reimplementation of The Elder Scrolls III: Morrowind - -OpenMW is an attempt at recreating the engine for the popular role-playing game -Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. - -Version: 0.28.0 -License: GPL (see GPL3.txt for more information) -Website: http://www.openmw.org - -Font Licenses: -EBGaramond-Regular.ttf: OFL (see OFL.txt for more information) -DejaVuLGCSansMono.ttf: custom (see DejaVu Font License.txt for more information) - - - -INSTALLATION - -Windows: -Run the installer. - -Linux: -Ubuntu (and most others) -Download the .deb file and install it in the usual way. - -Arch Linux -There's an OpenMW package available in the [community] Repository: -https://www.archlinux.org/packages/?sort=&q=openmw - -OS X: -Open DMG file, copy OpenMW folder anywhere, for example in /Applications - -BUILD FROM SOURCE - -https://wiki.openmw.org/index.php?title=Development_Environment_Setup - - -THE DATA PATH - -The data path tells OpenMW where to find your Morrowind files. If you run the launcher, OpenMW should be able to -pick up the location of these files on its own, if both Morrowind and OpenMW are installed properly -(installing Morrowind under WINE is considered a proper install). - -COMMAND LINE OPTIONS - -Syntax: openmw -Allowed options: - --help print help message - --version print version information and quit - --data arg (=data) set data directories (later directories - have higher priority) - --data-local arg set local data directory (highest - priority) - --fallback-archive arg (=fallback-archive) - set fallback BSA archives (later - archives have higher priority) - --resources arg (=resources) set resources directory - --start arg (=Beshara) set initial cell - --content arg content file(s): esm/esp, or - omwgame/omwaddon - --anim-verbose [=arg(=1)] (=0) output animation indices files - --no-sound [=arg(=1)] (=0) disable all sounds - --script-verbose [=arg(=1)] (=0) verbose script output - --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue - scripts) at startup - --script-console [=arg(=1)] (=0) enable console-only script - functionality - --script-run arg select a file containing a list of - console commands that is executed on - startup - --script-warn [=arg(=1)] (=1) handling of warnings when compiling - scripts - 0 - ignore warning - 1 - show warning but consider script as - correctly compiled anyway - 2 - treat warnings as errors - --skip-menu [=arg(=1)] (=0) skip main menu on game startup - --fs-strict [=arg(=1)] (=0) strict file system handling (no case - folding) - --encoding arg (=win1252) Character encoding used in OpenMW game - messages: - - win1250 - Central and Eastern European - such as Polish, Czech, Slovak, - Hungarian, Slovene, Bosnian, Croatian, - Serbian (Latin script), Romanian and - Albanian languages - - win1251 - Cyrillic alphabet such as - Russian, Bulgarian, Serbian Cyrillic - and other languages - - win1252 - Western European (Latin) - alphabet, used by default - --fallback arg fallback values - --no-grab Don't grab mouse cursor - --activate-dist arg (=-1) activation distance override - -CHANGELOG - -0.29.0 - -Bug #556: Video soundtrack not played when music volume is set to zero -Bug #829: OpenMW uses up all available vram, when playing for extended time -Bug #848: Wrong amount of footsteps playing in 1st person -Bug #888: Ascended Sleepers have movement issues -Bug #892: Explicit references are allowed on all script functions -Bug #999: Graphic Herbalism (mod): sometimes doesn't activate properly -Bug #1009: Lake Fjalding AI related slowdown. -Bug #1041: Music playback issues on OS X >= 10.9 -Bug #1043: No message box when advancing skill "Speechcraft" while in dialog window -Bug #1060: Some message boxes are cut off at the bottom -Bug #1062: Bittercup script does not work ('end' variable) -Bug #1074: Inventory paperdoll obscures armour rating -Bug #1077: Message after killing an essential NPC disappears too fast -Bug #1078: "Clutterbane" shows empty charge bar -Bug #1083: UndoWerewolf fails -Bug #1088: Better Clothes Bloodmoon Plus 1.5 by Spirited Treasure pants are not rendered -Bug #1090: Start scripts fail when going to a non-predefined cell -Bug #1091: Crash: Assertion `!q.isNaN() && "Invalid orientation supplied as parameter"' failed. -Bug #1093: Weapons of aggressive NPCs are invisible after you exit and re-enter interior -Bug #1105: Magicka is depleted when using uncastable spells -Bug #1106: Creatures should be able to run -Bug #1107: TR cliffs have way too huge collision boxes in OpenMW -Bug #1109: Cleaning True Light and Darkness with Tes3cmd makes Addamasartus , Zenarbael and Yasamsi flooded. -Bug #1114: Bad output for desktop-file-validate on openmw.desktop (and opencs.desktop) -Bug #1115: Memory leak when spying on Fargoth -Bug #1137: Script execution fails (drenSlaveOwners script) -Bug #1143: Mehra Milo quest (vivec informants) is broken -Bug #1145: Issues with moving gold between inventory and containers -Bug #1146: Issues with picking up stacks of gold -Bug #1147: Dwemer Crossbows are held incorrectly -Bug #1158: Armor rating should always stay below inventory mannequin -Bug #1159: Quick keys can be set during character generation -Bug #1160: Crash on equip lockpick when -Bug #1167: Editor: Referenceables are not correctly loaded when dealing with more than one content file -Feature #30: Loading/Saving (still missing a few parts) -Feature #101: AI Package: Activate -Feature #103: AI Package: Follow, FollowCell -Feature #138: Editor: Drag & Drop -Feature #428: Player death -Feature #505: Editor: Record Cloning -Feature #701: Levelled creatures -Feature #708: Improved Local Variable handling -Feature #709: Editor: Script verifier -Feature #764: Missing journal backend features -Feature #777: Creature weapons/shields -Feature #789: Editor: Referenceable record verifier -Feature #924: Load/Save GUI (still missing loading screen and progress bars) -Feature #946: Knockdown -Feature #947: Decrease fatigue when running, swimming and attacking -Feature #956: Melee Combat: Blocking -Feature #957: Area magic -Feature #960: Combat/AI combat for creatures -Feature #962: Combat-Related AI instructions -Feature #1075: Damage/Restore skill/attribute magic effects -Feature #1076: Soultrap magic effect -Feature #1081: Disease contraction -Feature #1086: Blood particles -Feature #1092: Interrupt resting -Feature #1101: Inventory equip scripts -Feature #1116: Version/Build number in Launcher window -Feature #1119: Resistance/weakness to normal weapons magic effect -Feature #1123: Slow Fall magic effect -Feature #1130: Auto-calculate spells -Feature #1164: Editor: Case-insensitive sorting in tables - -0.28.0 - -Bug #399: Inventory changes are not visible immediately -Bug #417: Apply weather instantly when teleporting -Bug #566: Global Map position marker not updated for interior cells -Bug #712: Looting corpse delay -Bug #716: Problem with the "Vurt's Ascadian Isles Mod" mod -Bug #805: Two TR meshes appear black (v0.24RC) -Bug #841: Third-person activation distance taken from camera rather than head -Bug #845: NPCs hold torches during the day -Bug #855: Vvardenfell Visages Volume I some hairs don´t appear since 0,24 -Bug #856: Maormer race by Mac Kom - The heads are way up -Bug #864: Walk locks during loading in 3rd person -Bug #871: active weapon/magic item icon is not immediately made blank if item is removed during dialog -Bug #882: Hircine's Ring doesn't always work -Bug #909: [Tamriel Rebuilt] crashes in Akamora -Bug #922: Launcher writing merged openmw.cfg files -Bug #943: Random magnitude should be calculated per effect -Bug #948: Negative fatigue level should be allowed -Bug #949: Particles in world space -Bug #950: Hard crash on x64 Linux running --new-game (on startup) -Bug #951: setMagicka and setFatigue have no effect -Bug #954: Problem with equipping inventory items when using a keyboard shortcut -Bug #955: Issues with equipping torches -Bug #966: Shield is visible when casting spell -Bug #967: Game crashes when equipping silver candlestick -Bug #970: Segmentation fault when starting at Bal Isra -Bug #977: Pressing down key in console doesn't go forward in history -Bug #979: Tooltip disappears when changing inventory -Bug #980: Barter: item category is remembered, but not shown -Bug #981: Mod: replacing model has wrong position/orientation -Bug #982: Launcher: Addon unchecking is not saved -Bug #983: Fix controllers to affect objects attached to the base node -Bug #985: Player can talk to NPCs who are in combat -Bug #989: OpenMW crashes when trying to include mod with capital .ESP -Bug #991: Merchants equip items with harmful constant effect enchantments -Bug #994: Don't cap skills/attributes when set via console -Bug #998: Setting the max health should also set the current health -Bug #1005: Torches are visible when casting spells and during hand to hand combat. -Bug #1006: Many NPCs have 0 skill -Bug #1007: Console fills up with text -Bug #1013: Player randomly loses health or dies -Bug #1014: Persuasion window is not centered in maximized window -Bug #1015: Player status window scroll state resets on status change -Bug #1016: Notification window not big enough for all skill level ups -Bug #1020: Saved window positions are not rescaled appropriately on resolution change -Bug #1022: Messages stuck permanently on screen when they pile up -Bug #1023: Journals doesn't open -Bug #1026: Game loses track of torch usage. -Bug #1028: Crash on pickup of jug in Unexplored Shipwreck, Upper level -Bug #1029: Quick keys menu: Select compatible replacement when tool used up -Bug #1042: TES3 header data wrong encoding -Bug #1045: OS X: deployed OpenCS won't launch -Bug #1046: All damaged weaponry is worth 1 gold -Bug #1048: Links in "locked" dialogue are still clickable -Bug #1052: Using color codes when naming your character actually changes the name's color -Bug #1054: Spell effects not visible in front of water -Bug #1055: Power-Spell animation starts even though you already casted it that day -Bug #1059: Cure disease potion removes all effects from player, even your race bonus and race ability -Bug #1063: Crash upon checking out game start ship area in Seyda Neen -Bug #1064: openmw binaries link to unnecessary libraries -Bug #1065: Landing from a high place in water still causes fall damage -Bug #1072: Drawing weapon increases torch brightness -Bug #1073: Merchants sell stacks of gold -Feature #43: Visuals for Magic Effects -Feature #51: Ranged Magic -Feature #52: Touch Range Magic -Feature #53: Self Range Magic -Feature #54: Spell Casting -Feature #70: Vampirism -Feature #100: Combat AI -Feature #171: Implement NIF record NiFlipController -Feature #410: Window to restore enchanted item charge -Feature #647: Enchanted item glow -Feature #723: Invisibility/Chameleon magic effects -Feature #737: Resist Magicka magic effect -Feature #758: GetLOS -Feature #926: Editor: Info-Record tables -Feature #958: Material controllers -Feature #959: Terrain bump, specular, & parallax mapping -Feature #990: Request: unlock mouse when in any menu -Feature #1018: Do not allow view mode switching while performing an action -Feature #1027: Vertex morph animation (NiGeomMorpherController) -Feature #1031: Handle NiBillboardNode -Feature #1051: Implement NIF texture slot DarkTexture -Task #873: Unify OGRE initialisation - -0.27.0 - -Bug #597: Assertion `dialogue->mId == id' failed in esmstore.cpp -Bug #794: incorrect display of decimal numbers -Bug #840: First-person sneaking camera height -Bug #887: Ambient sounds playing while paused -Bug #902: Problems with Polish character encoding -Bug #907: Entering third person using the mousewheel is possible even if it's impossible using the key -Bug #910: Some CDs not working correctly with Unshield installer -Bug #917: Quick character creation plugin does not work -Bug #918: Fatigue does not refill -Bug #919: The PC falls dead in Beshara - OpenMW nightly Win64 (708CDE2) -Feature #57: Acrobatics Skill -Feature #462: Editor: Start Dialogue -Feature #546: Modify ESX selector to handle new content file scheme -Feature #588: Editor: Adjust name/path of edited content files -Feature #644: Editor: Save -Feature #710: Editor: Configure script compiler context -Feature #790: God Mode -Feature #881: Editor: Allow only one instance of OpenCS -Feature #889: Editor: Record filtering -Feature #895: Extinguish torches -Feature #898: Breath meter enhancements -Feature #901: Editor: Default record filter -Feature #913: Merge --master and --plugin switches - -0.26.0 - -Bug #274: Inconsistencies in the terrain -Bug #557: Already-dead NPCs do not equip clothing/items. -Bug #592: Window resizing -Bug #612: [Tamriel Rebuilt] Missing terrain (South of Tel Oren) -Bug #664: Heart of lorkhan acts like a dead body (container) -Bug #767: Wonky ramp physics & water -Bug #780: Swimming out of water -Bug #792: Wrong ground alignment on actors when no clipping -Bug #796: Opening and closing door sound issue -Bug #797: No clipping hinders opening and closing of doors -Bug #799: sliders in enchanting window -Bug #838: Pressing key during startup procedure freezes the game -Bug #839: Combat/magic stances during character creation -Bug #843: [Tribunal] Dark Brotherhood assassin appears without equipment -Bug #844: Resting "until healed" option given even with full stats -Bug #846: Equipped torches are invisible. -Bug #847: Incorrect formula for autocalculated NPC initial health -Bug #850: Shealt weapon sound plays when leaving magic-ready stance -Bug #852: Some boots do not produce footstep sounds -Bug #860: FPS bar misalignment -Bug #861: Unable to print screen -Bug #863: No sneaking and jumping at the same time -Bug #866: Empty variables in [Movies] section of Morrowind.ini gets imported into OpenMW.cfg as blank fallback option and crashes game on start. -Bug #867: Dancing girls in "Suran, Desele's House of Earthly Delights" don't dance. -Bug #868: Idle animations are repeated -Bug #874: Underwater swimming close to the ground is jerky -Bug #875: Animation problem while swimming on the surface and looking up -Bug #876: Always a starting upper case letter in the inventory -Bug #878: Active spell effects don't update the layout properly when ended -Bug #891: Cell 24,-12 (Tamriel Rebuilt) crashes on load -Bug #896: New game sound issue -Feature #49: Melee Combat -Feature #71: Lycanthropy -Feature #393: Initialise MWMechanics::AiSequence from ESM::AIPackageList -Feature #622: Multiple positions for inventory window -Feature #627: Drowning -Feature #786: Allow the 'Activate' key to close the countdialog window -Feature #798: Morrowind installation via Launcher (Linux/Max OS only) -Feature #851: First/Third person transitions with mouse wheel -Task #689: change PhysicActor::enableCollisions -Task #707: Reorganise Compiler - -0.25.0 - -Bug #411: Launcher crash on OS X < 10.8 -Bug #604: Terrible performance drop in the Census and Excise Office. -Bug #676: Start Scripts fail to load -Bug #677: OpenMW does not accept script names with - -Bug #766: Extra space in front of topic links -Bug #793: AIWander Isn't Being Passed The Repeat Parameter -Bug #795: Sound playing with drawn weapon and crossing cell-border -Bug #800: can't select weapon for enchantment -Bug #801: Player can move while over-encumbered -Bug #802: Dead Keys not working -Bug #808: mouse capture -Bug #809: ini Importer does not work without an existing cfg file -Bug #812: Launcher will run OpenMW with no ESM or ESP selected -Bug #813: OpenMW defaults to Morrowind.ESM with no ESM or ESP selected -Bug #817: Dead NPCs and Creatures still have collision boxes -Bug #820: Incorrect sorting of answers (Dialogue) -Bug #826: mwinimport dumps core when given an unknown parameter -Bug #833: getting stuck in door -Bug #835: Journals/books not showing up properly. -Feature #38: SoundGen -Feature #105: AI Package: Wander -Feature #230: 64-bit compatibility for OS X -Feature #263: Hardware mouse cursors -Feature #449: Allow mouse outside of window while paused -Feature #736: First person animations -Feature #750: Using mouse wheel in third person mode -Feature #822: Autorepeat for slider buttons - -0.24.0 - -Bug #284: Book's text misalignment -Bug #445: Camera able to get slightly below floor / terrain -Bug #582: Seam issue in Red Mountain -Bug #632: Journal Next Button shows white square -Bug #653: IndexedStore ignores index -Bug #694: Parser does not recognize float values starting with . -Bug #699: Resource handling broken with Ogre 1.9 trunk -Bug #718: components/esm/loadcell is using the mwworld subsystem -Bug #729: Levelled item list tries to add nonexistent item -Bug #730: Arrow buttons in the settings menu do not work. -Bug #732: Erroneous behavior when binding keys -Bug #733: Unclickable dialogue topic -Bug #734: Book empty line problem -Bug #738: OnDeath only works with implicit references -Bug #740: Script compiler fails on scripts with special names -Bug #742: Wait while no clipping -Bug #743: Problem with changeweather console command -Bug #744: No wait dialogue after starting a new game -Bug #748: Player is not able to unselect objects with the console -Bug #751: AddItem should only spawn a message box when called from dialogue -Bug #752: The enter button has several functions in trade and looting that is not impelemted. -Bug #753: Fargoth's Ring Quest Strange Behavior -Bug #755: Launcher writes duplicate lines into settings.cfg -Bug #759: Second quest in mages guild does not work -Bug #763: Enchantment cast cost is wrong -Bug #770: The "Take" and "Close" buttons in the scroll GUI are stretched incorrectly -Bug #773: AIWander Isn't Being Passed The Correct idle Values -Bug #778: The journal can be opened at the start of a new game -Bug #779: Divayth Fyr starts as dead -Bug #787: "Batch count" on detailed FPS counter gets cut-off -Bug #788: chargen scroll layout does not match vanilla -Feature #60: Atlethics Skill -Feature #65: Security Skill -Feature #74: Interaction with non-load-doors -Feature #98: Render Weapon and Shield -Feature #102: AI Package: Escort, EscortCell -Feature #182: Advanced Journal GUI -Feature #288: Trading enhancements -Feature #405: Integrate "new game" into the menu -Feature #537: Highlight dialogue topic links -Feature #658: Rotate, RotateWorld script instructions and local rotations -Feature #690: Animation Layering -Feature #722: Night Eye/Blind magic effects -Feature #735: Move, MoveWorld script instructions. -Feature #760: Non-removable corpses - -0.23.0 - -Bug #522: Player collides with placeable items -Bug #553: Open/Close sounds played when accessing main menu w/ Journal Open -Bug #561: Tooltip word wrapping delay -Bug #578: Bribing works incorrectly -Bug #601: PositionCell fails on negative coordinates -Bug #606: Some NPCs hairs not rendered with Better Heads addon -Bug #609: Bad rendering of bone boots -Bug #613: Messagebox causing assert to fail -Bug #631: Segfault on shutdown -Bug #634: Exception when talking to Calvus Horatius in Mournhold, royal palace courtyard -Bug #635: Scale NPCs depending on race -Bug #643: Dialogue Race select function is inverted -Bug #646: Twohanded weapons don't work properly -Bug #654: Crash when dropping objects without a collision shape -Bug #655/656: Objects that were disabled or deleted (but not both) were added to the scene when re-entering a cell -Bug #660: "g" in "change" cut off in Race Menu -Bug #661: Arrille sells me the key to his upstairs room -Bug #662: Day counter starts at 2 instead of 1 -Bug #663: Cannot select "come unprepared" topic in dialog with Dagoth Ur -Bug #665: Pickpocket -> "Grab all" grabs all NPC inventory, even not listed in container window. -Bug #666: Looking up/down problem -Bug #667: Active effects border visible during loading -Bug #669: incorrect player position at new game start -Bug #670: race selection menu: sex, face and hair left button not totally clickable -Bug #671: new game: player is naked -Bug #674: buying or selling items doesn't change amount of gold -Bug #675: fatigue is not set to its maximum when starting a new game -Bug #678: Wrong rotation order causes RefData's rotation to be stored incorrectly -Bug #680: different gold coins in Tel Mara -Bug #682: Race menu ignores playable flag for some hairs and faces -Bug #685: Script compiler does not accept ":" after a function name -Bug #688: dispose corpse makes cross-hair to disappear -Bug #691: Auto equipping ignores equipment conditions -Bug #692: OpenMW doesnt load "loose file" texture packs that places resources directly in data folder -Bug #696: Draugr incorrect head offset -Bug #697: Sail transparency issue -Bug #700: "On the rocks" mod does not load its UV coordinates correctly. -Bug #702: Some race mods don't work -Bug #711: Crash during character creation -Bug #715: Growing Tauryon -Bug #725: Auto calculate stats -Bug #728: Failure to open container and talk dialogue -Bug #731: Crash with Mush-Mere's "background" topic -Feature #55/657: Item Repairing -Feature #62/87: Enchanting -Feature #99: Pathfinding -Feature #104: AI Package: Travel -Feature #129: Levelled items -Feature #204: Texture animations -Feature #239: Fallback-Settings -Feature #535: Console object selection improvements -Feature #629: Add levelup description in levelup layout dialog -Feature #630: Optional format subrecord in (tes3) header -Feature #641: Armor rating -Feature #645: OnDeath script function -Feature #683: Companion item UI -Feature #698: Basic Particles -Task #648: Split up components/esm/loadlocks -Task #695: mwgui cleanup - -0.22.0 - -Bug #311: Potential infinite recursion in script compiler -Bug #355: Keyboard repeat rate (in Xorg) are left disabled after game exit. -Bug #382: Weird effect in 3rd person on water -Bug #387: Always use detailed shape for physics raycasts -Bug #420: Potion/ingredient effects do not stack -Bug #429: Parts of dwemer door not picked up correctly for activation/tooltips -Bug #434/Bug #605: Object movement between cells not properly implemented -Bug #502: Duplicate player collision model at origin -Bug #509: Dialogue topic list shifts inappropriately -Bug #513: Sliding stairs -Bug #515: Launcher does not support non-latin strings -Bug #525: Race selection preview camera wrong position -Bug #526: Attributes / skills should not go below zero -Bug #529: Class and Birthsign menus options should be preselected -Bug #530: Lock window button graphic missing -Bug #532: Missing map menu graphics -Bug #545: ESX selector does not list ESM files properly -Bug #547: Global variables of type short are read incorrectly -Bug #550: Invisible meshes collision and tooltip -Bug #551: Performance drop when loading multiple ESM files -Bug #552: Don't list CG in options if it is not available -Bug #555: Character creation windows "OK" button broken -Bug #558: Segmentation fault when Alt-tabbing with console opened -Bug #559: Dialog window should not be available before character creation is finished -Bug #560: Tooltip borders should be stretched -Bug #562: Sound should not be played when an object cannot be picked up -Bug #565: Water animation speed + timescale -Bug #572: Better Bodies' textures don't work -Bug #573: OpenMW doesn't load if TR_Mainland.esm is enabled (Tamriel Rebuilt mod) -Bug #574: Moving left/right should not cancel auto-run -Bug #575: Crash entering the Chamber of Song -Bug #576: Missing includes -Bug #577: Left Gloves Addon causes ESMReader exception -Bug #579: Unable to open container "Kvama Egg Sack" -Bug #581: Mimicking vanilla Morrowind water -Bug #583: Gender not recognized -Bug #586: Wrong char gen behaviour -Bug #587: "End" script statements with spaces don't work -Bug #589: Closing message boxes by pressing the activation key -Bug #590: Ugly Dagoth Ur rendering -Bug #591: Race selection issues -Bug #593: Persuasion response should be random -Bug #595: Footless guard -Bug #599: Waterfalls are invisible from a certain distance -Bug #600: Waterfalls rendered incorrectly, cut off by water -Bug #607: New beast bodies mod crashes -Bug #608: Crash in cell "Mournhold, Royal Palace" -Bug #611: OpenMW doesn't find some of textures used in Tamriel Rebuilt -Bug #613: Messagebox causing assert to fail -Bug #615: Meshes invisible from above water -Bug #617: Potion effects should be hidden until discovered -Bug #619: certain moss hanging from tree has rendering bug -Bug #621: Batching bloodmoon's trees -Bug #623: NiMaterialProperty alpha unhandled -Bug #628: Launcher in latest master crashes the game -Bug #633: Crash on startup: Better Heads -Bug #636: Incorrect Char Gen Menu Behavior -Feature #29: Allow ESPs and multiple ESMs -Feature #94: Finish class selection-dialogue -Feature #149: Texture Alphas -Feature #237: Run Morrowind-ini importer from launcher -Feature #286: Update Active Spell Icons -Feature #334: Swimming animation -Feature #335: Walking animation -Feature #360: Proper collision shapes for NPCs and creatures -Feature #367: Lights that behave more like original morrowind implementation -Feature #477: Special local scripting variables -Feature #528: Message boxes should close when enter is pressed under certain conditions. -Feature #543: Add bsa files to the settings imported by the ini importer -Feature #594: coordinate space and utility functions -Feature #625: Zoom in vanity mode -Task #464: Refactor launcher ESX selector into a re-usable component -Task #624: Unified implementation of type-variable sub-records - -0.21.0 - -Bug #253: Dialogs don't work for Russian version of Morrowind -Bug #267: Activating creatures without dialogue can still activate the dialogue GUI -Bug #354: True flickering lights -Bug #386: The main menu's first entry is wrong (in french) -Bug #479: Adding the spell "Ash Woe Blight" to the player causes strange attribute oscillations -Bug #495: Activation Range -Bug #497: Failed Disposition check doesn't stop a dialogue entry from being returned -Bug #498: Failing a disposition check shouldn't eliminate topics from the the list of those available -Bug #500: Disposition for most NPCs is 0/100 -Bug #501: Getdisposition command wrongly returns base disposition -Bug #506: Journal UI doesn't update anymore -Bug #507: EnableRestMenu is not a valid command - change it to EnableRest -Bug #508: Crash in Ald Daedroth Shrine -Bug #517: Wrong price calculation when untrading an item -Bug #521: MWGui::InventoryWindow creates a duplicate player actor at the origin -Bug #524: Beast races are able to wear shoes -Bug #527: Background music fails to play -Bug #533: The arch at Gnisis entrance is not displayed -Bug #534: Terrain gets its correct shape only some time after the cell is loaded -Bug #536: The same entry can be added multiple times to the journal -Bug #539: Race selection is broken -Bug #544: Terrain normal map corrupt when the map is rendered -Feature #39: Video Playback -Feature #151: ^-escape sequences in text output -Feature #392: Add AI related script functions -Feature #456: Determine required ini fallback values and adjust the ini importer accordingly -Feature #460: Experimental DirArchives improvements -Feature #540: Execute scripts of objects in containers/inventories in active cells -Task #401: Review GMST fixing -Task #453: Unify case smashing/folding -Task #512: Rewrite utf8 component - -0.20.0 - -Bug #366: Changing the player's race during character creation does not change the look of the player character -Bug #430: Teleporting and using loading doors linking within the same cell reloads the cell -Bug #437: Stop animations when paused -Bug #438: Time displays as "0 a.m." when it should be "12 a.m." -Bug #439: Text in "name" field of potion/spell creation window is persistent -Bug #440: Starting date at a new game is off by one day -Bug #442: Console window doesn't close properly sometimes -Bug #448: Do not break container window formatting when item names are very long -Bug #458: Topics sometimes not automatically added to known topic list -Bug #476: Auto-Moving allows player movement after using DisablePlayerControls -Bug #478: After sleeping in a bed the rest dialogue window opens automtically again -Bug #492: On creating potions the ingredients are removed twice -Feature #63: Mercantile skill -Feature #82: Persuasion Dialogue -Feature #219: Missing dialogue filters/functions -Feature #369: Add a FailedAction -Feature #377: Select head/hair on character creation -Feature #391: Dummy AI package classes -Feature #435: Global Map, 2nd Layer -Feature #450: Persuasion -Feature #457: Add more script instructions -Feature #474: update the global variable pcrace when the player's race is changed -Task #158: Move dynamically generated classes from Player class to World Class -Task #159: ESMStore rework and cleanup -Task #163: More Component Namespace Cleanup -Task #402: Move player data from MWWorld::Player to the player's NPC record -Task #446: Fix no namespace in BulletShapeLoader - -0.19.0 - -Bug #374: Character shakes in 3rd person mode near the origin -Bug #404: Gamma correct rendering -Bug #407: Shoes of St. Rilm do not work -Bug #408: Rugs has collision even if they are not supposed to -Bug #412: Birthsign menu sorted incorrectly -Bug #413: Resolutions presented multiple times in launcher -Bug #414: launcher.cfg file stored in wrong directory -Bug #415: Wrong esm order in openmw.cfg -Bug #418: Sound listener position updates incorrectly -Bug #423: wrong usage of "Version" entry in openmw.desktop -Bug #426: Do not use hardcoded splash images -Bug #431: Don't use markers for raycast -Bug #432: Crash after picking up items from an NPC -Feature #21/#95: Sleeping/resting -Feature #61: Alchemy Skill -Feature #68: Death -Feature #69/#86: Spell Creation -Feature #72/#84: Travel -Feature #76: Global Map, 1st Layer -Feature #120: Trainer Window -Feature #152: Skill Increase from Skill Books -Feature #160: Record Saving -Task #400: Review GMST access - -0.18.0 - -Bug #310: Button of the "preferences menu" are too small -Bug #361: Hand-to-hand skill is always 100 -Bug #365: NPC and creature animation is jerky; Characters float around when they are not supposed to -Bug #372: playSound3D uses original coordinates instead of current coordinates. -Bug #373: Static OGRE build faulty -Bug #375: Alt-tab toggle view -Bug #376: Screenshots are disable -Bug #378: Exception when drinking self-made potions -Bug #380: Cloth visibility problem -Bug #384: Weird character on doors tooltip. -Bug #398: Some objects do not collide in MW, but do so in OpenMW -Feature #22: Implement level-up -Feature #36: Hide Marker -Feature #88: Hotkey Window -Feature #91: Level-Up Dialogue -Feature #118: Keyboard and Mouse-Button bindings -Feature #119: Spell Buying Window -Feature #133: Handle resources across multiple data directories -Feature #134: Generate a suitable default-value for --data-local -Feature #292: Object Movement/Creation Script Instructions -Feature #340: AIPackage data structures -Feature #356: Ingredients use -Feature #358: Input system rewrite -Feature #370: Target handling in actions -Feature #379: Door markers on the local map -Feature #389: AI framework -Feature #395: Using keys to open doors / containers -Feature #396: Loading screens -Feature #397: Inventory avatar image and race selection head preview -Task #339: Move sounds into Action - -0.17.0 - -Bug #225: Valgrind reports about 40MB of leaked memory -Bug #241: Some physics meshes still don't match -Bug #248: Some textures are too dark -Bug #300: Dependency on proprietary CG toolkit -Bug #302: Some objects don't collide although they should -Bug #308: Freeze in Balmora, Meldor: Armorer -Bug #313: openmw without a ~/.config/openmw folder segfault. -Bug #317: adding non-existing spell via console locks game -Bug #318: Wrong character normals -Bug #341: Building with Ogre Debug libraries does not use debug version of plugins -Bug #347: Crash when running openmw with --start="XYZ" -Bug #353: FindMyGUI.cmake breaks path on Windows -Bug #359: WindowManager throws exception at destruction -Bug #364: Laggy input on OS X due to bug in Ogre's event pump implementation -Feature #33: Allow objects to cross cell-borders -Feature #59: Dropping Items (replaced stopgap implementation with a proper one) -Feature #93: Main Menu -Feature #96/329/330/331/332/333: Player Control -Feature #180: Object rotation and scaling. -Feature #272: Incorrect NIF material sharing -Feature #314: Potion usage -Feature #324: Skill Gain -Feature #342: Drain/fortify dynamic stats/attributes magic effects -Feature #350: Allow console only script instructions -Feature #352: Run scripts in console on startup -Task #107: Refactor mw*-subsystems -Task #325: Make CreatureStats into a class -Task #345: Use Ogre's animation system -Task #351: Rewrite Action class to support automatic sound playing - -0.16.0 - -Bug #250: OpenMW launcher erratic behaviour -Bug #270: Crash because of underwater effect on OS X -Bug #277: Auto-equipping in some cells not working -Bug #294: Container GUI ignores disabled inventory menu -Bug #297: Stats review dialog shows all skills and attribute values as 0 -Bug #298: MechanicsManager::buildPlayer does not remove previous bonuses -Bug #299: Crash in World::disable -Bug #306: Non-existent ~/.config/openmw "crash" the launcher. -Bug #307: False "Data Files" location make the launcher "crash" -Feature #81: Spell Window -Feature #85: Alchemy Window -Feature #181: Support for x.y script syntax -Feature #242: Weapon and Spell icons -Feature #254: Ingame settings window -Feature #293: Allow "stacking" game modes -Feature #295: Class creation dialog tooltips -Feature #296: Clicking on the HUD elements should show/hide the respective window -Feature #301: Direction after using a Teleport Door -Feature #303: Allow object selection in the console -Feature #305: Allow the use of = as a synonym for == -Feature #312: Compensation for slow object access in poorly written Morrowind.esm scripts -Task #176: Restructure enabling/disabling of MW-references -Task #283: Integrate ogre.cfg file in settings file -Task #290: Auto-Close MW-reference related GUI windows - -0.15.0 - -Bug #5: Physics reimplementation (fixes various issues) -Bug #258: Resizing arrow's background is not transparent -Bug #268: Widening the stats window in X direction causes layout problems -Bug #269: Topic pane in dialgoue window is too small for some longer topics -Bug #271: Dialog choices are sorted incorrectly -Bug #281: The single quote character is not rendered on dialog windows -Bug #285: Terrain not handled properly in cells that are not predefined -Bug #289: Dialogue filter isn't doing case smashing/folding for item IDs -Feature #15: Collision with Terrain -Feature #17: Inventory-, Container- and Trade-Windows -Feature #44: Floating Labels above Focussed Objects -Feature #80: Tooltips -Feature #83: Barter Dialogue -Feature #90: Book and Scroll Windows -Feature #156: Item Stacking in Containers -Feature #213: Pulsating lights -Feature #218: Feather & Burden -Feature #256: Implement magic effect bookkeeping -Feature #259: Add missing information to Stats window -Feature #260: Correct case for dialogue topics -Feature #280: GUI texture atlasing -Feature #291: Ability to use GMST strings from GUI layout files -Task #255: Make MWWorld::Environment into a singleton - -0.14.0 - -Bug #1: Meshes rendered with wrong orientation -Bug #6/Task #220: Picking up small objects doesn't always work -Bug #127: tcg doesn't work -Bug #178: Compablity problems with Ogre 1.8.0 RC 1 -Bug #211: Wireframe mode (toggleWireframe command) should not apply to Console & other UI -Bug #227: Terrain crashes when moving away from predefined cells -Bug #229: On OS X Launcher cannot launch game if path to binary contains spaces -Bug #235: TGA texture loading problem -Bug #246: wireframe mode does not work in water -Feature #8/#232: Water Rendering -Feature #13: Terrain Rendering -Feature #37: Render Path Grid -Feature #66: Factions -Feature #77: Local Map -Feature #78: Compass/Mini-Map -Feature #97: Render Clothing/Armour -Feature #121: Window Pinning -Feature #205: Auto equip -Feature #217: Contiainer should track changes to its content -Feature #221: NPC Dialogue Window Enhancements -Feature #233: Game settings manager -Feature #240: Spell List and selected spell (no GUI yet) -Feature #243: Draw State -Task #113: Morrowind.ini Importer -Task #215: Refactor the sound code -Task #216: Update MyGUI - -0.13.0 - -Bug #145: Fixed sound problems after cell change -Bug #179: Pressing space in console triggers activation -Bug #186: CMake doesn't use the debug versions of Ogre libraries on Linux -Bug #189: ASCII 16 character added to console on it's activation on Mac OS X -Bug #190: Case Folding fails with music files -Bug #192: Keypresses write Text into Console no matter which gui element is active -Bug #196: Collision shapes out of place -Bug #202: ESMTool doesn't not work with localised ESM files anymore -Bug #203: Torch lights only visible on short distance -Bug #207: Ogre.log not written -Bug #209: Sounds do not play -Bug #210: Ogre crash at Dren plantation -Bug #214: Unsupported file format version -Bug #222: Launcher is writing openmw.cfg file to wrong location -Feature #9: NPC Dialogue Window -Feature #16/42: New sky/weather implementation -Feature #40: Fading -Feature #48: NPC Dialogue System -Feature #117: Equipping Items (backend only, no GUI yet, no rendering of equipped items yet) -Feature #161: Load REC_PGRD records -Feature #195: Wireframe-mode -Feature #198/199: Various sound effects -Feature #206: Allow picking data path from launcher if non is set -Task #108: Refactor window manager class -Task #172: Sound Manager Cleanup -Task #173: Create OpenEngine systems in the appropriate manager classes -Task #184: Adjust MSVC and gcc warning levels -Task #185: RefData rewrite -Task #201: Workaround for transparency issues -Task #208: silenced esm_reader.hpp warning - -0.12.0 - -Bug #154: FPS Drop -Bug #169: Local scripts continue running if associated object is deleted -Bug #174: OpenMW fails to start if the config directory doesn't exist -Bug #187: Missing lighting -Bug #188: Lights without a mesh are not rendered -Bug #191: Taking screenshot causes crash when running installed -Feature #28: Sort out the cell load problem -Feature #31: Allow the player to move away from pre-defined cells -Feature #35: Use alternate storage location for modified object position -Feature #45: NPC animations -Feature #46: Creature Animation -Feature #89: Basic Journal Window -Feature #110: Automatically pick up the path of existing MW-installations -Feature #183: More FPS display settings -Task #19: Refactor engine class -Task #109/Feature #162: Automate Packaging -Task #112: Catch exceptions thrown in input handling functions -Task #128/#168: Cleanup Configuration File Handling -Task #131: NPC Activation doesn't work properly -Task #144: MWRender cleanup -Task #155: cmake cleanup - -0.11.1 - -Bug #2: Resources loading doesn't work outside of bsa files -Bug #3: GUI does not render non-English characters -Bug #7: openmw.cfg location doesn't match -Bug #124: The TCL alias for ToggleCollision is missing. -Bug #125: Some command line options can't be used from a .cfg file -Bug #126: Toggle-type script instructions are less verbose compared with original MW -Bug #130: NPC-Record Loading fails for some NPCs -Bug #167: Launcher sets invalid parameters in ogre config -Feature #10: Journal -Feature #12: Rendering Optimisations -Feature #23: Change Launcher GUI to a tabbed interface -Feature #24: Integrate the OGRE settings window into the launcher -Feature #25: Determine openmw.cfg location (Launcher) -Feature #26: Launcher Profiles -Feature #79: MessageBox -Feature #116: Tab-Completion in Console -Feature #132: --data-local and multiple --data -Feature #143: Non-Rendering Performance-Optimisations -Feature #150: Accessing objects in cells via ID does only work for objects with all lower case IDs -Feature #157: Version Handling -Task #14: Replace tabs with 4 spaces -Task #18: Move components from global namespace into their own namespace -Task #123: refactor header files in components/esm - -0.10.0 - -* NPC dialogue window (not functional yet) -* Collisions with objects -* Refactor the PlayerPos class -* Adjust file locations -* CMake files and test linking for Bullet -* Replace Ogre raycasting test for activation with something more precise -* Adjust player movement according to collision results -* FPS display -* Various Portability Improvements -* Mac OS X support is back! - -0.9.0 - -* Exterior cells loading, unloading and management -* Character Creation GUI -* Character creation -* Make cell names case insensitive when doing internal lookups -* Music player -* NPCs rendering - -0.8.0 - -* GUI -* Complete and working script engine -* In game console -* Sky rendering -* Sound and music -* Tons of smaller stuff - -0.7.0 - -* This release is a complete rewrite in C++. -* All D code has been culled, and all modules have been rewritten. -* The game is now back up to the level of rendering interior cells and moving around, but physics, sound, GUI, and scripting still remain to be ported from the old codebase. - -0.6.0 - -* Coded a GUI system using MyGUI -* Skinned MyGUI to look like Morrowind (work in progress) -* Integrated the Monster script engine -* Rewrote some functions into script code -* Very early MyGUI < > Monster binding -* Fixed Windows sound problems (replaced old openal32.dll) - -0.5.0 - -* Collision detection with Bullet -* Experimental walk & fall character physics -* New key bindings: - * t toggle physics mode (walking, flying, ghost), - * n night eye, brightens the scene -* Fixed incompatability with DMD 1.032 and newer compilers -* * (thanks to tomqyp) -* Various minor changes and updates - -0.4.0 - -* Switched from Audiere to OpenAL -* * (BIG thanks to Chris Robinson) -* Added complete Makefile (again) as a alternative build tool -* More realistic lighting (thanks again to Chris Robinson) -* Various localization fixes tested with Russian and French versions -* Temporary workaround for the Unicode issue: invalid UTF displayed as '?' -* Added ns option to disable sound, for debugging -* Various bug fixes -* Cosmetic changes to placate gdc Wall - -0.3.0 - -* Built and tested on Windows XP -* Partial support for FreeBSD (exceptions do not work) -* You no longer have to download Monster separately -* Made an alternative for building without DSSS (but DSSS still works) -* Renamed main program from 'morro' to 'openmw' -* Made the config system more robust -* Added oc switch for showing Ogre config window on startup -* Removed some config files, these are auto generated when missing. -* Separated plugins.cfg into linux and windows versions. -* Updated Makefile and sources for increased portability -* confirmed to work against OIS 1.0.0 (Ubuntu repository package) - -0.2.0 - -* Compiles with gdc -* Switched to DSSS for building D code -* Includes the program esmtool - -0.1.0 - -first release +OpenMW: A reimplementation of The Elder Scrolls III: Morrowind + +OpenMW is an attempt at recreating the engine for the popular role-playing game +Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. + +Version: 0.28.0 +License: GPL (see GPL3.txt for more information) +Website: http://www.openmw.org + +Font Licenses: +EBGaramond-Regular.ttf: OFL (see OFL.txt for more information) +DejaVuLGCSansMono.ttf: custom (see DejaVu Font License.txt for more information) + + + +INSTALLATION + +Windows: +Run the installer. + +Linux: +Ubuntu (and most others) +Download the .deb file and install it in the usual way. + +Arch Linux +There's an OpenMW package available in the [community] Repository: +https://www.archlinux.org/packages/?sort=&q=openmw + +OS X: +Open DMG file, copy OpenMW folder anywhere, for example in /Applications + +BUILD FROM SOURCE + +https://wiki.openmw.org/index.php?title=Development_Environment_Setup + + +THE DATA PATH + +The data path tells OpenMW where to find your Morrowind files. If you run the launcher, OpenMW should be able to +pick up the location of these files on its own, if both Morrowind and OpenMW are installed properly +(installing Morrowind under WINE is considered a proper install). + +COMMAND LINE OPTIONS + +Syntax: openmw +Allowed options: + --help print help message + --version print version information and quit + --data arg (=data) set data directories (later directories + have higher priority) + --data-local arg set local data directory (highest + priority) + --fallback-archive arg (=fallback-archive) + set fallback BSA archives (later + archives have higher priority) + --resources arg (=resources) set resources directory + --start arg (=Beshara) set initial cell + --content arg content file(s): esm/esp, or + omwgame/omwaddon + --anim-verbose [=arg(=1)] (=0) output animation indices files + --no-sound [=arg(=1)] (=0) disable all sounds + --script-verbose [=arg(=1)] (=0) verbose script output + --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue + scripts) at startup + --script-console [=arg(=1)] (=0) enable console-only script + functionality + --script-run arg select a file containing a list of + console commands that is executed on + startup + --script-warn [=arg(=1)] (=1) handling of warnings when compiling + scripts + 0 - ignore warning + 1 - show warning but consider script as + correctly compiled anyway + 2 - treat warnings as errors + --skip-menu [=arg(=1)] (=0) skip main menu on game startup + --fs-strict [=arg(=1)] (=0) strict file system handling (no case + folding) + --encoding arg (=win1252) Character encoding used in OpenMW game + messages: + + win1250 - Central and Eastern European + such as Polish, Czech, Slovak, + Hungarian, Slovene, Bosnian, Croatian, + Serbian (Latin script), Romanian and + Albanian languages + + win1251 - Cyrillic alphabet such as + Russian, Bulgarian, Serbian Cyrillic + and other languages + + win1252 - Western European (Latin) + alphabet, used by default + --fallback arg fallback values + --no-grab Don't grab mouse cursor + --activate-dist arg (=-1) activation distance override + +CHANGELOG + +0.29.0 + +Bug #556: Video soundtrack not played when music volume is set to zero +Bug #829: OpenMW uses up all available vram, when playing for extended time +Bug #848: Wrong amount of footsteps playing in 1st person +Bug #888: Ascended Sleepers have movement issues +Bug #892: Explicit references are allowed on all script functions +Bug #999: Graphic Herbalism (mod): sometimes doesn't activate properly +Bug #1009: Lake Fjalding AI related slowdown. +Bug #1041: Music playback issues on OS X >= 10.9 +Bug #1043: No message box when advancing skill "Speechcraft" while in dialog window +Bug #1060: Some message boxes are cut off at the bottom +Bug #1062: Bittercup script does not work ('end' variable) +Bug #1074: Inventory paperdoll obscures armour rating +Bug #1077: Message after killing an essential NPC disappears too fast +Bug #1078: "Clutterbane" shows empty charge bar +Bug #1083: UndoWerewolf fails +Bug #1088: Better Clothes Bloodmoon Plus 1.5 by Spirited Treasure pants are not rendered +Bug #1090: Start scripts fail when going to a non-predefined cell +Bug #1091: Crash: Assertion `!q.isNaN() && "Invalid orientation supplied as parameter"' failed. +Bug #1093: Weapons of aggressive NPCs are invisible after you exit and re-enter interior +Bug #1105: Magicka is depleted when using uncastable spells +Bug #1106: Creatures should be able to run +Bug #1107: TR cliffs have way too huge collision boxes in OpenMW +Bug #1109: Cleaning True Light and Darkness with Tes3cmd makes Addamasartus , Zenarbael and Yasamsi flooded. +Bug #1114: Bad output for desktop-file-validate on openmw.desktop (and opencs.desktop) +Bug #1115: Memory leak when spying on Fargoth +Bug #1137: Script execution fails (drenSlaveOwners script) +Bug #1143: Mehra Milo quest (vivec informants) is broken +Bug #1145: Issues with moving gold between inventory and containers +Bug #1146: Issues with picking up stacks of gold +Bug #1147: Dwemer Crossbows are held incorrectly +Bug #1158: Armor rating should always stay below inventory mannequin +Bug #1159: Quick keys can be set during character generation +Bug #1160: Crash on equip lockpick when +Bug #1167: Editor: Referenceables are not correctly loaded when dealing with more than one content file +Feature #30: Loading/Saving (still missing a few parts) +Feature #101: AI Package: Activate +Feature #103: AI Package: Follow, FollowCell +Feature #138: Editor: Drag & Drop +Feature #428: Player death +Feature #505: Editor: Record Cloning +Feature #701: Levelled creatures +Feature #708: Improved Local Variable handling +Feature #709: Editor: Script verifier +Feature #764: Missing journal backend features +Feature #777: Creature weapons/shields +Feature #789: Editor: Referenceable record verifier +Feature #924: Load/Save GUI (still missing loading screen and progress bars) +Feature #946: Knockdown +Feature #947: Decrease fatigue when running, swimming and attacking +Feature #956: Melee Combat: Blocking +Feature #957: Area magic +Feature #960: Combat/AI combat for creatures +Feature #962: Combat-Related AI instructions +Feature #1075: Damage/Restore skill/attribute magic effects +Feature #1076: Soultrap magic effect +Feature #1081: Disease contraction +Feature #1086: Blood particles +Feature #1092: Interrupt resting +Feature #1101: Inventory equip scripts +Feature #1116: Version/Build number in Launcher window +Feature #1119: Resistance/weakness to normal weapons magic effect +Feature #1123: Slow Fall magic effect +Feature #1130: Auto-calculate spells +Feature #1164: Editor: Case-insensitive sorting in tables + +0.28.0 + +Bug #399: Inventory changes are not visible immediately +Bug #417: Apply weather instantly when teleporting +Bug #566: Global Map position marker not updated for interior cells +Bug #712: Looting corpse delay +Bug #716: Problem with the "Vurt's Ascadian Isles Mod" mod +Bug #805: Two TR meshes appear black (v0.24RC) +Bug #841: Third-person activation distance taken from camera rather than head +Bug #845: NPCs hold torches during the day +Bug #855: Vvardenfell Visages Volume I some hairs don´t appear since 0,24 +Bug #856: Maormer race by Mac Kom - The heads are way up +Bug #864: Walk locks during loading in 3rd person +Bug #871: active weapon/magic item icon is not immediately made blank if item is removed during dialog +Bug #882: Hircine's Ring doesn't always work +Bug #909: [Tamriel Rebuilt] crashes in Akamora +Bug #922: Launcher writing merged openmw.cfg files +Bug #943: Random magnitude should be calculated per effect +Bug #948: Negative fatigue level should be allowed +Bug #949: Particles in world space +Bug #950: Hard crash on x64 Linux running --new-game (on startup) +Bug #951: setMagicka and setFatigue have no effect +Bug #954: Problem with equipping inventory items when using a keyboard shortcut +Bug #955: Issues with equipping torches +Bug #966: Shield is visible when casting spell +Bug #967: Game crashes when equipping silver candlestick +Bug #970: Segmentation fault when starting at Bal Isra +Bug #977: Pressing down key in console doesn't go forward in history +Bug #979: Tooltip disappears when changing inventory +Bug #980: Barter: item category is remembered, but not shown +Bug #981: Mod: replacing model has wrong position/orientation +Bug #982: Launcher: Addon unchecking is not saved +Bug #983: Fix controllers to affect objects attached to the base node +Bug #985: Player can talk to NPCs who are in combat +Bug #989: OpenMW crashes when trying to include mod with capital .ESP +Bug #991: Merchants equip items with harmful constant effect enchantments +Bug #994: Don't cap skills/attributes when set via console +Bug #998: Setting the max health should also set the current health +Bug #1005: Torches are visible when casting spells and during hand to hand combat. +Bug #1006: Many NPCs have 0 skill +Bug #1007: Console fills up with text +Bug #1013: Player randomly loses health or dies +Bug #1014: Persuasion window is not centered in maximized window +Bug #1015: Player status window scroll state resets on status change +Bug #1016: Notification window not big enough for all skill level ups +Bug #1020: Saved window positions are not rescaled appropriately on resolution change +Bug #1022: Messages stuck permanently on screen when they pile up +Bug #1023: Journals doesn't open +Bug #1026: Game loses track of torch usage. +Bug #1028: Crash on pickup of jug in Unexplored Shipwreck, Upper level +Bug #1029: Quick keys menu: Select compatible replacement when tool used up +Bug #1042: TES3 header data wrong encoding +Bug #1045: OS X: deployed OpenCS won't launch +Bug #1046: All damaged weaponry is worth 1 gold +Bug #1048: Links in "locked" dialogue are still clickable +Bug #1052: Using color codes when naming your character actually changes the name's color +Bug #1054: Spell effects not visible in front of water +Bug #1055: Power-Spell animation starts even though you already casted it that day +Bug #1059: Cure disease potion removes all effects from player, even your race bonus and race ability +Bug #1063: Crash upon checking out game start ship area in Seyda Neen +Bug #1064: openmw binaries link to unnecessary libraries +Bug #1065: Landing from a high place in water still causes fall damage +Bug #1072: Drawing weapon increases torch brightness +Bug #1073: Merchants sell stacks of gold +Feature #43: Visuals for Magic Effects +Feature #51: Ranged Magic +Feature #52: Touch Range Magic +Feature #53: Self Range Magic +Feature #54: Spell Casting +Feature #70: Vampirism +Feature #100: Combat AI +Feature #171: Implement NIF record NiFlipController +Feature #410: Window to restore enchanted item charge +Feature #647: Enchanted item glow +Feature #723: Invisibility/Chameleon magic effects +Feature #737: Resist Magicka magic effect +Feature #758: GetLOS +Feature #926: Editor: Info-Record tables +Feature #958: Material controllers +Feature #959: Terrain bump, specular, & parallax mapping +Feature #990: Request: unlock mouse when in any menu +Feature #1018: Do not allow view mode switching while performing an action +Feature #1027: Vertex morph animation (NiGeomMorpherController) +Feature #1031: Handle NiBillboardNode +Feature #1051: Implement NIF texture slot DarkTexture +Task #873: Unify OGRE initialisation + +0.27.0 + +Bug #597: Assertion `dialogue->mId == id' failed in esmstore.cpp +Bug #794: incorrect display of decimal numbers +Bug #840: First-person sneaking camera height +Bug #887: Ambient sounds playing while paused +Bug #902: Problems with Polish character encoding +Bug #907: Entering third person using the mousewheel is possible even if it's impossible using the key +Bug #910: Some CDs not working correctly with Unshield installer +Bug #917: Quick character creation plugin does not work +Bug #918: Fatigue does not refill +Bug #919: The PC falls dead in Beshara - OpenMW nightly Win64 (708CDE2) +Feature #57: Acrobatics Skill +Feature #462: Editor: Start Dialogue +Feature #546: Modify ESX selector to handle new content file scheme +Feature #588: Editor: Adjust name/path of edited content files +Feature #644: Editor: Save +Feature #710: Editor: Configure script compiler context +Feature #790: God Mode +Feature #881: Editor: Allow only one instance of OpenCS +Feature #889: Editor: Record filtering +Feature #895: Extinguish torches +Feature #898: Breath meter enhancements +Feature #901: Editor: Default record filter +Feature #913: Merge --master and --plugin switches + +0.26.0 + +Bug #274: Inconsistencies in the terrain +Bug #557: Already-dead NPCs do not equip clothing/items. +Bug #592: Window resizing +Bug #612: [Tamriel Rebuilt] Missing terrain (South of Tel Oren) +Bug #664: Heart of lorkhan acts like a dead body (container) +Bug #767: Wonky ramp physics & water +Bug #780: Swimming out of water +Bug #792: Wrong ground alignment on actors when no clipping +Bug #796: Opening and closing door sound issue +Bug #797: No clipping hinders opening and closing of doors +Bug #799: sliders in enchanting window +Bug #838: Pressing key during startup procedure freezes the game +Bug #839: Combat/magic stances during character creation +Bug #843: [Tribunal] Dark Brotherhood assassin appears without equipment +Bug #844: Resting "until healed" option given even with full stats +Bug #846: Equipped torches are invisible. +Bug #847: Incorrect formula for autocalculated NPC initial health +Bug #850: Shealt weapon sound plays when leaving magic-ready stance +Bug #852: Some boots do not produce footstep sounds +Bug #860: FPS bar misalignment +Bug #861: Unable to print screen +Bug #863: No sneaking and jumping at the same time +Bug #866: Empty variables in [Movies] section of Morrowind.ini gets imported into OpenMW.cfg as blank fallback option and crashes game on start. +Bug #867: Dancing girls in "Suran, Desele's House of Earthly Delights" don't dance. +Bug #868: Idle animations are repeated +Bug #874: Underwater swimming close to the ground is jerky +Bug #875: Animation problem while swimming on the surface and looking up +Bug #876: Always a starting upper case letter in the inventory +Bug #878: Active spell effects don't update the layout properly when ended +Bug #891: Cell 24,-12 (Tamriel Rebuilt) crashes on load +Bug #896: New game sound issue +Feature #49: Melee Combat +Feature #71: Lycanthropy +Feature #393: Initialise MWMechanics::AiSequence from ESM::AIPackageList +Feature #622: Multiple positions for inventory window +Feature #627: Drowning +Feature #786: Allow the 'Activate' key to close the countdialog window +Feature #798: Morrowind installation via Launcher (Linux/Max OS only) +Feature #851: First/Third person transitions with mouse wheel +Task #689: change PhysicActor::enableCollisions +Task #707: Reorganise Compiler + +0.25.0 + +Bug #411: Launcher crash on OS X < 10.8 +Bug #604: Terrible performance drop in the Census and Excise Office. +Bug #676: Start Scripts fail to load +Bug #677: OpenMW does not accept script names with - +Bug #766: Extra space in front of topic links +Bug #793: AIWander Isn't Being Passed The Repeat Parameter +Bug #795: Sound playing with drawn weapon and crossing cell-border +Bug #800: can't select weapon for enchantment +Bug #801: Player can move while over-encumbered +Bug #802: Dead Keys not working +Bug #808: mouse capture +Bug #809: ini Importer does not work without an existing cfg file +Bug #812: Launcher will run OpenMW with no ESM or ESP selected +Bug #813: OpenMW defaults to Morrowind.ESM with no ESM or ESP selected +Bug #817: Dead NPCs and Creatures still have collision boxes +Bug #820: Incorrect sorting of answers (Dialogue) +Bug #826: mwinimport dumps core when given an unknown parameter +Bug #833: getting stuck in door +Bug #835: Journals/books not showing up properly. +Feature #38: SoundGen +Feature #105: AI Package: Wander +Feature #230: 64-bit compatibility for OS X +Feature #263: Hardware mouse cursors +Feature #449: Allow mouse outside of window while paused +Feature #736: First person animations +Feature #750: Using mouse wheel in third person mode +Feature #822: Autorepeat for slider buttons + +0.24.0 + +Bug #284: Book's text misalignment +Bug #445: Camera able to get slightly below floor / terrain +Bug #582: Seam issue in Red Mountain +Bug #632: Journal Next Button shows white square +Bug #653: IndexedStore ignores index +Bug #694: Parser does not recognize float values starting with . +Bug #699: Resource handling broken with Ogre 1.9 trunk +Bug #718: components/esm/loadcell is using the mwworld subsystem +Bug #729: Levelled item list tries to add nonexistent item +Bug #730: Arrow buttons in the settings menu do not work. +Bug #732: Erroneous behavior when binding keys +Bug #733: Unclickable dialogue topic +Bug #734: Book empty line problem +Bug #738: OnDeath only works with implicit references +Bug #740: Script compiler fails on scripts with special names +Bug #742: Wait while no clipping +Bug #743: Problem with changeweather console command +Bug #744: No wait dialogue after starting a new game +Bug #748: Player is not able to unselect objects with the console +Bug #751: AddItem should only spawn a message box when called from dialogue +Bug #752: The enter button has several functions in trade and looting that is not impelemted. +Bug #753: Fargoth's Ring Quest Strange Behavior +Bug #755: Launcher writes duplicate lines into settings.cfg +Bug #759: Second quest in mages guild does not work +Bug #763: Enchantment cast cost is wrong +Bug #770: The "Take" and "Close" buttons in the scroll GUI are stretched incorrectly +Bug #773: AIWander Isn't Being Passed The Correct idle Values +Bug #778: The journal can be opened at the start of a new game +Bug #779: Divayth Fyr starts as dead +Bug #787: "Batch count" on detailed FPS counter gets cut-off +Bug #788: chargen scroll layout does not match vanilla +Feature #60: Atlethics Skill +Feature #65: Security Skill +Feature #74: Interaction with non-load-doors +Feature #98: Render Weapon and Shield +Feature #102: AI Package: Escort, EscortCell +Feature #182: Advanced Journal GUI +Feature #288: Trading enhancements +Feature #405: Integrate "new game" into the menu +Feature #537: Highlight dialogue topic links +Feature #658: Rotate, RotateWorld script instructions and local rotations +Feature #690: Animation Layering +Feature #722: Night Eye/Blind magic effects +Feature #735: Move, MoveWorld script instructions. +Feature #760: Non-removable corpses + +0.23.0 + +Bug #522: Player collides with placeable items +Bug #553: Open/Close sounds played when accessing main menu w/ Journal Open +Bug #561: Tooltip word wrapping delay +Bug #578: Bribing works incorrectly +Bug #601: PositionCell fails on negative coordinates +Bug #606: Some NPCs hairs not rendered with Better Heads addon +Bug #609: Bad rendering of bone boots +Bug #613: Messagebox causing assert to fail +Bug #631: Segfault on shutdown +Bug #634: Exception when talking to Calvus Horatius in Mournhold, royal palace courtyard +Bug #635: Scale NPCs depending on race +Bug #643: Dialogue Race select function is inverted +Bug #646: Twohanded weapons don't work properly +Bug #654: Crash when dropping objects without a collision shape +Bug #655/656: Objects that were disabled or deleted (but not both) were added to the scene when re-entering a cell +Bug #660: "g" in "change" cut off in Race Menu +Bug #661: Arrille sells me the key to his upstairs room +Bug #662: Day counter starts at 2 instead of 1 +Bug #663: Cannot select "come unprepared" topic in dialog with Dagoth Ur +Bug #665: Pickpocket -> "Grab all" grabs all NPC inventory, even not listed in container window. +Bug #666: Looking up/down problem +Bug #667: Active effects border visible during loading +Bug #669: incorrect player position at new game start +Bug #670: race selection menu: sex, face and hair left button not totally clickable +Bug #671: new game: player is naked +Bug #674: buying or selling items doesn't change amount of gold +Bug #675: fatigue is not set to its maximum when starting a new game +Bug #678: Wrong rotation order causes RefData's rotation to be stored incorrectly +Bug #680: different gold coins in Tel Mara +Bug #682: Race menu ignores playable flag for some hairs and faces +Bug #685: Script compiler does not accept ":" after a function name +Bug #688: dispose corpse makes cross-hair to disappear +Bug #691: Auto equipping ignores equipment conditions +Bug #692: OpenMW doesnt load "loose file" texture packs that places resources directly in data folder +Bug #696: Draugr incorrect head offset +Bug #697: Sail transparency issue +Bug #700: "On the rocks" mod does not load its UV coordinates correctly. +Bug #702: Some race mods don't work +Bug #711: Crash during character creation +Bug #715: Growing Tauryon +Bug #725: Auto calculate stats +Bug #728: Failure to open container and talk dialogue +Bug #731: Crash with Mush-Mere's "background" topic +Feature #55/657: Item Repairing +Feature #62/87: Enchanting +Feature #99: Pathfinding +Feature #104: AI Package: Travel +Feature #129: Levelled items +Feature #204: Texture animations +Feature #239: Fallback-Settings +Feature #535: Console object selection improvements +Feature #629: Add levelup description in levelup layout dialog +Feature #630: Optional format subrecord in (tes3) header +Feature #641: Armor rating +Feature #645: OnDeath script function +Feature #683: Companion item UI +Feature #698: Basic Particles +Task #648: Split up components/esm/loadlocks +Task #695: mwgui cleanup + +0.22.0 + +Bug #311: Potential infinite recursion in script compiler +Bug #355: Keyboard repeat rate (in Xorg) are left disabled after game exit. +Bug #382: Weird effect in 3rd person on water +Bug #387: Always use detailed shape for physics raycasts +Bug #420: Potion/ingredient effects do not stack +Bug #429: Parts of dwemer door not picked up correctly for activation/tooltips +Bug #434/Bug #605: Object movement between cells not properly implemented +Bug #502: Duplicate player collision model at origin +Bug #509: Dialogue topic list shifts inappropriately +Bug #513: Sliding stairs +Bug #515: Launcher does not support non-latin strings +Bug #525: Race selection preview camera wrong position +Bug #526: Attributes / skills should not go below zero +Bug #529: Class and Birthsign menus options should be preselected +Bug #530: Lock window button graphic missing +Bug #532: Missing map menu graphics +Bug #545: ESX selector does not list ESM files properly +Bug #547: Global variables of type short are read incorrectly +Bug #550: Invisible meshes collision and tooltip +Bug #551: Performance drop when loading multiple ESM files +Bug #552: Don't list CG in options if it is not available +Bug #555: Character creation windows "OK" button broken +Bug #558: Segmentation fault when Alt-tabbing with console opened +Bug #559: Dialog window should not be available before character creation is finished +Bug #560: Tooltip borders should be stretched +Bug #562: Sound should not be played when an object cannot be picked up +Bug #565: Water animation speed + timescale +Bug #572: Better Bodies' textures don't work +Bug #573: OpenMW doesn't load if TR_Mainland.esm is enabled (Tamriel Rebuilt mod) +Bug #574: Moving left/right should not cancel auto-run +Bug #575: Crash entering the Chamber of Song +Bug #576: Missing includes +Bug #577: Left Gloves Addon causes ESMReader exception +Bug #579: Unable to open container "Kvama Egg Sack" +Bug #581: Mimicking vanilla Morrowind water +Bug #583: Gender not recognized +Bug #586: Wrong char gen behaviour +Bug #587: "End" script statements with spaces don't work +Bug #589: Closing message boxes by pressing the activation key +Bug #590: Ugly Dagoth Ur rendering +Bug #591: Race selection issues +Bug #593: Persuasion response should be random +Bug #595: Footless guard +Bug #599: Waterfalls are invisible from a certain distance +Bug #600: Waterfalls rendered incorrectly, cut off by water +Bug #607: New beast bodies mod crashes +Bug #608: Crash in cell "Mournhold, Royal Palace" +Bug #611: OpenMW doesn't find some of textures used in Tamriel Rebuilt +Bug #613: Messagebox causing assert to fail +Bug #615: Meshes invisible from above water +Bug #617: Potion effects should be hidden until discovered +Bug #619: certain moss hanging from tree has rendering bug +Bug #621: Batching bloodmoon's trees +Bug #623: NiMaterialProperty alpha unhandled +Bug #628: Launcher in latest master crashes the game +Bug #633: Crash on startup: Better Heads +Bug #636: Incorrect Char Gen Menu Behavior +Feature #29: Allow ESPs and multiple ESMs +Feature #94: Finish class selection-dialogue +Feature #149: Texture Alphas +Feature #237: Run Morrowind-ini importer from launcher +Feature #286: Update Active Spell Icons +Feature #334: Swimming animation +Feature #335: Walking animation +Feature #360: Proper collision shapes for NPCs and creatures +Feature #367: Lights that behave more like original morrowind implementation +Feature #477: Special local scripting variables +Feature #528: Message boxes should close when enter is pressed under certain conditions. +Feature #543: Add bsa files to the settings imported by the ini importer +Feature #594: coordinate space and utility functions +Feature #625: Zoom in vanity mode +Task #464: Refactor launcher ESX selector into a re-usable component +Task #624: Unified implementation of type-variable sub-records + +0.21.0 + +Bug #253: Dialogs don't work for Russian version of Morrowind +Bug #267: Activating creatures without dialogue can still activate the dialogue GUI +Bug #354: True flickering lights +Bug #386: The main menu's first entry is wrong (in french) +Bug #479: Adding the spell "Ash Woe Blight" to the player causes strange attribute oscillations +Bug #495: Activation Range +Bug #497: Failed Disposition check doesn't stop a dialogue entry from being returned +Bug #498: Failing a disposition check shouldn't eliminate topics from the the list of those available +Bug #500: Disposition for most NPCs is 0/100 +Bug #501: Getdisposition command wrongly returns base disposition +Bug #506: Journal UI doesn't update anymore +Bug #507: EnableRestMenu is not a valid command - change it to EnableRest +Bug #508: Crash in Ald Daedroth Shrine +Bug #517: Wrong price calculation when untrading an item +Bug #521: MWGui::InventoryWindow creates a duplicate player actor at the origin +Bug #524: Beast races are able to wear shoes +Bug #527: Background music fails to play +Bug #533: The arch at Gnisis entrance is not displayed +Bug #534: Terrain gets its correct shape only some time after the cell is loaded +Bug #536: The same entry can be added multiple times to the journal +Bug #539: Race selection is broken +Bug #544: Terrain normal map corrupt when the map is rendered +Feature #39: Video Playback +Feature #151: ^-escape sequences in text output +Feature #392: Add AI related script functions +Feature #456: Determine required ini fallback values and adjust the ini importer accordingly +Feature #460: Experimental DirArchives improvements +Feature #540: Execute scripts of objects in containers/inventories in active cells +Task #401: Review GMST fixing +Task #453: Unify case smashing/folding +Task #512: Rewrite utf8 component + +0.20.0 + +Bug #366: Changing the player's race during character creation does not change the look of the player character +Bug #430: Teleporting and using loading doors linking within the same cell reloads the cell +Bug #437: Stop animations when paused +Bug #438: Time displays as "0 a.m." when it should be "12 a.m." +Bug #439: Text in "name" field of potion/spell creation window is persistent +Bug #440: Starting date at a new game is off by one day +Bug #442: Console window doesn't close properly sometimes +Bug #448: Do not break container window formatting when item names are very long +Bug #458: Topics sometimes not automatically added to known topic list +Bug #476: Auto-Moving allows player movement after using DisablePlayerControls +Bug #478: After sleeping in a bed the rest dialogue window opens automtically again +Bug #492: On creating potions the ingredients are removed twice +Feature #63: Mercantile skill +Feature #82: Persuasion Dialogue +Feature #219: Missing dialogue filters/functions +Feature #369: Add a FailedAction +Feature #377: Select head/hair on character creation +Feature #391: Dummy AI package classes +Feature #435: Global Map, 2nd Layer +Feature #450: Persuasion +Feature #457: Add more script instructions +Feature #474: update the global variable pcrace when the player's race is changed +Task #158: Move dynamically generated classes from Player class to World Class +Task #159: ESMStore rework and cleanup +Task #163: More Component Namespace Cleanup +Task #402: Move player data from MWWorld::Player to the player's NPC record +Task #446: Fix no namespace in BulletShapeLoader + +0.19.0 + +Bug #374: Character shakes in 3rd person mode near the origin +Bug #404: Gamma correct rendering +Bug #407: Shoes of St. Rilm do not work +Bug #408: Rugs has collision even if they are not supposed to +Bug #412: Birthsign menu sorted incorrectly +Bug #413: Resolutions presented multiple times in launcher +Bug #414: launcher.cfg file stored in wrong directory +Bug #415: Wrong esm order in openmw.cfg +Bug #418: Sound listener position updates incorrectly +Bug #423: wrong usage of "Version" entry in openmw.desktop +Bug #426: Do not use hardcoded splash images +Bug #431: Don't use markers for raycast +Bug #432: Crash after picking up items from an NPC +Feature #21/#95: Sleeping/resting +Feature #61: Alchemy Skill +Feature #68: Death +Feature #69/#86: Spell Creation +Feature #72/#84: Travel +Feature #76: Global Map, 1st Layer +Feature #120: Trainer Window +Feature #152: Skill Increase from Skill Books +Feature #160: Record Saving +Task #400: Review GMST access + +0.18.0 + +Bug #310: Button of the "preferences menu" are too small +Bug #361: Hand-to-hand skill is always 100 +Bug #365: NPC and creature animation is jerky; Characters float around when they are not supposed to +Bug #372: playSound3D uses original coordinates instead of current coordinates. +Bug #373: Static OGRE build faulty +Bug #375: Alt-tab toggle view +Bug #376: Screenshots are disable +Bug #378: Exception when drinking self-made potions +Bug #380: Cloth visibility problem +Bug #384: Weird character on doors tooltip. +Bug #398: Some objects do not collide in MW, but do so in OpenMW +Feature #22: Implement level-up +Feature #36: Hide Marker +Feature #88: Hotkey Window +Feature #91: Level-Up Dialogue +Feature #118: Keyboard and Mouse-Button bindings +Feature #119: Spell Buying Window +Feature #133: Handle resources across multiple data directories +Feature #134: Generate a suitable default-value for --data-local +Feature #292: Object Movement/Creation Script Instructions +Feature #340: AIPackage data structures +Feature #356: Ingredients use +Feature #358: Input system rewrite +Feature #370: Target handling in actions +Feature #379: Door markers on the local map +Feature #389: AI framework +Feature #395: Using keys to open doors / containers +Feature #396: Loading screens +Feature #397: Inventory avatar image and race selection head preview +Task #339: Move sounds into Action + +0.17.0 + +Bug #225: Valgrind reports about 40MB of leaked memory +Bug #241: Some physics meshes still don't match +Bug #248: Some textures are too dark +Bug #300: Dependency on proprietary CG toolkit +Bug #302: Some objects don't collide although they should +Bug #308: Freeze in Balmora, Meldor: Armorer +Bug #313: openmw without a ~/.config/openmw folder segfault. +Bug #317: adding non-existing spell via console locks game +Bug #318: Wrong character normals +Bug #341: Building with Ogre Debug libraries does not use debug version of plugins +Bug #347: Crash when running openmw with --start="XYZ" +Bug #353: FindMyGUI.cmake breaks path on Windows +Bug #359: WindowManager throws exception at destruction +Bug #364: Laggy input on OS X due to bug in Ogre's event pump implementation +Feature #33: Allow objects to cross cell-borders +Feature #59: Dropping Items (replaced stopgap implementation with a proper one) +Feature #93: Main Menu +Feature #96/329/330/331/332/333: Player Control +Feature #180: Object rotation and scaling. +Feature #272: Incorrect NIF material sharing +Feature #314: Potion usage +Feature #324: Skill Gain +Feature #342: Drain/fortify dynamic stats/attributes magic effects +Feature #350: Allow console only script instructions +Feature #352: Run scripts in console on startup +Task #107: Refactor mw*-subsystems +Task #325: Make CreatureStats into a class +Task #345: Use Ogre's animation system +Task #351: Rewrite Action class to support automatic sound playing + +0.16.0 + +Bug #250: OpenMW launcher erratic behaviour +Bug #270: Crash because of underwater effect on OS X +Bug #277: Auto-equipping in some cells not working +Bug #294: Container GUI ignores disabled inventory menu +Bug #297: Stats review dialog shows all skills and attribute values as 0 +Bug #298: MechanicsManager::buildPlayer does not remove previous bonuses +Bug #299: Crash in World::disable +Bug #306: Non-existent ~/.config/openmw "crash" the launcher. +Bug #307: False "Data Files" location make the launcher "crash" +Feature #81: Spell Window +Feature #85: Alchemy Window +Feature #181: Support for x.y script syntax +Feature #242: Weapon and Spell icons +Feature #254: Ingame settings window +Feature #293: Allow "stacking" game modes +Feature #295: Class creation dialog tooltips +Feature #296: Clicking on the HUD elements should show/hide the respective window +Feature #301: Direction after using a Teleport Door +Feature #303: Allow object selection in the console +Feature #305: Allow the use of = as a synonym for == +Feature #312: Compensation for slow object access in poorly written Morrowind.esm scripts +Task #176: Restructure enabling/disabling of MW-references +Task #283: Integrate ogre.cfg file in settings file +Task #290: Auto-Close MW-reference related GUI windows + +0.15.0 + +Bug #5: Physics reimplementation (fixes various issues) +Bug #258: Resizing arrow's background is not transparent +Bug #268: Widening the stats window in X direction causes layout problems +Bug #269: Topic pane in dialgoue window is too small for some longer topics +Bug #271: Dialog choices are sorted incorrectly +Bug #281: The single quote character is not rendered on dialog windows +Bug #285: Terrain not handled properly in cells that are not predefined +Bug #289: Dialogue filter isn't doing case smashing/folding for item IDs +Feature #15: Collision with Terrain +Feature #17: Inventory-, Container- and Trade-Windows +Feature #44: Floating Labels above Focussed Objects +Feature #80: Tooltips +Feature #83: Barter Dialogue +Feature #90: Book and Scroll Windows +Feature #156: Item Stacking in Containers +Feature #213: Pulsating lights +Feature #218: Feather & Burden +Feature #256: Implement magic effect bookkeeping +Feature #259: Add missing information to Stats window +Feature #260: Correct case for dialogue topics +Feature #280: GUI texture atlasing +Feature #291: Ability to use GMST strings from GUI layout files +Task #255: Make MWWorld::Environment into a singleton + +0.14.0 + +Bug #1: Meshes rendered with wrong orientation +Bug #6/Task #220: Picking up small objects doesn't always work +Bug #127: tcg doesn't work +Bug #178: Compablity problems with Ogre 1.8.0 RC 1 +Bug #211: Wireframe mode (toggleWireframe command) should not apply to Console & other UI +Bug #227: Terrain crashes when moving away from predefined cells +Bug #229: On OS X Launcher cannot launch game if path to binary contains spaces +Bug #235: TGA texture loading problem +Bug #246: wireframe mode does not work in water +Feature #8/#232: Water Rendering +Feature #13: Terrain Rendering +Feature #37: Render Path Grid +Feature #66: Factions +Feature #77: Local Map +Feature #78: Compass/Mini-Map +Feature #97: Render Clothing/Armour +Feature #121: Window Pinning +Feature #205: Auto equip +Feature #217: Contiainer should track changes to its content +Feature #221: NPC Dialogue Window Enhancements +Feature #233: Game settings manager +Feature #240: Spell List and selected spell (no GUI yet) +Feature #243: Draw State +Task #113: Morrowind.ini Importer +Task #215: Refactor the sound code +Task #216: Update MyGUI + +0.13.0 + +Bug #145: Fixed sound problems after cell change +Bug #179: Pressing space in console triggers activation +Bug #186: CMake doesn't use the debug versions of Ogre libraries on Linux +Bug #189: ASCII 16 character added to console on it's activation on Mac OS X +Bug #190: Case Folding fails with music files +Bug #192: Keypresses write Text into Console no matter which gui element is active +Bug #196: Collision shapes out of place +Bug #202: ESMTool doesn't not work with localised ESM files anymore +Bug #203: Torch lights only visible on short distance +Bug #207: Ogre.log not written +Bug #209: Sounds do not play +Bug #210: Ogre crash at Dren plantation +Bug #214: Unsupported file format version +Bug #222: Launcher is writing openmw.cfg file to wrong location +Feature #9: NPC Dialogue Window +Feature #16/42: New sky/weather implementation +Feature #40: Fading +Feature #48: NPC Dialogue System +Feature #117: Equipping Items (backend only, no GUI yet, no rendering of equipped items yet) +Feature #161: Load REC_PGRD records +Feature #195: Wireframe-mode +Feature #198/199: Various sound effects +Feature #206: Allow picking data path from launcher if non is set +Task #108: Refactor window manager class +Task #172: Sound Manager Cleanup +Task #173: Create OpenEngine systems in the appropriate manager classes +Task #184: Adjust MSVC and gcc warning levels +Task #185: RefData rewrite +Task #201: Workaround for transparency issues +Task #208: silenced esm_reader.hpp warning + +0.12.0 + +Bug #154: FPS Drop +Bug #169: Local scripts continue running if associated object is deleted +Bug #174: OpenMW fails to start if the config directory doesn't exist +Bug #187: Missing lighting +Bug #188: Lights without a mesh are not rendered +Bug #191: Taking screenshot causes crash when running installed +Feature #28: Sort out the cell load problem +Feature #31: Allow the player to move away from pre-defined cells +Feature #35: Use alternate storage location for modified object position +Feature #45: NPC animations +Feature #46: Creature Animation +Feature #89: Basic Journal Window +Feature #110: Automatically pick up the path of existing MW-installations +Feature #183: More FPS display settings +Task #19: Refactor engine class +Task #109/Feature #162: Automate Packaging +Task #112: Catch exceptions thrown in input handling functions +Task #128/#168: Cleanup Configuration File Handling +Task #131: NPC Activation doesn't work properly +Task #144: MWRender cleanup +Task #155: cmake cleanup + +0.11.1 + +Bug #2: Resources loading doesn't work outside of bsa files +Bug #3: GUI does not render non-English characters +Bug #7: openmw.cfg location doesn't match +Bug #124: The TCL alias for ToggleCollision is missing. +Bug #125: Some command line options can't be used from a .cfg file +Bug #126: Toggle-type script instructions are less verbose compared with original MW +Bug #130: NPC-Record Loading fails for some NPCs +Bug #167: Launcher sets invalid parameters in ogre config +Feature #10: Journal +Feature #12: Rendering Optimisations +Feature #23: Change Launcher GUI to a tabbed interface +Feature #24: Integrate the OGRE settings window into the launcher +Feature #25: Determine openmw.cfg location (Launcher) +Feature #26: Launcher Profiles +Feature #79: MessageBox +Feature #116: Tab-Completion in Console +Feature #132: --data-local and multiple --data +Feature #143: Non-Rendering Performance-Optimisations +Feature #150: Accessing objects in cells via ID does only work for objects with all lower case IDs +Feature #157: Version Handling +Task #14: Replace tabs with 4 spaces +Task #18: Move components from global namespace into their own namespace +Task #123: refactor header files in components/esm + +0.10.0 + +* NPC dialogue window (not functional yet) +* Collisions with objects +* Refactor the PlayerPos class +* Adjust file locations +* CMake files and test linking for Bullet +* Replace Ogre raycasting test for activation with something more precise +* Adjust player movement according to collision results +* FPS display +* Various Portability Improvements +* Mac OS X support is back! + +0.9.0 + +* Exterior cells loading, unloading and management +* Character Creation GUI +* Character creation +* Make cell names case insensitive when doing internal lookups +* Music player +* NPCs rendering + +0.8.0 + +* GUI +* Complete and working script engine +* In game console +* Sky rendering +* Sound and music +* Tons of smaller stuff + +0.7.0 + +* This release is a complete rewrite in C++. +* All D code has been culled, and all modules have been rewritten. +* The game is now back up to the level of rendering interior cells and moving around, but physics, sound, GUI, and scripting still remain to be ported from the old codebase. + +0.6.0 + +* Coded a GUI system using MyGUI +* Skinned MyGUI to look like Morrowind (work in progress) +* Integrated the Monster script engine +* Rewrote some functions into script code +* Very early MyGUI < > Monster binding +* Fixed Windows sound problems (replaced old openal32.dll) + +0.5.0 + +* Collision detection with Bullet +* Experimental walk & fall character physics +* New key bindings: + * t toggle physics mode (walking, flying, ghost), + * n night eye, brightens the scene +* Fixed incompatability with DMD 1.032 and newer compilers +* * (thanks to tomqyp) +* Various minor changes and updates + +0.4.0 + +* Switched from Audiere to OpenAL +* * (BIG thanks to Chris Robinson) +* Added complete Makefile (again) as a alternative build tool +* More realistic lighting (thanks again to Chris Robinson) +* Various localization fixes tested with Russian and French versions +* Temporary workaround for the Unicode issue: invalid UTF displayed as '?' +* Added ns option to disable sound, for debugging +* Various bug fixes +* Cosmetic changes to placate gdc Wall + +0.3.0 + +* Built and tested on Windows XP +* Partial support for FreeBSD (exceptions do not work) +* You no longer have to download Monster separately +* Made an alternative for building without DSSS (but DSSS still works) +* Renamed main program from 'morro' to 'openmw' +* Made the config system more robust +* Added oc switch for showing Ogre config window on startup +* Removed some config files, these are auto generated when missing. +* Separated plugins.cfg into linux and windows versions. +* Updated Makefile and sources for increased portability +* confirmed to work against OIS 1.0.0 (Ubuntu repository package) + +0.2.0 + +* Compiles with gdc +* Switched to DSSS for building D code +* Includes the program esmtool + +0.1.0 + +first release From 1bd2664cb04b8b0a6c64f1312d43cf9195b07cf4 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 23 Feb 2014 18:42:40 +1100 Subject: [PATCH 024/114] aicombat pathfinding workaround --- apps/openmw/mwmechanics/aicombat.cpp | 35 ++++++++++++++++--------- apps/openmw/mwmechanics/pathfinding.cpp | 2 ++ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 6a49f2f5e..91c77702e 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -240,16 +240,20 @@ namespace MWMechanics //target is at far distance: build path to target OR follow target (if previously actor had reached it once) mFollowTarget = false; - buildNewPath(actor); + buildNewPath(actor); //not guaranteed, check before use //delete visited path node mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); - //try shortcut - if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) - mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); - else - mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + //if buildNewPath() failed leave mTargetAngle unchanged + if(!mPathFinder.getPath().empty()) + { + //try shortcut + if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) + mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); + else + mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + } mRotate = true; mMovement.mPosition[1] = 1; @@ -300,9 +304,13 @@ namespace MWMechanics dest.mZ = mTarget.getRefData().getPosition().pos[2]; Ogre::Vector3 newPathTarget = Ogre::Vector3(dest.mX, dest.mY, dest.mZ); - ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); - Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ); - float dist = Ogre::Math::Abs((newPathTarget - currPathTarget).length()); + float dist = -1; //hack to indicate first time, to construct a new path + if(!mPathFinder.getPath().empty()) + { + ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); + Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ); + dist = Ogre::Math::Abs((newPathTarget - currPathTarget).length()); + } float targetPosThreshold; bool isOutside = actor.getCell()->mCell->isExterior(); @@ -311,7 +319,7 @@ namespace MWMechanics else targetPosThreshold = 100; - if(dist > targetPosThreshold) + if((dist < 0) || (dist > targetPosThreshold)) { //construct new path only if target has moved away more than on ESM::Position pos = actor.getRefData().getPosition(); @@ -332,8 +340,11 @@ namespace MWMechanics //maybe here is a mistake (?): PathFinder::getPathSize() returns number of grid points in the path, //not the actual path length. Here we should know if the new path is actually more effective. //if(pathFinder2.getPathSize() < mPathFinder.getPathSize()) - newPathFinder.syncStart(mPathFinder.getPath()); - mPathFinder = newPathFinder; + if(!mPathFinder.getPath().empty()) + { + newPathFinder.syncStart(mPathFinder.getPath()); + mPathFinder = newPathFinder; + } } } } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 4407363a6..5996eb079 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -391,6 +391,8 @@ namespace MWMechanics void PathFinder::syncStart(const std::list &path) { + if (path.size() < 2) + return; //nothing to pop std::list::const_iterator oldStart = path.begin(); std::list::iterator iter = ++mPath.begin(); From 424617225118546b18220e3752f02f243580d3cb Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 23 Feb 2014 11:52:13 +0100 Subject: [PATCH 025/114] updated credits file --- credits.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/credits.txt b/credits.txt index 561931cde..601255763 100644 --- a/credits.txt +++ b/credits.txt @@ -20,6 +20,7 @@ Artem Kotsynyak (greye) athile Britt Mathis (galdor557) BrotherBrick +cc9cii Chris Robinson (KittyCat) Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) From 76c1d60d2395ecd2ef8d670e2f71f54f3d57eef0 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sat, 22 Feb 2014 01:17:58 +0400 Subject: [PATCH 026/114] Fixed OpenCS build on OS X --- apps/opencs/view/filter/editwidget.hpp | 2 +- apps/opencs/view/filter/filterbox.hpp | 2 +- apps/opencs/view/filter/recordfilterbox.hpp | 2 +- apps/opencs/view/world/tablesubview.hpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/filter/editwidget.hpp b/apps/opencs/view/filter/editwidget.hpp index 555b6d360..e7e34b8e9 100644 --- a/apps/opencs/view/filter/editwidget.hpp +++ b/apps/opencs/view/filter/editwidget.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include "../../model/filter/parser.hpp" #include "../../model/filter/node.hpp" diff --git a/apps/opencs/view/filter/filterbox.hpp b/apps/opencs/view/filter/filterbox.hpp index 3817d5e70..5954035fc 100644 --- a/apps/opencs/view/filter/filterbox.hpp +++ b/apps/opencs/view/filter/filterbox.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "../../model/filter/node.hpp" #include "../../model/world/universalid.hpp" diff --git a/apps/opencs/view/filter/recordfilterbox.hpp b/apps/opencs/view/filter/recordfilterbox.hpp index 3638dc6c3..fa5c9c3c2 100644 --- a/apps/opencs/view/filter/recordfilterbox.hpp +++ b/apps/opencs/view/filter/recordfilterbox.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index 1f67e0262..b3c253919 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -3,7 +3,7 @@ #include "../doc/subview.hpp" -#include +#include class QModelIndex; From 85c467f00f01e14b8a3cecffe1d722eaed7df3bf Mon Sep 17 00:00:00 2001 From: cc9cii Date: Mon, 24 Feb 2014 18:47:09 +1100 Subject: [PATCH 027/114] Minor cleanup for aicombat pathfinding workaround. --- apps/openmw/mwmechanics/aicombat.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 91c77702e..17a624f0f 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -240,12 +240,12 @@ namespace MWMechanics //target is at far distance: build path to target OR follow target (if previously actor had reached it once) mFollowTarget = false; - buildNewPath(actor); //not guaranteed, check before use + buildNewPath(actor); //may fail to build a path, check before use //delete visited path node mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); - //if buildNewPath() failed leave mTargetAngle unchanged + //if no new path leave mTargetAngle unchanged if(!mPathFinder.getPath().empty()) { //try shortcut @@ -253,8 +253,8 @@ namespace MWMechanics mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); else mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + mRotate = true; } - mRotate = true; mMovement.mPosition[1] = 1; mReadyToAttack = false; From 5b48ca114fee7742ad8bc8f18f0baaa9ced7e945 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Tue, 25 Feb 2014 07:41:10 +1100 Subject: [PATCH 028/114] aicombat pathfinding fix - check the correct list --- apps/openmw/mwmechanics/pathfinding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 5996eb079..8dbdd8770 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -391,7 +391,7 @@ namespace MWMechanics void PathFinder::syncStart(const std::list &path) { - if (path.size() < 2) + if (mPath.size() < 2) return; //nothing to pop std::list::const_iterator oldStart = path.begin(); std::list::iterator iter = ++mPath.begin(); From 6ec86564d169c0b656f797066786a2a891f608ba Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Feb 2014 11:58:32 +0100 Subject: [PATCH 029/114] re-enabled OGRE in OpenCS; cleaned up startup --- apps/opencs/editor.cpp | 45 +++++++++++++------------ apps/opencs/editor.hpp | 14 ++++++-- apps/opencs/main.cpp | 16 +++------ apps/opencs/view/world/scenesubview.cpp | 15 --------- 4 files changed, 41 insertions(+), 49 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 5d5ac4c55..2b2f41754 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -9,15 +9,22 @@ #include #include +#include + #include "model/doc/document.hpp" #include "model/world/data.hpp" -CS::Editor::Editor() - : mDocumentManager (mCfgMgr), mViewManager (mDocumentManager) +CS::Editor::Editor (OgreInit::OgreInit& ogreInit) +: mDocumentManager (mCfgMgr), mViewManager (mDocumentManager), + mIpcServerName ("org.openmw.OpenCS") { - mIpcServerName = "org.openmw.OpenCS"; + Files::PathContainer dataDirs = readConfig(); + + setupDataFiles (dataDirs); + + CSMSettings::UserSettings::instance().loadSettings ("opencs.cfg"); - setupDataFiles(); + ogreInit.init ((mCfgMgr.getUserConfigPath() / "opencsOgre.log").string()); mNewGame.setLocalData (mLocal); mFileDialog.setLocalData (mLocal); @@ -42,7 +49,16 @@ CS::Editor::Editor() this, SLOT (createNewGame (const boost::filesystem::path&))); } -void CS::Editor::setupDataFiles() +void CS::Editor::setupDataFiles (const Files::PathContainer& dataDirs) +{ + for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) + { + QString path = QString::fromStdString(iter->string()); + mFileDialog.addFiles(path); + } +} + +Files::PathContainer CS::Editor::readConfig() { boost::program_options::variables_map variables; boost::program_options::options_description desc("Syntax: opencs \nAllowed options"); @@ -58,6 +74,8 @@ void CS::Editor::setupDataFiles() mCfgMgr.readConfiguration(variables, desc); + mDocumentManager.setResourceDir (variables["resources"].as()); + Files::PathContainer dataDirs, dataLocal; if (!variables["data"].empty()) { dataDirs = Files::PathContainer(variables["data"].as()); @@ -83,23 +101,11 @@ void CS::Editor::setupDataFiles() messageBox.exec(); QApplication::exit (1); - return; } dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); - mDocumentManager.setResourceDir (variables["resources"].as()); - - for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) - { - - QString path = QString::fromStdString(iter->string()); - mFileDialog.addFiles(path); - } - - //load the settings into the userSettings instance. - const QString settingFileName = "opencs.cfg"; - CSMSettings::UserSettings::instance().loadSettings(settingFileName); + return dataDirs; } void CS::Editor::createGame() @@ -210,8 +216,6 @@ int CS::Editor::run() if (mLocal.empty()) return 1; -// temporarily disable OGRE-integration (need to fix path problem first) -#if 0 // TODO: setting Ogre::Root::getSingleton().setRenderSystem(Ogre::Root::getSingleton().getRenderSystemByName("OpenGL Rendering Subsystem")); @@ -228,7 +232,6 @@ int CS::Editor::run() #endif Ogre::RenderWindow* hiddenWindow = Ogre::Root::getSingleton().createRenderWindow("InactiveHidden", 1, 1, false, ¶ms); hiddenWindow->setActive(false); -#endif mStartup.show(); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 930aa9d64..0f1c7a682 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -10,6 +10,8 @@ #include #endif +#include + #include "model/settings/usersettings.hpp" #include "model/doc/documentmanager.hpp" @@ -20,6 +22,11 @@ #include "view/settings/usersettingsdialog.hpp" +namespace OgreInit +{ + class OgreInit; +} + namespace CS { class Editor : public QObject @@ -37,7 +44,10 @@ namespace CS boost::filesystem::path mLocal; - void setupDataFiles(); + void setupDataFiles (const Files::PathContainer& dataDirs); + + Files::PathContainer readConfig(); + ///< \return data paths // not implemented Editor (const Editor&); @@ -45,7 +55,7 @@ namespace CS public: - Editor(); + Editor (OgreInit::OgreInit& ogreInit); bool makeIPCServer(); void connectToIPCServer(); diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index 931d63312..212ed0836 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -40,15 +40,9 @@ int main(int argc, char *argv[]) { Q_INIT_RESOURCE (resources); - // TODO: Ogre startup shouldn't be here, but it currently has to: - // SceneWidget destructor will delete the created render window, which would be called _after_ Root has shut down :( - - Application mApplication (argc, argv); -// temporarily disable OGRE-integration (need to fix path problem first) -#if 0 OgreInit::OgreInit ogreInit; - ogreInit.init("./opencsOgre.log"); // TODO log path? -#endif + + Application application (argc, argv); #ifdef Q_OS_MAC QDir dir(QCoreApplication::applicationDirPath()); @@ -66,12 +60,12 @@ int main(int argc, char *argv[]) QStringList libraryPaths; libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); - mApplication.setLibraryPaths(libraryPaths); + application.setLibraryPaths(libraryPaths); #endif - mApplication.setWindowIcon (QIcon (":./opencs.png")); + application.setWindowIcon (QIcon (":./opencs.png")); - CS::Editor editor; + CS::Editor editor (ogreInit); if(!editor.makeIPCServer()) { diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 33ae327a0..705adf2ad 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -43,26 +43,11 @@ toolbar->addTool (new SceneToolMode (toolbar)); toolbar->addTool (new SceneToolMode (toolbar)); layout2->addWidget (toolbar, 0); -// temporarily disable OGRE-integration (need to fix path problem first) -#if 0 CSVRender::SceneWidget* sceneWidget = new CSVRender::SceneWidget(this); layout2->addWidget (sceneWidget, 1); layout->insertLayout (0, layout2, 1); -#endif - /// \todo replace with rendering widget - QPalette palette2 (palette()); - palette2.setColor (QPalette::Background, Qt::white); - QLabel *placeholder = new QLabel ("Here goes the 3D scene", this); - placeholder->setAutoFillBackground (true); - placeholder->setPalette (palette2); - placeholder->setAlignment (Qt::AlignHCenter); - - layout2->addWidget (placeholder, 1); - - layout->insertLayout (0, layout2, 1); - CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); From 4ec4599be31bc1b0b1426d31367ef7f051e13aa3 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Feb 2014 12:30:45 +0100 Subject: [PATCH 030/114] added navigation mode enum to SceneWidget --- apps/opencs/view/render/scenewidget.cpp | 2 +- apps/opencs/view/render/scenewidget.hpp | 33 +++++++++++++++---------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 6e327bb6e..88493fd76 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -15,7 +15,7 @@ namespace CSVRender : QWidget(parent) , mWindow(NULL) , mCamera(NULL) - , mSceneMgr(NULL) + , mSceneMgr(NULL), mNavigationMode (NavigationMode_Free) { setAttribute(Qt::WA_PaintOnScreen); setAttribute(Qt::WA_NoSystemBackground); diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 355a6331e..deba47fbb 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -17,24 +17,31 @@ namespace CSVRender { Q_OBJECT - public: - SceneWidget(QWidget *parent); - virtual ~SceneWidget(void); + public: - QPaintEngine* paintEngine() const; + enum NavigationMode + { + NavigationMode_Free + }; - private: - void paintEvent(QPaintEvent* e); - void resizeEvent(QResizeEvent* e); - bool event(QEvent* e); + SceneWidget(QWidget *parent); + virtual ~SceneWidget(void); - void updateOgreWindow(); + QPaintEngine* paintEngine() const; - Ogre::Camera* mCamera; - Ogre::SceneManager* mSceneMgr; - Ogre::RenderWindow* mWindow; - }; + private: + void paintEvent(QPaintEvent* e); + void resizeEvent(QResizeEvent* e); + bool event(QEvent* e); + + void updateOgreWindow(); + Ogre::Camera* mCamera; + Ogre::SceneManager* mSceneMgr; + Ogre::RenderWindow* mWindow; + + NavigationMode mNavigationMode; + }; } #endif From 8081c067a611b147b9bde00ce1b7aec92fabaa69 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Feb 2014 13:09:37 +0100 Subject: [PATCH 031/114] moving the camera with W and S keys --- apps/opencs/view/render/scenewidget.cpp | 61 +++++++++++++++++++++++-- apps/opencs/view/render/scenewidget.hpp | 14 +++++- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 88493fd76..280a85fd4 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -10,16 +11,18 @@ namespace CSVRender { - SceneWidget::SceneWidget(QWidget *parent) : QWidget(parent) , mWindow(NULL) , mCamera(NULL) - , mSceneMgr(NULL), mNavigationMode (NavigationMode_Free) + , mSceneMgr(NULL), mNavigationMode (NavigationMode_Free), mUpdate (false) + , mKeyForward (false), mKeyBackward (false) { setAttribute(Qt::WA_PaintOnScreen); setAttribute(Qt::WA_NoSystemBackground); + setFocusPolicy (Qt::StrongFocus); + mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); // Throw in a random color just to make sure multiple scenes work @@ -44,6 +47,11 @@ namespace CSVRender mCamera->lookAt(0,0,0); mCamera->setNearClipDistance(0.1); mCamera->setFarClipDistance(3000); + + QTimer *timer = new QTimer (this); + + connect (timer, SIGNAL (timeout()), this, SLOT (update())); + timer->start (20); /// \todo make this configurable } void SceneWidget::updateOgreWindow() @@ -93,7 +101,6 @@ namespace CSVRender e->accept(); } - QPaintEngine* SceneWidget::paintEngine() const { // We don't want another paint engine to get in the way. @@ -130,4 +137,52 @@ namespace CSVRender return QWidget::event(e); } + void SceneWidget::keyPressEvent (QKeyEvent *event) + { + switch (event->key()) + { + case Qt::Key_W: mKeyForward = true; break; + case Qt::Key_S: mKeyBackward = true; break; + default: QWidget::keyPressEvent (event); + } + } + + void SceneWidget::keyReleaseEvent (QKeyEvent *event) + { + switch (event->key()) + { + case Qt::Key_W: mKeyForward = false; break; + case Qt::Key_S: mKeyBackward = false; break; + default: QWidget::keyReleaseEvent (event); + } + } + + void SceneWidget::focusOutEvent (QFocusEvent *event) + { + mKeyForward = false; + mKeyBackward = false; + + QWidget::focusOutEvent (event); + } + + void SceneWidget::update() + { + if (mKeyForward && !mKeyBackward) + { + mCamera->move (mCamera->getDirection()); + mUpdate = true; + } + + if (!mKeyForward && mKeyBackward) + { + mCamera->move (-mCamera->getDirection()); + mUpdate = true; + } + + if (mUpdate) + { + mUpdate = false; + mWindow->update(); + } + } } diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index deba47fbb..777398353 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -12,7 +12,6 @@ namespace Ogre namespace CSVRender { - class SceneWidget : public QWidget { Q_OBJECT @@ -34,6 +33,12 @@ namespace CSVRender void resizeEvent(QResizeEvent* e); bool event(QEvent* e); + void keyPressEvent (QKeyEvent *event); + + void keyReleaseEvent (QKeyEvent *event); + + void focusOutEvent (QFocusEvent *event); + void updateOgreWindow(); Ogre::Camera* mCamera; @@ -41,6 +46,13 @@ namespace CSVRender Ogre::RenderWindow* mWindow; NavigationMode mNavigationMode; + bool mUpdate; + int mKeyForward; + int mKeyBackward; + + private slots: + + void update(); }; } From af50575259f2484a21d5e7e5656430a57ab2acd7 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Feb 2014 13:17:15 +0100 Subject: [PATCH 032/114] use shift key to speed up camera movement --- apps/opencs/view/render/scenewidget.cpp | 11 ++++++++--- apps/opencs/view/render/scenewidget.hpp | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 280a85fd4..4702b0f72 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -16,7 +16,7 @@ namespace CSVRender , mWindow(NULL) , mCamera(NULL) , mSceneMgr(NULL), mNavigationMode (NavigationMode_Free), mUpdate (false) - , mKeyForward (false), mKeyBackward (false) + , mKeyForward (false), mKeyBackward (false), mFast (false) { setAttribute(Qt::WA_PaintOnScreen); setAttribute(Qt::WA_NoSystemBackground); @@ -143,6 +143,7 @@ namespace CSVRender { case Qt::Key_W: mKeyForward = true; break; case Qt::Key_S: mKeyBackward = true; break; + case Qt::Key_Shift: mFast = true; break; default: QWidget::keyPressEvent (event); } } @@ -153,6 +154,7 @@ namespace CSVRender { case Qt::Key_W: mKeyForward = false; break; case Qt::Key_S: mKeyBackward = false; break; + case Qt::Key_Shift: mFast = false; break; default: QWidget::keyReleaseEvent (event); } } @@ -161,6 +163,7 @@ namespace CSVRender { mKeyForward = false; mKeyBackward = false; + mFast = false; QWidget::focusOutEvent (event); } @@ -169,13 +172,15 @@ namespace CSVRender { if (mKeyForward && !mKeyBackward) { - mCamera->move (mCamera->getDirection()); + int factor = mFast ? 4 : 1; + mCamera->move (factor * mCamera->getDirection()); mUpdate = true; } if (!mKeyForward && mKeyBackward) { - mCamera->move (-mCamera->getDirection()); + int factor = mFast ? 4 : 1; + mCamera->move (factor * -mCamera->getDirection()); mUpdate = true; } diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 777398353..0b1817682 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -49,6 +49,7 @@ namespace CSVRender bool mUpdate; int mKeyForward; int mKeyBackward; + bool mFast; private slots: From b6ae521aa5deaf3f0f9b99ccd160bb149f75bb02 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Feb 2014 13:31:15 +0100 Subject: [PATCH 033/114] moving the camera with A and D keys --- apps/opencs/view/render/scenewidget.cpp | 27 ++++++++++++++++++++++--- apps/opencs/view/render/scenewidget.hpp | 2 ++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 4702b0f72..4f7575024 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -16,7 +16,8 @@ namespace CSVRender , mWindow(NULL) , mCamera(NULL) , mSceneMgr(NULL), mNavigationMode (NavigationMode_Free), mUpdate (false) - , mKeyForward (false), mKeyBackward (false), mFast (false) + , mKeyForward (false), mKeyBackward (false), mKeyLeft (false), mKeyRight (false) + , mFast (false) { setAttribute(Qt::WA_PaintOnScreen); setAttribute(Qt::WA_NoSystemBackground); @@ -143,6 +144,8 @@ namespace CSVRender { case Qt::Key_W: mKeyForward = true; break; case Qt::Key_S: mKeyBackward = true; break; + case Qt::Key_A: mKeyLeft = true; break; + case Qt::Key_D: mKeyRight = true; break; case Qt::Key_Shift: mFast = true; break; default: QWidget::keyPressEvent (event); } @@ -154,6 +157,8 @@ namespace CSVRender { case Qt::Key_W: mKeyForward = false; break; case Qt::Key_S: mKeyBackward = false; break; + case Qt::Key_A: mKeyLeft = false; break; + case Qt::Key_D: mKeyRight = false; break; case Qt::Key_Shift: mFast = false; break; default: QWidget::keyReleaseEvent (event); } @@ -163,6 +168,8 @@ namespace CSVRender { mKeyForward = false; mKeyBackward = false; + mKeyLeft = false; + mKeyRight = false; mFast = false; QWidget::focusOutEvent (event); @@ -170,20 +177,34 @@ namespace CSVRender void SceneWidget::update() { + int factor = mFast ? 4 : 1; + if (mKeyForward && !mKeyBackward) { - int factor = mFast ? 4 : 1; mCamera->move (factor * mCamera->getDirection()); mUpdate = true; } if (!mKeyForward && mKeyBackward) { - int factor = mFast ? 4 : 1; mCamera->move (factor * -mCamera->getDirection()); mUpdate = true; } + if (mKeyLeft && !mKeyRight) + { + Ogre::Vector3 direction = mCamera->getDerivedRight(); + mCamera->move (factor * -direction); + mUpdate = true; + } + + if (!mKeyLeft && mKeyRight) + { + Ogre::Vector3 direction = mCamera->getDerivedRight(); + mCamera->move (factor * direction); + mUpdate = true; + } + if (mUpdate) { mUpdate = false; diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 0b1817682..d5042d97b 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -49,6 +49,8 @@ namespace CSVRender bool mUpdate; int mKeyForward; int mKeyBackward; + int mKeyLeft; + int mKeyRight; bool mFast; private slots: From b9107addc09bf817f711586274bdd2e39278827b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Feb 2014 13:39:49 +0100 Subject: [PATCH 034/114] mouse wheel zooming --- apps/opencs/view/render/scenewidget.cpp | 13 ++++++++++++- apps/opencs/view/render/scenewidget.hpp | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 4f7575024..2cc9c93b5 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -164,6 +164,17 @@ namespace CSVRender } } + void SceneWidget::wheelEvent (QWheelEvent *event) + { + if (int delta = event->delta()) + { + int factor = mFast ? 4 : 1; /// \todo make this configurable + /// \todo make mouse sensitivity configurable (the factor 2) + mCamera->move ((factor * mCamera->getDirection() * delta)/2); + mUpdate = true; + } + } + void SceneWidget::focusOutEvent (QFocusEvent *event) { mKeyForward = false; @@ -177,7 +188,7 @@ namespace CSVRender void SceneWidget::update() { - int factor = mFast ? 4 : 1; + int factor = mFast ? 4 : 1; /// \todo make this configurable if (mKeyForward && !mKeyBackward) { diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index d5042d97b..6b6a8486d 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -39,6 +39,8 @@ namespace CSVRender void focusOutEvent (QFocusEvent *event); + void wheelEvent (QWheelEvent *event); + void updateOgreWindow(); Ogre::Camera* mCamera; From 07d20a20131b652a009c3def4aa9af70da7fe30f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Feb 2014 14:09:07 +0100 Subject: [PATCH 035/114] camera panning via left button drag --- apps/opencs/view/render/scenewidget.cpp | 48 ++++++++++++++++++++++++- apps/opencs/view/render/scenewidget.hpp | 8 +++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 2cc9c93b5..ffe867ae5 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -17,7 +17,7 @@ namespace CSVRender , mCamera(NULL) , mSceneMgr(NULL), mNavigationMode (NavigationMode_Free), mUpdate (false) , mKeyForward (false), mKeyBackward (false), mKeyLeft (false), mKeyRight (false) - , mFast (false) + , mFast (false), mDragging (false) { setAttribute(Qt::WA_PaintOnScreen); setAttribute(Qt::WA_NoSystemBackground); @@ -175,6 +175,52 @@ namespace CSVRender } } + void SceneWidget::leaveEvent (QEvent *event) + { + mDragging = false; + } + + void SceneWidget::mouseMoveEvent (QMouseEvent *event) + { + int factor = mFast ? 4 : 1; /// \todo make this configurable + + if (event->buttons() & Qt::LeftButton) + { + if (mDragging) + { + QPoint diff = mOldPos-event->pos(); + mOldPos = event->pos(); + + if (diff.x()) + { + Ogre::Vector3 direction = mCamera->getDerivedRight(); + /// \todo make mouse sensitivity configurable (the factor 2) + mCamera->move ((factor * direction * diff.x())/2); + mUpdate = true; + } + + if (diff.y()) + { + Ogre::Vector3 direction = mCamera->getDerivedUp(); + /// \todo make mouse sensitivity configurable (the factor 2) + mCamera->move ((factor * -direction * diff.y())/2); + mUpdate = true; + } + } + else + { + mDragging = true; + mOldPos = event->pos(); + } + } + } + + void SceneWidget::mouseReleaseEvent (QMouseEvent *event) + { + if (event->buttons() & Qt::LeftButton) + mDragging = false; + } + void SceneWidget::focusOutEvent (QFocusEvent *event) { mKeyForward = false; diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 6b6a8486d..1b78346f7 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -41,6 +41,12 @@ namespace CSVRender void wheelEvent (QWheelEvent *event); + void leaveEvent (QEvent *event); + + void mouseMoveEvent (QMouseEvent *event); + + void mouseReleaseEvent (QMouseEvent *event); + void updateOgreWindow(); Ogre::Camera* mCamera; @@ -54,6 +60,8 @@ namespace CSVRender int mKeyLeft; int mKeyRight; bool mFast; + bool mDragging; + QPoint mOldPos; private slots: From e9871999495d90622d18406981bbec8d7091209b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Feb 2014 14:14:53 +0100 Subject: [PATCH 036/114] getting rid of some magic numbers --- apps/opencs/view/render/scenewidget.cpp | 28 ++++++++++++------------- apps/opencs/view/render/scenewidget.hpp | 4 ++++ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index ffe867ae5..35a29c668 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -18,6 +18,7 @@ namespace CSVRender , mSceneMgr(NULL), mNavigationMode (NavigationMode_Free), mUpdate (false) , mKeyForward (false), mKeyBackward (false), mKeyLeft (false), mKeyRight (false) , mFast (false), mDragging (false) + , mMouseSensitivity (2), mFastFactor (4) /// \todo make these configurable { setAttribute(Qt::WA_PaintOnScreen); setAttribute(Qt::WA_NoSystemBackground); @@ -168,9 +169,7 @@ namespace CSVRender { if (int delta = event->delta()) { - int factor = mFast ? 4 : 1; /// \todo make this configurable - /// \todo make mouse sensitivity configurable (the factor 2) - mCamera->move ((factor * mCamera->getDirection() * delta)/2); + mCamera->move ((getFastFactor() * mCamera->getDirection() * delta)/mMouseSensitivity); mUpdate = true; } } @@ -182,8 +181,6 @@ namespace CSVRender void SceneWidget::mouseMoveEvent (QMouseEvent *event) { - int factor = mFast ? 4 : 1; /// \todo make this configurable - if (event->buttons() & Qt::LeftButton) { if (mDragging) @@ -194,16 +191,14 @@ namespace CSVRender if (diff.x()) { Ogre::Vector3 direction = mCamera->getDerivedRight(); - /// \todo make mouse sensitivity configurable (the factor 2) - mCamera->move ((factor * direction * diff.x())/2); + mCamera->move ((getFastFactor() * direction * diff.x())/mMouseSensitivity); mUpdate = true; } if (diff.y()) { Ogre::Vector3 direction = mCamera->getDerivedUp(); - /// \todo make mouse sensitivity configurable (the factor 2) - mCamera->move ((factor * -direction * diff.y())/2); + mCamera->move ((getFastFactor() * -direction * diff.y())/mMouseSensitivity); mUpdate = true; } } @@ -234,31 +229,29 @@ namespace CSVRender void SceneWidget::update() { - int factor = mFast ? 4 : 1; /// \todo make this configurable - if (mKeyForward && !mKeyBackward) { - mCamera->move (factor * mCamera->getDirection()); + mCamera->move (getFastFactor() * mCamera->getDirection()); mUpdate = true; } if (!mKeyForward && mKeyBackward) { - mCamera->move (factor * -mCamera->getDirection()); + mCamera->move (getFastFactor() * -mCamera->getDirection()); mUpdate = true; } if (mKeyLeft && !mKeyRight) { Ogre::Vector3 direction = mCamera->getDerivedRight(); - mCamera->move (factor * -direction); + mCamera->move (getFastFactor() * -direction); mUpdate = true; } if (!mKeyLeft && mKeyRight) { Ogre::Vector3 direction = mCamera->getDerivedRight(); - mCamera->move (factor * direction); + mCamera->move (getFastFactor() * direction); mUpdate = true; } @@ -268,4 +261,9 @@ namespace CSVRender mWindow->update(); } } + + int SceneWidget::getFastFactor() const + { + return mFast ? mFastFactor : 1; + } } diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 1b78346f7..37ef3cb94 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -49,6 +49,8 @@ namespace CSVRender void updateOgreWindow(); + int getFastFactor() const; + Ogre::Camera* mCamera; Ogre::SceneManager* mSceneMgr; Ogre::RenderWindow* mWindow; @@ -62,6 +64,8 @@ namespace CSVRender bool mFast; bool mDragging; QPoint mOldPos; + int mMouseSensitivity; + int mFastFactor; private slots: From e33bd4d50841fa14812882fde150435ed3063a98 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Feb 2014 14:24:35 +0100 Subject: [PATCH 037/114] camera rotation via ctrl-left button drag --- apps/opencs/view/render/scenewidget.cpp | 44 +++++++++++++++++++------ apps/opencs/view/render/scenewidget.hpp | 1 + 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 35a29c668..b498fba06 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -17,7 +17,7 @@ namespace CSVRender , mCamera(NULL) , mSceneMgr(NULL), mNavigationMode (NavigationMode_Free), mUpdate (false) , mKeyForward (false), mKeyBackward (false), mKeyLeft (false), mKeyRight (false) - , mFast (false), mDragging (false) + , mFast (false), mDragging (false), mMod1 (false) , mMouseSensitivity (2), mFastFactor (4) /// \todo make these configurable { setAttribute(Qt::WA_PaintOnScreen); @@ -148,6 +148,7 @@ namespace CSVRender case Qt::Key_A: mKeyLeft = true; break; case Qt::Key_D: mKeyRight = true; break; case Qt::Key_Shift: mFast = true; break; + case Qt::Key_Control: mMod1 = true; break; default: QWidget::keyPressEvent (event); } } @@ -161,6 +162,7 @@ namespace CSVRender case Qt::Key_A: mKeyLeft = false; break; case Qt::Key_D: mKeyRight = false; break; case Qt::Key_Shift: mFast = false; break; + case Qt::Key_Control: mMod1 = false; break; default: QWidget::keyReleaseEvent (event); } } @@ -188,18 +190,39 @@ namespace CSVRender QPoint diff = mOldPos-event->pos(); mOldPos = event->pos(); - if (diff.x()) + if (mMod1) { - Ogre::Vector3 direction = mCamera->getDerivedRight(); - mCamera->move ((getFastFactor() * direction * diff.x())/mMouseSensitivity); - mUpdate = true; + // turn camera + if (diff.x()) + { + mCamera->yaw ( + Ogre::Degree ((getFastFactor() * diff.x())/mMouseSensitivity)); + mUpdate = true; + } + + if (diff.y()) + { + mCamera->pitch ( + Ogre::Degree ((getFastFactor() * diff.y())/mMouseSensitivity)); + mUpdate = true; + } } - - if (diff.y()) + else { - Ogre::Vector3 direction = mCamera->getDerivedUp(); - mCamera->move ((getFastFactor() * -direction * diff.y())/mMouseSensitivity); - mUpdate = true; + // pan camera + if (diff.x()) + { + Ogre::Vector3 direction = mCamera->getDerivedRight(); + mCamera->move ((getFastFactor() * direction * diff.x())/mMouseSensitivity); + mUpdate = true; + } + + if (diff.y()) + { + Ogre::Vector3 direction = mCamera->getDerivedUp(); + mCamera->move ((getFastFactor() * -direction * diff.y())/mMouseSensitivity); + mUpdate = true; + } } } else @@ -223,6 +246,7 @@ namespace CSVRender mKeyLeft = false; mKeyRight = false; mFast = false; + mMod1 = false; QWidget::focusOutEvent (event); } diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 37ef3cb94..58b122dee 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -63,6 +63,7 @@ namespace CSVRender int mKeyRight; bool mFast; bool mDragging; + bool mMod1; QPoint mOldPos; int mMouseSensitivity; int mFastFactor; From 644f5cb8de97add530f34a02968b5fe09e91ba50 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Feb 2014 14:28:55 +0100 Subject: [PATCH 038/114] fixed mouse button release detection --- apps/opencs/view/render/scenewidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index b498fba06..d6f12b529 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -235,7 +235,7 @@ namespace CSVRender void SceneWidget::mouseReleaseEvent (QMouseEvent *event) { - if (event->buttons() & Qt::LeftButton) + if (!(event->buttons() & Qt::LeftButton)) mDragging = false; } From 526d75df516cf72ec43ec29d4dc449bc015f30c4 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Feb 2014 14:30:38 +0100 Subject: [PATCH 039/114] inverted ctrl key --- apps/opencs/view/render/scenewidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index d6f12b529..c608df376 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -190,7 +190,7 @@ namespace CSVRender QPoint diff = mOldPos-event->pos(); mOldPos = event->pos(); - if (mMod1) + if (!mMod1) { // turn camera if (diff.x()) From 8f73cc9268cee9bcf4fabb85ea0ef9656bc20b8c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 25 Feb 2014 14:59:46 +0100 Subject: [PATCH 040/114] added navigation mode selection --- apps/opencs/view/render/scenewidget.cpp | 11 ++++++++- apps/opencs/view/render/scenewidget.hpp | 8 ++++-- apps/opencs/view/world/scenesubview.cpp | 33 ++++++++++++++++--------- apps/opencs/view/world/scenesubview.hpp | 10 ++++++++ 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index c608df376..07fe56f8f 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -15,7 +15,7 @@ namespace CSVRender : QWidget(parent) , mWindow(NULL) , mCamera(NULL) - , mSceneMgr(NULL), mNavigationMode (NavigationMode_Free), mUpdate (false) + , mSceneMgr(NULL), mNavigationMode (NavigationMode_1stPerson), mUpdate (false) , mKeyForward (false), mKeyBackward (false), mKeyLeft (false), mKeyRight (false) , mFast (false), mDragging (false), mMod1 (false) , mMouseSensitivity (2), mFastFactor (4) /// \todo make these configurable @@ -94,6 +94,15 @@ namespace CSVRender Ogre::Root::getSingleton().destroyRenderTarget(mWindow); } + void SceneWidget::setNavigationMode (NavigationMode mode) + { + if (mode!=mNavigationMode) + { + mNavigationMode = mode; + + } + } + void SceneWidget::paintEvent(QPaintEvent* e) { if (!mWindow) diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 58b122dee..55f62d661 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -20,14 +20,18 @@ namespace CSVRender enum NavigationMode { - NavigationMode_Free + NavigationMode_1stPerson, + NavigationMode_Free, + NavigationMode_Orbit }; SceneWidget(QWidget *parent); - virtual ~SceneWidget(void); + virtual ~SceneWidget(); QPaintEngine* paintEngine() const; + void setNavigationMode (NavigationMode mode); + private: void paintEvent(QPaintEvent* e); void resizeEvent(QResizeEvent* e); diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 705adf2ad..339a88519 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -32,20 +32,21 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D layout2->setContentsMargins (QMargins (0, 0, 0, 0)); SceneToolbar *toolbar = new SceneToolbar (48, this); -// test -SceneToolMode *tool = new SceneToolMode (toolbar); -tool->addButton (":door.png", "a"); -tool->addButton (":GMST.png", "b"); -tool->addButton (":Info.png", "c"); -toolbar->addTool (tool); -toolbar->addTool (new SceneToolMode (toolbar)); -toolbar->addTool (new SceneToolMode (toolbar)); -toolbar->addTool (new SceneToolMode (toolbar)); + + // navigation mode + SceneToolMode *tool = new SceneToolMode (toolbar); + tool->addButton (":door.png", "1st"); /// \todo replace icons + tool->addButton (":GMST.png", "free"); + tool->addButton (":Info.png", "orbit"); + toolbar->addTool (tool); + connect (tool, SIGNAL (modeChanged (const std::string&)), + this, SLOT (selectNavigationMode (const std::string&))); + layout2->addWidget (toolbar, 0); - CSVRender::SceneWidget* sceneWidget = new CSVRender::SceneWidget(this); + mScene = new CSVRender::SceneWidget(this); - layout2->addWidget (sceneWidget, 1); + layout2->addWidget (mScene, 1); layout->insertLayout (0, layout2, 1); @@ -76,3 +77,13 @@ void CSVWorld::SceneSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); } + +void CSVWorld::SceneSubView::selectNavigationMode (const std::string& mode) +{ + if (mode=="1st") + mScene->setNavigationMode (CSVRender::SceneWidget::NavigationMode_1stPerson); + else if (mode=="free") + mScene->setNavigationMode (CSVRender::SceneWidget::NavigationMode_Free); + else if (mode=="orbit") + mScene->setNavigationMode (CSVRender::SceneWidget::NavigationMode_Orbit); +} diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index a0fed908d..65e1614c1 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -10,6 +10,11 @@ namespace CSMDoc class Document; } +namespace CSVRender +{ + class SceneWidget; +} + namespace CSVWorld { class Table; @@ -21,6 +26,7 @@ namespace CSVWorld Q_OBJECT TableBottomBox *mBottom; + CSVRender::SceneWidget *mScene; public: @@ -31,6 +37,10 @@ namespace CSVWorld virtual void updateEditorSetting (const QString& key, const QString& value); virtual void setStatusBar (bool show); + + private slots: + + void selectNavigationMode (const std::string& mode); }; } From 2421f23c2f83ab7d8d2d2b153fcfb43cb761aaee Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 27 Feb 2014 23:59:23 +1100 Subject: [PATCH 041/114] cleanup the mess... --- apps/opencs/model/world/columnbase.hpp | 254 +-- apps/opencs/model/world/tablemimedata.cpp | 890 ++++----- apps/opencs/model/world/tablemimedata.hpp | 124 +- apps/opencs/view/filter/editwidget.cpp | 406 ++-- apps/opencs/view/filter/editwidget.hpp | 114 +- apps/opencs/view/filter/filterbox.cpp | 100 +- apps/opencs/view/filter/filterbox.hpp | 90 +- apps/opencs/view/filter/recordfilterbox.cpp | 64 +- apps/opencs/view/filter/recordfilterbox.hpp | 74 +- apps/opencs/view/world/table.cpp | 1090 +++++------ apps/opencs/view/world/table.hpp | 254 +-- apps/opencs/view/world/tablesubview.cpp | 262 +-- apps/opencs/view/world/tablesubview.hpp | 126 +- apps/openmw/mwmechanics/aicombat.cpp | 37 +- apps/openmw/mwmechanics/pathfinding.cpp | 2 + apps/openmw/mwstate/statemanagerimp.cpp | 704 +++---- components/esm/esmwriter.cpp | 406 ++-- components/esm/esmwriter.hpp | 228 +-- credits.txt | 1 + readme.txt | 1902 +++++++++---------- 20 files changed, 3571 insertions(+), 3557 deletions(-) diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 4ce45ffe8..e04333608 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -1,127 +1,127 @@ -#ifndef CSM_WOLRD_COLUMNBASE_H -#define CSM_WOLRD_COLUMNBASE_H - -#include - -#include -#include - -#include "record.hpp" - -namespace CSMWorld -{ - struct ColumnBase - { - enum Roles - { - Role_Flags = Qt::UserRole, - Role_Display = Qt::UserRole+1 - }; - - enum Flags - { - Flag_Table = 1, // column should be displayed in table view - Flag_Dialogue = 2 // column should be displayed in dialogue view - }; - - enum Display - { - Display_None, //Do not use - Display_String, - - //CONCRETE TYPES STARTS HERE - Display_Skill, - Display_Class, - Display_Faction, - Display_Race, - Display_Sound, - Display_Region, - Display_Birthsign, - Display_Spell, - Display_Cell, - Display_Referenceable, - Display_Activator, - Display_Potion, - Display_Apparatus, - Display_Armor, - Display_Book, - Display_Clothing, - Display_Container, - Display_Creature, - Display_Door, - Display_Ingredient, - Display_CreatureLevelledList, - Display_ItemLevelledList, - Display_Light, - Display_Lockpick, - Display_Miscellaneous, - Display_Npc, - Display_Probe, - Display_Repair, - Display_Static, - Display_Weapon, - Display_Reference, - Display_Filter, - Display_Topic, - Display_Journal, - Display_TopicInfo, - Display_JournalInfo, - Display_Scene, - //CONCRETE TYPES ENDS HERE - - Display_Integer, - Display_Float, - Display_Var, - Display_GmstVarType, - Display_GlobalVarType, - Display_Specialisation, - Display_Attribute, - Display_Boolean, - Display_SpellType, - Display_Script, - Display_ApparatusType, - Display_ArmorType, - Display_ClothingType, - Display_CreatureType, - Display_WeaponType, - Display_RecordState, - Display_RefRecordType, - Display_DialogueType, - Display_QuestStatusType, - Display_Gender - }; - - int mColumnId; - int mFlags; - Display mDisplayType; - - ColumnBase (int columnId, Display displayType, int flag); - - virtual ~ColumnBase(); - - virtual bool isEditable() const = 0; - - virtual bool isUserEditable() const; - ///< Can this column be edited directly by the user? - - virtual std::string getTitle() const; - }; - - template - struct Column : public ColumnBase - { - int mFlags; - - Column (int columnId, Display displayType, int flags = Flag_Table | Flag_Dialogue) - : ColumnBase (columnId, displayType, flags) {} - - virtual QVariant get (const Record& record) const = 0; - - virtual void set (Record& record, const QVariant& data) - { - throw std::logic_error ("Column " + getTitle() + " is not editable"); - } - }; -} - -#endif +#ifndef CSM_WOLRD_COLUMNBASE_H +#define CSM_WOLRD_COLUMNBASE_H + +#include + +#include +#include + +#include "record.hpp" + +namespace CSMWorld +{ + struct ColumnBase + { + enum Roles + { + Role_Flags = Qt::UserRole, + Role_Display = Qt::UserRole+1 + }; + + enum Flags + { + Flag_Table = 1, // column should be displayed in table view + Flag_Dialogue = 2 // column should be displayed in dialogue view + }; + + enum Display + { + Display_None, //Do not use + Display_String, + + //CONCRETE TYPES STARTS HERE + Display_Skill, + Display_Class, + Display_Faction, + Display_Race, + Display_Sound, + Display_Region, + Display_Birthsign, + Display_Spell, + Display_Cell, + Display_Referenceable, + Display_Activator, + Display_Potion, + Display_Apparatus, + Display_Armor, + Display_Book, + Display_Clothing, + Display_Container, + Display_Creature, + Display_Door, + Display_Ingredient, + Display_CreatureLevelledList, + Display_ItemLevelledList, + Display_Light, + Display_Lockpick, + Display_Miscellaneous, + Display_Npc, + Display_Probe, + Display_Repair, + Display_Static, + Display_Weapon, + Display_Reference, + Display_Filter, + Display_Topic, + Display_Journal, + Display_TopicInfo, + Display_JournalInfo, + Display_Scene, + //CONCRETE TYPES ENDS HERE + + Display_Integer, + Display_Float, + Display_Var, + Display_GmstVarType, + Display_GlobalVarType, + Display_Specialisation, + Display_Attribute, + Display_Boolean, + Display_SpellType, + Display_Script, + Display_ApparatusType, + Display_ArmorType, + Display_ClothingType, + Display_CreatureType, + Display_WeaponType, + Display_RecordState, + Display_RefRecordType, + Display_DialogueType, + Display_QuestStatusType, + Display_Gender + }; + + int mColumnId; + int mFlags; + Display mDisplayType; + + ColumnBase (int columnId, Display displayType, int flag); + + virtual ~ColumnBase(); + + virtual bool isEditable() const = 0; + + virtual bool isUserEditable() const; + ///< Can this column be edited directly by the user? + + virtual std::string getTitle() const; + }; + + template + struct Column : public ColumnBase + { + int mFlags; + + Column (int columnId, Display displayType, int flags = Flag_Table | Flag_Dialogue) + : ColumnBase (columnId, displayType, flags) {} + + virtual QVariant get (const Record& record) const = 0; + + virtual void set (Record& record, const QVariant& data) + { + throw std::logic_error ("Column " + getTitle() + " is not editable"); + } + }; +} + +#endif diff --git a/apps/opencs/model/world/tablemimedata.cpp b/apps/opencs/model/world/tablemimedata.cpp index f5e1acfb4..b56c9c8c2 100644 --- a/apps/opencs/model/world/tablemimedata.cpp +++ b/apps/opencs/model/world/tablemimedata.cpp @@ -1,446 +1,446 @@ -#include "tablemimedata.hpp" -#include - -#include "universalid.hpp" -#include "columnbase.hpp" - -CSMWorld::TableMimeData::TableMimeData (UniversalId id, const CSMDoc::Document& document) : -mDocument(document) -{ - mUniversalId.push_back (id); - mObjectsFormats << QString::fromStdString ("tabledata/" + id.getTypeName()); -} - -CSMWorld::TableMimeData::TableMimeData (std::vector< CSMWorld::UniversalId >& id, const CSMDoc::Document& document) : - mUniversalId (id), mDocument(document) -{ - for (std::vector::iterator it (mUniversalId.begin()); it != mUniversalId.end(); ++it) - { - mObjectsFormats << QString::fromStdString ("tabledata/" + it->getTypeName()); - } -} - -QStringList CSMWorld::TableMimeData::formats() const -{ - return mObjectsFormats; -} - -CSMWorld::TableMimeData::~TableMimeData() -{ -} - -std::string CSMWorld::TableMimeData::getIcon() const -{ - if (mUniversalId.empty()) - { - throw ("TableMimeData holds no UniversalId"); - } - - std::string tmpIcon; - bool firstIteration = true; - - for (unsigned i = 0; i < mUniversalId.size(); ++i) - { - if (firstIteration) - { - firstIteration = false; - tmpIcon = mUniversalId[i].getIcon(); - continue; - } - - if (tmpIcon != mUniversalId[i].getIcon()) - { - return ":/multitype.png"; //icon stolen from gnome - } - - tmpIcon = mUniversalId[i].getIcon(); - } - - return mUniversalId.begin()->getIcon(); //All objects are of the same type; -} - -std::vector< CSMWorld::UniversalId > CSMWorld::TableMimeData::getData() const -{ - return mUniversalId; -} - - -bool CSMWorld::TableMimeData::holdsType (CSMWorld::UniversalId::Type type) const -{ - for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) - { - if (it->getType() == type) - { - return true; - } - } - - return false; -} - -bool CSMWorld::TableMimeData::holdsType (CSMWorld::ColumnBase::Display type) const -{ - for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) - { - if (it->getType() == convertEnums (type)) - { - return true; - } - } - - return false; -} - -CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::UniversalId::Type type) const -{ - for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) - { - if (it->getType() == type) - { - return *it; - } - } - - throw ("TableMimeData object does not hold object of the seeked type"); -} - -CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::ColumnBase::Display type) const -{ - for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) - { - if (it->getType() == convertEnums (type)) - { - return *it; - } - } - - throw ("TableMimeData object does not hold object of the seeked type"); -} - -bool CSMWorld::TableMimeData::fromDocument (const CSMDoc::Document& document) const -{ - return &document == &mDocument; -} - -CSMWorld::UniversalId::Type CSMWorld::TableMimeData::convertEnums (CSMWorld::ColumnBase::Display type) -{ - switch (type) - { - case CSMWorld::ColumnBase::Display_Race: - return CSMWorld::UniversalId::Type_Race; - - - case CSMWorld::ColumnBase::Display_Skill: - return CSMWorld::UniversalId::Type_Skill; - - - case CSMWorld::ColumnBase::Display_Class: - return CSMWorld::UniversalId::Type_Class; - - - case CSMWorld::ColumnBase::Display_Faction: - return CSMWorld::UniversalId::Type_Faction; - - - case CSMWorld::ColumnBase::Display_Sound: - return CSMWorld::UniversalId::Type_Sound; - - - case CSMWorld::ColumnBase::Display_Region: - return CSMWorld::UniversalId::Type_Region; - - - case CSMWorld::ColumnBase::Display_Birthsign: - return CSMWorld::UniversalId::Type_Birthsign; - - - case CSMWorld::ColumnBase::Display_Spell: - return CSMWorld::UniversalId::Type_Spell; - - - case CSMWorld::ColumnBase::Display_Cell: - return CSMWorld::UniversalId::Type_Cell; - - - case CSMWorld::ColumnBase::Display_Referenceable: - return CSMWorld::UniversalId::Type_Referenceable; - - - case CSMWorld::ColumnBase::Display_Activator: - return CSMWorld::UniversalId::Type_Activator; - - - case CSMWorld::ColumnBase::Display_Potion: - return CSMWorld::UniversalId::Type_Potion; - - - case CSMWorld::ColumnBase::Display_Apparatus: - return CSMWorld::UniversalId::Type_Apparatus; - - - case CSMWorld::ColumnBase::Display_Armor: - return CSMWorld::UniversalId::Type_Armor; - - - case CSMWorld::ColumnBase::Display_Book: - return CSMWorld::UniversalId::Type_Book; - - - case CSMWorld::ColumnBase::Display_Clothing: - return CSMWorld::UniversalId::Type_Clothing; - - - case CSMWorld::ColumnBase::Display_Container: - return CSMWorld::UniversalId::Type_Container; - - - case CSMWorld::ColumnBase::Display_Creature: - return CSMWorld::UniversalId::Type_Creature; - - - case CSMWorld::ColumnBase::Display_Door: - return CSMWorld::UniversalId::Type_Door; - - - case CSMWorld::ColumnBase::Display_Ingredient: - return CSMWorld::UniversalId::Type_Ingredient; - - - case CSMWorld::ColumnBase::Display_CreatureLevelledList: - return CSMWorld::UniversalId::Type_CreatureLevelledList; - - - case CSMWorld::ColumnBase::Display_ItemLevelledList: - return CSMWorld::UniversalId::Type_ItemLevelledList; - - - case CSMWorld::ColumnBase::Display_Light: - return CSMWorld::UniversalId::Type_Light; - - - case CSMWorld::ColumnBase::Display_Lockpick: - return CSMWorld::UniversalId::Type_Lockpick; - - - case CSMWorld::ColumnBase::Display_Miscellaneous: - return CSMWorld::UniversalId::Type_Miscellaneous; - - - case CSMWorld::ColumnBase::Display_Npc: - return CSMWorld::UniversalId::Type_Npc; - - - case CSMWorld::ColumnBase::Display_Probe: - return CSMWorld::UniversalId::Type_Probe; - - - case CSMWorld::ColumnBase::Display_Repair: - return CSMWorld::UniversalId::Type_Repair; - - - case CSMWorld::ColumnBase::Display_Static: - return CSMWorld::UniversalId::Type_Static; - - - case CSMWorld::ColumnBase::Display_Weapon: - return CSMWorld::UniversalId::Type_Weapon; - - - case CSMWorld::ColumnBase::Display_Reference: - return CSMWorld::UniversalId::Type_Reference; - - - case CSMWorld::ColumnBase::Display_Filter: - return CSMWorld::UniversalId::Type_Filter; - - - case CSMWorld::ColumnBase::Display_Topic: - return CSMWorld::UniversalId::Type_Topic; - - - case CSMWorld::ColumnBase::Display_Journal: - return CSMWorld::UniversalId::Type_Journal; - - - case CSMWorld::ColumnBase::Display_TopicInfo: - return CSMWorld::UniversalId::Type_TopicInfo; - - - case CSMWorld::ColumnBase::Display_JournalInfo: - return CSMWorld::UniversalId::Type_JournalInfo; - - - case CSMWorld::ColumnBase::Display_Scene: - return CSMWorld::UniversalId::Type_Scene; - - - case CSMWorld::ColumnBase::Display_Script: - return CSMWorld::UniversalId::Type_Script; - - - default: - return CSMWorld::UniversalId::Type_None; - - } -} - -CSMWorld::ColumnBase::Display CSMWorld::TableMimeData::convertEnums (CSMWorld::UniversalId::Type type) -{ - switch (type) - { - case CSMWorld::UniversalId::Type_Race: - return CSMWorld::ColumnBase::Display_Race; - - - case CSMWorld::UniversalId::Type_Skill: - return CSMWorld::ColumnBase::Display_Skill; - - - case CSMWorld::UniversalId::Type_Class: - return CSMWorld::ColumnBase::Display_Class; - - - case CSMWorld::UniversalId::Type_Faction: - return CSMWorld::ColumnBase::Display_Faction; - - - case CSMWorld::UniversalId::Type_Sound: - return CSMWorld::ColumnBase::Display_Sound; - - - case CSMWorld::UniversalId::Type_Region: - return CSMWorld::ColumnBase::Display_Region; - - - case CSMWorld::UniversalId::Type_Birthsign: - return CSMWorld::ColumnBase::Display_Birthsign; - - - case CSMWorld::UniversalId::Type_Spell: - return CSMWorld::ColumnBase::Display_Spell; - - - case CSMWorld::UniversalId::Type_Cell: - return CSMWorld::ColumnBase::Display_Cell; - - - case CSMWorld::UniversalId::Type_Referenceable: - return CSMWorld::ColumnBase::Display_Referenceable; - - - case CSMWorld::UniversalId::Type_Activator: - return CSMWorld::ColumnBase::Display_Activator; - - - case CSMWorld::UniversalId::Type_Potion: - return CSMWorld::ColumnBase::Display_Potion; - - - case CSMWorld::UniversalId::Type_Apparatus: - return CSMWorld::ColumnBase::Display_Apparatus; - - - case CSMWorld::UniversalId::Type_Armor: - return CSMWorld::ColumnBase::Display_Armor; - - - case CSMWorld::UniversalId::Type_Book: - return CSMWorld::ColumnBase::Display_Book; - - - case CSMWorld::UniversalId::Type_Clothing: - return CSMWorld::ColumnBase::Display_Clothing; - - - case CSMWorld::UniversalId::Type_Container: - return CSMWorld::ColumnBase::Display_Container; - - - case CSMWorld::UniversalId::Type_Creature: - return CSMWorld::ColumnBase::Display_Creature; - - - case CSMWorld::UniversalId::Type_Door: - return CSMWorld::ColumnBase::Display_Door; - - - case CSMWorld::UniversalId::Type_Ingredient: - return CSMWorld::ColumnBase::Display_Ingredient; - - - case CSMWorld::UniversalId::Type_CreatureLevelledList: - return CSMWorld::ColumnBase::Display_CreatureLevelledList; - - - case CSMWorld::UniversalId::Type_ItemLevelledList: - return CSMWorld::ColumnBase::Display_ItemLevelledList; - - - case CSMWorld::UniversalId::Type_Light: - return CSMWorld::ColumnBase::Display_Light; - - - case CSMWorld::UniversalId::Type_Lockpick: - return CSMWorld::ColumnBase::Display_Lockpick; - - - case CSMWorld::UniversalId::Type_Miscellaneous: - return CSMWorld::ColumnBase::Display_Miscellaneous; - - - case CSMWorld::UniversalId::Type_Npc: - return CSMWorld::ColumnBase::Display_Npc; - - - case CSMWorld::UniversalId::Type_Probe: - return CSMWorld::ColumnBase::Display_Probe; - - - case CSMWorld::UniversalId::Type_Repair: - return CSMWorld::ColumnBase::Display_Repair; - - - case CSMWorld::UniversalId::Type_Static: - return CSMWorld::ColumnBase::Display_Static; - - - case CSMWorld::UniversalId::Type_Weapon: - return CSMWorld::ColumnBase::Display_Weapon; - - - case CSMWorld::UniversalId::Type_Reference: - return CSMWorld::ColumnBase::Display_Reference; - - - case CSMWorld::UniversalId::Type_Filter: - return CSMWorld::ColumnBase::Display_Filter; - - - case CSMWorld::UniversalId::Type_Topic: - return CSMWorld::ColumnBase::Display_Topic; - - - case CSMWorld::UniversalId::Type_Journal: - return CSMWorld::ColumnBase::Display_Journal; - - - case CSMWorld::UniversalId::Type_TopicInfo: - return CSMWorld::ColumnBase::Display_TopicInfo; - - - case CSMWorld::UniversalId::Type_JournalInfo: - return CSMWorld::ColumnBase::Display_JournalInfo; - - - case CSMWorld::UniversalId::Type_Scene: - return CSMWorld::ColumnBase::Display_Scene; - - - case CSMWorld::UniversalId::Type_Script: - return CSMWorld::ColumnBase::Display_Script; - - - default: - return CSMWorld::ColumnBase::Display_None; - } +#include "tablemimedata.hpp" +#include + +#include "universalid.hpp" +#include "columnbase.hpp" + +CSMWorld::TableMimeData::TableMimeData (UniversalId id, const CSMDoc::Document& document) : +mDocument(document) +{ + mUniversalId.push_back (id); + mObjectsFormats << QString::fromStdString ("tabledata/" + id.getTypeName()); +} + +CSMWorld::TableMimeData::TableMimeData (std::vector< CSMWorld::UniversalId >& id, const CSMDoc::Document& document) : + mUniversalId (id), mDocument(document) +{ + for (std::vector::iterator it (mUniversalId.begin()); it != mUniversalId.end(); ++it) + { + mObjectsFormats << QString::fromStdString ("tabledata/" + it->getTypeName()); + } +} + +QStringList CSMWorld::TableMimeData::formats() const +{ + return mObjectsFormats; +} + +CSMWorld::TableMimeData::~TableMimeData() +{ +} + +std::string CSMWorld::TableMimeData::getIcon() const +{ + if (mUniversalId.empty()) + { + throw ("TableMimeData holds no UniversalId"); + } + + std::string tmpIcon; + bool firstIteration = true; + + for (unsigned i = 0; i < mUniversalId.size(); ++i) + { + if (firstIteration) + { + firstIteration = false; + tmpIcon = mUniversalId[i].getIcon(); + continue; + } + + if (tmpIcon != mUniversalId[i].getIcon()) + { + return ":/multitype.png"; //icon stolen from gnome + } + + tmpIcon = mUniversalId[i].getIcon(); + } + + return mUniversalId.begin()->getIcon(); //All objects are of the same type; +} + +std::vector< CSMWorld::UniversalId > CSMWorld::TableMimeData::getData() const +{ + return mUniversalId; +} + + +bool CSMWorld::TableMimeData::holdsType (CSMWorld::UniversalId::Type type) const +{ + for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) + { + if (it->getType() == type) + { + return true; + } + } + + return false; +} + +bool CSMWorld::TableMimeData::holdsType (CSMWorld::ColumnBase::Display type) const +{ + for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) + { + if (it->getType() == convertEnums (type)) + { + return true; + } + } + + return false; +} + +CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::UniversalId::Type type) const +{ + for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) + { + if (it->getType() == type) + { + return *it; + } + } + + throw ("TableMimeData object does not hold object of the seeked type"); +} + +CSMWorld::UniversalId CSMWorld::TableMimeData::returnMatching (CSMWorld::ColumnBase::Display type) const +{ + for (std::vector::const_iterator it = mUniversalId.begin(); it != mUniversalId.end(); ++it) + { + if (it->getType() == convertEnums (type)) + { + return *it; + } + } + + throw ("TableMimeData object does not hold object of the seeked type"); +} + +bool CSMWorld::TableMimeData::fromDocument (const CSMDoc::Document& document) const +{ + return &document == &mDocument; +} + +CSMWorld::UniversalId::Type CSMWorld::TableMimeData::convertEnums (CSMWorld::ColumnBase::Display type) +{ + switch (type) + { + case CSMWorld::ColumnBase::Display_Race: + return CSMWorld::UniversalId::Type_Race; + + + case CSMWorld::ColumnBase::Display_Skill: + return CSMWorld::UniversalId::Type_Skill; + + + case CSMWorld::ColumnBase::Display_Class: + return CSMWorld::UniversalId::Type_Class; + + + case CSMWorld::ColumnBase::Display_Faction: + return CSMWorld::UniversalId::Type_Faction; + + + case CSMWorld::ColumnBase::Display_Sound: + return CSMWorld::UniversalId::Type_Sound; + + + case CSMWorld::ColumnBase::Display_Region: + return CSMWorld::UniversalId::Type_Region; + + + case CSMWorld::ColumnBase::Display_Birthsign: + return CSMWorld::UniversalId::Type_Birthsign; + + + case CSMWorld::ColumnBase::Display_Spell: + return CSMWorld::UniversalId::Type_Spell; + + + case CSMWorld::ColumnBase::Display_Cell: + return CSMWorld::UniversalId::Type_Cell; + + + case CSMWorld::ColumnBase::Display_Referenceable: + return CSMWorld::UniversalId::Type_Referenceable; + + + case CSMWorld::ColumnBase::Display_Activator: + return CSMWorld::UniversalId::Type_Activator; + + + case CSMWorld::ColumnBase::Display_Potion: + return CSMWorld::UniversalId::Type_Potion; + + + case CSMWorld::ColumnBase::Display_Apparatus: + return CSMWorld::UniversalId::Type_Apparatus; + + + case CSMWorld::ColumnBase::Display_Armor: + return CSMWorld::UniversalId::Type_Armor; + + + case CSMWorld::ColumnBase::Display_Book: + return CSMWorld::UniversalId::Type_Book; + + + case CSMWorld::ColumnBase::Display_Clothing: + return CSMWorld::UniversalId::Type_Clothing; + + + case CSMWorld::ColumnBase::Display_Container: + return CSMWorld::UniversalId::Type_Container; + + + case CSMWorld::ColumnBase::Display_Creature: + return CSMWorld::UniversalId::Type_Creature; + + + case CSMWorld::ColumnBase::Display_Door: + return CSMWorld::UniversalId::Type_Door; + + + case CSMWorld::ColumnBase::Display_Ingredient: + return CSMWorld::UniversalId::Type_Ingredient; + + + case CSMWorld::ColumnBase::Display_CreatureLevelledList: + return CSMWorld::UniversalId::Type_CreatureLevelledList; + + + case CSMWorld::ColumnBase::Display_ItemLevelledList: + return CSMWorld::UniversalId::Type_ItemLevelledList; + + + case CSMWorld::ColumnBase::Display_Light: + return CSMWorld::UniversalId::Type_Light; + + + case CSMWorld::ColumnBase::Display_Lockpick: + return CSMWorld::UniversalId::Type_Lockpick; + + + case CSMWorld::ColumnBase::Display_Miscellaneous: + return CSMWorld::UniversalId::Type_Miscellaneous; + + + case CSMWorld::ColumnBase::Display_Npc: + return CSMWorld::UniversalId::Type_Npc; + + + case CSMWorld::ColumnBase::Display_Probe: + return CSMWorld::UniversalId::Type_Probe; + + + case CSMWorld::ColumnBase::Display_Repair: + return CSMWorld::UniversalId::Type_Repair; + + + case CSMWorld::ColumnBase::Display_Static: + return CSMWorld::UniversalId::Type_Static; + + + case CSMWorld::ColumnBase::Display_Weapon: + return CSMWorld::UniversalId::Type_Weapon; + + + case CSMWorld::ColumnBase::Display_Reference: + return CSMWorld::UniversalId::Type_Reference; + + + case CSMWorld::ColumnBase::Display_Filter: + return CSMWorld::UniversalId::Type_Filter; + + + case CSMWorld::ColumnBase::Display_Topic: + return CSMWorld::UniversalId::Type_Topic; + + + case CSMWorld::ColumnBase::Display_Journal: + return CSMWorld::UniversalId::Type_Journal; + + + case CSMWorld::ColumnBase::Display_TopicInfo: + return CSMWorld::UniversalId::Type_TopicInfo; + + + case CSMWorld::ColumnBase::Display_JournalInfo: + return CSMWorld::UniversalId::Type_JournalInfo; + + + case CSMWorld::ColumnBase::Display_Scene: + return CSMWorld::UniversalId::Type_Scene; + + + case CSMWorld::ColumnBase::Display_Script: + return CSMWorld::UniversalId::Type_Script; + + + default: + return CSMWorld::UniversalId::Type_None; + + } +} + +CSMWorld::ColumnBase::Display CSMWorld::TableMimeData::convertEnums (CSMWorld::UniversalId::Type type) +{ + switch (type) + { + case CSMWorld::UniversalId::Type_Race: + return CSMWorld::ColumnBase::Display_Race; + + + case CSMWorld::UniversalId::Type_Skill: + return CSMWorld::ColumnBase::Display_Skill; + + + case CSMWorld::UniversalId::Type_Class: + return CSMWorld::ColumnBase::Display_Class; + + + case CSMWorld::UniversalId::Type_Faction: + return CSMWorld::ColumnBase::Display_Faction; + + + case CSMWorld::UniversalId::Type_Sound: + return CSMWorld::ColumnBase::Display_Sound; + + + case CSMWorld::UniversalId::Type_Region: + return CSMWorld::ColumnBase::Display_Region; + + + case CSMWorld::UniversalId::Type_Birthsign: + return CSMWorld::ColumnBase::Display_Birthsign; + + + case CSMWorld::UniversalId::Type_Spell: + return CSMWorld::ColumnBase::Display_Spell; + + + case CSMWorld::UniversalId::Type_Cell: + return CSMWorld::ColumnBase::Display_Cell; + + + case CSMWorld::UniversalId::Type_Referenceable: + return CSMWorld::ColumnBase::Display_Referenceable; + + + case CSMWorld::UniversalId::Type_Activator: + return CSMWorld::ColumnBase::Display_Activator; + + + case CSMWorld::UniversalId::Type_Potion: + return CSMWorld::ColumnBase::Display_Potion; + + + case CSMWorld::UniversalId::Type_Apparatus: + return CSMWorld::ColumnBase::Display_Apparatus; + + + case CSMWorld::UniversalId::Type_Armor: + return CSMWorld::ColumnBase::Display_Armor; + + + case CSMWorld::UniversalId::Type_Book: + return CSMWorld::ColumnBase::Display_Book; + + + case CSMWorld::UniversalId::Type_Clothing: + return CSMWorld::ColumnBase::Display_Clothing; + + + case CSMWorld::UniversalId::Type_Container: + return CSMWorld::ColumnBase::Display_Container; + + + case CSMWorld::UniversalId::Type_Creature: + return CSMWorld::ColumnBase::Display_Creature; + + + case CSMWorld::UniversalId::Type_Door: + return CSMWorld::ColumnBase::Display_Door; + + + case CSMWorld::UniversalId::Type_Ingredient: + return CSMWorld::ColumnBase::Display_Ingredient; + + + case CSMWorld::UniversalId::Type_CreatureLevelledList: + return CSMWorld::ColumnBase::Display_CreatureLevelledList; + + + case CSMWorld::UniversalId::Type_ItemLevelledList: + return CSMWorld::ColumnBase::Display_ItemLevelledList; + + + case CSMWorld::UniversalId::Type_Light: + return CSMWorld::ColumnBase::Display_Light; + + + case CSMWorld::UniversalId::Type_Lockpick: + return CSMWorld::ColumnBase::Display_Lockpick; + + + case CSMWorld::UniversalId::Type_Miscellaneous: + return CSMWorld::ColumnBase::Display_Miscellaneous; + + + case CSMWorld::UniversalId::Type_Npc: + return CSMWorld::ColumnBase::Display_Npc; + + + case CSMWorld::UniversalId::Type_Probe: + return CSMWorld::ColumnBase::Display_Probe; + + + case CSMWorld::UniversalId::Type_Repair: + return CSMWorld::ColumnBase::Display_Repair; + + + case CSMWorld::UniversalId::Type_Static: + return CSMWorld::ColumnBase::Display_Static; + + + case CSMWorld::UniversalId::Type_Weapon: + return CSMWorld::ColumnBase::Display_Weapon; + + + case CSMWorld::UniversalId::Type_Reference: + return CSMWorld::ColumnBase::Display_Reference; + + + case CSMWorld::UniversalId::Type_Filter: + return CSMWorld::ColumnBase::Display_Filter; + + + case CSMWorld::UniversalId::Type_Topic: + return CSMWorld::ColumnBase::Display_Topic; + + + case CSMWorld::UniversalId::Type_Journal: + return CSMWorld::ColumnBase::Display_Journal; + + + case CSMWorld::UniversalId::Type_TopicInfo: + return CSMWorld::ColumnBase::Display_TopicInfo; + + + case CSMWorld::UniversalId::Type_JournalInfo: + return CSMWorld::ColumnBase::Display_JournalInfo; + + + case CSMWorld::UniversalId::Type_Scene: + return CSMWorld::ColumnBase::Display_Scene; + + + case CSMWorld::UniversalId::Type_Script: + return CSMWorld::ColumnBase::Display_Script; + + + default: + return CSMWorld::ColumnBase::Display_None; + } } \ No newline at end of file diff --git a/apps/opencs/model/world/tablemimedata.hpp b/apps/opencs/model/world/tablemimedata.hpp index 0b0143abd..7687f3555 100644 --- a/apps/opencs/model/world/tablemimedata.hpp +++ b/apps/opencs/model/world/tablemimedata.hpp @@ -1,63 +1,63 @@ - -#ifndef TABLEMIMEDATA_H -#define TABLEMIMEDATA_H - -#include - -#include -#include - -#include "universalid.hpp" -#include "columnbase.hpp" - -namespace CSMDoc -{ - class Document; -} - -namespace CSMWorld -{ - -/// \brief Subclass of QmimeData, augmented to contain and transport UniversalIds. -/// -/// This class provides way to construct mimedata object holding the universalid copy -/// Universalid is used in the majority of the tables to store type, id, argument types. -/// This way universalid grants a way to retrive record from the concrete table. -/// Please note, that tablemimedata object can hold multiple universalIds in the vector. - - class TableMimeData : public QMimeData - { - public: - TableMimeData(UniversalId id, const CSMDoc::Document& document); - - TableMimeData(std::vector& id, const CSMDoc::Document& document); - - ~TableMimeData(); - - virtual QStringList formats() const; - - std::string getIcon() const; - - std::vector getData() const; - - bool holdsType(UniversalId::Type type) const; - - bool holdsType(CSMWorld::ColumnBase::Display type) const; - - bool fromDocument(const CSMDoc::Document& document) const; - - UniversalId returnMatching(UniversalId::Type type) const; - - UniversalId returnMatching(CSMWorld::ColumnBase::Display type) const; - - static CSMWorld::UniversalId::Type convertEnums(CSMWorld::ColumnBase::Display type); - static CSMWorld::ColumnBase::Display convertEnums(CSMWorld::UniversalId::Type type); - - private: - std::vector mUniversalId; - QStringList mObjectsFormats; - const CSMDoc::Document& mDocument; - - }; -} + +#ifndef TABLEMIMEDATA_H +#define TABLEMIMEDATA_H + +#include + +#include +#include + +#include "universalid.hpp" +#include "columnbase.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + +/// \brief Subclass of QmimeData, augmented to contain and transport UniversalIds. +/// +/// This class provides way to construct mimedata object holding the universalid copy +/// Universalid is used in the majority of the tables to store type, id, argument types. +/// This way universalid grants a way to retrive record from the concrete table. +/// Please note, that tablemimedata object can hold multiple universalIds in the vector. + + class TableMimeData : public QMimeData + { + public: + TableMimeData(UniversalId id, const CSMDoc::Document& document); + + TableMimeData(std::vector& id, const CSMDoc::Document& document); + + ~TableMimeData(); + + virtual QStringList formats() const; + + std::string getIcon() const; + + std::vector getData() const; + + bool holdsType(UniversalId::Type type) const; + + bool holdsType(CSMWorld::ColumnBase::Display type) const; + + bool fromDocument(const CSMDoc::Document& document) const; + + UniversalId returnMatching(UniversalId::Type type) const; + + UniversalId returnMatching(CSMWorld::ColumnBase::Display type) const; + + static CSMWorld::UniversalId::Type convertEnums(CSMWorld::ColumnBase::Display type); + static CSMWorld::ColumnBase::Display convertEnums(CSMWorld::UniversalId::Type type); + + private: + std::vector mUniversalId; + QStringList mObjectsFormats; + const CSMDoc::Document& mDocument; + + }; +} #endif // TABLEMIMEDATA_H \ No newline at end of file diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index 841919a9e..cc1578bdd 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -1,203 +1,203 @@ - -#include "editwidget.hpp" - -#include -#include -#include - -#include "../../model/world/data.hpp" - -CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) -: QLineEdit (parent), mParser (data) -{ - mPalette = palette(); - connect (this, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); - - QAbstractItemModel *model = data.getTableModel (CSMWorld::UniversalId::Type_Filters); - - connect (model, SIGNAL (dataChanged (const QModelIndex &, const QModelIndex&)), - this, SLOT (filterDataChanged (const QModelIndex &, const QModelIndex&)), - Qt::QueuedConnection); - connect (model, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), - this, SLOT (filterRowsRemoved (const QModelIndex&, int, int)), - Qt::QueuedConnection); - connect (model, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (filterRowsInserted (const QModelIndex&, int, int)), - Qt::QueuedConnection); -} - -void CSVFilter::EditWidget::textChanged (const QString& text) -{ - if (mParser.parse (text.toUtf8().constData())) - { - setPalette (mPalette); - emit filterChanged (mParser.getFilter()); - } - else - { - QPalette palette (mPalette); - palette.setColor (QPalette::Text, Qt::red); - setPalette (palette); - - /// \todo improve error reporting; mark only the faulty part - } -} - -void CSVFilter::EditWidget::filterDataChanged (const QModelIndex& topLeft, - const QModelIndex& bottomRight) -{ - textChanged (text()); -} - -void CSVFilter::EditWidget::filterRowsRemoved (const QModelIndex& parent, int start, int end) -{ - textChanged (text()); -} - -void CSVFilter::EditWidget::filterRowsInserted (const QModelIndex& parent, int start, int end) -{ - textChanged (text()); -} - -void CSVFilter::EditWidget::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource, - Qt::DropAction action) -{ - const unsigned count = filterSource.size(); - bool multipleElements = false; - - switch (count) //setting multipleElements; - { - case 0: //empty - return; //nothing to do here - - case 1: //only single - multipleElements = false; - break; - - default: - multipleElements = true; - break; - } - - Qt::KeyboardModifiers key = QApplication::keyboardModifiers(); - QString oldContent (text()); - - bool replaceMode = false; - std::string orAnd; - - switch (key) //setting replaceMode and string used to glue expressions - { - case Qt::ShiftModifier: - orAnd = "!or("; - replaceMode = false; - break; - - case Qt::ControlModifier: - orAnd = "!and("; - replaceMode = false; - break; - - default: - replaceMode = true; - break; - } - - if (oldContent.isEmpty() || !oldContent.contains (QRegExp ("^!.*$", Qt::CaseInsensitive))) //if line edit is empty or it does not contain one shot filter go into replace mode - { - replaceMode = true; - } - - if (!replaceMode) - { - oldContent.remove ('!'); - } - - std::stringstream ss; - - if (multipleElements) - { - if (replaceMode) - { - ss<<"!or("; - } else { - ss << orAnd << oldContent.toStdString() << ','; - } - - for (unsigned i = 0; i < count; ++i) - { - ss<4) - { - clear(); - insert (QString::fromUtf8(ss.str().c_str())); - } -} - -std::string CSVFilter::EditWidget::generateFilter (std::pair< std::string, std::vector< std::string > >& seekedString) const -{ - const unsigned columns = seekedString.second.size(); - - bool multipleColumns = false; - switch (columns) - { - case 0: //empty - return ""; //no column to filter - - case 1: //one column to look for - multipleColumns = false; - break; - - default: - multipleColumns = true; - break; - } - - std::stringstream ss; - if (multipleColumns) - { - ss<<"or("; - for (unsigned i = 0; i < columns; ++i) - { - ss<<"string("<<'"'< +#include +#include + +#include "../../model/world/data.hpp" + +CSVFilter::EditWidget::EditWidget (CSMWorld::Data& data, QWidget *parent) +: QLineEdit (parent), mParser (data) +{ + mPalette = palette(); + connect (this, SIGNAL (textChanged (const QString&)), this, SLOT (textChanged (const QString&))); + + QAbstractItemModel *model = data.getTableModel (CSMWorld::UniversalId::Type_Filters); + + connect (model, SIGNAL (dataChanged (const QModelIndex &, const QModelIndex&)), + this, SLOT (filterDataChanged (const QModelIndex &, const QModelIndex&)), + Qt::QueuedConnection); + connect (model, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), + this, SLOT (filterRowsRemoved (const QModelIndex&, int, int)), + Qt::QueuedConnection); + connect (model, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (filterRowsInserted (const QModelIndex&, int, int)), + Qt::QueuedConnection); +} + +void CSVFilter::EditWidget::textChanged (const QString& text) +{ + if (mParser.parse (text.toUtf8().constData())) + { + setPalette (mPalette); + emit filterChanged (mParser.getFilter()); + } + else + { + QPalette palette (mPalette); + palette.setColor (QPalette::Text, Qt::red); + setPalette (palette); + + /// \todo improve error reporting; mark only the faulty part + } +} + +void CSVFilter::EditWidget::filterDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + textChanged (text()); +} + +void CSVFilter::EditWidget::filterRowsRemoved (const QModelIndex& parent, int start, int end) +{ + textChanged (text()); +} + +void CSVFilter::EditWidget::filterRowsInserted (const QModelIndex& parent, int start, int end) +{ + textChanged (text()); +} + +void CSVFilter::EditWidget::createFilterRequest (std::vector< std::pair< std::string, std::vector< std::string > > >& filterSource, + Qt::DropAction action) +{ + const unsigned count = filterSource.size(); + bool multipleElements = false; + + switch (count) //setting multipleElements; + { + case 0: //empty + return; //nothing to do here + + case 1: //only single + multipleElements = false; + break; + + default: + multipleElements = true; + break; + } + + Qt::KeyboardModifiers key = QApplication::keyboardModifiers(); + QString oldContent (text()); + + bool replaceMode = false; + std::string orAnd; + + switch (key) //setting replaceMode and string used to glue expressions + { + case Qt::ShiftModifier: + orAnd = "!or("; + replaceMode = false; + break; + + case Qt::ControlModifier: + orAnd = "!and("; + replaceMode = false; + break; + + default: + replaceMode = true; + break; + } + + if (oldContent.isEmpty() || !oldContent.contains (QRegExp ("^!.*$", Qt::CaseInsensitive))) //if line edit is empty or it does not contain one shot filter go into replace mode + { + replaceMode = true; + } + + if (!replaceMode) + { + oldContent.remove ('!'); + } + + std::stringstream ss; + + if (multipleElements) + { + if (replaceMode) + { + ss<<"!or("; + } else { + ss << orAnd << oldContent.toStdString() << ','; + } + + for (unsigned i = 0; i < count; ++i) + { + ss<4) + { + clear(); + insert (QString::fromUtf8(ss.str().c_str())); + } +} + +std::string CSVFilter::EditWidget::generateFilter (std::pair< std::string, std::vector< std::string > >& seekedString) const +{ + const unsigned columns = seekedString.second.size(); + + bool multipleColumns = false; + switch (columns) + { + case 0: //empty + return ""; //no column to filter + + case 1: //one column to look for + multipleColumns = false; + break; + + default: + multipleColumns = true; + break; + } + + std::stringstream ss; + if (multipleColumns) + { + ss<<"or("; + for (unsigned i = 0; i < columns; ++i) + { + ss<<"string("<<'"'< - -#include -#include -#include - -#include "../../model/filter/parser.hpp" -#include "../../model/filter/node.hpp" - -class QModelIndex; - -namespace CSMWorld -{ - class Data; -} - -namespace CSVFilter -{ - class EditWidget : public QLineEdit - { - Q_OBJECT - - CSMFilter::Parser mParser; - QPalette mPalette; - - public: - - EditWidget (CSMWorld::Data& data, QWidget *parent = 0); - - signals: - - void filterChanged (boost::shared_ptr filter); - - private: - std::string generateFilter(std::pair >& seekedString) const; - - private slots: - - void textChanged (const QString& text); - - void filterDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - - void filterRowsRemoved (const QModelIndex& parent, int start, int end); - - void filterRowsInserted (const QModelIndex& parent, int start, int end); - - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - - void useFilterRequest(const std::string& idOfFilter); - }; -} - -#endif +#ifndef CSV_FILTER_EDITWIDGET_H +#define CSV_FILTER_EDITWIDGET_H + +#include + +#include +#include +#include + +#include "../../model/filter/parser.hpp" +#include "../../model/filter/node.hpp" + +class QModelIndex; + +namespace CSMWorld +{ + class Data; +} + +namespace CSVFilter +{ + class EditWidget : public QLineEdit + { + Q_OBJECT + + CSMFilter::Parser mParser; + QPalette mPalette; + + public: + + EditWidget (CSMWorld::Data& data, QWidget *parent = 0); + + signals: + + void filterChanged (boost::shared_ptr filter); + + private: + std::string generateFilter(std::pair >& seekedString) const; + + private slots: + + void textChanged (const QString& text); + + void filterDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void filterRowsRemoved (const QModelIndex& parent, int start, int end); + + void filterRowsInserted (const QModelIndex& parent, int start, int end); + + void createFilterRequest(std::vector > >& filterSource, + Qt::DropAction action); + + void useFilterRequest(const std::string& idOfFilter); + }; +} + +#endif diff --git a/apps/opencs/view/filter/filterbox.cpp b/apps/opencs/view/filter/filterbox.cpp index 4954efefb..a33288025 100644 --- a/apps/opencs/view/filter/filterbox.cpp +++ b/apps/opencs/view/filter/filterbox.cpp @@ -1,50 +1,50 @@ - -#include "filterbox.hpp" - -#include -#include - -#include "recordfilterbox.hpp" - -#include - -CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent) -: QWidget (parent) -{ - QHBoxLayout *layout = new QHBoxLayout (this); - - layout->setContentsMargins (0, 0, 0, 0); - - RecordFilterBox *recordFilterBox = new RecordFilterBox (data, this); - - layout->addWidget (recordFilterBox); - - setLayout (layout); - - connect (recordFilterBox, - SIGNAL (filterChanged (boost::shared_ptr)), - this, SIGNAL (recordFilterChanged (boost::shared_ptr))); - - connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - recordFilterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); - - connect(this, SIGNAL(useFilterRequest(const std::string&)), recordFilterBox, SIGNAL(useFilterRequest(const std::string&))); - setAcceptDrops(true); -} - -void CSVFilter::FilterBox::dropEvent (QDropEvent* event) -{ - std::vector data = dynamic_cast (event->mimeData())->getData(); - - emit recordDropped(data, event->proposedAction()); -} - -void CSVFilter::FilterBox::dragEnterEvent (QDragEnterEvent* event) -{ - event->acceptProposedAction(); -} - -void CSVFilter::FilterBox::dragMoveEvent (QDragMoveEvent* event) -{ - event->accept(); -} + +#include "filterbox.hpp" + +#include +#include + +#include "recordfilterbox.hpp" + +#include + +CSVFilter::FilterBox::FilterBox (CSMWorld::Data& data, QWidget *parent) +: QWidget (parent) +{ + QHBoxLayout *layout = new QHBoxLayout (this); + + layout->setContentsMargins (0, 0, 0, 0); + + RecordFilterBox *recordFilterBox = new RecordFilterBox (data, this); + + layout->addWidget (recordFilterBox); + + setLayout (layout); + + connect (recordFilterBox, + SIGNAL (filterChanged (boost::shared_ptr)), + this, SIGNAL (recordFilterChanged (boost::shared_ptr))); + + connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), + recordFilterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); + + connect(this, SIGNAL(useFilterRequest(const std::string&)), recordFilterBox, SIGNAL(useFilterRequest(const std::string&))); + setAcceptDrops(true); +} + +void CSVFilter::FilterBox::dropEvent (QDropEvent* event) +{ + std::vector data = dynamic_cast (event->mimeData())->getData(); + + emit recordDropped(data, event->proposedAction()); +} + +void CSVFilter::FilterBox::dragEnterEvent (QDragEnterEvent* event) +{ + event->acceptProposedAction(); +} + +void CSVFilter::FilterBox::dragMoveEvent (QDragMoveEvent* event) +{ + event->accept(); +} diff --git a/apps/opencs/view/filter/filterbox.hpp b/apps/opencs/view/filter/filterbox.hpp index 263e89e73..5954035fc 100644 --- a/apps/opencs/view/filter/filterbox.hpp +++ b/apps/opencs/view/filter/filterbox.hpp @@ -1,45 +1,45 @@ -#ifndef CSV_FILTER_FILTERBOX_H -#define CSV_FILTER_FILTERBOX_H - -#include - -#include -#include - -#include "../../model/filter/node.hpp" -#include "../../model/world/universalid.hpp" - -namespace CSMWorld -{ - class Data; -} - -namespace CSVFilter -{ - class FilterBox : public QWidget - { - Q_OBJECT - - void dragEnterEvent (QDragEnterEvent* event); - - void dropEvent (QDropEvent* event); - - void dragMoveEvent(QDragMoveEvent *event); - - public: - - FilterBox (CSMWorld::Data& data, QWidget *parent = 0); - - signals: - - void recordFilterChanged (boost::shared_ptr filter); - void recordDropped (std::vector& types, Qt::DropAction action); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - void useFilterRequest(const std::string& idOfFilter); - }; - -} - -#endif - +#ifndef CSV_FILTER_FILTERBOX_H +#define CSV_FILTER_FILTERBOX_H + +#include + +#include +#include + +#include "../../model/filter/node.hpp" +#include "../../model/world/universalid.hpp" + +namespace CSMWorld +{ + class Data; +} + +namespace CSVFilter +{ + class FilterBox : public QWidget + { + Q_OBJECT + + void dragEnterEvent (QDragEnterEvent* event); + + void dropEvent (QDropEvent* event); + + void dragMoveEvent(QDragMoveEvent *event); + + public: + + FilterBox (CSMWorld::Data& data, QWidget *parent = 0); + + signals: + + void recordFilterChanged (boost::shared_ptr filter); + void recordDropped (std::vector& types, Qt::DropAction action); + void createFilterRequest(std::vector > >& filterSource, + Qt::DropAction action); + void useFilterRequest(const std::string& idOfFilter); + }; + +} + +#endif + diff --git a/apps/opencs/view/filter/recordfilterbox.cpp b/apps/opencs/view/filter/recordfilterbox.cpp index 59c083bd8..2a1a1407f 100644 --- a/apps/opencs/view/filter/recordfilterbox.cpp +++ b/apps/opencs/view/filter/recordfilterbox.cpp @@ -1,32 +1,32 @@ - -#include "recordfilterbox.hpp" - -#include -#include - -#include "editwidget.hpp" - -CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *parent) -: QWidget (parent) -{ - QHBoxLayout *layout = new QHBoxLayout (this); - - layout->setContentsMargins (0, 0, 0, 0); - - layout->addWidget (new QLabel ("Record Filter", this)); - - EditWidget *editWidget = new EditWidget (data, this); - - layout->addWidget (editWidget); - - setLayout (layout); - - connect ( - editWidget, SIGNAL (filterChanged (boost::shared_ptr)), - this, SIGNAL (filterChanged (boost::shared_ptr))); - - connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - editWidget, SLOT(createFilterRequest(std::vector > >&, Qt::DropAction))); - - connect(this, SIGNAL(useFilterRequest(const std::string&)), editWidget, SLOT(useFilterRequest(const std::string&))); -} + +#include "recordfilterbox.hpp" + +#include +#include + +#include "editwidget.hpp" + +CSVFilter::RecordFilterBox::RecordFilterBox (CSMWorld::Data& data, QWidget *parent) +: QWidget (parent) +{ + QHBoxLayout *layout = new QHBoxLayout (this); + + layout->setContentsMargins (0, 0, 0, 0); + + layout->addWidget (new QLabel ("Record Filter", this)); + + EditWidget *editWidget = new EditWidget (data, this); + + layout->addWidget (editWidget); + + setLayout (layout); + + connect ( + editWidget, SIGNAL (filterChanged (boost::shared_ptr)), + this, SIGNAL (filterChanged (boost::shared_ptr))); + + connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), + editWidget, SLOT(createFilterRequest(std::vector > >&, Qt::DropAction))); + + connect(this, SIGNAL(useFilterRequest(const std::string&)), editWidget, SLOT(useFilterRequest(const std::string&))); +} diff --git a/apps/opencs/view/filter/recordfilterbox.hpp b/apps/opencs/view/filter/recordfilterbox.hpp index fd384a32b..fa5c9c3c2 100644 --- a/apps/opencs/view/filter/recordfilterbox.hpp +++ b/apps/opencs/view/filter/recordfilterbox.hpp @@ -1,38 +1,38 @@ -#ifndef CSV_FILTER_RECORDFILTERBOX_H -#define CSV_FILTER_RECORDFILTERBOX_H - -#include - -#include -#include - -#include - -#include "../../model/filter/node.hpp" - -namespace CSMWorld -{ - class Data; -} - -namespace CSVFilter -{ - class RecordFilterBox : public QWidget - { - Q_OBJECT - - public: - - RecordFilterBox (CSMWorld::Data& data, QWidget *parent = 0); - - signals: - - void filterChanged (boost::shared_ptr filter); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - void useFilterRequest(const std::string& idOfFilter); - }; - -} - +#ifndef CSV_FILTER_RECORDFILTERBOX_H +#define CSV_FILTER_RECORDFILTERBOX_H + +#include + +#include +#include + +#include + +#include "../../model/filter/node.hpp" + +namespace CSMWorld +{ + class Data; +} + +namespace CSVFilter +{ + class RecordFilterBox : public QWidget + { + Q_OBJECT + + public: + + RecordFilterBox (CSMWorld::Data& data, QWidget *parent = 0); + + signals: + + void filterChanged (boost::shared_ptr filter); + void createFilterRequest(std::vector > >& filterSource, + Qt::DropAction action); + void useFilterRequest(const std::string& idOfFilter); + }; + +} + #endif \ No newline at end of file diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index bc67c68b5..edf3bc6de 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -1,546 +1,546 @@ - -#include "table.hpp" - -#include - -#include -#include -#include -#include -#include -#include - -#include "../../model/world/data.hpp" -#include "../../model/world/commands.hpp" -#include "../../model/world/idtableproxymodel.hpp" -#include "../../model/world/idtable.hpp" -#include "../../model/world/record.hpp" -#include "../../model/world/columns.hpp" -#include "../../model/world/tablemimedata.hpp" -#include "../../model/world/tablemimedata.hpp" - -#include "recordstatusdelegate.hpp" -#include "util.hpp" - -void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) -{ - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - QMenu menu (this); - - /// \todo add menu items for select all and clear selection - - if (!mEditLock) - { - if (selectedRows.size()==1) - { - menu.addAction (mEditAction); - if (mCreateAction) - menu.addAction(mCloneAction); - } - - if (mCreateAction) - menu.addAction (mCreateAction); - - /// \todo Reverting temporarily disabled on tables that support reordering, because - /// revert logic currently can not handle reordering. - if (mModel->getReordering()==CSMWorld::IdTable::Reordering_None) - if (listRevertableSelectedIds().size()>0) - menu.addAction (mRevertAction); - - if (listDeletableSelectedIds().size()>0) - menu.addAction (mDeleteAction); - - if (mModel->getReordering()==CSMWorld::IdTable::Reordering_WithinTopic) - { - /// \todo allow reordering of multiple rows - if (selectedRows.size()==1) - { - int row =selectedRows.begin()->row(); - - int column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Topic); - - if (column==-1) - column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Journal); - - if (column!=-1) - { - if (row>0 && mProxyModel->data (mProxyModel->index (row, column))== - mProxyModel->data (mProxyModel->index (row-1, column))) - { - menu.addAction (mMoveUpAction); - } - - if (rowrowCount()-1 && mProxyModel->data (mProxyModel->index (row, column))== - mProxyModel->data (mProxyModel->index (row+1, column))) - { - menu.addAction (mMoveDownAction); - } - } - } - } - } - - menu.exec (event->globalPos()); -} - -std::vector CSVWorld::Table::listRevertableSelectedIds() const -{ - std::vector revertableIds; - - if (mProxyModel->columnCount()>0) - { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) - { - QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); - - CSMWorld::RecordBase::State state = - static_cast ( - mModel->data (mModel->index (index.row(), 1)).toInt()); - - if (state!=CSMWorld::RecordBase::State_BaseOnly) - { - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - - std::string id = mModel->data (mModel->index (index.row(), columnIndex)). - toString().toUtf8().constData(); - - revertableIds.push_back (id); - } - } - } - - return revertableIds; -} - -std::vector CSVWorld::Table::listDeletableSelectedIds() const -{ - std::vector deletableIds; - - if (mProxyModel->columnCount()>0) - { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) - { - QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); - - // check record state - CSMWorld::RecordBase::State state = - static_cast ( - mModel->data (mModel->index (index.row(), 1)).toInt()); - - if (state==CSMWorld::RecordBase::State_Deleted) - continue; - - // check other columns (only relevant for a subset of the tables) - int dialogueTypeIndex = - mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_DialogueType); - - if (dialogueTypeIndex!=-1) - { - int type = mModel->data (mModel->index (index.row(), dialogueTypeIndex)).toInt(); - - if (type!=ESM::Dialogue::Topic && type!=ESM::Dialogue::Journal) - continue; - } - - // add the id to the collection - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - - std::string id = mModel->data (mModel->index (index.row(), columnIndex)). - toString().toUtf8().constData(); - - deletableIds.push_back (id); - } - } - - return deletableIds; -} - -CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, - bool createAndDelete, bool sorting, const CSMDoc::Document& document) - : mUndoStack (undoStack), mCreateAction (0), mCloneAction(0), mEditLock (false), mRecordStatusDisplay (0), mDocument(document) -{ - mModel = &dynamic_cast (*data.getTableModel (id)); - - mProxyModel = new CSMWorld::IdTableProxyModel (this); - mProxyModel->setSourceModel (mModel); - - setModel (mProxyModel); - horizontalHeader()->setResizeMode (QHeaderView::Interactive); - verticalHeader()->hide(); - setSortingEnabled (sorting); - setSelectionBehavior (QAbstractItemView::SelectRows); - setSelectionMode (QAbstractItemView::ExtendedSelection); - - int columns = mModel->columnCount(); - - for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); - - if (flags & CSMWorld::ColumnBase::Flag_Table) - { - CSMWorld::ColumnBase::Display display = static_cast ( - mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - - CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate (display, - undoStack, this); - - mDelegates.push_back (delegate); - setItemDelegateForColumn (i, delegate); - } - else - hideColumn (i); - } - - mEditAction = new QAction (tr ("Edit Record"), this); - connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord())); - addAction (mEditAction); - - if (createAndDelete) - { - mCreateAction = new QAction (tr ("Add Record"), this); - connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest())); - addAction (mCreateAction); - - mCloneAction = new QAction (tr ("Clone Record"), this); - connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord())); - addAction(mCloneAction); - } - - mRevertAction = new QAction (tr ("Revert Record"), this); - connect (mRevertAction, SIGNAL (triggered()), this, SLOT (revertRecord())); - addAction (mRevertAction); - - mDeleteAction = new QAction (tr ("Delete Record"), this); - connect (mDeleteAction, SIGNAL (triggered()), this, SLOT (deleteRecord())); - addAction (mDeleteAction); - - mMoveUpAction = new QAction (tr ("Move Up"), this); - connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord())); - addAction (mMoveUpAction); - - mMoveDownAction = new QAction (tr ("Move Down"), this); - connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); - addAction (mMoveDownAction); - - connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), - this, SLOT (tableSizeUpdate())); - - /// \note This signal could instead be connected to a slot that filters out changes not affecting - /// the records status column (for permanence reasons) - connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), - this, SLOT (tableSizeUpdate())); - - connect (selectionModel(), SIGNAL (selectionChanged (const QItemSelection&, const QItemSelection&)), - this, SLOT (selectionSizeUpdate ())); - - setAcceptDrops(true); -} - -void CSVWorld::Table::setEditLock (bool locked) -{ - for (std::vector::iterator iter (mDelegates.begin()); iter!=mDelegates.end(); ++iter) - (*iter)->setEditLock (locked); - - mEditLock = locked; -} - -CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const -{ - return CSMWorld::UniversalId ( - static_cast (mProxyModel->data (mProxyModel->index (row, 2)).toInt()), - mProxyModel->data (mProxyModel->index (row, 0)).toString().toStdString()); -} - -void CSVWorld::Table::revertRecord() -{ - if (!mEditLock) - { - std::vector revertableIds = listRevertableSelectedIds(); - - if (revertableIds.size()>0) - { - if (revertableIds.size()>1) - mUndoStack.beginMacro (tr ("Revert multiple records")); - - for (std::vector::const_iterator iter (revertableIds.begin()); iter!=revertableIds.end(); ++iter) - mUndoStack.push (new CSMWorld::RevertCommand (*mModel, *iter)); - - if (revertableIds.size()>1) - mUndoStack.endMacro(); - } - } -} - -void CSVWorld::Table::deleteRecord() -{ - if (!mEditLock) - { - std::vector deletableIds = listDeletableSelectedIds(); - - if (deletableIds.size()>0) - { - if (deletableIds.size()>1) - mUndoStack.beginMacro (tr ("Delete multiple records")); - - for (std::vector::const_iterator iter (deletableIds.begin()); iter!=deletableIds.end(); ++iter) - mUndoStack.push (new CSMWorld::DeleteCommand (*mModel, *iter)); - - if (deletableIds.size()>1) - mUndoStack.endMacro(); - } - } -} - -void CSVWorld::Table::editRecord() -{ - if (!mEditLock) - { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - if (selectedRows.size()==1) - emit editRequest (selectedRows.begin()->row()); - } -} - -void CSVWorld::Table::cloneRecord() -{ - if (!mEditLock) - { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - const CSMWorld::UniversalId& toClone = getUniversalId(selectedRows.begin()->row()); - if (selectedRows.size()==1 && !mModel->getRecord(toClone.getId()).isDeleted()) - { - emit cloneRequest (toClone); - } - } -} - -void CSVWorld::Table::moveUpRecord() -{ - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - if (selectedRows.size()==1) - { - int row2 =selectedRows.begin()->row(); - - if (row2>0) - { - int row = row2-1; - - row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); - row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); - - if (row2<=row) - throw std::runtime_error ("Inconsistent row order"); - - std::vector newOrder (row2-row+1); - newOrder[0] = row2-row; - newOrder[row2-row] = 0; - for (int i=1; iselectedRows(); - - if (selectedRows.size()==1) - { - int row =selectedRows.begin()->row(); - - if (rowrowCount()-1) - { - int row2 = row+1; - - row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); - row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); - - if (row2<=row) - throw std::runtime_error ("Inconsistent row order"); - - std::vector newOrder (row2-row+1); - newOrder[0] = row2-row; - newOrder[row2-row] = 0; - for (int i=1; icolumnCount(); - - for (int i=0; i (*delegate). - updateEditorSetting (settingName, settingValue)) - emit dataChanged (mModel->index (0, i), mModel->index (mModel->rowCount()-1, i)); -} - -void CSVWorld::Table::tableSizeUpdate() -{ - int size = 0; - int deleted = 0; - int modified = 0; - - if (mProxyModel->columnCount()>0) - { - int rows = mProxyModel->rowCount(); - - for (int i=0; imapToSource (mProxyModel->index (i, 0)); - - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); - int state = mModel->data (mModel->index (index.row(), columnIndex)).toInt(); - - switch (state) - { - case CSMWorld::RecordBase::State_BaseOnly: ++size; break; - case CSMWorld::RecordBase::State_Modified: ++size; ++modified; break; - case CSMWorld::RecordBase::State_ModifiedOnly: ++size; ++modified; break; - case CSMWorld::RecordBase:: State_Deleted: ++deleted; ++modified; break; - } - } - } - - tableSizeChanged (size, deleted, modified); -} - -void CSVWorld::Table::selectionSizeUpdate() -{ - selectionSizeChanged (selectionModel()->selectedRows().size()); -} - -void CSVWorld::Table::requestFocus (const std::string& id) -{ - QModelIndex index = mProxyModel->getModelIndex (id, 0); - - if (index.isValid()) - scrollTo (index, QAbstractItemView::PositionAtTop); -} - -void CSVWorld::Table::recordFilterChanged (boost::shared_ptr filter) -{ - mProxyModel->setFilter (filter); -} - -void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event) -{ - if (event->buttons() & Qt::LeftButton) - { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - if (selectedRows.size() == 0) - { - return; - } - - QDrag* drag = new QDrag (this); - CSMWorld::TableMimeData* mime = NULL; - - if (selectedRows.size() == 1) - { - mime = new CSMWorld::TableMimeData (getUniversalId (selectedRows.begin()->row()), mDocument); - } - else - { - std::vector idToDrag; - - foreach (QModelIndex it, selectedRows) //I had a dream. Dream where you could use C++11 in OpenMW. - { - idToDrag.push_back (getUniversalId (it.row())); - } - - mime = new CSMWorld::TableMimeData (idToDrag, mDocument); - } - - drag->setMimeData (mime); - drag->setPixmap (QString::fromStdString (mime->getIcon())); - - Qt::DropActions action = Qt::IgnoreAction; - switch (QApplication::keyboardModifiers()) - { - case Qt::ControlModifier: - action = Qt::CopyAction; - break; - - case Qt::ShiftModifier: - action = Qt::MoveAction; - break; - } - - drag->exec(action); - } - -} - -void CSVWorld::Table::dragEnterEvent(QDragEnterEvent *event) -{ - event->acceptProposedAction(); -} - -void CSVWorld::Table::dropEvent(QDropEvent *event) -{ - QModelIndex index = indexAt (event->pos()); - - if (!index.isValid()) - { - return; - } - - const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); - if (mime->fromDocument (mDocument)) - { - CSMWorld::ColumnBase::Display display = static_cast - (mModel->headerData (index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - - if (mime->holdsType (display)) - { - CSMWorld::UniversalId record (mime->returnMatching (display)); - - std::auto_ptr command (new CSMWorld::ModifyCommand - (*mProxyModel, index, QVariant (QString::fromUtf8 (record.getId().c_str())))); - - mUndoStack.push (command.release()); - } - } //TODO handle drops from different document -} - -void CSVWorld::Table::dragMoveEvent(QDragMoveEvent *event) -{ - event->accept(); -} - -std::vector CSVWorld::Table::getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const -{ - const int count = mModel->columnCount(); - - std::vector titles; - for (int i = 0; i < count; ++i) - { - CSMWorld::ColumnBase::Display columndisplay = static_cast - (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - - if (display == columndisplay) - { - titles.push_back(mModel->headerData (i, Qt::Horizontal).toString().toStdString()); - } - } - return titles; + +#include "table.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include "../../model/world/data.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/idtableproxymodel.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/record.hpp" +#include "../../model/world/columns.hpp" +#include "../../model/world/tablemimedata.hpp" +#include "../../model/world/tablemimedata.hpp" + +#include "recordstatusdelegate.hpp" +#include "util.hpp" + +void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + QMenu menu (this); + + /// \todo add menu items for select all and clear selection + + if (!mEditLock) + { + if (selectedRows.size()==1) + { + menu.addAction (mEditAction); + if (mCreateAction) + menu.addAction(mCloneAction); + } + + if (mCreateAction) + menu.addAction (mCreateAction); + + /// \todo Reverting temporarily disabled on tables that support reordering, because + /// revert logic currently can not handle reordering. + if (mModel->getReordering()==CSMWorld::IdTable::Reordering_None) + if (listRevertableSelectedIds().size()>0) + menu.addAction (mRevertAction); + + if (listDeletableSelectedIds().size()>0) + menu.addAction (mDeleteAction); + + if (mModel->getReordering()==CSMWorld::IdTable::Reordering_WithinTopic) + { + /// \todo allow reordering of multiple rows + if (selectedRows.size()==1) + { + int row =selectedRows.begin()->row(); + + int column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Topic); + + if (column==-1) + column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Journal); + + if (column!=-1) + { + if (row>0 && mProxyModel->data (mProxyModel->index (row, column))== + mProxyModel->data (mProxyModel->index (row-1, column))) + { + menu.addAction (mMoveUpAction); + } + + if (rowrowCount()-1 && mProxyModel->data (mProxyModel->index (row, column))== + mProxyModel->data (mProxyModel->index (row+1, column))) + { + menu.addAction (mMoveDownAction); + } + } + } + } + } + + menu.exec (event->globalPos()); +} + +std::vector CSVWorld::Table::listRevertableSelectedIds() const +{ + std::vector revertableIds; + + if (mProxyModel->columnCount()>0) + { + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + ++iter) + { + QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); + + CSMWorld::RecordBase::State state = + static_cast ( + mModel->data (mModel->index (index.row(), 1)).toInt()); + + if (state!=CSMWorld::RecordBase::State_BaseOnly) + { + int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + std::string id = mModel->data (mModel->index (index.row(), columnIndex)). + toString().toUtf8().constData(); + + revertableIds.push_back (id); + } + } + } + + return revertableIds; +} + +std::vector CSVWorld::Table::listDeletableSelectedIds() const +{ + std::vector deletableIds; + + if (mProxyModel->columnCount()>0) + { + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + ++iter) + { + QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); + + // check record state + CSMWorld::RecordBase::State state = + static_cast ( + mModel->data (mModel->index (index.row(), 1)).toInt()); + + if (state==CSMWorld::RecordBase::State_Deleted) + continue; + + // check other columns (only relevant for a subset of the tables) + int dialogueTypeIndex = + mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_DialogueType); + + if (dialogueTypeIndex!=-1) + { + int type = mModel->data (mModel->index (index.row(), dialogueTypeIndex)).toInt(); + + if (type!=ESM::Dialogue::Topic && type!=ESM::Dialogue::Journal) + continue; + } + + // add the id to the collection + int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + std::string id = mModel->data (mModel->index (index.row(), columnIndex)). + toString().toUtf8().constData(); + + deletableIds.push_back (id); + } + } + + return deletableIds; +} + +CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, + bool createAndDelete, bool sorting, const CSMDoc::Document& document) + : mUndoStack (undoStack), mCreateAction (0), mCloneAction(0), mEditLock (false), mRecordStatusDisplay (0), mDocument(document) +{ + mModel = &dynamic_cast (*data.getTableModel (id)); + + mProxyModel = new CSMWorld::IdTableProxyModel (this); + mProxyModel->setSourceModel (mModel); + + setModel (mProxyModel); + horizontalHeader()->setResizeMode (QHeaderView::Interactive); + verticalHeader()->hide(); + setSortingEnabled (sorting); + setSelectionBehavior (QAbstractItemView::SelectRows); + setSelectionMode (QAbstractItemView::ExtendedSelection); + + int columns = mModel->columnCount(); + + for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); + + if (flags & CSMWorld::ColumnBase::Flag_Table) + { + CSMWorld::ColumnBase::Display display = static_cast ( + mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + + CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate (display, + undoStack, this); + + mDelegates.push_back (delegate); + setItemDelegateForColumn (i, delegate); + } + else + hideColumn (i); + } + + mEditAction = new QAction (tr ("Edit Record"), this); + connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord())); + addAction (mEditAction); + + if (createAndDelete) + { + mCreateAction = new QAction (tr ("Add Record"), this); + connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest())); + addAction (mCreateAction); + + mCloneAction = new QAction (tr ("Clone Record"), this); + connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord())); + addAction(mCloneAction); + } + + mRevertAction = new QAction (tr ("Revert Record"), this); + connect (mRevertAction, SIGNAL (triggered()), this, SLOT (revertRecord())); + addAction (mRevertAction); + + mDeleteAction = new QAction (tr ("Delete Record"), this); + connect (mDeleteAction, SIGNAL (triggered()), this, SLOT (deleteRecord())); + addAction (mDeleteAction); + + mMoveUpAction = new QAction (tr ("Move Up"), this); + connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord())); + addAction (mMoveUpAction); + + mMoveDownAction = new QAction (tr ("Move Down"), this); + connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); + addAction (mMoveDownAction); + + connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (tableSizeUpdate())); + + /// \note This signal could instead be connected to a slot that filters out changes not affecting + /// the records status column (for permanence reasons) + connect (mProxyModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (tableSizeUpdate())); + + connect (selectionModel(), SIGNAL (selectionChanged (const QItemSelection&, const QItemSelection&)), + this, SLOT (selectionSizeUpdate ())); + + setAcceptDrops(true); +} + +void CSVWorld::Table::setEditLock (bool locked) +{ + for (std::vector::iterator iter (mDelegates.begin()); iter!=mDelegates.end(); ++iter) + (*iter)->setEditLock (locked); + + mEditLock = locked; +} + +CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const +{ + return CSMWorld::UniversalId ( + static_cast (mProxyModel->data (mProxyModel->index (row, 2)).toInt()), + mProxyModel->data (mProxyModel->index (row, 0)).toString().toStdString()); +} + +void CSVWorld::Table::revertRecord() +{ + if (!mEditLock) + { + std::vector revertableIds = listRevertableSelectedIds(); + + if (revertableIds.size()>0) + { + if (revertableIds.size()>1) + mUndoStack.beginMacro (tr ("Revert multiple records")); + + for (std::vector::const_iterator iter (revertableIds.begin()); iter!=revertableIds.end(); ++iter) + mUndoStack.push (new CSMWorld::RevertCommand (*mModel, *iter)); + + if (revertableIds.size()>1) + mUndoStack.endMacro(); + } + } +} + +void CSVWorld::Table::deleteRecord() +{ + if (!mEditLock) + { + std::vector deletableIds = listDeletableSelectedIds(); + + if (deletableIds.size()>0) + { + if (deletableIds.size()>1) + mUndoStack.beginMacro (tr ("Delete multiple records")); + + for (std::vector::const_iterator iter (deletableIds.begin()); iter!=deletableIds.end(); ++iter) + mUndoStack.push (new CSMWorld::DeleteCommand (*mModel, *iter)); + + if (deletableIds.size()>1) + mUndoStack.endMacro(); + } + } +} + +void CSVWorld::Table::editRecord() +{ + if (!mEditLock) + { + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + if (selectedRows.size()==1) + emit editRequest (selectedRows.begin()->row()); + } +} + +void CSVWorld::Table::cloneRecord() +{ + if (!mEditLock) + { + QModelIndexList selectedRows = selectionModel()->selectedRows(); + const CSMWorld::UniversalId& toClone = getUniversalId(selectedRows.begin()->row()); + if (selectedRows.size()==1 && !mModel->getRecord(toClone.getId()).isDeleted()) + { + emit cloneRequest (toClone); + } + } +} + +void CSVWorld::Table::moveUpRecord() +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + if (selectedRows.size()==1) + { + int row2 =selectedRows.begin()->row(); + + if (row2>0) + { + int row = row2-1; + + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); + + if (row2<=row) + throw std::runtime_error ("Inconsistent row order"); + + std::vector newOrder (row2-row+1); + newOrder[0] = row2-row; + newOrder[row2-row] = 0; + for (int i=1; iselectedRows(); + + if (selectedRows.size()==1) + { + int row =selectedRows.begin()->row(); + + if (rowrowCount()-1) + { + int row2 = row+1; + + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); + + if (row2<=row) + throw std::runtime_error ("Inconsistent row order"); + + std::vector newOrder (row2-row+1); + newOrder[0] = row2-row; + newOrder[row2-row] = 0; + for (int i=1; icolumnCount(); + + for (int i=0; i (*delegate). + updateEditorSetting (settingName, settingValue)) + emit dataChanged (mModel->index (0, i), mModel->index (mModel->rowCount()-1, i)); +} + +void CSVWorld::Table::tableSizeUpdate() +{ + int size = 0; + int deleted = 0; + int modified = 0; + + if (mProxyModel->columnCount()>0) + { + int rows = mProxyModel->rowCount(); + + for (int i=0; imapToSource (mProxyModel->index (i, 0)); + + int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + int state = mModel->data (mModel->index (index.row(), columnIndex)).toInt(); + + switch (state) + { + case CSMWorld::RecordBase::State_BaseOnly: ++size; break; + case CSMWorld::RecordBase::State_Modified: ++size; ++modified; break; + case CSMWorld::RecordBase::State_ModifiedOnly: ++size; ++modified; break; + case CSMWorld::RecordBase:: State_Deleted: ++deleted; ++modified; break; + } + } + } + + tableSizeChanged (size, deleted, modified); +} + +void CSVWorld::Table::selectionSizeUpdate() +{ + selectionSizeChanged (selectionModel()->selectedRows().size()); +} + +void CSVWorld::Table::requestFocus (const std::string& id) +{ + QModelIndex index = mProxyModel->getModelIndex (id, 0); + + if (index.isValid()) + scrollTo (index, QAbstractItemView::PositionAtTop); +} + +void CSVWorld::Table::recordFilterChanged (boost::shared_ptr filter) +{ + mProxyModel->setFilter (filter); +} + +void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event) +{ + if (event->buttons() & Qt::LeftButton) + { + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + if (selectedRows.size() == 0) + { + return; + } + + QDrag* drag = new QDrag (this); + CSMWorld::TableMimeData* mime = NULL; + + if (selectedRows.size() == 1) + { + mime = new CSMWorld::TableMimeData (getUniversalId (selectedRows.begin()->row()), mDocument); + } + else + { + std::vector idToDrag; + + foreach (QModelIndex it, selectedRows) //I had a dream. Dream where you could use C++11 in OpenMW. + { + idToDrag.push_back (getUniversalId (it.row())); + } + + mime = new CSMWorld::TableMimeData (idToDrag, mDocument); + } + + drag->setMimeData (mime); + drag->setPixmap (QString::fromStdString (mime->getIcon())); + + Qt::DropActions action = Qt::IgnoreAction; + switch (QApplication::keyboardModifiers()) + { + case Qt::ControlModifier: + action = Qt::CopyAction; + break; + + case Qt::ShiftModifier: + action = Qt::MoveAction; + break; + } + + drag->exec(action); + } + +} + +void CSVWorld::Table::dragEnterEvent(QDragEnterEvent *event) +{ + event->acceptProposedAction(); +} + +void CSVWorld::Table::dropEvent(QDropEvent *event) +{ + QModelIndex index = indexAt (event->pos()); + + if (!index.isValid()) + { + return; + } + + const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); + if (mime->fromDocument (mDocument)) + { + CSMWorld::ColumnBase::Display display = static_cast + (mModel->headerData (index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + + if (mime->holdsType (display)) + { + CSMWorld::UniversalId record (mime->returnMatching (display)); + + std::auto_ptr command (new CSMWorld::ModifyCommand + (*mProxyModel, index, QVariant (QString::fromUtf8 (record.getId().c_str())))); + + mUndoStack.push (command.release()); + } + } //TODO handle drops from different document +} + +void CSVWorld::Table::dragMoveEvent(QDragMoveEvent *event) +{ + event->accept(); +} + +std::vector CSVWorld::Table::getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const +{ + const int count = mModel->columnCount(); + + std::vector titles; + for (int i = 0; i < count; ++i) + { + CSMWorld::ColumnBase::Display columndisplay = static_cast + (mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + + if (display == columndisplay) + { + titles.push_back(mModel->headerData (i, Qt::Horizontal).toString().toStdString()); + } + } + return titles; } \ No newline at end of file diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 04733a53e..615a31b4d 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -1,127 +1,127 @@ -#ifndef CSV_WORLD_TABLE_H -#define CSV_WORLD_TABLE_H - -#include -#include - -#include -#include - -#include "../../model/filter/node.hpp" -#include "../../model/world/columnbase.hpp" - -namespace CSMDoc { - class Document; -} - -class QUndoStack; -class QAction; - -namespace CSMWorld -{ - class Data; - class UniversalId; - class IdTableProxyModel; - class IdTable; -} - -namespace CSVWorld -{ - class CommandDelegate; - - ///< Table widget - class Table : public QTableView - { - Q_OBJECT - - std::vector mDelegates; - QUndoStack& mUndoStack; - QAction *mEditAction; - QAction *mCreateAction; - QAction *mCloneAction; - QAction *mRevertAction; - QAction *mDeleteAction; - QAction *mMoveUpAction; - QAction *mMoveDownAction; - CSMWorld::IdTableProxyModel *mProxyModel; - CSMWorld::IdTable *mModel; - bool mEditLock; - int mRecordStatusDisplay; - - /// \brief This variable is used exclusivly for checking if dropEvents came from the same document. Most likely you - /// should NOT use it for anything else. - const CSMDoc::Document& mDocument; - - private: - - void contextMenuEvent (QContextMenuEvent *event); - - std::vector listRevertableSelectedIds() const; - - std::vector listDeletableSelectedIds() const; - - void mouseMoveEvent(QMouseEvent *event); - - void dragEnterEvent(QDragEnterEvent *event); - - void dragMoveEvent(QDragMoveEvent *event); - - void dropEvent(QDropEvent *event); - - public: - - Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete, - bool sorting, const CSMDoc::Document& document); - - ///< \param createAndDelete Allow creation and deletion of records. - /// \param sorting Allow changing order of rows in the view via column headers. - - void setEditLock (bool locked); - - CSMWorld::UniversalId getUniversalId (int row) const; - - void updateEditorSetting (const QString &settingName, const QString &settingValue); - - std::vector getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const; - - signals: - - void editRequest (int row); - - void selectionSizeChanged (int size); - - void tableSizeChanged (int size, int deleted, int modified); - ///< \param size Number of not deleted records - /// \param deleted Number of deleted records - /// \param modified Number of added and modified records - - void createRequest(); - void cloneRequest(const CSMWorld::UniversalId&); - - private slots: - - void revertRecord(); - - void deleteRecord(); - - void editRecord(); - - void cloneRecord(); - - void moveUpRecord(); - - void moveDownRecord(); - - public slots: - - void tableSizeUpdate(); - - void selectionSizeUpdate(); - - void requestFocus (const std::string& id); - - void recordFilterChanged (boost::shared_ptr filter); - }; -} - -#endif +#ifndef CSV_WORLD_TABLE_H +#define CSV_WORLD_TABLE_H + +#include +#include + +#include +#include + +#include "../../model/filter/node.hpp" +#include "../../model/world/columnbase.hpp" + +namespace CSMDoc { + class Document; +} + +class QUndoStack; +class QAction; + +namespace CSMWorld +{ + class Data; + class UniversalId; + class IdTableProxyModel; + class IdTable; +} + +namespace CSVWorld +{ + class CommandDelegate; + + ///< Table widget + class Table : public QTableView + { + Q_OBJECT + + std::vector mDelegates; + QUndoStack& mUndoStack; + QAction *mEditAction; + QAction *mCreateAction; + QAction *mCloneAction; + QAction *mRevertAction; + QAction *mDeleteAction; + QAction *mMoveUpAction; + QAction *mMoveDownAction; + CSMWorld::IdTableProxyModel *mProxyModel; + CSMWorld::IdTable *mModel; + bool mEditLock; + int mRecordStatusDisplay; + + /// \brief This variable is used exclusivly for checking if dropEvents came from the same document. Most likely you + /// should NOT use it for anything else. + const CSMDoc::Document& mDocument; + + private: + + void contextMenuEvent (QContextMenuEvent *event); + + std::vector listRevertableSelectedIds() const; + + std::vector listDeletableSelectedIds() const; + + void mouseMoveEvent(QMouseEvent *event); + + void dragEnterEvent(QDragEnterEvent *event); + + void dragMoveEvent(QDragMoveEvent *event); + + void dropEvent(QDropEvent *event); + + public: + + Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete, + bool sorting, const CSMDoc::Document& document); + + ///< \param createAndDelete Allow creation and deletion of records. + /// \param sorting Allow changing order of rows in the view via column headers. + + void setEditLock (bool locked); + + CSMWorld::UniversalId getUniversalId (int row) const; + + void updateEditorSetting (const QString &settingName, const QString &settingValue); + + std::vector getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const; + + signals: + + void editRequest (int row); + + void selectionSizeChanged (int size); + + void tableSizeChanged (int size, int deleted, int modified); + ///< \param size Number of not deleted records + /// \param deleted Number of deleted records + /// \param modified Number of added and modified records + + void createRequest(); + void cloneRequest(const CSMWorld::UniversalId&); + + private slots: + + void revertRecord(); + + void deleteRecord(); + + void editRecord(); + + void cloneRecord(); + + void moveUpRecord(); + + void moveDownRecord(); + + public slots: + + void tableSizeUpdate(); + + void selectionSizeUpdate(); + + void requestFocus (const std::string& id); + + void recordFilterChanged (boost::shared_ptr filter); + }; +} + +#endif diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 782ccfd24..e330d4775 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -1,132 +1,132 @@ - -#include "tablesubview.hpp" - -#include -#include - -#include "../../model/doc/document.hpp" -#include "../../model/world/tablemimedata.hpp" - -#include "../filter/filterbox.hpp" -#include "table.hpp" -#include "tablebottombox.hpp" -#include "creator.hpp" - -CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory, bool sorting) -: SubView (id) -{ - QVBoxLayout *layout = new QVBoxLayout; - - layout->setContentsMargins (QMargins (0, 0, 0, 0)); - - layout->addWidget (mBottom = - new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this), 0); - - layout->insertWidget (0, mTable = - new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete(), sorting, document), 2); - - CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); - - layout->insertWidget (0, filterBox); - - QWidget *widget = new QWidget; - - widget->setLayout (layout); - - setWidget (widget); - - connect (mTable, SIGNAL (editRequest (int)), this, SLOT (editRequest (int))); - - connect (mTable, SIGNAL (selectionSizeChanged (int)), - mBottom, SLOT (selectionSizeChanged (int))); - connect (mTable, SIGNAL (tableSizeChanged (int, int, int)), - mBottom, SLOT (tableSizeChanged (int, int, int))); - - mTable->tableSizeUpdate(); - mTable->selectionSizeUpdate(); - mTable->viewport()->installEventFilter(this); - mBottom->installEventFilter(this); - filterBox->installEventFilter(this); - - if (mBottom->canCreateAndDelete()) - { - connect (mTable, SIGNAL (createRequest()), mBottom, SLOT (createRequest())); - - connect (mTable, SIGNAL (cloneRequest(const CSMWorld::UniversalId&)), this, - SLOT(cloneRequest(const CSMWorld::UniversalId&))); - - connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)), - mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type))); - } - connect (mBottom, SIGNAL (requestFocus (const std::string&)), - mTable, SLOT (requestFocus (const std::string&))); - - connect (filterBox, - SIGNAL (recordFilterChanged (boost::shared_ptr)), - mTable, SLOT (recordFilterChanged (boost::shared_ptr))); - - connect(filterBox, SIGNAL(recordDropped(std::vector&, Qt::DropAction)), - this, SLOT(createFilterRequest(std::vector&, Qt::DropAction))); - - connect(this, SIGNAL(useFilterRequest(const std::string&)), filterBox, SIGNAL(useFilterRequest(const std::string&))); - - connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), - filterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); -} - -void CSVWorld::TableSubView::setEditLock (bool locked) -{ - mTable->setEditLock (locked); - mBottom->setEditLock (locked); -} - -void CSVWorld::TableSubView::editRequest (int row) -{ - focusId (mTable->getUniversalId (row)); -} - -void CSVWorld::TableSubView::updateEditorSetting(const QString &settingName, const QString &settingValue) -{ - mTable->updateEditorSetting(settingName, settingValue); -} - -void CSVWorld::TableSubView::setStatusBar (bool show) -{ - mBottom->setStatusBar (show); -} - -void CSVWorld::TableSubView::cloneRequest(const CSMWorld::UniversalId& toClone) -{ - emit cloneRequest(toClone.getId(), toClone.getType()); -} - -void CSVWorld::TableSubView::createFilterRequest (std::vector< CSMWorld::UniversalId>& types, Qt::DropAction action) -{ - std::vector > > filterSource; - - for (std::vector::iterator it = types.begin(); it != types.end(); ++it) - { - std::pair > pair( //splited long line - std::make_pair(it->getId(), mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(it->getType())))); - - filterSource.push_back(pair); - } - emit createFilterRequest(filterSource, action); -} - -bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) -{ - if (event->type() == QEvent::Drop) - { - QDropEvent* drop = dynamic_cast(event); - const CSMWorld::TableMimeData* data = dynamic_cast(drop->mimeData()); - bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter); - if (handled) - { - emit useFilterRequest(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); - } - return handled; - } - return false; + +#include "tablesubview.hpp" + +#include +#include + +#include "../../model/doc/document.hpp" +#include "../../model/world/tablemimedata.hpp" + +#include "../filter/filterbox.hpp" +#include "table.hpp" +#include "tablebottombox.hpp" +#include "creator.hpp" + +CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, + const CreatorFactoryBase& creatorFactory, bool sorting) +: SubView (id) +{ + QVBoxLayout *layout = new QVBoxLayout; + + layout->setContentsMargins (QMargins (0, 0, 0, 0)); + + layout->addWidget (mBottom = + new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this), 0); + + layout->insertWidget (0, mTable = + new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete(), sorting, document), 2); + + CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); + + layout->insertWidget (0, filterBox); + + QWidget *widget = new QWidget; + + widget->setLayout (layout); + + setWidget (widget); + + connect (mTable, SIGNAL (editRequest (int)), this, SLOT (editRequest (int))); + + connect (mTable, SIGNAL (selectionSizeChanged (int)), + mBottom, SLOT (selectionSizeChanged (int))); + connect (mTable, SIGNAL (tableSizeChanged (int, int, int)), + mBottom, SLOT (tableSizeChanged (int, int, int))); + + mTable->tableSizeUpdate(); + mTable->selectionSizeUpdate(); + mTable->viewport()->installEventFilter(this); + mBottom->installEventFilter(this); + filterBox->installEventFilter(this); + + if (mBottom->canCreateAndDelete()) + { + connect (mTable, SIGNAL (createRequest()), mBottom, SLOT (createRequest())); + + connect (mTable, SIGNAL (cloneRequest(const CSMWorld::UniversalId&)), this, + SLOT(cloneRequest(const CSMWorld::UniversalId&))); + + connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)), + mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type))); + } + connect (mBottom, SIGNAL (requestFocus (const std::string&)), + mTable, SLOT (requestFocus (const std::string&))); + + connect (filterBox, + SIGNAL (recordFilterChanged (boost::shared_ptr)), + mTable, SLOT (recordFilterChanged (boost::shared_ptr))); + + connect(filterBox, SIGNAL(recordDropped(std::vector&, Qt::DropAction)), + this, SLOT(createFilterRequest(std::vector&, Qt::DropAction))); + + connect(this, SIGNAL(useFilterRequest(const std::string&)), filterBox, SIGNAL(useFilterRequest(const std::string&))); + + connect(this, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction)), + filterBox, SIGNAL(createFilterRequest(std::vector > >&, Qt::DropAction))); +} + +void CSVWorld::TableSubView::setEditLock (bool locked) +{ + mTable->setEditLock (locked); + mBottom->setEditLock (locked); +} + +void CSVWorld::TableSubView::editRequest (int row) +{ + focusId (mTable->getUniversalId (row)); +} + +void CSVWorld::TableSubView::updateEditorSetting(const QString &settingName, const QString &settingValue) +{ + mTable->updateEditorSetting(settingName, settingValue); +} + +void CSVWorld::TableSubView::setStatusBar (bool show) +{ + mBottom->setStatusBar (show); +} + +void CSVWorld::TableSubView::cloneRequest(const CSMWorld::UniversalId& toClone) +{ + emit cloneRequest(toClone.getId(), toClone.getType()); +} + +void CSVWorld::TableSubView::createFilterRequest (std::vector< CSMWorld::UniversalId>& types, Qt::DropAction action) +{ + std::vector > > filterSource; + + for (std::vector::iterator it = types.begin(); it != types.end(); ++it) + { + std::pair > pair( //splited long line + std::make_pair(it->getId(), mTable->getColumnsWithDisplay(CSMWorld::TableMimeData::convertEnums(it->getType())))); + + filterSource.push_back(pair); + } + emit createFilterRequest(filterSource, action); +} + +bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) +{ + if (event->type() == QEvent::Drop) + { + QDropEvent* drop = dynamic_cast(event); + const CSMWorld::TableMimeData* data = dynamic_cast(drop->mimeData()); + bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter); + if (handled) + { + emit useFilterRequest(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); + } + return handled; + } + return false; } \ No newline at end of file diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index 4e578b180..b3c253919 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -1,63 +1,63 @@ -#ifndef CSV_WORLD_TABLESUBVIEW_H -#define CSV_WORLD_TABLESUBVIEW_H - -#include "../doc/subview.hpp" - -#include - -class QModelIndex; - -namespace CSMWorld -{ - class IdTable; -} - -namespace CSMDoc -{ - class Document; -} - -namespace CSVWorld -{ - class Table; - class TableBottomBox; - class CreatorFactoryBase; - - class TableSubView : public CSVDoc::SubView - { - Q_OBJECT - - Table *mTable; - TableBottomBox *mBottom; - - public: - - TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory, bool sorting); - - virtual void setEditLock (bool locked); - - virtual void updateEditorSetting (const QString& key, const QString& value); - - virtual void setStatusBar (bool show); - - protected: - bool eventFilter(QObject* object, QEvent *event); - - signals: - void cloneRequest(const std::string&, - const CSMWorld::UniversalId::Type); - void createFilterRequest(std::vector > >& filterSource, - Qt::DropAction action); - void useFilterRequest(const std::string& idOfFilter); - - private slots: - - void editRequest (int row); - void cloneRequest (const CSMWorld::UniversalId& toClone); - void createFilterRequest(std::vector< CSMWorld::UniversalId >& types, - Qt::DropAction action); - }; -} - -#endif +#ifndef CSV_WORLD_TABLESUBVIEW_H +#define CSV_WORLD_TABLESUBVIEW_H + +#include "../doc/subview.hpp" + +#include + +class QModelIndex; + +namespace CSMWorld +{ + class IdTable; +} + +namespace CSMDoc +{ + class Document; +} + +namespace CSVWorld +{ + class Table; + class TableBottomBox; + class CreatorFactoryBase; + + class TableSubView : public CSVDoc::SubView + { + Q_OBJECT + + Table *mTable; + TableBottomBox *mBottom; + + public: + + TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, + const CreatorFactoryBase& creatorFactory, bool sorting); + + virtual void setEditLock (bool locked); + + virtual void updateEditorSetting (const QString& key, const QString& value); + + virtual void setStatusBar (bool show); + + protected: + bool eventFilter(QObject* object, QEvent *event); + + signals: + void cloneRequest(const std::string&, + const CSMWorld::UniversalId::Type); + void createFilterRequest(std::vector > >& filterSource, + Qt::DropAction action); + void useFilterRequest(const std::string& idOfFilter); + + private slots: + + void editRequest (int row); + void cloneRequest (const CSMWorld::UniversalId& toClone); + void createFilterRequest(std::vector< CSMWorld::UniversalId >& types, + Qt::DropAction action); + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 6a49f2f5e..17a624f0f 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -240,17 +240,21 @@ namespace MWMechanics //target is at far distance: build path to target OR follow target (if previously actor had reached it once) mFollowTarget = false; - buildNewPath(actor); + buildNewPath(actor); //may fail to build a path, check before use //delete visited path node mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); - //try shortcut - if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) - mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); - else - mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - mRotate = true; + //if no new path leave mTargetAngle unchanged + if(!mPathFinder.getPath().empty()) + { + //try shortcut + if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) + mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); + else + mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + mRotate = true; + } mMovement.mPosition[1] = 1; mReadyToAttack = false; @@ -300,9 +304,13 @@ namespace MWMechanics dest.mZ = mTarget.getRefData().getPosition().pos[2]; Ogre::Vector3 newPathTarget = Ogre::Vector3(dest.mX, dest.mY, dest.mZ); - ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); - Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ); - float dist = Ogre::Math::Abs((newPathTarget - currPathTarget).length()); + float dist = -1; //hack to indicate first time, to construct a new path + if(!mPathFinder.getPath().empty()) + { + ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); + Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ); + dist = Ogre::Math::Abs((newPathTarget - currPathTarget).length()); + } float targetPosThreshold; bool isOutside = actor.getCell()->mCell->isExterior(); @@ -311,7 +319,7 @@ namespace MWMechanics else targetPosThreshold = 100; - if(dist > targetPosThreshold) + if((dist < 0) || (dist > targetPosThreshold)) { //construct new path only if target has moved away more than on ESM::Position pos = actor.getRefData().getPosition(); @@ -332,8 +340,11 @@ namespace MWMechanics //maybe here is a mistake (?): PathFinder::getPathSize() returns number of grid points in the path, //not the actual path length. Here we should know if the new path is actually more effective. //if(pathFinder2.getPathSize() < mPathFinder.getPathSize()) - newPathFinder.syncStart(mPathFinder.getPath()); - mPathFinder = newPathFinder; + if(!mPathFinder.getPath().empty()) + { + newPathFinder.syncStart(mPathFinder.getPath()); + mPathFinder = newPathFinder; + } } } } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 4407363a6..8dbdd8770 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -391,6 +391,8 @@ namespace MWMechanics void PathFinder::syncStart(const std::list &path) { + if (mPath.size() < 2) + return; //nothing to pop std::list::const_iterator oldStart = path.begin(); std::list::iterator iter = ++mPath.begin(); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index f9b5b4d04..265069dc4 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -1,352 +1,352 @@ - -#include "statemanagerimp.hpp" - -#include -#include -#include -#include - -#include - -#include - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/journal.hpp" -#include "../mwbase/dialoguemanager.hpp" -#include "../mwbase/windowmanager.hpp" -#include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/scriptmanager.hpp" -#include "../mwbase/soundmanager.hpp" - -#include "../mwworld/player.hpp" -#include "../mwworld/class.hpp" - -#include "../mwmechanics/npcstats.hpp" - -#include "../mwscript/globalscripts.hpp" - -void MWState::StateManager::cleanup (bool force) -{ - if (mState!=State_NoGame || force) - { - MWBase::Environment::get().getSoundManager()->clear(); - MWBase::Environment::get().getDialogueManager()->clear(); - MWBase::Environment::get().getJournal()->clear(); - MWBase::Environment::get().getScriptManager()->getGlobalScripts().clear(); - MWBase::Environment::get().getWorld()->clear(); - MWBase::Environment::get().getWindowManager()->clear(); - - mState = State_NoGame; - mCharacterManager.clearCurrentCharacter(); - mTimePlayed = 0; - } -} - -std::map MWState::StateManager::buildContentFileIndexMap (const ESM::ESMReader& reader) - const -{ - const std::vector& current = - MWBase::Environment::get().getWorld()->getContentFiles(); - - const std::vector& prev = reader.getGameFiles(); - - std::map map; - - for (int iPrev = 0; iPrev (prev.size()); ++iPrev) - { - std::string id = Misc::StringUtils::lowerCase (prev[iPrev].name); - - for (int iCurrent = 0; iCurrent (current.size()); ++iCurrent) - if (id==Misc::StringUtils::lowerCase (current[iCurrent])) - { - map.insert (std::make_pair (iPrev, iCurrent)); - break; - } - } - - return map; -} - -MWState::StateManager::StateManager (const boost::filesystem::path& saves, const std::string& game) -: mQuitRequest (false), mAskLoadRecent(false), mState (State_NoGame), mCharacterManager (saves, game), mTimePlayed (0) -{ - -} - -void MWState::StateManager::requestQuit() -{ - mQuitRequest = true; -} - -bool MWState::StateManager::hasQuitRequest() const -{ - return mQuitRequest; -} - -void MWState::StateManager::askLoadRecent() -{ - if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu) - return; - - if( !mAskLoadRecent ) - { - if(getCurrentCharacter()->begin() == getCurrentCharacter()->end() )//no saves - { - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); - } - else - { - MWState::Slot lastSave = *getCurrentCharacter()->begin(); - std::vector buttons; - buttons.push_back("#{sYes}"); - buttons.push_back("#{sNo}"); - std::string tag("%s"); - std::string message = MWBase::Environment::get().getWindowManager()->getGameSettingString("sLoadLastSaveMsg", tag); - size_t pos = message.find(tag); - message.replace(pos, tag.length(), lastSave.mProfile.mDescription); - MWBase::Environment::get().getWindowManager()->messageBox(message, buttons); - mAskLoadRecent = true; - } - } -} - -MWState::StateManager::State MWState::StateManager::getState() const -{ - return mState; -} - -void MWState::StateManager::newGame (bool bypass) -{ - cleanup(); - - if (!bypass) - { - MWBase::Environment::get().getWorld()->startNewGame(); - MWBase::Environment::get().getWindowManager()->setNewGame (true); - } - else - MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1); - - MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); - - mState = State_Running; -} - -void MWState::StateManager::endGame() -{ - mState = State_Ended; - MWBase::Environment::get().getWorld()->useDeathCamera(); -} - -void MWState::StateManager::saveGame (const std::string& description, const Slot *slot) -{ - ESM::SavedGame profile; - - MWBase::World& world = *MWBase::Environment::get().getWorld(); - - MWWorld::Ptr player = world.getPlayer().getPlayer(); - - profile.mContentFiles = world.getContentFiles(); - - profile.mPlayerName = player.getClass().getName (player); - profile.mPlayerLevel = player.getClass().getNpcStats (player).getLevel(); - profile.mPlayerClass = player.get()->mBase->mClass; - - profile.mPlayerCell = world.getCellName(); - - profile.mInGameTime.mGameHour = world.getTimeStamp().getHour(); - profile.mInGameTime.mDay = world.getDay(); - profile.mInGameTime.mMonth = world.getMonth(); - profile.mInGameTime.mYear = world.getYear(); - profile.mTimePlayed = mTimePlayed; - profile.mDescription = description; - - int screenshotW = 259*2, screenshotH = 133*2; // *2 to get some nice antialiasing - Ogre::Image screenshot; - world.screenshot(screenshot, screenshotW, screenshotH); - Ogre::DataStreamPtr encoded = screenshot.encode("jpg"); - profile.mScreenshot.resize(encoded->size()); - encoded->read(&profile.mScreenshot[0], encoded->size()); - - if (!slot) - slot = mCharacterManager.getCurrentCharacter()->createSlot (profile); - else - slot = mCharacterManager.getCurrentCharacter()->updateSlot (slot, profile); - - std::ofstream stream (slot->mPath.string().c_str(), std::ios::binary); - - ESM::ESMWriter writer; - - const std::vector& current = - MWBase::Environment::get().getWorld()->getContentFiles(); - - for (std::vector::const_iterator iter (current.begin()); iter!=current.end(); - ++iter) - writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0 - - writer.setFormat (ESM::Header::CurrentFormat); - writer.setRecordCount ( - 1 // saved game header - +MWBase::Environment::get().getJournal()->countSavedGameRecords() - +MWBase::Environment::get().getWorld()->countSavedGameRecords() - +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() - +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() - +1 // global map - ); - - writer.save (stream); - - writer.startRecord (ESM::REC_SAVE); - slot->mProfile.save (writer); - writer.endRecord (ESM::REC_SAVE); - - MWBase::Environment::get().getJournal()->write (writer); - MWBase::Environment::get().getDialogueManager()->write (writer); - MWBase::Environment::get().getWorld()->write (writer); - MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer); - MWBase::Environment::get().getWindowManager()->write(writer); - - writer.close(); - - Settings::Manager::setString ("character", "Saves", - slot->mPath.parent_path().filename().string()); -} - -void MWState::StateManager::loadGame (const Character *character, const Slot *slot) -{ - try - { - cleanup(); - - mTimePlayed = slot->mProfile.mTimePlayed; - - ESM::ESMReader reader; - reader.open (slot->mPath.string()); - - std::map contentFileMap = buildContentFileIndexMap (reader); - - while (reader.hasMoreRecs()) - { - ESM::NAME n = reader.getRecName(); - reader.getRecHeader(); - - switch (n.val) - { - case ESM::REC_SAVE: - - // don't need to read that here - reader.skipRecord(); - break; - - case ESM::REC_JOUR: - case ESM::REC_QUES: - - MWBase::Environment::get().getJournal()->readRecord (reader, n.val); - break; - - case ESM::REC_DIAS: - - MWBase::Environment::get().getDialogueManager()->readRecord (reader, n.val); - break; - - case ESM::REC_ALCH: - case ESM::REC_ARMO: - case ESM::REC_BOOK: - case ESM::REC_CLAS: - case ESM::REC_CLOT: - case ESM::REC_ENCH: - case ESM::REC_NPC_: - case ESM::REC_SPEL: - case ESM::REC_WEAP: - case ESM::REC_GLOB: - case ESM::REC_PLAY: - case ESM::REC_CSTA: - - MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap); - break; - - case ESM::REC_GSCR: - - MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.val); - break; - - case ESM::REC_GMAP: - - MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val); - break; - - default: - - // ignore invalid records - /// \todo log error - reader.skipRecord(); - } - } - - mCharacterManager.setCurrentCharacter(character); - - mState = State_Running; - - Settings::Manager::setString ("character", "Saves", - slot->mPath.parent_path().filename().string()); - - MWBase::Environment::get().getWindowManager()->setNewGame(false); - MWBase::Environment::get().getWorld()->setupPlayer(); - MWBase::Environment::get().getWorld()->renderPlayer(); - MWBase::Environment::get().getWindowManager()->updatePlayer(); - MWBase::Environment::get().getMechanicsManager()->playerLoaded(); - - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - - ESM::CellId cellId = ptr.getCell()->mCell->getCellId(); - - MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition()); - } - catch (const std::exception& e) - { - std::cerr << "failed to load saved game: " << e.what() << std::endl; - cleanup (true); - } -} - -MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) -{ - return mCharacterManager.getCurrentCharacter (create); -} - -MWState::StateManager::CharacterIterator MWState::StateManager::characterBegin() -{ - return mCharacterManager.begin(); -} - -MWState::StateManager::CharacterIterator MWState::StateManager::characterEnd() -{ - return mCharacterManager.end(); -} - -void MWState::StateManager::update (float duration) -{ - mTimePlayed += duration; - - // Note: It would be nicer to trigger this from InputManager, i.e. the very beginning of the frame update. - if (mAskLoadRecent) - { - int iButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); - if(iButton==0) - { - mAskLoadRecent = false; - //Load last saved game for current character - MWState::Character *curCharacter = getCurrentCharacter(); - MWState::Slot lastSave = *curCharacter->begin(); - loadGame(curCharacter, &lastSave); - } - else if(iButton==1) - { - mAskLoadRecent = false; - MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); - } - } -} + +#include "statemanagerimp.hpp" + +#include +#include +#include +#include + +#include + +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/journal.hpp" +#include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/scriptmanager.hpp" +#include "../mwbase/soundmanager.hpp" + +#include "../mwworld/player.hpp" +#include "../mwworld/class.hpp" + +#include "../mwmechanics/npcstats.hpp" + +#include "../mwscript/globalscripts.hpp" + +void MWState::StateManager::cleanup (bool force) +{ + if (mState!=State_NoGame || force) + { + MWBase::Environment::get().getSoundManager()->clear(); + MWBase::Environment::get().getDialogueManager()->clear(); + MWBase::Environment::get().getJournal()->clear(); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().clear(); + MWBase::Environment::get().getWorld()->clear(); + MWBase::Environment::get().getWindowManager()->clear(); + + mState = State_NoGame; + mCharacterManager.clearCurrentCharacter(); + mTimePlayed = 0; + } +} + +std::map MWState::StateManager::buildContentFileIndexMap (const ESM::ESMReader& reader) + const +{ + const std::vector& current = + MWBase::Environment::get().getWorld()->getContentFiles(); + + const std::vector& prev = reader.getGameFiles(); + + std::map map; + + for (int iPrev = 0; iPrev (prev.size()); ++iPrev) + { + std::string id = Misc::StringUtils::lowerCase (prev[iPrev].name); + + for (int iCurrent = 0; iCurrent (current.size()); ++iCurrent) + if (id==Misc::StringUtils::lowerCase (current[iCurrent])) + { + map.insert (std::make_pair (iPrev, iCurrent)); + break; + } + } + + return map; +} + +MWState::StateManager::StateManager (const boost::filesystem::path& saves, const std::string& game) +: mQuitRequest (false), mAskLoadRecent(false), mState (State_NoGame), mCharacterManager (saves, game), mTimePlayed (0) +{ + +} + +void MWState::StateManager::requestQuit() +{ + mQuitRequest = true; +} + +bool MWState::StateManager::hasQuitRequest() const +{ + return mQuitRequest; +} + +void MWState::StateManager::askLoadRecent() +{ + if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu) + return; + + if( !mAskLoadRecent ) + { + if(getCurrentCharacter()->begin() == getCurrentCharacter()->end() )//no saves + { + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + } + else + { + MWState::Slot lastSave = *getCurrentCharacter()->begin(); + std::vector buttons; + buttons.push_back("#{sYes}"); + buttons.push_back("#{sNo}"); + std::string tag("%s"); + std::string message = MWBase::Environment::get().getWindowManager()->getGameSettingString("sLoadLastSaveMsg", tag); + size_t pos = message.find(tag); + message.replace(pos, tag.length(), lastSave.mProfile.mDescription); + MWBase::Environment::get().getWindowManager()->messageBox(message, buttons); + mAskLoadRecent = true; + } + } +} + +MWState::StateManager::State MWState::StateManager::getState() const +{ + return mState; +} + +void MWState::StateManager::newGame (bool bypass) +{ + cleanup(); + + if (!bypass) + { + MWBase::Environment::get().getWorld()->startNewGame(); + MWBase::Environment::get().getWindowManager()->setNewGame (true); + } + else + MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1); + + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); + + mState = State_Running; +} + +void MWState::StateManager::endGame() +{ + mState = State_Ended; + MWBase::Environment::get().getWorld()->useDeathCamera(); +} + +void MWState::StateManager::saveGame (const std::string& description, const Slot *slot) +{ + ESM::SavedGame profile; + + MWBase::World& world = *MWBase::Environment::get().getWorld(); + + MWWorld::Ptr player = world.getPlayer().getPlayer(); + + profile.mContentFiles = world.getContentFiles(); + + profile.mPlayerName = player.getClass().getName (player); + profile.mPlayerLevel = player.getClass().getNpcStats (player).getLevel(); + profile.mPlayerClass = player.get()->mBase->mClass; + + profile.mPlayerCell = world.getCellName(); + + profile.mInGameTime.mGameHour = world.getTimeStamp().getHour(); + profile.mInGameTime.mDay = world.getDay(); + profile.mInGameTime.mMonth = world.getMonth(); + profile.mInGameTime.mYear = world.getYear(); + profile.mTimePlayed = mTimePlayed; + profile.mDescription = description; + + int screenshotW = 259*2, screenshotH = 133*2; // *2 to get some nice antialiasing + Ogre::Image screenshot; + world.screenshot(screenshot, screenshotW, screenshotH); + Ogre::DataStreamPtr encoded = screenshot.encode("jpg"); + profile.mScreenshot.resize(encoded->size()); + encoded->read(&profile.mScreenshot[0], encoded->size()); + + if (!slot) + slot = mCharacterManager.getCurrentCharacter()->createSlot (profile); + else + slot = mCharacterManager.getCurrentCharacter()->updateSlot (slot, profile); + + std::ofstream stream (slot->mPath.string().c_str(), std::ios::binary); + + ESM::ESMWriter writer; + + const std::vector& current = + MWBase::Environment::get().getWorld()->getContentFiles(); + + for (std::vector::const_iterator iter (current.begin()); iter!=current.end(); + ++iter) + writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0 + + writer.setFormat (ESM::Header::CurrentFormat); + writer.setRecordCount ( + 1 // saved game header + +MWBase::Environment::get().getJournal()->countSavedGameRecords() + +MWBase::Environment::get().getWorld()->countSavedGameRecords() + +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() + +MWBase::Environment::get().getDialogueManager()->countSavedGameRecords() + +1 // global map + ); + + writer.save (stream); + + writer.startRecord (ESM::REC_SAVE); + slot->mProfile.save (writer); + writer.endRecord (ESM::REC_SAVE); + + MWBase::Environment::get().getJournal()->write (writer); + MWBase::Environment::get().getDialogueManager()->write (writer); + MWBase::Environment::get().getWorld()->write (writer); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer); + MWBase::Environment::get().getWindowManager()->write(writer); + + writer.close(); + + Settings::Manager::setString ("character", "Saves", + slot->mPath.parent_path().filename().string()); +} + +void MWState::StateManager::loadGame (const Character *character, const Slot *slot) +{ + try + { + cleanup(); + + mTimePlayed = slot->mProfile.mTimePlayed; + + ESM::ESMReader reader; + reader.open (slot->mPath.string()); + + std::map contentFileMap = buildContentFileIndexMap (reader); + + while (reader.hasMoreRecs()) + { + ESM::NAME n = reader.getRecName(); + reader.getRecHeader(); + + switch (n.val) + { + case ESM::REC_SAVE: + + // don't need to read that here + reader.skipRecord(); + break; + + case ESM::REC_JOUR: + case ESM::REC_QUES: + + MWBase::Environment::get().getJournal()->readRecord (reader, n.val); + break; + + case ESM::REC_DIAS: + + MWBase::Environment::get().getDialogueManager()->readRecord (reader, n.val); + break; + + case ESM::REC_ALCH: + case ESM::REC_ARMO: + case ESM::REC_BOOK: + case ESM::REC_CLAS: + case ESM::REC_CLOT: + case ESM::REC_ENCH: + case ESM::REC_NPC_: + case ESM::REC_SPEL: + case ESM::REC_WEAP: + case ESM::REC_GLOB: + case ESM::REC_PLAY: + case ESM::REC_CSTA: + + MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap); + break; + + case ESM::REC_GSCR: + + MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.val); + break; + + case ESM::REC_GMAP: + + MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val); + break; + + default: + + // ignore invalid records + /// \todo log error + reader.skipRecord(); + } + } + + mCharacterManager.setCurrentCharacter(character); + + mState = State_Running; + + Settings::Manager::setString ("character", "Saves", + slot->mPath.parent_path().filename().string()); + + MWBase::Environment::get().getWindowManager()->setNewGame(false); + MWBase::Environment::get().getWorld()->setupPlayer(); + MWBase::Environment::get().getWorld()->renderPlayer(); + MWBase::Environment::get().getWindowManager()->updatePlayer(); + MWBase::Environment::get().getMechanicsManager()->playerLoaded(); + + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + + ESM::CellId cellId = ptr.getCell()->mCell->getCellId(); + + MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition()); + } + catch (const std::exception& e) + { + std::cerr << "failed to load saved game: " << e.what() << std::endl; + cleanup (true); + } +} + +MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) +{ + return mCharacterManager.getCurrentCharacter (create); +} + +MWState::StateManager::CharacterIterator MWState::StateManager::characterBegin() +{ + return mCharacterManager.begin(); +} + +MWState::StateManager::CharacterIterator MWState::StateManager::characterEnd() +{ + return mCharacterManager.end(); +} + +void MWState::StateManager::update (float duration) +{ + mTimePlayed += duration; + + // Note: It would be nicer to trigger this from InputManager, i.e. the very beginning of the frame update. + if (mAskLoadRecent) + { + int iButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); + if(iButton==0) + { + mAskLoadRecent = false; + //Load last saved game for current character + MWState::Character *curCharacter = getCurrentCharacter(); + MWState::Slot lastSave = *curCharacter->begin(); + loadGame(curCharacter, &lastSave); + } + else if(iButton==1) + { + mAskLoadRecent = false; + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + } + } +} diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index b7207486b..91f123eb7 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -1,203 +1,203 @@ -#include "esmwriter.hpp" - -#include -#include -#include - -namespace ESM -{ - ESMWriter::ESMWriter() : mEncoder (0), mRecordCount (0), mCounting (true) {} - - unsigned int ESMWriter::getVersion() const - { - return mHeader.mData.version; - } - - void ESMWriter::setVersion(unsigned int ver) - { - mHeader.mData.version = ver; - } - - void ESMWriter::setAuthor(const std::string& auth) - { - mHeader.mData.author.assign (auth); - } - - void ESMWriter::setDescription(const std::string& desc) - { - mHeader.mData.desc.assign (desc); - } - - void ESMWriter::setRecordCount (int count) - { - mHeader.mData.records = count; - } - - void ESMWriter::setFormat (int format) - { - mHeader.mFormat = format; - } - - void ESMWriter::clearMaster() - { - mHeader.mMaster.clear(); - } - - void ESMWriter::addMaster(const std::string& name, uint64_t size) - { - Header::MasterData d; - d.name = name; - d.size = size; - mHeader.mMaster.push_back(d); - } - - void ESMWriter::save(std::ostream& file) - { - mRecordCount = 0; - mRecords.clear(); - mCounting = true; - mStream = &file; - - startRecord("TES3", 0); - - mHeader.save (*this); - - endRecord("TES3"); - } - - void ESMWriter::close() - { - if (!mRecords.empty()) - throw std::runtime_error ("Unclosed record remaining"); - } - - void ESMWriter::startRecord(const std::string& name, uint32_t flags) - { - mRecordCount++; - - writeName(name); - RecordData rec; - rec.name = name; - rec.position = mStream->tellp(); - rec.size = 0; - writeT(0); // Size goes here - writeT(0); // Unused header? - writeT(flags); - mRecords.push_back(rec); - - assert(mRecords.back().size == 0); - } - - void ESMWriter::startRecord (uint32_t name, uint32_t flags) - { - std::string type; - for (int i=0; i<4; ++i) - /// \todo make endianess agnostic - type += reinterpret_cast (&name)[i]; - - startRecord (type, flags); - } - - void ESMWriter::startSubRecord(const std::string& name) - { - writeName(name); - RecordData rec; - rec.name = name; - rec.position = mStream->tellp(); - rec.size = 0; - writeT(0); // Size goes here - mRecords.push_back(rec); - - assert(mRecords.back().size == 0); - } - - void ESMWriter::endRecord(const std::string& name) - { - RecordData rec = mRecords.back(); - assert(rec.name == name); - mRecords.pop_back(); - - mStream->seekp(rec.position); - - mCounting = false; - write (reinterpret_cast (&rec.size), sizeof(uint32_t)); - mCounting = true; - - mStream->seekp(0, std::ios::end); - - } - - void ESMWriter::endRecord (uint32_t name) - { - std::string type; - for (int i=0; i<4; ++i) - /// \todo make endianess agnostic - type += reinterpret_cast (&name)[i]; - - endRecord (type); - } - - void ESMWriter::writeHNString(const std::string& name, const std::string& data) - { - startSubRecord(name); - writeHString(data); - endRecord(name); - } - - void ESMWriter::writeHNString(const std::string& name, const std::string& data, size_t size) - { - assert(data.size() <= size); - startSubRecord(name); - writeHString(data); - - if (data.size() < size) - { - for (size_t i = data.size(); i < size; ++i) - write("\0",1); - } - - endRecord(name); - } - - void ESMWriter::writeHString(const std::string& data) - { - if (data.size() == 0) - write("\0", 1); - else - { - // Convert to UTF8 and return - std::string string = mEncoder ? mEncoder->getLegacyEnc(data) : data; - - write(string.c_str(), string.size()); - } - } - - void ESMWriter::writeHCString(const std::string& data) - { - writeHString(data); - if (data.size() > 0 && data[data.size()-1] != '\0') - write("\0", 1); - } - - void ESMWriter::writeName(const std::string& name) - { - assert((name.size() == 4 && name[3] != '\0')); - write(name.c_str(), name.size()); - } - - void ESMWriter::write(const char* data, size_t size) - { - if (mCounting && !mRecords.empty()) - { - for (std::list::iterator it = mRecords.begin(); it != mRecords.end(); ++it) - it->size += size; - } - - mStream->write(data, size); - } - - void ESMWriter::setEncoder(ToUTF8::Utf8Encoder* encoder) - { - mEncoder = encoder; - } -} +#include "esmwriter.hpp" + +#include +#include +#include + +namespace ESM +{ + ESMWriter::ESMWriter() : mEncoder (0), mRecordCount (0), mCounting (true) {} + + unsigned int ESMWriter::getVersion() const + { + return mHeader.mData.version; + } + + void ESMWriter::setVersion(unsigned int ver) + { + mHeader.mData.version = ver; + } + + void ESMWriter::setAuthor(const std::string& auth) + { + mHeader.mData.author.assign (auth); + } + + void ESMWriter::setDescription(const std::string& desc) + { + mHeader.mData.desc.assign (desc); + } + + void ESMWriter::setRecordCount (int count) + { + mHeader.mData.records = count; + } + + void ESMWriter::setFormat (int format) + { + mHeader.mFormat = format; + } + + void ESMWriter::clearMaster() + { + mHeader.mMaster.clear(); + } + + void ESMWriter::addMaster(const std::string& name, uint64_t size) + { + Header::MasterData d; + d.name = name; + d.size = size; + mHeader.mMaster.push_back(d); + } + + void ESMWriter::save(std::ostream& file) + { + mRecordCount = 0; + mRecords.clear(); + mCounting = true; + mStream = &file; + + startRecord("TES3", 0); + + mHeader.save (*this); + + endRecord("TES3"); + } + + void ESMWriter::close() + { + if (!mRecords.empty()) + throw std::runtime_error ("Unclosed record remaining"); + } + + void ESMWriter::startRecord(const std::string& name, uint32_t flags) + { + mRecordCount++; + + writeName(name); + RecordData rec; + rec.name = name; + rec.position = mStream->tellp(); + rec.size = 0; + writeT(0); // Size goes here + writeT(0); // Unused header? + writeT(flags); + mRecords.push_back(rec); + + assert(mRecords.back().size == 0); + } + + void ESMWriter::startRecord (uint32_t name, uint32_t flags) + { + std::string type; + for (int i=0; i<4; ++i) + /// \todo make endianess agnostic + type += reinterpret_cast (&name)[i]; + + startRecord (type, flags); + } + + void ESMWriter::startSubRecord(const std::string& name) + { + writeName(name); + RecordData rec; + rec.name = name; + rec.position = mStream->tellp(); + rec.size = 0; + writeT(0); // Size goes here + mRecords.push_back(rec); + + assert(mRecords.back().size == 0); + } + + void ESMWriter::endRecord(const std::string& name) + { + RecordData rec = mRecords.back(); + assert(rec.name == name); + mRecords.pop_back(); + + mStream->seekp(rec.position); + + mCounting = false; + write (reinterpret_cast (&rec.size), sizeof(uint32_t)); + mCounting = true; + + mStream->seekp(0, std::ios::end); + + } + + void ESMWriter::endRecord (uint32_t name) + { + std::string type; + for (int i=0; i<4; ++i) + /// \todo make endianess agnostic + type += reinterpret_cast (&name)[i]; + + endRecord (type); + } + + void ESMWriter::writeHNString(const std::string& name, const std::string& data) + { + startSubRecord(name); + writeHString(data); + endRecord(name); + } + + void ESMWriter::writeHNString(const std::string& name, const std::string& data, size_t size) + { + assert(data.size() <= size); + startSubRecord(name); + writeHString(data); + + if (data.size() < size) + { + for (size_t i = data.size(); i < size; ++i) + write("\0",1); + } + + endRecord(name); + } + + void ESMWriter::writeHString(const std::string& data) + { + if (data.size() == 0) + write("\0", 1); + else + { + // Convert to UTF8 and return + std::string string = mEncoder ? mEncoder->getLegacyEnc(data) : data; + + write(string.c_str(), string.size()); + } + } + + void ESMWriter::writeHCString(const std::string& data) + { + writeHString(data); + if (data.size() > 0 && data[data.size()-1] != '\0') + write("\0", 1); + } + + void ESMWriter::writeName(const std::string& name) + { + assert((name.size() == 4 && name[3] != '\0')); + write(name.c_str(), name.size()); + } + + void ESMWriter::write(const char* data, size_t size) + { + if (mCounting && !mRecords.empty()) + { + for (std::list::iterator it = mRecords.begin(); it != mRecords.end(); ++it) + it->size += size; + } + + mStream->write(data, size); + } + + void ESMWriter::setEncoder(ToUTF8::Utf8Encoder* encoder) + { + mEncoder = encoder; + } +} diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index 304876dbf..33650e678 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -1,114 +1,114 @@ -#ifndef OPENMW_ESM_WRITER_H -#define OPENMW_ESM_WRITER_H - -#include -#include - -#include - -#include "esmcommon.hpp" -#include "loadtes3.hpp" - -namespace ESM { - -class ESMWriter -{ - struct RecordData - { - std::string name; - std::streampos position; - uint32_t size; - }; - - public: - - ESMWriter(); - - unsigned int getVersion() const; - void setVersion(unsigned int ver = 0x3fa66666); - void setEncoder(ToUTF8::Utf8Encoder *encoding); - void setAuthor(const std::string& author); - void setDescription(const std::string& desc); - void setRecordCount (int count); - void setFormat (int format); - - void clearMaster(); - - void addMaster(const std::string& name, uint64_t size); - - void save(std::ostream& file); - ///< Start saving a file by writing the TES3 header. - - void close(); - ///< \note Does not close the stream. - - void writeHNString(const std::string& name, const std::string& data); - void writeHNString(const std::string& name, const std::string& data, size_t size); - void writeHNCString(const std::string& name, const std::string& data) - { - startSubRecord(name); - writeHCString(data); - endRecord(name); - } - void writeHNOString(const std::string& name, const std::string& data) - { - if (!data.empty()) - writeHNString(name, data); - } - void writeHNOCString(const std::string& name, const std::string& data) - { - if (!data.empty()) - writeHNCString(name, data); - } - - template - void writeHNT(const std::string& name, const T& data) - { - startSubRecord(name); - writeT(data); - endRecord(name); - } - - template - void writeHNT(const std::string& name, const T& data, int size) - { - startSubRecord(name); - writeT(data, size); - endRecord(name); - } - - template - void writeT(const T& data) - { - write((char*)&data, sizeof(T)); - } - - template - void writeT(const T& data, size_t size) - { - write((char*)&data, size); - } - - void startRecord(const std::string& name, uint32_t flags = 0); - void startRecord(uint32_t name, uint32_t flags = 0); - void startSubRecord(const std::string& name); - void endRecord(const std::string& name); - void endRecord(uint32_t name); - void writeHString(const std::string& data); - void writeHCString(const std::string& data); - void writeName(const std::string& data); - void write(const char* data, size_t size); - - private: - std::list mRecords; - std::ostream* mStream; - std::streampos mHeaderPos; - ToUTF8::Utf8Encoder* mEncoder; - int mRecordCount; - bool mCounting; - - Header mHeader; - }; -} - -#endif +#ifndef OPENMW_ESM_WRITER_H +#define OPENMW_ESM_WRITER_H + +#include +#include + +#include + +#include "esmcommon.hpp" +#include "loadtes3.hpp" + +namespace ESM { + +class ESMWriter +{ + struct RecordData + { + std::string name; + std::streampos position; + uint32_t size; + }; + + public: + + ESMWriter(); + + unsigned int getVersion() const; + void setVersion(unsigned int ver = 0x3fa66666); + void setEncoder(ToUTF8::Utf8Encoder *encoding); + void setAuthor(const std::string& author); + void setDescription(const std::string& desc); + void setRecordCount (int count); + void setFormat (int format); + + void clearMaster(); + + void addMaster(const std::string& name, uint64_t size); + + void save(std::ostream& file); + ///< Start saving a file by writing the TES3 header. + + void close(); + ///< \note Does not close the stream. + + void writeHNString(const std::string& name, const std::string& data); + void writeHNString(const std::string& name, const std::string& data, size_t size); + void writeHNCString(const std::string& name, const std::string& data) + { + startSubRecord(name); + writeHCString(data); + endRecord(name); + } + void writeHNOString(const std::string& name, const std::string& data) + { + if (!data.empty()) + writeHNString(name, data); + } + void writeHNOCString(const std::string& name, const std::string& data) + { + if (!data.empty()) + writeHNCString(name, data); + } + + template + void writeHNT(const std::string& name, const T& data) + { + startSubRecord(name); + writeT(data); + endRecord(name); + } + + template + void writeHNT(const std::string& name, const T& data, int size) + { + startSubRecord(name); + writeT(data, size); + endRecord(name); + } + + template + void writeT(const T& data) + { + write((char*)&data, sizeof(T)); + } + + template + void writeT(const T& data, size_t size) + { + write((char*)&data, size); + } + + void startRecord(const std::string& name, uint32_t flags = 0); + void startRecord(uint32_t name, uint32_t flags = 0); + void startSubRecord(const std::string& name); + void endRecord(const std::string& name); + void endRecord(uint32_t name); + void writeHString(const std::string& data); + void writeHCString(const std::string& data); + void writeName(const std::string& data); + void write(const char* data, size_t size); + + private: + std::list mRecords; + std::ostream* mStream; + std::streampos mHeaderPos; + ToUTF8::Utf8Encoder* mEncoder; + int mRecordCount; + bool mCounting; + + Header mHeader; + }; +} + +#endif diff --git a/credits.txt b/credits.txt index 561931cde..601255763 100644 --- a/credits.txt +++ b/credits.txt @@ -20,6 +20,7 @@ Artem Kotsynyak (greye) athile Britt Mathis (galdor557) BrotherBrick +cc9cii Chris Robinson (KittyCat) Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) diff --git a/readme.txt b/readme.txt index 4cc9c6234..a23cd1077 100644 --- a/readme.txt +++ b/readme.txt @@ -1,951 +1,951 @@ -OpenMW: A reimplementation of The Elder Scrolls III: Morrowind - -OpenMW is an attempt at recreating the engine for the popular role-playing game -Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. - -Version: 0.28.0 -License: GPL (see GPL3.txt for more information) -Website: http://www.openmw.org - -Font Licenses: -EBGaramond-Regular.ttf: OFL (see OFL.txt for more information) -DejaVuLGCSansMono.ttf: custom (see DejaVu Font License.txt for more information) - - - -INSTALLATION - -Windows: -Run the installer. - -Linux: -Ubuntu (and most others) -Download the .deb file and install it in the usual way. - -Arch Linux -There's an OpenMW package available in the [community] Repository: -https://www.archlinux.org/packages/?sort=&q=openmw - -OS X: -Open DMG file, copy OpenMW folder anywhere, for example in /Applications - -BUILD FROM SOURCE - -https://wiki.openmw.org/index.php?title=Development_Environment_Setup - - -THE DATA PATH - -The data path tells OpenMW where to find your Morrowind files. If you run the launcher, OpenMW should be able to -pick up the location of these files on its own, if both Morrowind and OpenMW are installed properly -(installing Morrowind under WINE is considered a proper install). - -COMMAND LINE OPTIONS - -Syntax: openmw -Allowed options: - --help print help message - --version print version information and quit - --data arg (=data) set data directories (later directories - have higher priority) - --data-local arg set local data directory (highest - priority) - --fallback-archive arg (=fallback-archive) - set fallback BSA archives (later - archives have higher priority) - --resources arg (=resources) set resources directory - --start arg (=Beshara) set initial cell - --content arg content file(s): esm/esp, or - omwgame/omwaddon - --anim-verbose [=arg(=1)] (=0) output animation indices files - --no-sound [=arg(=1)] (=0) disable all sounds - --script-verbose [=arg(=1)] (=0) verbose script output - --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue - scripts) at startup - --script-console [=arg(=1)] (=0) enable console-only script - functionality - --script-run arg select a file containing a list of - console commands that is executed on - startup - --script-warn [=arg(=1)] (=1) handling of warnings when compiling - scripts - 0 - ignore warning - 1 - show warning but consider script as - correctly compiled anyway - 2 - treat warnings as errors - --skip-menu [=arg(=1)] (=0) skip main menu on game startup - --fs-strict [=arg(=1)] (=0) strict file system handling (no case - folding) - --encoding arg (=win1252) Character encoding used in OpenMW game - messages: - - win1250 - Central and Eastern European - such as Polish, Czech, Slovak, - Hungarian, Slovene, Bosnian, Croatian, - Serbian (Latin script), Romanian and - Albanian languages - - win1251 - Cyrillic alphabet such as - Russian, Bulgarian, Serbian Cyrillic - and other languages - - win1252 - Western European (Latin) - alphabet, used by default - --fallback arg fallback values - --no-grab Don't grab mouse cursor - --activate-dist arg (=-1) activation distance override - -CHANGELOG - -0.29.0 - -Bug #556: Video soundtrack not played when music volume is set to zero -Bug #829: OpenMW uses up all available vram, when playing for extended time -Bug #848: Wrong amount of footsteps playing in 1st person -Bug #888: Ascended Sleepers have movement issues -Bug #892: Explicit references are allowed on all script functions -Bug #999: Graphic Herbalism (mod): sometimes doesn't activate properly -Bug #1009: Lake Fjalding AI related slowdown. -Bug #1041: Music playback issues on OS X >= 10.9 -Bug #1043: No message box when advancing skill "Speechcraft" while in dialog window -Bug #1060: Some message boxes are cut off at the bottom -Bug #1062: Bittercup script does not work ('end' variable) -Bug #1074: Inventory paperdoll obscures armour rating -Bug #1077: Message after killing an essential NPC disappears too fast -Bug #1078: "Clutterbane" shows empty charge bar -Bug #1083: UndoWerewolf fails -Bug #1088: Better Clothes Bloodmoon Plus 1.5 by Spirited Treasure pants are not rendered -Bug #1090: Start scripts fail when going to a non-predefined cell -Bug #1091: Crash: Assertion `!q.isNaN() && "Invalid orientation supplied as parameter"' failed. -Bug #1093: Weapons of aggressive NPCs are invisible after you exit and re-enter interior -Bug #1105: Magicka is depleted when using uncastable spells -Bug #1106: Creatures should be able to run -Bug #1107: TR cliffs have way too huge collision boxes in OpenMW -Bug #1109: Cleaning True Light and Darkness with Tes3cmd makes Addamasartus , Zenarbael and Yasamsi flooded. -Bug #1114: Bad output for desktop-file-validate on openmw.desktop (and opencs.desktop) -Bug #1115: Memory leak when spying on Fargoth -Bug #1137: Script execution fails (drenSlaveOwners script) -Bug #1143: Mehra Milo quest (vivec informants) is broken -Bug #1145: Issues with moving gold between inventory and containers -Bug #1146: Issues with picking up stacks of gold -Bug #1147: Dwemer Crossbows are held incorrectly -Bug #1158: Armor rating should always stay below inventory mannequin -Bug #1159: Quick keys can be set during character generation -Bug #1160: Crash on equip lockpick when -Bug #1167: Editor: Referenceables are not correctly loaded when dealing with more than one content file -Feature #30: Loading/Saving (still missing a few parts) -Feature #101: AI Package: Activate -Feature #103: AI Package: Follow, FollowCell -Feature #138: Editor: Drag & Drop -Feature #428: Player death -Feature #505: Editor: Record Cloning -Feature #701: Levelled creatures -Feature #708: Improved Local Variable handling -Feature #709: Editor: Script verifier -Feature #764: Missing journal backend features -Feature #777: Creature weapons/shields -Feature #789: Editor: Referenceable record verifier -Feature #924: Load/Save GUI (still missing loading screen and progress bars) -Feature #946: Knockdown -Feature #947: Decrease fatigue when running, swimming and attacking -Feature #956: Melee Combat: Blocking -Feature #957: Area magic -Feature #960: Combat/AI combat for creatures -Feature #962: Combat-Related AI instructions -Feature #1075: Damage/Restore skill/attribute magic effects -Feature #1076: Soultrap magic effect -Feature #1081: Disease contraction -Feature #1086: Blood particles -Feature #1092: Interrupt resting -Feature #1101: Inventory equip scripts -Feature #1116: Version/Build number in Launcher window -Feature #1119: Resistance/weakness to normal weapons magic effect -Feature #1123: Slow Fall magic effect -Feature #1130: Auto-calculate spells -Feature #1164: Editor: Case-insensitive sorting in tables - -0.28.0 - -Bug #399: Inventory changes are not visible immediately -Bug #417: Apply weather instantly when teleporting -Bug #566: Global Map position marker not updated for interior cells -Bug #712: Looting corpse delay -Bug #716: Problem with the "Vurt's Ascadian Isles Mod" mod -Bug #805: Two TR meshes appear black (v0.24RC) -Bug #841: Third-person activation distance taken from camera rather than head -Bug #845: NPCs hold torches during the day -Bug #855: Vvardenfell Visages Volume I some hairs don´t appear since 0,24 -Bug #856: Maormer race by Mac Kom - The heads are way up -Bug #864: Walk locks during loading in 3rd person -Bug #871: active weapon/magic item icon is not immediately made blank if item is removed during dialog -Bug #882: Hircine's Ring doesn't always work -Bug #909: [Tamriel Rebuilt] crashes in Akamora -Bug #922: Launcher writing merged openmw.cfg files -Bug #943: Random magnitude should be calculated per effect -Bug #948: Negative fatigue level should be allowed -Bug #949: Particles in world space -Bug #950: Hard crash on x64 Linux running --new-game (on startup) -Bug #951: setMagicka and setFatigue have no effect -Bug #954: Problem with equipping inventory items when using a keyboard shortcut -Bug #955: Issues with equipping torches -Bug #966: Shield is visible when casting spell -Bug #967: Game crashes when equipping silver candlestick -Bug #970: Segmentation fault when starting at Bal Isra -Bug #977: Pressing down key in console doesn't go forward in history -Bug #979: Tooltip disappears when changing inventory -Bug #980: Barter: item category is remembered, but not shown -Bug #981: Mod: replacing model has wrong position/orientation -Bug #982: Launcher: Addon unchecking is not saved -Bug #983: Fix controllers to affect objects attached to the base node -Bug #985: Player can talk to NPCs who are in combat -Bug #989: OpenMW crashes when trying to include mod with capital .ESP -Bug #991: Merchants equip items with harmful constant effect enchantments -Bug #994: Don't cap skills/attributes when set via console -Bug #998: Setting the max health should also set the current health -Bug #1005: Torches are visible when casting spells and during hand to hand combat. -Bug #1006: Many NPCs have 0 skill -Bug #1007: Console fills up with text -Bug #1013: Player randomly loses health or dies -Bug #1014: Persuasion window is not centered in maximized window -Bug #1015: Player status window scroll state resets on status change -Bug #1016: Notification window not big enough for all skill level ups -Bug #1020: Saved window positions are not rescaled appropriately on resolution change -Bug #1022: Messages stuck permanently on screen when they pile up -Bug #1023: Journals doesn't open -Bug #1026: Game loses track of torch usage. -Bug #1028: Crash on pickup of jug in Unexplored Shipwreck, Upper level -Bug #1029: Quick keys menu: Select compatible replacement when tool used up -Bug #1042: TES3 header data wrong encoding -Bug #1045: OS X: deployed OpenCS won't launch -Bug #1046: All damaged weaponry is worth 1 gold -Bug #1048: Links in "locked" dialogue are still clickable -Bug #1052: Using color codes when naming your character actually changes the name's color -Bug #1054: Spell effects not visible in front of water -Bug #1055: Power-Spell animation starts even though you already casted it that day -Bug #1059: Cure disease potion removes all effects from player, even your race bonus and race ability -Bug #1063: Crash upon checking out game start ship area in Seyda Neen -Bug #1064: openmw binaries link to unnecessary libraries -Bug #1065: Landing from a high place in water still causes fall damage -Bug #1072: Drawing weapon increases torch brightness -Bug #1073: Merchants sell stacks of gold -Feature #43: Visuals for Magic Effects -Feature #51: Ranged Magic -Feature #52: Touch Range Magic -Feature #53: Self Range Magic -Feature #54: Spell Casting -Feature #70: Vampirism -Feature #100: Combat AI -Feature #171: Implement NIF record NiFlipController -Feature #410: Window to restore enchanted item charge -Feature #647: Enchanted item glow -Feature #723: Invisibility/Chameleon magic effects -Feature #737: Resist Magicka magic effect -Feature #758: GetLOS -Feature #926: Editor: Info-Record tables -Feature #958: Material controllers -Feature #959: Terrain bump, specular, & parallax mapping -Feature #990: Request: unlock mouse when in any menu -Feature #1018: Do not allow view mode switching while performing an action -Feature #1027: Vertex morph animation (NiGeomMorpherController) -Feature #1031: Handle NiBillboardNode -Feature #1051: Implement NIF texture slot DarkTexture -Task #873: Unify OGRE initialisation - -0.27.0 - -Bug #597: Assertion `dialogue->mId == id' failed in esmstore.cpp -Bug #794: incorrect display of decimal numbers -Bug #840: First-person sneaking camera height -Bug #887: Ambient sounds playing while paused -Bug #902: Problems with Polish character encoding -Bug #907: Entering third person using the mousewheel is possible even if it's impossible using the key -Bug #910: Some CDs not working correctly with Unshield installer -Bug #917: Quick character creation plugin does not work -Bug #918: Fatigue does not refill -Bug #919: The PC falls dead in Beshara - OpenMW nightly Win64 (708CDE2) -Feature #57: Acrobatics Skill -Feature #462: Editor: Start Dialogue -Feature #546: Modify ESX selector to handle new content file scheme -Feature #588: Editor: Adjust name/path of edited content files -Feature #644: Editor: Save -Feature #710: Editor: Configure script compiler context -Feature #790: God Mode -Feature #881: Editor: Allow only one instance of OpenCS -Feature #889: Editor: Record filtering -Feature #895: Extinguish torches -Feature #898: Breath meter enhancements -Feature #901: Editor: Default record filter -Feature #913: Merge --master and --plugin switches - -0.26.0 - -Bug #274: Inconsistencies in the terrain -Bug #557: Already-dead NPCs do not equip clothing/items. -Bug #592: Window resizing -Bug #612: [Tamriel Rebuilt] Missing terrain (South of Tel Oren) -Bug #664: Heart of lorkhan acts like a dead body (container) -Bug #767: Wonky ramp physics & water -Bug #780: Swimming out of water -Bug #792: Wrong ground alignment on actors when no clipping -Bug #796: Opening and closing door sound issue -Bug #797: No clipping hinders opening and closing of doors -Bug #799: sliders in enchanting window -Bug #838: Pressing key during startup procedure freezes the game -Bug #839: Combat/magic stances during character creation -Bug #843: [Tribunal] Dark Brotherhood assassin appears without equipment -Bug #844: Resting "until healed" option given even with full stats -Bug #846: Equipped torches are invisible. -Bug #847: Incorrect formula for autocalculated NPC initial health -Bug #850: Shealt weapon sound plays when leaving magic-ready stance -Bug #852: Some boots do not produce footstep sounds -Bug #860: FPS bar misalignment -Bug #861: Unable to print screen -Bug #863: No sneaking and jumping at the same time -Bug #866: Empty variables in [Movies] section of Morrowind.ini gets imported into OpenMW.cfg as blank fallback option and crashes game on start. -Bug #867: Dancing girls in "Suran, Desele's House of Earthly Delights" don't dance. -Bug #868: Idle animations are repeated -Bug #874: Underwater swimming close to the ground is jerky -Bug #875: Animation problem while swimming on the surface and looking up -Bug #876: Always a starting upper case letter in the inventory -Bug #878: Active spell effects don't update the layout properly when ended -Bug #891: Cell 24,-12 (Tamriel Rebuilt) crashes on load -Bug #896: New game sound issue -Feature #49: Melee Combat -Feature #71: Lycanthropy -Feature #393: Initialise MWMechanics::AiSequence from ESM::AIPackageList -Feature #622: Multiple positions for inventory window -Feature #627: Drowning -Feature #786: Allow the 'Activate' key to close the countdialog window -Feature #798: Morrowind installation via Launcher (Linux/Max OS only) -Feature #851: First/Third person transitions with mouse wheel -Task #689: change PhysicActor::enableCollisions -Task #707: Reorganise Compiler - -0.25.0 - -Bug #411: Launcher crash on OS X < 10.8 -Bug #604: Terrible performance drop in the Census and Excise Office. -Bug #676: Start Scripts fail to load -Bug #677: OpenMW does not accept script names with - -Bug #766: Extra space in front of topic links -Bug #793: AIWander Isn't Being Passed The Repeat Parameter -Bug #795: Sound playing with drawn weapon and crossing cell-border -Bug #800: can't select weapon for enchantment -Bug #801: Player can move while over-encumbered -Bug #802: Dead Keys not working -Bug #808: mouse capture -Bug #809: ini Importer does not work without an existing cfg file -Bug #812: Launcher will run OpenMW with no ESM or ESP selected -Bug #813: OpenMW defaults to Morrowind.ESM with no ESM or ESP selected -Bug #817: Dead NPCs and Creatures still have collision boxes -Bug #820: Incorrect sorting of answers (Dialogue) -Bug #826: mwinimport dumps core when given an unknown parameter -Bug #833: getting stuck in door -Bug #835: Journals/books not showing up properly. -Feature #38: SoundGen -Feature #105: AI Package: Wander -Feature #230: 64-bit compatibility for OS X -Feature #263: Hardware mouse cursors -Feature #449: Allow mouse outside of window while paused -Feature #736: First person animations -Feature #750: Using mouse wheel in third person mode -Feature #822: Autorepeat for slider buttons - -0.24.0 - -Bug #284: Book's text misalignment -Bug #445: Camera able to get slightly below floor / terrain -Bug #582: Seam issue in Red Mountain -Bug #632: Journal Next Button shows white square -Bug #653: IndexedStore ignores index -Bug #694: Parser does not recognize float values starting with . -Bug #699: Resource handling broken with Ogre 1.9 trunk -Bug #718: components/esm/loadcell is using the mwworld subsystem -Bug #729: Levelled item list tries to add nonexistent item -Bug #730: Arrow buttons in the settings menu do not work. -Bug #732: Erroneous behavior when binding keys -Bug #733: Unclickable dialogue topic -Bug #734: Book empty line problem -Bug #738: OnDeath only works with implicit references -Bug #740: Script compiler fails on scripts with special names -Bug #742: Wait while no clipping -Bug #743: Problem with changeweather console command -Bug #744: No wait dialogue after starting a new game -Bug #748: Player is not able to unselect objects with the console -Bug #751: AddItem should only spawn a message box when called from dialogue -Bug #752: The enter button has several functions in trade and looting that is not impelemted. -Bug #753: Fargoth's Ring Quest Strange Behavior -Bug #755: Launcher writes duplicate lines into settings.cfg -Bug #759: Second quest in mages guild does not work -Bug #763: Enchantment cast cost is wrong -Bug #770: The "Take" and "Close" buttons in the scroll GUI are stretched incorrectly -Bug #773: AIWander Isn't Being Passed The Correct idle Values -Bug #778: The journal can be opened at the start of a new game -Bug #779: Divayth Fyr starts as dead -Bug #787: "Batch count" on detailed FPS counter gets cut-off -Bug #788: chargen scroll layout does not match vanilla -Feature #60: Atlethics Skill -Feature #65: Security Skill -Feature #74: Interaction with non-load-doors -Feature #98: Render Weapon and Shield -Feature #102: AI Package: Escort, EscortCell -Feature #182: Advanced Journal GUI -Feature #288: Trading enhancements -Feature #405: Integrate "new game" into the menu -Feature #537: Highlight dialogue topic links -Feature #658: Rotate, RotateWorld script instructions and local rotations -Feature #690: Animation Layering -Feature #722: Night Eye/Blind magic effects -Feature #735: Move, MoveWorld script instructions. -Feature #760: Non-removable corpses - -0.23.0 - -Bug #522: Player collides with placeable items -Bug #553: Open/Close sounds played when accessing main menu w/ Journal Open -Bug #561: Tooltip word wrapping delay -Bug #578: Bribing works incorrectly -Bug #601: PositionCell fails on negative coordinates -Bug #606: Some NPCs hairs not rendered with Better Heads addon -Bug #609: Bad rendering of bone boots -Bug #613: Messagebox causing assert to fail -Bug #631: Segfault on shutdown -Bug #634: Exception when talking to Calvus Horatius in Mournhold, royal palace courtyard -Bug #635: Scale NPCs depending on race -Bug #643: Dialogue Race select function is inverted -Bug #646: Twohanded weapons don't work properly -Bug #654: Crash when dropping objects without a collision shape -Bug #655/656: Objects that were disabled or deleted (but not both) were added to the scene when re-entering a cell -Bug #660: "g" in "change" cut off in Race Menu -Bug #661: Arrille sells me the key to his upstairs room -Bug #662: Day counter starts at 2 instead of 1 -Bug #663: Cannot select "come unprepared" topic in dialog with Dagoth Ur -Bug #665: Pickpocket -> "Grab all" grabs all NPC inventory, even not listed in container window. -Bug #666: Looking up/down problem -Bug #667: Active effects border visible during loading -Bug #669: incorrect player position at new game start -Bug #670: race selection menu: sex, face and hair left button not totally clickable -Bug #671: new game: player is naked -Bug #674: buying or selling items doesn't change amount of gold -Bug #675: fatigue is not set to its maximum when starting a new game -Bug #678: Wrong rotation order causes RefData's rotation to be stored incorrectly -Bug #680: different gold coins in Tel Mara -Bug #682: Race menu ignores playable flag for some hairs and faces -Bug #685: Script compiler does not accept ":" after a function name -Bug #688: dispose corpse makes cross-hair to disappear -Bug #691: Auto equipping ignores equipment conditions -Bug #692: OpenMW doesnt load "loose file" texture packs that places resources directly in data folder -Bug #696: Draugr incorrect head offset -Bug #697: Sail transparency issue -Bug #700: "On the rocks" mod does not load its UV coordinates correctly. -Bug #702: Some race mods don't work -Bug #711: Crash during character creation -Bug #715: Growing Tauryon -Bug #725: Auto calculate stats -Bug #728: Failure to open container and talk dialogue -Bug #731: Crash with Mush-Mere's "background" topic -Feature #55/657: Item Repairing -Feature #62/87: Enchanting -Feature #99: Pathfinding -Feature #104: AI Package: Travel -Feature #129: Levelled items -Feature #204: Texture animations -Feature #239: Fallback-Settings -Feature #535: Console object selection improvements -Feature #629: Add levelup description in levelup layout dialog -Feature #630: Optional format subrecord in (tes3) header -Feature #641: Armor rating -Feature #645: OnDeath script function -Feature #683: Companion item UI -Feature #698: Basic Particles -Task #648: Split up components/esm/loadlocks -Task #695: mwgui cleanup - -0.22.0 - -Bug #311: Potential infinite recursion in script compiler -Bug #355: Keyboard repeat rate (in Xorg) are left disabled after game exit. -Bug #382: Weird effect in 3rd person on water -Bug #387: Always use detailed shape for physics raycasts -Bug #420: Potion/ingredient effects do not stack -Bug #429: Parts of dwemer door not picked up correctly for activation/tooltips -Bug #434/Bug #605: Object movement between cells not properly implemented -Bug #502: Duplicate player collision model at origin -Bug #509: Dialogue topic list shifts inappropriately -Bug #513: Sliding stairs -Bug #515: Launcher does not support non-latin strings -Bug #525: Race selection preview camera wrong position -Bug #526: Attributes / skills should not go below zero -Bug #529: Class and Birthsign menus options should be preselected -Bug #530: Lock window button graphic missing -Bug #532: Missing map menu graphics -Bug #545: ESX selector does not list ESM files properly -Bug #547: Global variables of type short are read incorrectly -Bug #550: Invisible meshes collision and tooltip -Bug #551: Performance drop when loading multiple ESM files -Bug #552: Don't list CG in options if it is not available -Bug #555: Character creation windows "OK" button broken -Bug #558: Segmentation fault when Alt-tabbing with console opened -Bug #559: Dialog window should not be available before character creation is finished -Bug #560: Tooltip borders should be stretched -Bug #562: Sound should not be played when an object cannot be picked up -Bug #565: Water animation speed + timescale -Bug #572: Better Bodies' textures don't work -Bug #573: OpenMW doesn't load if TR_Mainland.esm is enabled (Tamriel Rebuilt mod) -Bug #574: Moving left/right should not cancel auto-run -Bug #575: Crash entering the Chamber of Song -Bug #576: Missing includes -Bug #577: Left Gloves Addon causes ESMReader exception -Bug #579: Unable to open container "Kvama Egg Sack" -Bug #581: Mimicking vanilla Morrowind water -Bug #583: Gender not recognized -Bug #586: Wrong char gen behaviour -Bug #587: "End" script statements with spaces don't work -Bug #589: Closing message boxes by pressing the activation key -Bug #590: Ugly Dagoth Ur rendering -Bug #591: Race selection issues -Bug #593: Persuasion response should be random -Bug #595: Footless guard -Bug #599: Waterfalls are invisible from a certain distance -Bug #600: Waterfalls rendered incorrectly, cut off by water -Bug #607: New beast bodies mod crashes -Bug #608: Crash in cell "Mournhold, Royal Palace" -Bug #611: OpenMW doesn't find some of textures used in Tamriel Rebuilt -Bug #613: Messagebox causing assert to fail -Bug #615: Meshes invisible from above water -Bug #617: Potion effects should be hidden until discovered -Bug #619: certain moss hanging from tree has rendering bug -Bug #621: Batching bloodmoon's trees -Bug #623: NiMaterialProperty alpha unhandled -Bug #628: Launcher in latest master crashes the game -Bug #633: Crash on startup: Better Heads -Bug #636: Incorrect Char Gen Menu Behavior -Feature #29: Allow ESPs and multiple ESMs -Feature #94: Finish class selection-dialogue -Feature #149: Texture Alphas -Feature #237: Run Morrowind-ini importer from launcher -Feature #286: Update Active Spell Icons -Feature #334: Swimming animation -Feature #335: Walking animation -Feature #360: Proper collision shapes for NPCs and creatures -Feature #367: Lights that behave more like original morrowind implementation -Feature #477: Special local scripting variables -Feature #528: Message boxes should close when enter is pressed under certain conditions. -Feature #543: Add bsa files to the settings imported by the ini importer -Feature #594: coordinate space and utility functions -Feature #625: Zoom in vanity mode -Task #464: Refactor launcher ESX selector into a re-usable component -Task #624: Unified implementation of type-variable sub-records - -0.21.0 - -Bug #253: Dialogs don't work for Russian version of Morrowind -Bug #267: Activating creatures without dialogue can still activate the dialogue GUI -Bug #354: True flickering lights -Bug #386: The main menu's first entry is wrong (in french) -Bug #479: Adding the spell "Ash Woe Blight" to the player causes strange attribute oscillations -Bug #495: Activation Range -Bug #497: Failed Disposition check doesn't stop a dialogue entry from being returned -Bug #498: Failing a disposition check shouldn't eliminate topics from the the list of those available -Bug #500: Disposition for most NPCs is 0/100 -Bug #501: Getdisposition command wrongly returns base disposition -Bug #506: Journal UI doesn't update anymore -Bug #507: EnableRestMenu is not a valid command - change it to EnableRest -Bug #508: Crash in Ald Daedroth Shrine -Bug #517: Wrong price calculation when untrading an item -Bug #521: MWGui::InventoryWindow creates a duplicate player actor at the origin -Bug #524: Beast races are able to wear shoes -Bug #527: Background music fails to play -Bug #533: The arch at Gnisis entrance is not displayed -Bug #534: Terrain gets its correct shape only some time after the cell is loaded -Bug #536: The same entry can be added multiple times to the journal -Bug #539: Race selection is broken -Bug #544: Terrain normal map corrupt when the map is rendered -Feature #39: Video Playback -Feature #151: ^-escape sequences in text output -Feature #392: Add AI related script functions -Feature #456: Determine required ini fallback values and adjust the ini importer accordingly -Feature #460: Experimental DirArchives improvements -Feature #540: Execute scripts of objects in containers/inventories in active cells -Task #401: Review GMST fixing -Task #453: Unify case smashing/folding -Task #512: Rewrite utf8 component - -0.20.0 - -Bug #366: Changing the player's race during character creation does not change the look of the player character -Bug #430: Teleporting and using loading doors linking within the same cell reloads the cell -Bug #437: Stop animations when paused -Bug #438: Time displays as "0 a.m." when it should be "12 a.m." -Bug #439: Text in "name" field of potion/spell creation window is persistent -Bug #440: Starting date at a new game is off by one day -Bug #442: Console window doesn't close properly sometimes -Bug #448: Do not break container window formatting when item names are very long -Bug #458: Topics sometimes not automatically added to known topic list -Bug #476: Auto-Moving allows player movement after using DisablePlayerControls -Bug #478: After sleeping in a bed the rest dialogue window opens automtically again -Bug #492: On creating potions the ingredients are removed twice -Feature #63: Mercantile skill -Feature #82: Persuasion Dialogue -Feature #219: Missing dialogue filters/functions -Feature #369: Add a FailedAction -Feature #377: Select head/hair on character creation -Feature #391: Dummy AI package classes -Feature #435: Global Map, 2nd Layer -Feature #450: Persuasion -Feature #457: Add more script instructions -Feature #474: update the global variable pcrace when the player's race is changed -Task #158: Move dynamically generated classes from Player class to World Class -Task #159: ESMStore rework and cleanup -Task #163: More Component Namespace Cleanup -Task #402: Move player data from MWWorld::Player to the player's NPC record -Task #446: Fix no namespace in BulletShapeLoader - -0.19.0 - -Bug #374: Character shakes in 3rd person mode near the origin -Bug #404: Gamma correct rendering -Bug #407: Shoes of St. Rilm do not work -Bug #408: Rugs has collision even if they are not supposed to -Bug #412: Birthsign menu sorted incorrectly -Bug #413: Resolutions presented multiple times in launcher -Bug #414: launcher.cfg file stored in wrong directory -Bug #415: Wrong esm order in openmw.cfg -Bug #418: Sound listener position updates incorrectly -Bug #423: wrong usage of "Version" entry in openmw.desktop -Bug #426: Do not use hardcoded splash images -Bug #431: Don't use markers for raycast -Bug #432: Crash after picking up items from an NPC -Feature #21/#95: Sleeping/resting -Feature #61: Alchemy Skill -Feature #68: Death -Feature #69/#86: Spell Creation -Feature #72/#84: Travel -Feature #76: Global Map, 1st Layer -Feature #120: Trainer Window -Feature #152: Skill Increase from Skill Books -Feature #160: Record Saving -Task #400: Review GMST access - -0.18.0 - -Bug #310: Button of the "preferences menu" are too small -Bug #361: Hand-to-hand skill is always 100 -Bug #365: NPC and creature animation is jerky; Characters float around when they are not supposed to -Bug #372: playSound3D uses original coordinates instead of current coordinates. -Bug #373: Static OGRE build faulty -Bug #375: Alt-tab toggle view -Bug #376: Screenshots are disable -Bug #378: Exception when drinking self-made potions -Bug #380: Cloth visibility problem -Bug #384: Weird character on doors tooltip. -Bug #398: Some objects do not collide in MW, but do so in OpenMW -Feature #22: Implement level-up -Feature #36: Hide Marker -Feature #88: Hotkey Window -Feature #91: Level-Up Dialogue -Feature #118: Keyboard and Mouse-Button bindings -Feature #119: Spell Buying Window -Feature #133: Handle resources across multiple data directories -Feature #134: Generate a suitable default-value for --data-local -Feature #292: Object Movement/Creation Script Instructions -Feature #340: AIPackage data structures -Feature #356: Ingredients use -Feature #358: Input system rewrite -Feature #370: Target handling in actions -Feature #379: Door markers on the local map -Feature #389: AI framework -Feature #395: Using keys to open doors / containers -Feature #396: Loading screens -Feature #397: Inventory avatar image and race selection head preview -Task #339: Move sounds into Action - -0.17.0 - -Bug #225: Valgrind reports about 40MB of leaked memory -Bug #241: Some physics meshes still don't match -Bug #248: Some textures are too dark -Bug #300: Dependency on proprietary CG toolkit -Bug #302: Some objects don't collide although they should -Bug #308: Freeze in Balmora, Meldor: Armorer -Bug #313: openmw without a ~/.config/openmw folder segfault. -Bug #317: adding non-existing spell via console locks game -Bug #318: Wrong character normals -Bug #341: Building with Ogre Debug libraries does not use debug version of plugins -Bug #347: Crash when running openmw with --start="XYZ" -Bug #353: FindMyGUI.cmake breaks path on Windows -Bug #359: WindowManager throws exception at destruction -Bug #364: Laggy input on OS X due to bug in Ogre's event pump implementation -Feature #33: Allow objects to cross cell-borders -Feature #59: Dropping Items (replaced stopgap implementation with a proper one) -Feature #93: Main Menu -Feature #96/329/330/331/332/333: Player Control -Feature #180: Object rotation and scaling. -Feature #272: Incorrect NIF material sharing -Feature #314: Potion usage -Feature #324: Skill Gain -Feature #342: Drain/fortify dynamic stats/attributes magic effects -Feature #350: Allow console only script instructions -Feature #352: Run scripts in console on startup -Task #107: Refactor mw*-subsystems -Task #325: Make CreatureStats into a class -Task #345: Use Ogre's animation system -Task #351: Rewrite Action class to support automatic sound playing - -0.16.0 - -Bug #250: OpenMW launcher erratic behaviour -Bug #270: Crash because of underwater effect on OS X -Bug #277: Auto-equipping in some cells not working -Bug #294: Container GUI ignores disabled inventory menu -Bug #297: Stats review dialog shows all skills and attribute values as 0 -Bug #298: MechanicsManager::buildPlayer does not remove previous bonuses -Bug #299: Crash in World::disable -Bug #306: Non-existent ~/.config/openmw "crash" the launcher. -Bug #307: False "Data Files" location make the launcher "crash" -Feature #81: Spell Window -Feature #85: Alchemy Window -Feature #181: Support for x.y script syntax -Feature #242: Weapon and Spell icons -Feature #254: Ingame settings window -Feature #293: Allow "stacking" game modes -Feature #295: Class creation dialog tooltips -Feature #296: Clicking on the HUD elements should show/hide the respective window -Feature #301: Direction after using a Teleport Door -Feature #303: Allow object selection in the console -Feature #305: Allow the use of = as a synonym for == -Feature #312: Compensation for slow object access in poorly written Morrowind.esm scripts -Task #176: Restructure enabling/disabling of MW-references -Task #283: Integrate ogre.cfg file in settings file -Task #290: Auto-Close MW-reference related GUI windows - -0.15.0 - -Bug #5: Physics reimplementation (fixes various issues) -Bug #258: Resizing arrow's background is not transparent -Bug #268: Widening the stats window in X direction causes layout problems -Bug #269: Topic pane in dialgoue window is too small for some longer topics -Bug #271: Dialog choices are sorted incorrectly -Bug #281: The single quote character is not rendered on dialog windows -Bug #285: Terrain not handled properly in cells that are not predefined -Bug #289: Dialogue filter isn't doing case smashing/folding for item IDs -Feature #15: Collision with Terrain -Feature #17: Inventory-, Container- and Trade-Windows -Feature #44: Floating Labels above Focussed Objects -Feature #80: Tooltips -Feature #83: Barter Dialogue -Feature #90: Book and Scroll Windows -Feature #156: Item Stacking in Containers -Feature #213: Pulsating lights -Feature #218: Feather & Burden -Feature #256: Implement magic effect bookkeeping -Feature #259: Add missing information to Stats window -Feature #260: Correct case for dialogue topics -Feature #280: GUI texture atlasing -Feature #291: Ability to use GMST strings from GUI layout files -Task #255: Make MWWorld::Environment into a singleton - -0.14.0 - -Bug #1: Meshes rendered with wrong orientation -Bug #6/Task #220: Picking up small objects doesn't always work -Bug #127: tcg doesn't work -Bug #178: Compablity problems with Ogre 1.8.0 RC 1 -Bug #211: Wireframe mode (toggleWireframe command) should not apply to Console & other UI -Bug #227: Terrain crashes when moving away from predefined cells -Bug #229: On OS X Launcher cannot launch game if path to binary contains spaces -Bug #235: TGA texture loading problem -Bug #246: wireframe mode does not work in water -Feature #8/#232: Water Rendering -Feature #13: Terrain Rendering -Feature #37: Render Path Grid -Feature #66: Factions -Feature #77: Local Map -Feature #78: Compass/Mini-Map -Feature #97: Render Clothing/Armour -Feature #121: Window Pinning -Feature #205: Auto equip -Feature #217: Contiainer should track changes to its content -Feature #221: NPC Dialogue Window Enhancements -Feature #233: Game settings manager -Feature #240: Spell List and selected spell (no GUI yet) -Feature #243: Draw State -Task #113: Morrowind.ini Importer -Task #215: Refactor the sound code -Task #216: Update MyGUI - -0.13.0 - -Bug #145: Fixed sound problems after cell change -Bug #179: Pressing space in console triggers activation -Bug #186: CMake doesn't use the debug versions of Ogre libraries on Linux -Bug #189: ASCII 16 character added to console on it's activation on Mac OS X -Bug #190: Case Folding fails with music files -Bug #192: Keypresses write Text into Console no matter which gui element is active -Bug #196: Collision shapes out of place -Bug #202: ESMTool doesn't not work with localised ESM files anymore -Bug #203: Torch lights only visible on short distance -Bug #207: Ogre.log not written -Bug #209: Sounds do not play -Bug #210: Ogre crash at Dren plantation -Bug #214: Unsupported file format version -Bug #222: Launcher is writing openmw.cfg file to wrong location -Feature #9: NPC Dialogue Window -Feature #16/42: New sky/weather implementation -Feature #40: Fading -Feature #48: NPC Dialogue System -Feature #117: Equipping Items (backend only, no GUI yet, no rendering of equipped items yet) -Feature #161: Load REC_PGRD records -Feature #195: Wireframe-mode -Feature #198/199: Various sound effects -Feature #206: Allow picking data path from launcher if non is set -Task #108: Refactor window manager class -Task #172: Sound Manager Cleanup -Task #173: Create OpenEngine systems in the appropriate manager classes -Task #184: Adjust MSVC and gcc warning levels -Task #185: RefData rewrite -Task #201: Workaround for transparency issues -Task #208: silenced esm_reader.hpp warning - -0.12.0 - -Bug #154: FPS Drop -Bug #169: Local scripts continue running if associated object is deleted -Bug #174: OpenMW fails to start if the config directory doesn't exist -Bug #187: Missing lighting -Bug #188: Lights without a mesh are not rendered -Bug #191: Taking screenshot causes crash when running installed -Feature #28: Sort out the cell load problem -Feature #31: Allow the player to move away from pre-defined cells -Feature #35: Use alternate storage location for modified object position -Feature #45: NPC animations -Feature #46: Creature Animation -Feature #89: Basic Journal Window -Feature #110: Automatically pick up the path of existing MW-installations -Feature #183: More FPS display settings -Task #19: Refactor engine class -Task #109/Feature #162: Automate Packaging -Task #112: Catch exceptions thrown in input handling functions -Task #128/#168: Cleanup Configuration File Handling -Task #131: NPC Activation doesn't work properly -Task #144: MWRender cleanup -Task #155: cmake cleanup - -0.11.1 - -Bug #2: Resources loading doesn't work outside of bsa files -Bug #3: GUI does not render non-English characters -Bug #7: openmw.cfg location doesn't match -Bug #124: The TCL alias for ToggleCollision is missing. -Bug #125: Some command line options can't be used from a .cfg file -Bug #126: Toggle-type script instructions are less verbose compared with original MW -Bug #130: NPC-Record Loading fails for some NPCs -Bug #167: Launcher sets invalid parameters in ogre config -Feature #10: Journal -Feature #12: Rendering Optimisations -Feature #23: Change Launcher GUI to a tabbed interface -Feature #24: Integrate the OGRE settings window into the launcher -Feature #25: Determine openmw.cfg location (Launcher) -Feature #26: Launcher Profiles -Feature #79: MessageBox -Feature #116: Tab-Completion in Console -Feature #132: --data-local and multiple --data -Feature #143: Non-Rendering Performance-Optimisations -Feature #150: Accessing objects in cells via ID does only work for objects with all lower case IDs -Feature #157: Version Handling -Task #14: Replace tabs with 4 spaces -Task #18: Move components from global namespace into their own namespace -Task #123: refactor header files in components/esm - -0.10.0 - -* NPC dialogue window (not functional yet) -* Collisions with objects -* Refactor the PlayerPos class -* Adjust file locations -* CMake files and test linking for Bullet -* Replace Ogre raycasting test for activation with something more precise -* Adjust player movement according to collision results -* FPS display -* Various Portability Improvements -* Mac OS X support is back! - -0.9.0 - -* Exterior cells loading, unloading and management -* Character Creation GUI -* Character creation -* Make cell names case insensitive when doing internal lookups -* Music player -* NPCs rendering - -0.8.0 - -* GUI -* Complete and working script engine -* In game console -* Sky rendering -* Sound and music -* Tons of smaller stuff - -0.7.0 - -* This release is a complete rewrite in C++. -* All D code has been culled, and all modules have been rewritten. -* The game is now back up to the level of rendering interior cells and moving around, but physics, sound, GUI, and scripting still remain to be ported from the old codebase. - -0.6.0 - -* Coded a GUI system using MyGUI -* Skinned MyGUI to look like Morrowind (work in progress) -* Integrated the Monster script engine -* Rewrote some functions into script code -* Very early MyGUI < > Monster binding -* Fixed Windows sound problems (replaced old openal32.dll) - -0.5.0 - -* Collision detection with Bullet -* Experimental walk & fall character physics -* New key bindings: - * t toggle physics mode (walking, flying, ghost), - * n night eye, brightens the scene -* Fixed incompatability with DMD 1.032 and newer compilers -* * (thanks to tomqyp) -* Various minor changes and updates - -0.4.0 - -* Switched from Audiere to OpenAL -* * (BIG thanks to Chris Robinson) -* Added complete Makefile (again) as a alternative build tool -* More realistic lighting (thanks again to Chris Robinson) -* Various localization fixes tested with Russian and French versions -* Temporary workaround for the Unicode issue: invalid UTF displayed as '?' -* Added ns option to disable sound, for debugging -* Various bug fixes -* Cosmetic changes to placate gdc Wall - -0.3.0 - -* Built and tested on Windows XP -* Partial support for FreeBSD (exceptions do not work) -* You no longer have to download Monster separately -* Made an alternative for building without DSSS (but DSSS still works) -* Renamed main program from 'morro' to 'openmw' -* Made the config system more robust -* Added oc switch for showing Ogre config window on startup -* Removed some config files, these are auto generated when missing. -* Separated plugins.cfg into linux and windows versions. -* Updated Makefile and sources for increased portability -* confirmed to work against OIS 1.0.0 (Ubuntu repository package) - -0.2.0 - -* Compiles with gdc -* Switched to DSSS for building D code -* Includes the program esmtool - -0.1.0 - -first release +OpenMW: A reimplementation of The Elder Scrolls III: Morrowind + +OpenMW is an attempt at recreating the engine for the popular role-playing game +Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. + +Version: 0.28.0 +License: GPL (see GPL3.txt for more information) +Website: http://www.openmw.org + +Font Licenses: +EBGaramond-Regular.ttf: OFL (see OFL.txt for more information) +DejaVuLGCSansMono.ttf: custom (see DejaVu Font License.txt for more information) + + + +INSTALLATION + +Windows: +Run the installer. + +Linux: +Ubuntu (and most others) +Download the .deb file and install it in the usual way. + +Arch Linux +There's an OpenMW package available in the [community] Repository: +https://www.archlinux.org/packages/?sort=&q=openmw + +OS X: +Open DMG file, copy OpenMW folder anywhere, for example in /Applications + +BUILD FROM SOURCE + +https://wiki.openmw.org/index.php?title=Development_Environment_Setup + + +THE DATA PATH + +The data path tells OpenMW where to find your Morrowind files. If you run the launcher, OpenMW should be able to +pick up the location of these files on its own, if both Morrowind and OpenMW are installed properly +(installing Morrowind under WINE is considered a proper install). + +COMMAND LINE OPTIONS + +Syntax: openmw +Allowed options: + --help print help message + --version print version information and quit + --data arg (=data) set data directories (later directories + have higher priority) + --data-local arg set local data directory (highest + priority) + --fallback-archive arg (=fallback-archive) + set fallback BSA archives (later + archives have higher priority) + --resources arg (=resources) set resources directory + --start arg (=Beshara) set initial cell + --content arg content file(s): esm/esp, or + omwgame/omwaddon + --anim-verbose [=arg(=1)] (=0) output animation indices files + --no-sound [=arg(=1)] (=0) disable all sounds + --script-verbose [=arg(=1)] (=0) verbose script output + --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue + scripts) at startup + --script-console [=arg(=1)] (=0) enable console-only script + functionality + --script-run arg select a file containing a list of + console commands that is executed on + startup + --script-warn [=arg(=1)] (=1) handling of warnings when compiling + scripts + 0 - ignore warning + 1 - show warning but consider script as + correctly compiled anyway + 2 - treat warnings as errors + --skip-menu [=arg(=1)] (=0) skip main menu on game startup + --fs-strict [=arg(=1)] (=0) strict file system handling (no case + folding) + --encoding arg (=win1252) Character encoding used in OpenMW game + messages: + + win1250 - Central and Eastern European + such as Polish, Czech, Slovak, + Hungarian, Slovene, Bosnian, Croatian, + Serbian (Latin script), Romanian and + Albanian languages + + win1251 - Cyrillic alphabet such as + Russian, Bulgarian, Serbian Cyrillic + and other languages + + win1252 - Western European (Latin) + alphabet, used by default + --fallback arg fallback values + --no-grab Don't grab mouse cursor + --activate-dist arg (=-1) activation distance override + +CHANGELOG + +0.29.0 + +Bug #556: Video soundtrack not played when music volume is set to zero +Bug #829: OpenMW uses up all available vram, when playing for extended time +Bug #848: Wrong amount of footsteps playing in 1st person +Bug #888: Ascended Sleepers have movement issues +Bug #892: Explicit references are allowed on all script functions +Bug #999: Graphic Herbalism (mod): sometimes doesn't activate properly +Bug #1009: Lake Fjalding AI related slowdown. +Bug #1041: Music playback issues on OS X >= 10.9 +Bug #1043: No message box when advancing skill "Speechcraft" while in dialog window +Bug #1060: Some message boxes are cut off at the bottom +Bug #1062: Bittercup script does not work ('end' variable) +Bug #1074: Inventory paperdoll obscures armour rating +Bug #1077: Message after killing an essential NPC disappears too fast +Bug #1078: "Clutterbane" shows empty charge bar +Bug #1083: UndoWerewolf fails +Bug #1088: Better Clothes Bloodmoon Plus 1.5 by Spirited Treasure pants are not rendered +Bug #1090: Start scripts fail when going to a non-predefined cell +Bug #1091: Crash: Assertion `!q.isNaN() && "Invalid orientation supplied as parameter"' failed. +Bug #1093: Weapons of aggressive NPCs are invisible after you exit and re-enter interior +Bug #1105: Magicka is depleted when using uncastable spells +Bug #1106: Creatures should be able to run +Bug #1107: TR cliffs have way too huge collision boxes in OpenMW +Bug #1109: Cleaning True Light and Darkness with Tes3cmd makes Addamasartus , Zenarbael and Yasamsi flooded. +Bug #1114: Bad output for desktop-file-validate on openmw.desktop (and opencs.desktop) +Bug #1115: Memory leak when spying on Fargoth +Bug #1137: Script execution fails (drenSlaveOwners script) +Bug #1143: Mehra Milo quest (vivec informants) is broken +Bug #1145: Issues with moving gold between inventory and containers +Bug #1146: Issues with picking up stacks of gold +Bug #1147: Dwemer Crossbows are held incorrectly +Bug #1158: Armor rating should always stay below inventory mannequin +Bug #1159: Quick keys can be set during character generation +Bug #1160: Crash on equip lockpick when +Bug #1167: Editor: Referenceables are not correctly loaded when dealing with more than one content file +Feature #30: Loading/Saving (still missing a few parts) +Feature #101: AI Package: Activate +Feature #103: AI Package: Follow, FollowCell +Feature #138: Editor: Drag & Drop +Feature #428: Player death +Feature #505: Editor: Record Cloning +Feature #701: Levelled creatures +Feature #708: Improved Local Variable handling +Feature #709: Editor: Script verifier +Feature #764: Missing journal backend features +Feature #777: Creature weapons/shields +Feature #789: Editor: Referenceable record verifier +Feature #924: Load/Save GUI (still missing loading screen and progress bars) +Feature #946: Knockdown +Feature #947: Decrease fatigue when running, swimming and attacking +Feature #956: Melee Combat: Blocking +Feature #957: Area magic +Feature #960: Combat/AI combat for creatures +Feature #962: Combat-Related AI instructions +Feature #1075: Damage/Restore skill/attribute magic effects +Feature #1076: Soultrap magic effect +Feature #1081: Disease contraction +Feature #1086: Blood particles +Feature #1092: Interrupt resting +Feature #1101: Inventory equip scripts +Feature #1116: Version/Build number in Launcher window +Feature #1119: Resistance/weakness to normal weapons magic effect +Feature #1123: Slow Fall magic effect +Feature #1130: Auto-calculate spells +Feature #1164: Editor: Case-insensitive sorting in tables + +0.28.0 + +Bug #399: Inventory changes are not visible immediately +Bug #417: Apply weather instantly when teleporting +Bug #566: Global Map position marker not updated for interior cells +Bug #712: Looting corpse delay +Bug #716: Problem with the "Vurt's Ascadian Isles Mod" mod +Bug #805: Two TR meshes appear black (v0.24RC) +Bug #841: Third-person activation distance taken from camera rather than head +Bug #845: NPCs hold torches during the day +Bug #855: Vvardenfell Visages Volume I some hairs don´t appear since 0,24 +Bug #856: Maormer race by Mac Kom - The heads are way up +Bug #864: Walk locks during loading in 3rd person +Bug #871: active weapon/magic item icon is not immediately made blank if item is removed during dialog +Bug #882: Hircine's Ring doesn't always work +Bug #909: [Tamriel Rebuilt] crashes in Akamora +Bug #922: Launcher writing merged openmw.cfg files +Bug #943: Random magnitude should be calculated per effect +Bug #948: Negative fatigue level should be allowed +Bug #949: Particles in world space +Bug #950: Hard crash on x64 Linux running --new-game (on startup) +Bug #951: setMagicka and setFatigue have no effect +Bug #954: Problem with equipping inventory items when using a keyboard shortcut +Bug #955: Issues with equipping torches +Bug #966: Shield is visible when casting spell +Bug #967: Game crashes when equipping silver candlestick +Bug #970: Segmentation fault when starting at Bal Isra +Bug #977: Pressing down key in console doesn't go forward in history +Bug #979: Tooltip disappears when changing inventory +Bug #980: Barter: item category is remembered, but not shown +Bug #981: Mod: replacing model has wrong position/orientation +Bug #982: Launcher: Addon unchecking is not saved +Bug #983: Fix controllers to affect objects attached to the base node +Bug #985: Player can talk to NPCs who are in combat +Bug #989: OpenMW crashes when trying to include mod with capital .ESP +Bug #991: Merchants equip items with harmful constant effect enchantments +Bug #994: Don't cap skills/attributes when set via console +Bug #998: Setting the max health should also set the current health +Bug #1005: Torches are visible when casting spells and during hand to hand combat. +Bug #1006: Many NPCs have 0 skill +Bug #1007: Console fills up with text +Bug #1013: Player randomly loses health or dies +Bug #1014: Persuasion window is not centered in maximized window +Bug #1015: Player status window scroll state resets on status change +Bug #1016: Notification window not big enough for all skill level ups +Bug #1020: Saved window positions are not rescaled appropriately on resolution change +Bug #1022: Messages stuck permanently on screen when they pile up +Bug #1023: Journals doesn't open +Bug #1026: Game loses track of torch usage. +Bug #1028: Crash on pickup of jug in Unexplored Shipwreck, Upper level +Bug #1029: Quick keys menu: Select compatible replacement when tool used up +Bug #1042: TES3 header data wrong encoding +Bug #1045: OS X: deployed OpenCS won't launch +Bug #1046: All damaged weaponry is worth 1 gold +Bug #1048: Links in "locked" dialogue are still clickable +Bug #1052: Using color codes when naming your character actually changes the name's color +Bug #1054: Spell effects not visible in front of water +Bug #1055: Power-Spell animation starts even though you already casted it that day +Bug #1059: Cure disease potion removes all effects from player, even your race bonus and race ability +Bug #1063: Crash upon checking out game start ship area in Seyda Neen +Bug #1064: openmw binaries link to unnecessary libraries +Bug #1065: Landing from a high place in water still causes fall damage +Bug #1072: Drawing weapon increases torch brightness +Bug #1073: Merchants sell stacks of gold +Feature #43: Visuals for Magic Effects +Feature #51: Ranged Magic +Feature #52: Touch Range Magic +Feature #53: Self Range Magic +Feature #54: Spell Casting +Feature #70: Vampirism +Feature #100: Combat AI +Feature #171: Implement NIF record NiFlipController +Feature #410: Window to restore enchanted item charge +Feature #647: Enchanted item glow +Feature #723: Invisibility/Chameleon magic effects +Feature #737: Resist Magicka magic effect +Feature #758: GetLOS +Feature #926: Editor: Info-Record tables +Feature #958: Material controllers +Feature #959: Terrain bump, specular, & parallax mapping +Feature #990: Request: unlock mouse when in any menu +Feature #1018: Do not allow view mode switching while performing an action +Feature #1027: Vertex morph animation (NiGeomMorpherController) +Feature #1031: Handle NiBillboardNode +Feature #1051: Implement NIF texture slot DarkTexture +Task #873: Unify OGRE initialisation + +0.27.0 + +Bug #597: Assertion `dialogue->mId == id' failed in esmstore.cpp +Bug #794: incorrect display of decimal numbers +Bug #840: First-person sneaking camera height +Bug #887: Ambient sounds playing while paused +Bug #902: Problems with Polish character encoding +Bug #907: Entering third person using the mousewheel is possible even if it's impossible using the key +Bug #910: Some CDs not working correctly with Unshield installer +Bug #917: Quick character creation plugin does not work +Bug #918: Fatigue does not refill +Bug #919: The PC falls dead in Beshara - OpenMW nightly Win64 (708CDE2) +Feature #57: Acrobatics Skill +Feature #462: Editor: Start Dialogue +Feature #546: Modify ESX selector to handle new content file scheme +Feature #588: Editor: Adjust name/path of edited content files +Feature #644: Editor: Save +Feature #710: Editor: Configure script compiler context +Feature #790: God Mode +Feature #881: Editor: Allow only one instance of OpenCS +Feature #889: Editor: Record filtering +Feature #895: Extinguish torches +Feature #898: Breath meter enhancements +Feature #901: Editor: Default record filter +Feature #913: Merge --master and --plugin switches + +0.26.0 + +Bug #274: Inconsistencies in the terrain +Bug #557: Already-dead NPCs do not equip clothing/items. +Bug #592: Window resizing +Bug #612: [Tamriel Rebuilt] Missing terrain (South of Tel Oren) +Bug #664: Heart of lorkhan acts like a dead body (container) +Bug #767: Wonky ramp physics & water +Bug #780: Swimming out of water +Bug #792: Wrong ground alignment on actors when no clipping +Bug #796: Opening and closing door sound issue +Bug #797: No clipping hinders opening and closing of doors +Bug #799: sliders in enchanting window +Bug #838: Pressing key during startup procedure freezes the game +Bug #839: Combat/magic stances during character creation +Bug #843: [Tribunal] Dark Brotherhood assassin appears without equipment +Bug #844: Resting "until healed" option given even with full stats +Bug #846: Equipped torches are invisible. +Bug #847: Incorrect formula for autocalculated NPC initial health +Bug #850: Shealt weapon sound plays when leaving magic-ready stance +Bug #852: Some boots do not produce footstep sounds +Bug #860: FPS bar misalignment +Bug #861: Unable to print screen +Bug #863: No sneaking and jumping at the same time +Bug #866: Empty variables in [Movies] section of Morrowind.ini gets imported into OpenMW.cfg as blank fallback option and crashes game on start. +Bug #867: Dancing girls in "Suran, Desele's House of Earthly Delights" don't dance. +Bug #868: Idle animations are repeated +Bug #874: Underwater swimming close to the ground is jerky +Bug #875: Animation problem while swimming on the surface and looking up +Bug #876: Always a starting upper case letter in the inventory +Bug #878: Active spell effects don't update the layout properly when ended +Bug #891: Cell 24,-12 (Tamriel Rebuilt) crashes on load +Bug #896: New game sound issue +Feature #49: Melee Combat +Feature #71: Lycanthropy +Feature #393: Initialise MWMechanics::AiSequence from ESM::AIPackageList +Feature #622: Multiple positions for inventory window +Feature #627: Drowning +Feature #786: Allow the 'Activate' key to close the countdialog window +Feature #798: Morrowind installation via Launcher (Linux/Max OS only) +Feature #851: First/Third person transitions with mouse wheel +Task #689: change PhysicActor::enableCollisions +Task #707: Reorganise Compiler + +0.25.0 + +Bug #411: Launcher crash on OS X < 10.8 +Bug #604: Terrible performance drop in the Census and Excise Office. +Bug #676: Start Scripts fail to load +Bug #677: OpenMW does not accept script names with - +Bug #766: Extra space in front of topic links +Bug #793: AIWander Isn't Being Passed The Repeat Parameter +Bug #795: Sound playing with drawn weapon and crossing cell-border +Bug #800: can't select weapon for enchantment +Bug #801: Player can move while over-encumbered +Bug #802: Dead Keys not working +Bug #808: mouse capture +Bug #809: ini Importer does not work without an existing cfg file +Bug #812: Launcher will run OpenMW with no ESM or ESP selected +Bug #813: OpenMW defaults to Morrowind.ESM with no ESM or ESP selected +Bug #817: Dead NPCs and Creatures still have collision boxes +Bug #820: Incorrect sorting of answers (Dialogue) +Bug #826: mwinimport dumps core when given an unknown parameter +Bug #833: getting stuck in door +Bug #835: Journals/books not showing up properly. +Feature #38: SoundGen +Feature #105: AI Package: Wander +Feature #230: 64-bit compatibility for OS X +Feature #263: Hardware mouse cursors +Feature #449: Allow mouse outside of window while paused +Feature #736: First person animations +Feature #750: Using mouse wheel in third person mode +Feature #822: Autorepeat for slider buttons + +0.24.0 + +Bug #284: Book's text misalignment +Bug #445: Camera able to get slightly below floor / terrain +Bug #582: Seam issue in Red Mountain +Bug #632: Journal Next Button shows white square +Bug #653: IndexedStore ignores index +Bug #694: Parser does not recognize float values starting with . +Bug #699: Resource handling broken with Ogre 1.9 trunk +Bug #718: components/esm/loadcell is using the mwworld subsystem +Bug #729: Levelled item list tries to add nonexistent item +Bug #730: Arrow buttons in the settings menu do not work. +Bug #732: Erroneous behavior when binding keys +Bug #733: Unclickable dialogue topic +Bug #734: Book empty line problem +Bug #738: OnDeath only works with implicit references +Bug #740: Script compiler fails on scripts with special names +Bug #742: Wait while no clipping +Bug #743: Problem with changeweather console command +Bug #744: No wait dialogue after starting a new game +Bug #748: Player is not able to unselect objects with the console +Bug #751: AddItem should only spawn a message box when called from dialogue +Bug #752: The enter button has several functions in trade and looting that is not impelemted. +Bug #753: Fargoth's Ring Quest Strange Behavior +Bug #755: Launcher writes duplicate lines into settings.cfg +Bug #759: Second quest in mages guild does not work +Bug #763: Enchantment cast cost is wrong +Bug #770: The "Take" and "Close" buttons in the scroll GUI are stretched incorrectly +Bug #773: AIWander Isn't Being Passed The Correct idle Values +Bug #778: The journal can be opened at the start of a new game +Bug #779: Divayth Fyr starts as dead +Bug #787: "Batch count" on detailed FPS counter gets cut-off +Bug #788: chargen scroll layout does not match vanilla +Feature #60: Atlethics Skill +Feature #65: Security Skill +Feature #74: Interaction with non-load-doors +Feature #98: Render Weapon and Shield +Feature #102: AI Package: Escort, EscortCell +Feature #182: Advanced Journal GUI +Feature #288: Trading enhancements +Feature #405: Integrate "new game" into the menu +Feature #537: Highlight dialogue topic links +Feature #658: Rotate, RotateWorld script instructions and local rotations +Feature #690: Animation Layering +Feature #722: Night Eye/Blind magic effects +Feature #735: Move, MoveWorld script instructions. +Feature #760: Non-removable corpses + +0.23.0 + +Bug #522: Player collides with placeable items +Bug #553: Open/Close sounds played when accessing main menu w/ Journal Open +Bug #561: Tooltip word wrapping delay +Bug #578: Bribing works incorrectly +Bug #601: PositionCell fails on negative coordinates +Bug #606: Some NPCs hairs not rendered with Better Heads addon +Bug #609: Bad rendering of bone boots +Bug #613: Messagebox causing assert to fail +Bug #631: Segfault on shutdown +Bug #634: Exception when talking to Calvus Horatius in Mournhold, royal palace courtyard +Bug #635: Scale NPCs depending on race +Bug #643: Dialogue Race select function is inverted +Bug #646: Twohanded weapons don't work properly +Bug #654: Crash when dropping objects without a collision shape +Bug #655/656: Objects that were disabled or deleted (but not both) were added to the scene when re-entering a cell +Bug #660: "g" in "change" cut off in Race Menu +Bug #661: Arrille sells me the key to his upstairs room +Bug #662: Day counter starts at 2 instead of 1 +Bug #663: Cannot select "come unprepared" topic in dialog with Dagoth Ur +Bug #665: Pickpocket -> "Grab all" grabs all NPC inventory, even not listed in container window. +Bug #666: Looking up/down problem +Bug #667: Active effects border visible during loading +Bug #669: incorrect player position at new game start +Bug #670: race selection menu: sex, face and hair left button not totally clickable +Bug #671: new game: player is naked +Bug #674: buying or selling items doesn't change amount of gold +Bug #675: fatigue is not set to its maximum when starting a new game +Bug #678: Wrong rotation order causes RefData's rotation to be stored incorrectly +Bug #680: different gold coins in Tel Mara +Bug #682: Race menu ignores playable flag for some hairs and faces +Bug #685: Script compiler does not accept ":" after a function name +Bug #688: dispose corpse makes cross-hair to disappear +Bug #691: Auto equipping ignores equipment conditions +Bug #692: OpenMW doesnt load "loose file" texture packs that places resources directly in data folder +Bug #696: Draugr incorrect head offset +Bug #697: Sail transparency issue +Bug #700: "On the rocks" mod does not load its UV coordinates correctly. +Bug #702: Some race mods don't work +Bug #711: Crash during character creation +Bug #715: Growing Tauryon +Bug #725: Auto calculate stats +Bug #728: Failure to open container and talk dialogue +Bug #731: Crash with Mush-Mere's "background" topic +Feature #55/657: Item Repairing +Feature #62/87: Enchanting +Feature #99: Pathfinding +Feature #104: AI Package: Travel +Feature #129: Levelled items +Feature #204: Texture animations +Feature #239: Fallback-Settings +Feature #535: Console object selection improvements +Feature #629: Add levelup description in levelup layout dialog +Feature #630: Optional format subrecord in (tes3) header +Feature #641: Armor rating +Feature #645: OnDeath script function +Feature #683: Companion item UI +Feature #698: Basic Particles +Task #648: Split up components/esm/loadlocks +Task #695: mwgui cleanup + +0.22.0 + +Bug #311: Potential infinite recursion in script compiler +Bug #355: Keyboard repeat rate (in Xorg) are left disabled after game exit. +Bug #382: Weird effect in 3rd person on water +Bug #387: Always use detailed shape for physics raycasts +Bug #420: Potion/ingredient effects do not stack +Bug #429: Parts of dwemer door not picked up correctly for activation/tooltips +Bug #434/Bug #605: Object movement between cells not properly implemented +Bug #502: Duplicate player collision model at origin +Bug #509: Dialogue topic list shifts inappropriately +Bug #513: Sliding stairs +Bug #515: Launcher does not support non-latin strings +Bug #525: Race selection preview camera wrong position +Bug #526: Attributes / skills should not go below zero +Bug #529: Class and Birthsign menus options should be preselected +Bug #530: Lock window button graphic missing +Bug #532: Missing map menu graphics +Bug #545: ESX selector does not list ESM files properly +Bug #547: Global variables of type short are read incorrectly +Bug #550: Invisible meshes collision and tooltip +Bug #551: Performance drop when loading multiple ESM files +Bug #552: Don't list CG in options if it is not available +Bug #555: Character creation windows "OK" button broken +Bug #558: Segmentation fault when Alt-tabbing with console opened +Bug #559: Dialog window should not be available before character creation is finished +Bug #560: Tooltip borders should be stretched +Bug #562: Sound should not be played when an object cannot be picked up +Bug #565: Water animation speed + timescale +Bug #572: Better Bodies' textures don't work +Bug #573: OpenMW doesn't load if TR_Mainland.esm is enabled (Tamriel Rebuilt mod) +Bug #574: Moving left/right should not cancel auto-run +Bug #575: Crash entering the Chamber of Song +Bug #576: Missing includes +Bug #577: Left Gloves Addon causes ESMReader exception +Bug #579: Unable to open container "Kvama Egg Sack" +Bug #581: Mimicking vanilla Morrowind water +Bug #583: Gender not recognized +Bug #586: Wrong char gen behaviour +Bug #587: "End" script statements with spaces don't work +Bug #589: Closing message boxes by pressing the activation key +Bug #590: Ugly Dagoth Ur rendering +Bug #591: Race selection issues +Bug #593: Persuasion response should be random +Bug #595: Footless guard +Bug #599: Waterfalls are invisible from a certain distance +Bug #600: Waterfalls rendered incorrectly, cut off by water +Bug #607: New beast bodies mod crashes +Bug #608: Crash in cell "Mournhold, Royal Palace" +Bug #611: OpenMW doesn't find some of textures used in Tamriel Rebuilt +Bug #613: Messagebox causing assert to fail +Bug #615: Meshes invisible from above water +Bug #617: Potion effects should be hidden until discovered +Bug #619: certain moss hanging from tree has rendering bug +Bug #621: Batching bloodmoon's trees +Bug #623: NiMaterialProperty alpha unhandled +Bug #628: Launcher in latest master crashes the game +Bug #633: Crash on startup: Better Heads +Bug #636: Incorrect Char Gen Menu Behavior +Feature #29: Allow ESPs and multiple ESMs +Feature #94: Finish class selection-dialogue +Feature #149: Texture Alphas +Feature #237: Run Morrowind-ini importer from launcher +Feature #286: Update Active Spell Icons +Feature #334: Swimming animation +Feature #335: Walking animation +Feature #360: Proper collision shapes for NPCs and creatures +Feature #367: Lights that behave more like original morrowind implementation +Feature #477: Special local scripting variables +Feature #528: Message boxes should close when enter is pressed under certain conditions. +Feature #543: Add bsa files to the settings imported by the ini importer +Feature #594: coordinate space and utility functions +Feature #625: Zoom in vanity mode +Task #464: Refactor launcher ESX selector into a re-usable component +Task #624: Unified implementation of type-variable sub-records + +0.21.0 + +Bug #253: Dialogs don't work for Russian version of Morrowind +Bug #267: Activating creatures without dialogue can still activate the dialogue GUI +Bug #354: True flickering lights +Bug #386: The main menu's first entry is wrong (in french) +Bug #479: Adding the spell "Ash Woe Blight" to the player causes strange attribute oscillations +Bug #495: Activation Range +Bug #497: Failed Disposition check doesn't stop a dialogue entry from being returned +Bug #498: Failing a disposition check shouldn't eliminate topics from the the list of those available +Bug #500: Disposition for most NPCs is 0/100 +Bug #501: Getdisposition command wrongly returns base disposition +Bug #506: Journal UI doesn't update anymore +Bug #507: EnableRestMenu is not a valid command - change it to EnableRest +Bug #508: Crash in Ald Daedroth Shrine +Bug #517: Wrong price calculation when untrading an item +Bug #521: MWGui::InventoryWindow creates a duplicate player actor at the origin +Bug #524: Beast races are able to wear shoes +Bug #527: Background music fails to play +Bug #533: The arch at Gnisis entrance is not displayed +Bug #534: Terrain gets its correct shape only some time after the cell is loaded +Bug #536: The same entry can be added multiple times to the journal +Bug #539: Race selection is broken +Bug #544: Terrain normal map corrupt when the map is rendered +Feature #39: Video Playback +Feature #151: ^-escape sequences in text output +Feature #392: Add AI related script functions +Feature #456: Determine required ini fallback values and adjust the ini importer accordingly +Feature #460: Experimental DirArchives improvements +Feature #540: Execute scripts of objects in containers/inventories in active cells +Task #401: Review GMST fixing +Task #453: Unify case smashing/folding +Task #512: Rewrite utf8 component + +0.20.0 + +Bug #366: Changing the player's race during character creation does not change the look of the player character +Bug #430: Teleporting and using loading doors linking within the same cell reloads the cell +Bug #437: Stop animations when paused +Bug #438: Time displays as "0 a.m." when it should be "12 a.m." +Bug #439: Text in "name" field of potion/spell creation window is persistent +Bug #440: Starting date at a new game is off by one day +Bug #442: Console window doesn't close properly sometimes +Bug #448: Do not break container window formatting when item names are very long +Bug #458: Topics sometimes not automatically added to known topic list +Bug #476: Auto-Moving allows player movement after using DisablePlayerControls +Bug #478: After sleeping in a bed the rest dialogue window opens automtically again +Bug #492: On creating potions the ingredients are removed twice +Feature #63: Mercantile skill +Feature #82: Persuasion Dialogue +Feature #219: Missing dialogue filters/functions +Feature #369: Add a FailedAction +Feature #377: Select head/hair on character creation +Feature #391: Dummy AI package classes +Feature #435: Global Map, 2nd Layer +Feature #450: Persuasion +Feature #457: Add more script instructions +Feature #474: update the global variable pcrace when the player's race is changed +Task #158: Move dynamically generated classes from Player class to World Class +Task #159: ESMStore rework and cleanup +Task #163: More Component Namespace Cleanup +Task #402: Move player data from MWWorld::Player to the player's NPC record +Task #446: Fix no namespace in BulletShapeLoader + +0.19.0 + +Bug #374: Character shakes in 3rd person mode near the origin +Bug #404: Gamma correct rendering +Bug #407: Shoes of St. Rilm do not work +Bug #408: Rugs has collision even if they are not supposed to +Bug #412: Birthsign menu sorted incorrectly +Bug #413: Resolutions presented multiple times in launcher +Bug #414: launcher.cfg file stored in wrong directory +Bug #415: Wrong esm order in openmw.cfg +Bug #418: Sound listener position updates incorrectly +Bug #423: wrong usage of "Version" entry in openmw.desktop +Bug #426: Do not use hardcoded splash images +Bug #431: Don't use markers for raycast +Bug #432: Crash after picking up items from an NPC +Feature #21/#95: Sleeping/resting +Feature #61: Alchemy Skill +Feature #68: Death +Feature #69/#86: Spell Creation +Feature #72/#84: Travel +Feature #76: Global Map, 1st Layer +Feature #120: Trainer Window +Feature #152: Skill Increase from Skill Books +Feature #160: Record Saving +Task #400: Review GMST access + +0.18.0 + +Bug #310: Button of the "preferences menu" are too small +Bug #361: Hand-to-hand skill is always 100 +Bug #365: NPC and creature animation is jerky; Characters float around when they are not supposed to +Bug #372: playSound3D uses original coordinates instead of current coordinates. +Bug #373: Static OGRE build faulty +Bug #375: Alt-tab toggle view +Bug #376: Screenshots are disable +Bug #378: Exception when drinking self-made potions +Bug #380: Cloth visibility problem +Bug #384: Weird character on doors tooltip. +Bug #398: Some objects do not collide in MW, but do so in OpenMW +Feature #22: Implement level-up +Feature #36: Hide Marker +Feature #88: Hotkey Window +Feature #91: Level-Up Dialogue +Feature #118: Keyboard and Mouse-Button bindings +Feature #119: Spell Buying Window +Feature #133: Handle resources across multiple data directories +Feature #134: Generate a suitable default-value for --data-local +Feature #292: Object Movement/Creation Script Instructions +Feature #340: AIPackage data structures +Feature #356: Ingredients use +Feature #358: Input system rewrite +Feature #370: Target handling in actions +Feature #379: Door markers on the local map +Feature #389: AI framework +Feature #395: Using keys to open doors / containers +Feature #396: Loading screens +Feature #397: Inventory avatar image and race selection head preview +Task #339: Move sounds into Action + +0.17.0 + +Bug #225: Valgrind reports about 40MB of leaked memory +Bug #241: Some physics meshes still don't match +Bug #248: Some textures are too dark +Bug #300: Dependency on proprietary CG toolkit +Bug #302: Some objects don't collide although they should +Bug #308: Freeze in Balmora, Meldor: Armorer +Bug #313: openmw without a ~/.config/openmw folder segfault. +Bug #317: adding non-existing spell via console locks game +Bug #318: Wrong character normals +Bug #341: Building with Ogre Debug libraries does not use debug version of plugins +Bug #347: Crash when running openmw with --start="XYZ" +Bug #353: FindMyGUI.cmake breaks path on Windows +Bug #359: WindowManager throws exception at destruction +Bug #364: Laggy input on OS X due to bug in Ogre's event pump implementation +Feature #33: Allow objects to cross cell-borders +Feature #59: Dropping Items (replaced stopgap implementation with a proper one) +Feature #93: Main Menu +Feature #96/329/330/331/332/333: Player Control +Feature #180: Object rotation and scaling. +Feature #272: Incorrect NIF material sharing +Feature #314: Potion usage +Feature #324: Skill Gain +Feature #342: Drain/fortify dynamic stats/attributes magic effects +Feature #350: Allow console only script instructions +Feature #352: Run scripts in console on startup +Task #107: Refactor mw*-subsystems +Task #325: Make CreatureStats into a class +Task #345: Use Ogre's animation system +Task #351: Rewrite Action class to support automatic sound playing + +0.16.0 + +Bug #250: OpenMW launcher erratic behaviour +Bug #270: Crash because of underwater effect on OS X +Bug #277: Auto-equipping in some cells not working +Bug #294: Container GUI ignores disabled inventory menu +Bug #297: Stats review dialog shows all skills and attribute values as 0 +Bug #298: MechanicsManager::buildPlayer does not remove previous bonuses +Bug #299: Crash in World::disable +Bug #306: Non-existent ~/.config/openmw "crash" the launcher. +Bug #307: False "Data Files" location make the launcher "crash" +Feature #81: Spell Window +Feature #85: Alchemy Window +Feature #181: Support for x.y script syntax +Feature #242: Weapon and Spell icons +Feature #254: Ingame settings window +Feature #293: Allow "stacking" game modes +Feature #295: Class creation dialog tooltips +Feature #296: Clicking on the HUD elements should show/hide the respective window +Feature #301: Direction after using a Teleport Door +Feature #303: Allow object selection in the console +Feature #305: Allow the use of = as a synonym for == +Feature #312: Compensation for slow object access in poorly written Morrowind.esm scripts +Task #176: Restructure enabling/disabling of MW-references +Task #283: Integrate ogre.cfg file in settings file +Task #290: Auto-Close MW-reference related GUI windows + +0.15.0 + +Bug #5: Physics reimplementation (fixes various issues) +Bug #258: Resizing arrow's background is not transparent +Bug #268: Widening the stats window in X direction causes layout problems +Bug #269: Topic pane in dialgoue window is too small for some longer topics +Bug #271: Dialog choices are sorted incorrectly +Bug #281: The single quote character is not rendered on dialog windows +Bug #285: Terrain not handled properly in cells that are not predefined +Bug #289: Dialogue filter isn't doing case smashing/folding for item IDs +Feature #15: Collision with Terrain +Feature #17: Inventory-, Container- and Trade-Windows +Feature #44: Floating Labels above Focussed Objects +Feature #80: Tooltips +Feature #83: Barter Dialogue +Feature #90: Book and Scroll Windows +Feature #156: Item Stacking in Containers +Feature #213: Pulsating lights +Feature #218: Feather & Burden +Feature #256: Implement magic effect bookkeeping +Feature #259: Add missing information to Stats window +Feature #260: Correct case for dialogue topics +Feature #280: GUI texture atlasing +Feature #291: Ability to use GMST strings from GUI layout files +Task #255: Make MWWorld::Environment into a singleton + +0.14.0 + +Bug #1: Meshes rendered with wrong orientation +Bug #6/Task #220: Picking up small objects doesn't always work +Bug #127: tcg doesn't work +Bug #178: Compablity problems with Ogre 1.8.0 RC 1 +Bug #211: Wireframe mode (toggleWireframe command) should not apply to Console & other UI +Bug #227: Terrain crashes when moving away from predefined cells +Bug #229: On OS X Launcher cannot launch game if path to binary contains spaces +Bug #235: TGA texture loading problem +Bug #246: wireframe mode does not work in water +Feature #8/#232: Water Rendering +Feature #13: Terrain Rendering +Feature #37: Render Path Grid +Feature #66: Factions +Feature #77: Local Map +Feature #78: Compass/Mini-Map +Feature #97: Render Clothing/Armour +Feature #121: Window Pinning +Feature #205: Auto equip +Feature #217: Contiainer should track changes to its content +Feature #221: NPC Dialogue Window Enhancements +Feature #233: Game settings manager +Feature #240: Spell List and selected spell (no GUI yet) +Feature #243: Draw State +Task #113: Morrowind.ini Importer +Task #215: Refactor the sound code +Task #216: Update MyGUI + +0.13.0 + +Bug #145: Fixed sound problems after cell change +Bug #179: Pressing space in console triggers activation +Bug #186: CMake doesn't use the debug versions of Ogre libraries on Linux +Bug #189: ASCII 16 character added to console on it's activation on Mac OS X +Bug #190: Case Folding fails with music files +Bug #192: Keypresses write Text into Console no matter which gui element is active +Bug #196: Collision shapes out of place +Bug #202: ESMTool doesn't not work with localised ESM files anymore +Bug #203: Torch lights only visible on short distance +Bug #207: Ogre.log not written +Bug #209: Sounds do not play +Bug #210: Ogre crash at Dren plantation +Bug #214: Unsupported file format version +Bug #222: Launcher is writing openmw.cfg file to wrong location +Feature #9: NPC Dialogue Window +Feature #16/42: New sky/weather implementation +Feature #40: Fading +Feature #48: NPC Dialogue System +Feature #117: Equipping Items (backend only, no GUI yet, no rendering of equipped items yet) +Feature #161: Load REC_PGRD records +Feature #195: Wireframe-mode +Feature #198/199: Various sound effects +Feature #206: Allow picking data path from launcher if non is set +Task #108: Refactor window manager class +Task #172: Sound Manager Cleanup +Task #173: Create OpenEngine systems in the appropriate manager classes +Task #184: Adjust MSVC and gcc warning levels +Task #185: RefData rewrite +Task #201: Workaround for transparency issues +Task #208: silenced esm_reader.hpp warning + +0.12.0 + +Bug #154: FPS Drop +Bug #169: Local scripts continue running if associated object is deleted +Bug #174: OpenMW fails to start if the config directory doesn't exist +Bug #187: Missing lighting +Bug #188: Lights without a mesh are not rendered +Bug #191: Taking screenshot causes crash when running installed +Feature #28: Sort out the cell load problem +Feature #31: Allow the player to move away from pre-defined cells +Feature #35: Use alternate storage location for modified object position +Feature #45: NPC animations +Feature #46: Creature Animation +Feature #89: Basic Journal Window +Feature #110: Automatically pick up the path of existing MW-installations +Feature #183: More FPS display settings +Task #19: Refactor engine class +Task #109/Feature #162: Automate Packaging +Task #112: Catch exceptions thrown in input handling functions +Task #128/#168: Cleanup Configuration File Handling +Task #131: NPC Activation doesn't work properly +Task #144: MWRender cleanup +Task #155: cmake cleanup + +0.11.1 + +Bug #2: Resources loading doesn't work outside of bsa files +Bug #3: GUI does not render non-English characters +Bug #7: openmw.cfg location doesn't match +Bug #124: The TCL alias for ToggleCollision is missing. +Bug #125: Some command line options can't be used from a .cfg file +Bug #126: Toggle-type script instructions are less verbose compared with original MW +Bug #130: NPC-Record Loading fails for some NPCs +Bug #167: Launcher sets invalid parameters in ogre config +Feature #10: Journal +Feature #12: Rendering Optimisations +Feature #23: Change Launcher GUI to a tabbed interface +Feature #24: Integrate the OGRE settings window into the launcher +Feature #25: Determine openmw.cfg location (Launcher) +Feature #26: Launcher Profiles +Feature #79: MessageBox +Feature #116: Tab-Completion in Console +Feature #132: --data-local and multiple --data +Feature #143: Non-Rendering Performance-Optimisations +Feature #150: Accessing objects in cells via ID does only work for objects with all lower case IDs +Feature #157: Version Handling +Task #14: Replace tabs with 4 spaces +Task #18: Move components from global namespace into their own namespace +Task #123: refactor header files in components/esm + +0.10.0 + +* NPC dialogue window (not functional yet) +* Collisions with objects +* Refactor the PlayerPos class +* Adjust file locations +* CMake files and test linking for Bullet +* Replace Ogre raycasting test for activation with something more precise +* Adjust player movement according to collision results +* FPS display +* Various Portability Improvements +* Mac OS X support is back! + +0.9.0 + +* Exterior cells loading, unloading and management +* Character Creation GUI +* Character creation +* Make cell names case insensitive when doing internal lookups +* Music player +* NPCs rendering + +0.8.0 + +* GUI +* Complete and working script engine +* In game console +* Sky rendering +* Sound and music +* Tons of smaller stuff + +0.7.0 + +* This release is a complete rewrite in C++. +* All D code has been culled, and all modules have been rewritten. +* The game is now back up to the level of rendering interior cells and moving around, but physics, sound, GUI, and scripting still remain to be ported from the old codebase. + +0.6.0 + +* Coded a GUI system using MyGUI +* Skinned MyGUI to look like Morrowind (work in progress) +* Integrated the Monster script engine +* Rewrote some functions into script code +* Very early MyGUI < > Monster binding +* Fixed Windows sound problems (replaced old openal32.dll) + +0.5.0 + +* Collision detection with Bullet +* Experimental walk & fall character physics +* New key bindings: + * t toggle physics mode (walking, flying, ghost), + * n night eye, brightens the scene +* Fixed incompatability with DMD 1.032 and newer compilers +* * (thanks to tomqyp) +* Various minor changes and updates + +0.4.0 + +* Switched from Audiere to OpenAL +* * (BIG thanks to Chris Robinson) +* Added complete Makefile (again) as a alternative build tool +* More realistic lighting (thanks again to Chris Robinson) +* Various localization fixes tested with Russian and French versions +* Temporary workaround for the Unicode issue: invalid UTF displayed as '?' +* Added ns option to disable sound, for debugging +* Various bug fixes +* Cosmetic changes to placate gdc Wall + +0.3.0 + +* Built and tested on Windows XP +* Partial support for FreeBSD (exceptions do not work) +* You no longer have to download Monster separately +* Made an alternative for building without DSSS (but DSSS still works) +* Renamed main program from 'morro' to 'openmw' +* Made the config system more robust +* Added oc switch for showing Ogre config window on startup +* Removed some config files, these are auto generated when missing. +* Separated plugins.cfg into linux and windows versions. +* Updated Makefile and sources for increased portability +* confirmed to work against OIS 1.0.0 (Ubuntu repository package) + +0.2.0 + +* Compiles with gdc +* Switched to DSSS for building D code +* Includes the program esmtool + +0.1.0 + +first release From 8fa88f448085ee18fadced6a1beabc4bbe050d15 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 28 Feb 2014 00:13:57 +1100 Subject: [PATCH 042/114] sync with upstream --- apps/opencs/view/filter/editwidget.hpp | 2 +- apps/opencs/view/filter/filterbox.hpp | 2 +- apps/opencs/view/filter/recordfilterbox.hpp | 2 +- apps/opencs/view/world/tablesubview.hpp | 2 +- apps/openmw/mwmechanics/aicombat.cpp | 37 ++++++++------------- apps/openmw/mwmechanics/pathfinding.cpp | 2 -- credits.txt | 1 - 7 files changed, 17 insertions(+), 31 deletions(-) diff --git a/apps/opencs/view/filter/editwidget.hpp b/apps/opencs/view/filter/editwidget.hpp index e7e34b8e9..555b6d360 100644 --- a/apps/opencs/view/filter/editwidget.hpp +++ b/apps/opencs/view/filter/editwidget.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include "../../model/filter/parser.hpp" #include "../../model/filter/node.hpp" diff --git a/apps/opencs/view/filter/filterbox.hpp b/apps/opencs/view/filter/filterbox.hpp index 5954035fc..3817d5e70 100644 --- a/apps/opencs/view/filter/filterbox.hpp +++ b/apps/opencs/view/filter/filterbox.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "../../model/filter/node.hpp" #include "../../model/world/universalid.hpp" diff --git a/apps/opencs/view/filter/recordfilterbox.hpp b/apps/opencs/view/filter/recordfilterbox.hpp index fa5c9c3c2..3638dc6c3 100644 --- a/apps/opencs/view/filter/recordfilterbox.hpp +++ b/apps/opencs/view/filter/recordfilterbox.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index b3c253919..1f67e0262 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -3,7 +3,7 @@ #include "../doc/subview.hpp" -#include +#include class QModelIndex; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 17a624f0f..6a49f2f5e 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -240,21 +240,17 @@ namespace MWMechanics //target is at far distance: build path to target OR follow target (if previously actor had reached it once) mFollowTarget = false; - buildNewPath(actor); //may fail to build a path, check before use + buildNewPath(actor); //delete visited path node mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); - //if no new path leave mTargetAngle unchanged - if(!mPathFinder.getPath().empty()) - { - //try shortcut - if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) - mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); - else - mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - mRotate = true; - } + //try shortcut + if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) + mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); + else + mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + mRotate = true; mMovement.mPosition[1] = 1; mReadyToAttack = false; @@ -304,13 +300,9 @@ namespace MWMechanics dest.mZ = mTarget.getRefData().getPosition().pos[2]; Ogre::Vector3 newPathTarget = Ogre::Vector3(dest.mX, dest.mY, dest.mZ); - float dist = -1; //hack to indicate first time, to construct a new path - if(!mPathFinder.getPath().empty()) - { - ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); - Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ); - dist = Ogre::Math::Abs((newPathTarget - currPathTarget).length()); - } + ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); + Ogre::Vector3 currPathTarget(lastPt.mX, lastPt.mY, lastPt.mZ); + float dist = Ogre::Math::Abs((newPathTarget - currPathTarget).length()); float targetPosThreshold; bool isOutside = actor.getCell()->mCell->isExterior(); @@ -319,7 +311,7 @@ namespace MWMechanics else targetPosThreshold = 100; - if((dist < 0) || (dist > targetPosThreshold)) + if(dist > targetPosThreshold) { //construct new path only if target has moved away more than on ESM::Position pos = actor.getRefData().getPosition(); @@ -340,11 +332,8 @@ namespace MWMechanics //maybe here is a mistake (?): PathFinder::getPathSize() returns number of grid points in the path, //not the actual path length. Here we should know if the new path is actually more effective. //if(pathFinder2.getPathSize() < mPathFinder.getPathSize()) - if(!mPathFinder.getPath().empty()) - { - newPathFinder.syncStart(mPathFinder.getPath()); - mPathFinder = newPathFinder; - } + newPathFinder.syncStart(mPathFinder.getPath()); + mPathFinder = newPathFinder; } } } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 8dbdd8770..4407363a6 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -391,8 +391,6 @@ namespace MWMechanics void PathFinder::syncStart(const std::list &path) { - if (mPath.size() < 2) - return; //nothing to pop std::list::const_iterator oldStart = path.begin(); std::list::iterator iter = ++mPath.begin(); diff --git a/credits.txt b/credits.txt index 601255763..561931cde 100644 --- a/credits.txt +++ b/credits.txt @@ -20,7 +20,6 @@ Artem Kotsynyak (greye) athile Britt Mathis (galdor557) BrotherBrick -cc9cii Chris Robinson (KittyCat) Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) From 56ae85df0c28079b3b228d49b84079ec0e5179f0 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 28 Feb 2014 00:28:02 +1100 Subject: [PATCH 043/114] Fix 32bit Windows crash while taking the save screenshot. --- apps/openmw/mwrender/renderingmanager.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 0a82d67fb..a4ce92347 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -980,13 +980,11 @@ void RenderingManager::screenshot(Image &image, int w, int h) Ogre::PixelFormat pf = rt->suggestPixelFormat(); - std::vector data; - data.resize(w * h * Ogre::PixelUtil::getNumElemBytes(pf)); - - Ogre::PixelBox pb(w, h, 1, pf, &data[0]); - rt->copyContentsToMemory(pb); - - image.loadDynamicImage(&data[0], w, h, pf); + image.loadDynamicImage( + OGRE_ALLOC_T(Ogre::uchar, w * h * Ogre::PixelUtil::getNumElemBytes(pf), Ogre::MEMCATEGORY_GENERAL), + w, h, 1, pf, true // autoDelete=true, frees memory we allocate + ); + rt->copyContentsToMemory(image.getPixelBox()); // getPixelBox returns a box sharing the same memory as the image Ogre::TextureManager::getSingleton().remove(tempName); mRendering.getCamera()->setAspectRatio(oldAspect); From 5c11a9451184d69a801a9c5663ccc58d4d7c1082 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 28 Feb 2014 00:29:57 +1100 Subject: [PATCH 044/114] Fix 32bit Windows non-debug build with MSVC 11.0 crash during startup. --- libs/openengine/bullet/physic.cpp | 6 +++--- libs/openengine/bullet/physic.hpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 481b99bad..f124abb99 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -23,7 +23,7 @@ namespace Physic mBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation); mRaycastingBody = mEngine->createAndAdjustRigidBody(mMesh, mName, scale, position, rotation, &mBoxScaledTranslation, &mBoxRotation, true); Ogre::Quaternion inverse = mBoxRotation.Inverse(); - mBoxRotationInverse = btQuaternion(inverse.x, inverse.y, inverse.z,inverse.w); + mBoxRotationInverse = Ogre::Quaternion(inverse.w, inverse.x, inverse.y,inverse.z); mEngine->addRigidBody(mBody, false, mRaycastingBody,true); //Add rigid body to dynamics world, but do not add to object map } @@ -85,8 +85,8 @@ namespace Physic Ogre::Quaternion PhysicActor::getRotation() { assert(mBody); - btQuaternion quat = mBody->getWorldTransform().getRotation() * mBoxRotationInverse; - return Ogre::Quaternion(quat.getW(), quat.getX(), quat.getY(), quat.getZ()); + btQuaternion quat = mBody->getWorldTransform().getRotation(); + return Ogre::Quaternion(quat.getW(), quat.getX(), quat.getY(), quat.getZ()) * mBoxRotationInverse; } void PhysicActor::setScale(float scale){ diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 6cd7244b8..4ef611dc8 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -162,7 +162,7 @@ namespace Physic Ogre::Vector3 mBoxScaledTranslation; Ogre::Quaternion mBoxRotation; - btQuaternion mBoxRotationInverse; + Ogre::Quaternion mBoxRotationInverse; Ogre::Vector3 mForce; bool mOnGround; From 14c3bfcf62da0b90c232f68bdde756c3552d609c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 27 Feb 2014 14:46:06 +0100 Subject: [PATCH 045/114] added navigation class --- apps/opencs/CMakeLists.txt | 8 +++-- apps/opencs/view/render/navigation.cpp | 19 ++++++++++++ apps/opencs/view/render/navigation.hpp | 43 ++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 apps/opencs/view/render/navigation.cpp create mode 100644 apps/opencs/view/render/navigation.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 6bcad1d08..1ba24039b 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -64,8 +64,12 @@ opencs_units (view/world ) opencs_units (view/render - scenewidget - ) + scenewidget + ) + +opencs_units_noqt (view/render + navigation + ) opencs_units_noqt (view/world dialoguesubview subviews diff --git a/apps/opencs/view/render/navigation.cpp b/apps/opencs/view/render/navigation.cpp new file mode 100644 index 000000000..14ae7f0b7 --- /dev/null +++ b/apps/opencs/view/render/navigation.cpp @@ -0,0 +1,19 @@ + +#include "navigation.hpp" + +float CSVRender::Navigation::getFactor (bool mouse) const +{ + float factor = mFastModeFactor; + + if (mouse) + factor /= 2; /// \todo make this configurable + + return factor; +} + +CSVRender::Navigation::~Navigation() {} + +void CSVRender::Navigation::setFastModeFactor (float factor) +{ + mFastModeFactor = factor; +} \ No newline at end of file diff --git a/apps/opencs/view/render/navigation.hpp b/apps/opencs/view/render/navigation.hpp new file mode 100644 index 000000000..964ceeac6 --- /dev/null +++ b/apps/opencs/view/render/navigation.hpp @@ -0,0 +1,43 @@ +#ifndef OPENCS_VIEW_NAVIGATION_H +#define OPENCS_VIEW_NAVIGATION_H + +class QPoint; + +namespace Ogre +{ + class Camera; +} + +namespace CSVRender +{ + class Navigation + { + float mFastModeFactor; + + protected: + + float getFactor (bool mouse) const; + + public: + + virtual ~Navigation(); + + void setFastModeFactor (float factor); + ///< Set currently applying fast mode factor. + + virtual bool activate (Ogre::Camera *camera) = 0; + ///< \return Update required? + + virtual bool wheelMoved (int delta) = 0; + ///< \return Update required? + + virtual bool mouseMoved (const QPoint& delta, int mode) = 0; + ///< \param mode: 0: default mouse key, 1: default mouse key and modifier key 1 + /// \return Update required? + + virtual bool handleMovementKeys (int vertical, int horizontal) = 0; + ///< \return Update required? + }; +} + +#endif From c977b2a756c4a0db93b13b48bb51a4331afe49e5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 27 Feb 2014 15:23:14 +0100 Subject: [PATCH 046/114] moved implementation of the 1st person camera into a separate file --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/render/navigation1st.cpp | 60 +++++++++++ apps/opencs/view/render/navigation1st.hpp | 32 ++++++ apps/opencs/view/render/scenewidget.cpp | 120 +++++++++------------- apps/opencs/view/render/scenewidget.hpp | 15 +-- apps/opencs/view/world/scenesubview.cpp | 12 ++- apps/opencs/view/world/scenesubview.hpp | 3 + 7 files changed, 161 insertions(+), 83 deletions(-) create mode 100644 apps/opencs/view/render/navigation1st.cpp create mode 100644 apps/opencs/view/render/navigation1st.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 1ba24039b..bb4a9d329 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -68,7 +68,7 @@ opencs_units (view/render ) opencs_units_noqt (view/render - navigation + navigation navigation1st ) opencs_units_noqt (view/world diff --git a/apps/opencs/view/render/navigation1st.cpp b/apps/opencs/view/render/navigation1st.cpp new file mode 100644 index 000000000..5a3bc7b65 --- /dev/null +++ b/apps/opencs/view/render/navigation1st.cpp @@ -0,0 +1,60 @@ + +#include "navigation1st.hpp" + +#include + +#include + +CSVRender::Navigation1st::Navigation1st() : mCamera (0) {} + +bool CSVRender::Navigation1st::activate (Ogre::Camera *camera) +{ + mCamera = camera; + mCamera->setFixedYawAxis (true); + return false; +} + +bool CSVRender::Navigation1st::wheelMoved (int delta) +{ + mCamera->move (getFactor (true) * mCamera->getDirection() * delta); + return true; +} + +bool CSVRender::Navigation1st::mouseMoved (const QPoint& delta, int mode) +{ + if (mode==0) + { + // turn camera + if (delta.x()) + mCamera->yaw (Ogre::Degree (getFactor (true) * delta.x())); + + if (delta.y()) + mCamera->pitch (Ogre::Degree (getFactor (true) * delta.y())); + + return true; + } + else if (mode==1) + { + // pan camera + if (delta.x()) + mCamera->move (getFactor (true) * mCamera->getDerivedRight() * delta.x()); + + if (delta.y()) + mCamera->move (getFactor (true) * -mCamera->getDerivedUp() * delta.y()); + + return true; + } + + return false; +} + +bool CSVRender::Navigation1st::handleMovementKeys (int vertical, int horizontal) +{ + if (vertical) + mCamera->move (getFactor (false) * mCamera->getDirection() * vertical); + + if (horizontal) + mCamera->move (getFactor (true) * mCamera->getDerivedRight() * horizontal); + + return true; +} diff --git a/apps/opencs/view/render/navigation1st.hpp b/apps/opencs/view/render/navigation1st.hpp new file mode 100644 index 000000000..815e0b117 --- /dev/null +++ b/apps/opencs/view/render/navigation1st.hpp @@ -0,0 +1,32 @@ +#ifndef OPENCS_VIEW_NAVIGATION1ST_H +#define OPENCS_VIEW_NAVIGATION1ST_H + +#include "navigation.hpp" + +namespace CSVRender +{ + /// \brief First person-like camera controls + class Navigation1st : public Navigation + { + Ogre::Camera *mCamera; + + public: + + Navigation1st(); + + virtual bool activate (Ogre::Camera *camera); + ///< \return Update required? + + virtual bool wheelMoved (int delta); + ///< \return Update required? + + virtual bool mouseMoved (const QPoint& delta, int mode); + ///< \param mode: 0: default mouse key, 1: default mouse key and modifier key 1 + /// \return Update required? + + virtual bool handleMovementKeys (int vertical, int horizontal); + ///< \return Update required? + }; +} + +#endif diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 07fe56f8f..41868dfb5 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -9,16 +9,18 @@ #include #include +#include "navigation.hpp" + namespace CSVRender { SceneWidget::SceneWidget(QWidget *parent) : QWidget(parent) , mWindow(NULL) , mCamera(NULL) - , mSceneMgr(NULL), mNavigationMode (NavigationMode_1stPerson), mUpdate (false) + , mSceneMgr(NULL), mNavigation (0), mUpdate (false) , mKeyForward (false), mKeyBackward (false), mKeyLeft (false), mKeyRight (false) , mFast (false), mDragging (false), mMod1 (false) - , mMouseSensitivity (2), mFastFactor (4) /// \todo make these configurable + , mFastFactor (4) /// \todo make this configurable { setAttribute(Qt::WA_PaintOnScreen); setAttribute(Qt::WA_NoSystemBackground); @@ -94,12 +96,12 @@ namespace CSVRender Ogre::Root::getSingleton().destroyRenderTarget(mWindow); } - void SceneWidget::setNavigationMode (NavigationMode mode) + void SceneWidget::setNavigation (Navigation *navigation) { - if (mode!=mNavigationMode) + if ((mNavigation = navigation)) { - mNavigationMode = mode; - + mNavigation->setFastModeFactor (mFast ? mFastFactor : 1); + mNavigation->activate (mCamera); } } @@ -156,8 +158,17 @@ namespace CSVRender case Qt::Key_S: mKeyBackward = true; break; case Qt::Key_A: mKeyLeft = true; break; case Qt::Key_D: mKeyRight = true; break; - case Qt::Key_Shift: mFast = true; break; case Qt::Key_Control: mMod1 = true; break; + + case Qt::Key_Shift: + + mFast = true; + + if (mNavigation) + mNavigation->setFastModeFactor (mFastFactor); + + break; + default: QWidget::keyPressEvent (event); } } @@ -170,19 +181,27 @@ namespace CSVRender case Qt::Key_S: mKeyBackward = false; break; case Qt::Key_A: mKeyLeft = false; break; case Qt::Key_D: mKeyRight = false; break; - case Qt::Key_Shift: mFast = false; break; case Qt::Key_Control: mMod1 = false; break; + + case Qt::Key_Shift: + + mFast = false; + + if (mNavigation) + mNavigation->setFastModeFactor (1); + + break; + default: QWidget::keyReleaseEvent (event); } } void SceneWidget::wheelEvent (QWheelEvent *event) { - if (int delta = event->delta()) - { - mCamera->move ((getFastFactor() * mCamera->getDirection() * delta)/mMouseSensitivity); - mUpdate = true; - } + if (mNavigation) + if (event->delta()) + if (mNavigation->wheelMoved (event->delta())) + mUpdate = true; } void SceneWidget::leaveEvent (QEvent *event) @@ -199,40 +218,9 @@ namespace CSVRender QPoint diff = mOldPos-event->pos(); mOldPos = event->pos(); - if (!mMod1) - { - // turn camera - if (diff.x()) - { - mCamera->yaw ( - Ogre::Degree ((getFastFactor() * diff.x())/mMouseSensitivity)); + if (mNavigation) + if (mNavigation->mouseMoved (diff, mMod1 ? 1 : 0)) mUpdate = true; - } - - if (diff.y()) - { - mCamera->pitch ( - Ogre::Degree ((getFastFactor() * diff.y())/mMouseSensitivity)); - mUpdate = true; - } - } - else - { - // pan camera - if (diff.x()) - { - Ogre::Vector3 direction = mCamera->getDerivedRight(); - mCamera->move ((getFastFactor() * direction * diff.x())/mMouseSensitivity); - mUpdate = true; - } - - if (diff.y()) - { - Ogre::Vector3 direction = mCamera->getDerivedUp(); - mCamera->move ((getFastFactor() * -direction * diff.y())/mMouseSensitivity); - mUpdate = true; - } - } } else { @@ -262,30 +250,24 @@ namespace CSVRender void SceneWidget::update() { - if (mKeyForward && !mKeyBackward) - { - mCamera->move (getFastFactor() * mCamera->getDirection()); - mUpdate = true; - } - - if (!mKeyForward && mKeyBackward) - { - mCamera->move (getFastFactor() * -mCamera->getDirection()); - mUpdate = true; - } - - if (mKeyLeft && !mKeyRight) - { - Ogre::Vector3 direction = mCamera->getDerivedRight(); - mCamera->move (getFastFactor() * -direction); - mUpdate = true; - } - - if (!mKeyLeft && mKeyRight) + if (mNavigation) { - Ogre::Vector3 direction = mCamera->getDerivedRight(); - mCamera->move (getFastFactor() * direction); - mUpdate = true; + int horizontal = 0; + int vertical = 0; + + if (mKeyForward && !mKeyBackward) + vertical = 1; + else if (!mKeyForward && mKeyBackward) + vertical = -1; + + if (mKeyLeft && !mKeyRight) + horizontal = -1; + else if (!mKeyLeft && mKeyRight) + horizontal = 1; + + if (horizontal || vertical) + if (mNavigation->handleMovementKeys (vertical, horizontal)) + mUpdate = true; } if (mUpdate) diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 55f62d661..247dbbb1a 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -12,25 +12,21 @@ namespace Ogre namespace CSVRender { + class Navigation; + class SceneWidget : public QWidget { Q_OBJECT public: - enum NavigationMode - { - NavigationMode_1stPerson, - NavigationMode_Free, - NavigationMode_Orbit - }; - SceneWidget(QWidget *parent); virtual ~SceneWidget(); QPaintEngine* paintEngine() const; - void setNavigationMode (NavigationMode mode); + void setNavigation (Navigation *navigation); + ///< \attention The ownership of \a navigation is not transferred to *this. private: void paintEvent(QPaintEvent* e); @@ -59,7 +55,7 @@ namespace CSVRender Ogre::SceneManager* mSceneMgr; Ogre::RenderWindow* mWindow; - NavigationMode mNavigationMode; + Navigation *mNavigation; bool mUpdate; int mKeyForward; int mKeyBackward; @@ -69,7 +65,6 @@ namespace CSVRender bool mDragging; bool mMod1; QPoint mOldPos; - int mMouseSensitivity; int mFastFactor; private slots: diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 339a88519..5b7b6a587 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -59,6 +59,8 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D widget->setLayout (layout); setWidget (widget); + + mScene->setNavigation (&m1st); } void CSVWorld::SceneSubView::setEditLock (bool locked) @@ -81,9 +83,13 @@ void CSVWorld::SceneSubView::setStatusBar (bool show) void CSVWorld::SceneSubView::selectNavigationMode (const std::string& mode) { if (mode=="1st") - mScene->setNavigationMode (CSVRender::SceneWidget::NavigationMode_1stPerson); + mScene->setNavigation (&m1st); else if (mode=="free") - mScene->setNavigationMode (CSVRender::SceneWidget::NavigationMode_Free); + { + + } else if (mode=="orbit") - mScene->setNavigationMode (CSVRender::SceneWidget::NavigationMode_Orbit); + { + + } } diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index 65e1614c1..f948ecefb 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -3,6 +3,8 @@ #include "../doc/subview.hpp" +#include "../render/navigation1st.hpp" + class QModelIndex; namespace CSMDoc @@ -27,6 +29,7 @@ namespace CSVWorld TableBottomBox *mBottom; CSVRender::SceneWidget *mScene; + CSVRender::Navigation1st m1st; public: From 4eea2c7a86dfa5df0607066e38e212cd1017a1d4 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 27 Feb 2014 15:42:59 +0100 Subject: [PATCH 047/114] added free navigation mode --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/render/navigationfree.cpp | 60 ++++++++++++++++++++++ apps/opencs/view/render/navigationfree.hpp | 32 ++++++++++++ apps/opencs/view/world/scenesubview.cpp | 2 +- apps/opencs/view/world/scenesubview.hpp | 2 + 5 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 apps/opencs/view/render/navigationfree.cpp create mode 100644 apps/opencs/view/render/navigationfree.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index bb4a9d329..eba7a7acb 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -68,7 +68,7 @@ opencs_units (view/render ) opencs_units_noqt (view/render - navigation navigation1st + navigation navigation1st navigationfree ) opencs_units_noqt (view/world diff --git a/apps/opencs/view/render/navigationfree.cpp b/apps/opencs/view/render/navigationfree.cpp new file mode 100644 index 000000000..d470722bb --- /dev/null +++ b/apps/opencs/view/render/navigationfree.cpp @@ -0,0 +1,60 @@ + +#include "navigationfree.hpp" + +#include + +#include + +CSVRender::NavigationFree::NavigationFree() : mCamera (0) {} + +bool CSVRender::NavigationFree::activate (Ogre::Camera *camera) +{ + mCamera = camera; + mCamera->setFixedYawAxis (false); + return false; +} + +bool CSVRender::NavigationFree::wheelMoved (int delta) +{ + mCamera->move (getFactor (true) * mCamera->getDirection() * delta); + return true; +} + +bool CSVRender::NavigationFree::mouseMoved (const QPoint& delta, int mode) +{ + if (mode==0) + { + // turn camera + if (delta.x()) + mCamera->yaw (Ogre::Degree (getFactor (true) * delta.x())); + + if (delta.y()) + mCamera->pitch (Ogre::Degree (getFactor (true) * delta.y())); + + return true; + } + else if (mode==1) + { + // pan camera + if (delta.x()) + mCamera->move (getFactor (true) * mCamera->getDerivedRight() * delta.x()); + + if (delta.y()) + mCamera->move (getFactor (true) * -mCamera->getDerivedUp() * delta.y()); + + return true; + } + + return false; +} + +bool CSVRender::NavigationFree::handleMovementKeys (int vertical, int horizontal) +{ + if (vertical) + mCamera->move (getFactor (false) * mCamera->getDerivedUp() * vertical); + + if (horizontal) + mCamera->move (getFactor (true) * mCamera->getDerivedRight() * horizontal); + + return true; +} diff --git a/apps/opencs/view/render/navigationfree.hpp b/apps/opencs/view/render/navigationfree.hpp new file mode 100644 index 000000000..fa6fdad11 --- /dev/null +++ b/apps/opencs/view/render/navigationfree.hpp @@ -0,0 +1,32 @@ +#ifndef OPENCS_VIEW_NAVIGATIONFREE_H +#define OPENCS_VIEW_NAVIGATIONFREE_H + +#include "navigation.hpp" + +namespace CSVRender +{ + /// \brief Free camera controls + class NavigationFree : public Navigation + { + Ogre::Camera *mCamera; + + public: + + NavigationFree(); + + virtual bool activate (Ogre::Camera *camera); + ///< \return Update required? + + virtual bool wheelMoved (int delta); + ///< \return Update required? + + virtual bool mouseMoved (const QPoint& delta, int mode); + ///< \param mode: 0: default mouse key, 1: default mouse key and modifier key 1 + /// \return Update required? + + virtual bool handleMovementKeys (int vertical, int horizontal); + ///< \return Update required? + }; +} + +#endif diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 5b7b6a587..fd327b4d1 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -86,7 +86,7 @@ void CSVWorld::SceneSubView::selectNavigationMode (const std::string& mode) mScene->setNavigation (&m1st); else if (mode=="free") { - + mScene->setNavigation (&mFree); } else if (mode=="orbit") { diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index f948ecefb..8e42540e1 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -4,6 +4,7 @@ #include "../doc/subview.hpp" #include "../render/navigation1st.hpp" +#include "../render/navigationfree.hpp" class QModelIndex; @@ -30,6 +31,7 @@ namespace CSVWorld TableBottomBox *mBottom; CSVRender::SceneWidget *mScene; CSVRender::Navigation1st m1st; + CSVRender::NavigationFree mFree; public: From ae637d6abc712b84248330f5634182a96902757f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 28 Feb 2014 11:58:03 +0100 Subject: [PATCH 048/114] rolling --- apps/opencs/view/render/navigation.hpp | 3 +++ apps/opencs/view/render/navigation1st.cpp | 6 ++++++ apps/opencs/view/render/navigation1st.hpp | 3 +++ apps/opencs/view/render/navigationfree.cpp | 6 ++++++ apps/opencs/view/render/navigationfree.hpp | 3 +++ apps/opencs/view/render/scenewidget.cpp | 17 +++++++++++++++++ apps/opencs/view/render/scenewidget.hpp | 10 ++++++---- 7 files changed, 44 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/navigation.hpp b/apps/opencs/view/render/navigation.hpp index 964ceeac6..873519609 100644 --- a/apps/opencs/view/render/navigation.hpp +++ b/apps/opencs/view/render/navigation.hpp @@ -37,6 +37,9 @@ namespace CSVRender virtual bool handleMovementKeys (int vertical, int horizontal) = 0; ///< \return Update required? + + virtual bool handleRollKeys (int delta) = 0; + ///< \return Update required? }; } diff --git a/apps/opencs/view/render/navigation1st.cpp b/apps/opencs/view/render/navigation1st.cpp index 5a3bc7b65..b177c019b 100644 --- a/apps/opencs/view/render/navigation1st.cpp +++ b/apps/opencs/view/render/navigation1st.cpp @@ -58,3 +58,9 @@ bool CSVRender::Navigation1st::handleMovementKeys (int vertical, int horizontal) return true; } + +bool CSVRender::Navigation1st::handleRollKeys (int delta) +{ + // we don't roll this way in 1st person mode + return false; +} diff --git a/apps/opencs/view/render/navigation1st.hpp b/apps/opencs/view/render/navigation1st.hpp index 815e0b117..d1e09d236 100644 --- a/apps/opencs/view/render/navigation1st.hpp +++ b/apps/opencs/view/render/navigation1st.hpp @@ -26,6 +26,9 @@ namespace CSVRender virtual bool handleMovementKeys (int vertical, int horizontal); ///< \return Update required? + + virtual bool handleRollKeys (int delta); + ///< \return Update required? }; } diff --git a/apps/opencs/view/render/navigationfree.cpp b/apps/opencs/view/render/navigationfree.cpp index d470722bb..950e137a7 100644 --- a/apps/opencs/view/render/navigationfree.cpp +++ b/apps/opencs/view/render/navigationfree.cpp @@ -58,3 +58,9 @@ bool CSVRender::NavigationFree::handleMovementKeys (int vertical, int horizontal return true; } + +bool CSVRender::NavigationFree::handleRollKeys (int delta) +{ + mCamera->roll (Ogre::Degree (getFactor (false) * delta)); + return true; +} diff --git a/apps/opencs/view/render/navigationfree.hpp b/apps/opencs/view/render/navigationfree.hpp index fa6fdad11..e30722f75 100644 --- a/apps/opencs/view/render/navigationfree.hpp +++ b/apps/opencs/view/render/navigationfree.hpp @@ -26,6 +26,9 @@ namespace CSVRender virtual bool handleMovementKeys (int vertical, int horizontal); ///< \return Update required? + + virtual bool handleRollKeys (int delta); + ///< \return Update required? }; } diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 41868dfb5..f864e1528 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -19,6 +19,7 @@ namespace CSVRender , mCamera(NULL) , mSceneMgr(NULL), mNavigation (0), mUpdate (false) , mKeyForward (false), mKeyBackward (false), mKeyLeft (false), mKeyRight (false) + , mKeyRollLeft (false), mKeyRollRight (false) , mFast (false), mDragging (false), mMod1 (false) , mFastFactor (4) /// \todo make this configurable { @@ -158,6 +159,8 @@ namespace CSVRender case Qt::Key_S: mKeyBackward = true; break; case Qt::Key_A: mKeyLeft = true; break; case Qt::Key_D: mKeyRight = true; break; + case Qt::Key_Q: mKeyRollLeft = true; break; + case Qt::Key_E: mKeyRollRight = true; break; case Qt::Key_Control: mMod1 = true; break; case Qt::Key_Shift: @@ -181,6 +184,8 @@ namespace CSVRender case Qt::Key_S: mKeyBackward = false; break; case Qt::Key_A: mKeyLeft = false; break; case Qt::Key_D: mKeyRight = false; break; + case Qt::Key_Q: mKeyRollLeft = false; break; + case Qt::Key_E: mKeyRollRight = false; break; case Qt::Key_Control: mMod1 = false; break; case Qt::Key_Shift: @@ -268,6 +273,18 @@ namespace CSVRender if (horizontal || vertical) if (mNavigation->handleMovementKeys (vertical, horizontal)) mUpdate = true; + + int roll = 0; + + if (mKeyRollLeft && !mKeyRollRight) + roll = 1; + else if (!mKeyRollLeft && mKeyRollRight) + roll = -1; + + if (roll) + if (mNavigation->handleRollKeys (roll)) + mUpdate = true; + } if (mUpdate) diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 247dbbb1a..4a18c4244 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -57,10 +57,12 @@ namespace CSVRender Navigation *mNavigation; bool mUpdate; - int mKeyForward; - int mKeyBackward; - int mKeyLeft; - int mKeyRight; + bool mKeyForward; + bool mKeyBackward; + bool mKeyLeft; + bool mKeyRight; + bool mKeyRollLeft; + bool mKeyRollRight; bool mFast; bool mDragging; bool mMod1; From 4d3abeedcbac1aaf583e16124cccf4df0618e26a Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 28 Feb 2014 13:37:01 +0100 Subject: [PATCH 049/114] keep camera upright in 1st person mode --- apps/opencs/view/render/navigation1st.cpp | 23 +++++++++++++++++++++-- apps/opencs/view/render/scenewidget.cpp | 3 ++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/render/navigation1st.cpp b/apps/opencs/view/render/navigation1st.cpp index b177c019b..b892d3e3e 100644 --- a/apps/opencs/view/render/navigation1st.cpp +++ b/apps/opencs/view/render/navigation1st.cpp @@ -11,7 +11,17 @@ bool CSVRender::Navigation1st::activate (Ogre::Camera *camera) { mCamera = camera; mCamera->setFixedYawAxis (true); - return false; + + Ogre::Radian pitch = mCamera->getOrientation().getPitch(); + + Ogre::Radian limit (Ogre::Math::PI/2-0.5); + + if (pitch>limit) + mCamera->pitch (-(pitch-limit)); + else if (pitch<-limit) + mCamera->pitch (pitch-limit); + + return true; } bool CSVRender::Navigation1st::wheelMoved (int delta) @@ -29,7 +39,16 @@ bool CSVRender::Navigation1st::mouseMoved (const QPoint& delta, int mode) mCamera->yaw (Ogre::Degree (getFactor (true) * delta.x())); if (delta.y()) - mCamera->pitch (Ogre::Degree (getFactor (true) * delta.y())); + { + Ogre::Radian oldPitch = mCamera->getOrientation().getPitch(); + float deltaPitch = getFactor (true) * delta.y(); + Ogre::Radian newPitch = oldPitch + Ogre::Degree (deltaPitch); + + Ogre::Radian limit (Ogre::Math::PI/2-0.5); + + if ((deltaPitch>0 && newPitch-limit)) + mCamera->pitch (Ogre::Degree (deltaPitch)); + } return true; } diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index f864e1528..6f07c1b0d 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -102,7 +102,8 @@ namespace CSVRender if ((mNavigation = navigation)) { mNavigation->setFastModeFactor (mFast ? mFastFactor : 1); - mNavigation->activate (mCamera); + if (mNavigation->activate (mCamera)) + mUpdate = true; } } From a99aa15d148cc85b0918955675c2d9e8cbceb13b Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 28 Feb 2014 17:23:58 +0100 Subject: [PATCH 050/114] Fix a copy&paste mistake leaving start rotation uninitialized --- apps/openmw/engine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index e80bd954e..af8d49426 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -456,7 +456,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) else { pos.pos[0] = pos.pos[1] = pos.pos[2] = 0; - pos.rot[0] = pos.rot[1] = pos.pos[2] = 0; + pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; world->changeToExteriorCell (pos); } @@ -621,4 +621,4 @@ void OMW::Engine::setActivationDistanceOverride (int distance) void OMW::Engine::setWarningsMode (int mode) { mWarningsMode = mode; -} \ No newline at end of file +} From 46867ec0cfb2887b638f4912a59a6d49e1027a2a Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 1 Mar 2014 07:24:20 +1100 Subject: [PATCH 051/114] Fix file handles being left open on windows builds --- components/files/lowlevelfile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/files/lowlevelfile.cpp b/components/files/lowlevelfile.cpp index 71fd1b523..06ee9fb4e 100644 --- a/components/files/lowlevelfile.cpp +++ b/components/files/lowlevelfile.cpp @@ -219,7 +219,7 @@ LowLevelFile::LowLevelFile () LowLevelFile::~LowLevelFile () { - if (mHandle == INVALID_HANDLE_VALUE) + if (mHandle != INVALID_HANDLE_VALUE) CloseHandle (mHandle); } From 0e0d2331d60002dce43a9fd27a3eb636b62d4c89 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 28 Feb 2014 21:53:11 +0100 Subject: [PATCH 052/114] updated changelog --- readme.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.txt b/readme.txt index a23cd1077..26abdaecc 100644 --- a/readme.txt +++ b/readme.txt @@ -133,6 +133,7 @@ Bug #1158: Armor rating should always stay below inventory mannequin Bug #1159: Quick keys can be set during character generation Bug #1160: Crash on equip lockpick when Bug #1167: Editor: Referenceables are not correctly loaded when dealing with more than one content file +Bug #1184: Game Save: overwriting an existing save does not actually overwrites the file Feature #30: Loading/Saving (still missing a few parts) Feature #101: AI Package: Activate Feature #103: AI Package: Follow, FollowCell From 536a0e0ab0a19bbedd3fd03be5374fd49497031b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 1 Mar 2014 14:15:04 +0100 Subject: [PATCH 053/114] added orbit navigation mode --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/render/navigationorbit.cpp | 100 ++++++++++++++++++++ apps/opencs/view/render/navigationorbit.hpp | 42 ++++++++ apps/opencs/view/world/scenesubview.cpp | 6 +- apps/opencs/view/world/scenesubview.hpp | 2 + 5 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 apps/opencs/view/render/navigationorbit.cpp create mode 100644 apps/opencs/view/render/navigationorbit.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index eba7a7acb..119280f13 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -68,7 +68,7 @@ opencs_units (view/render ) opencs_units_noqt (view/render - navigation navigation1st navigationfree + navigation navigation1st navigationfree navigationorbit ) opencs_units_noqt (view/world diff --git a/apps/opencs/view/render/navigationorbit.cpp b/apps/opencs/view/render/navigationorbit.cpp new file mode 100644 index 000000000..c6e729e96 --- /dev/null +++ b/apps/opencs/view/render/navigationorbit.cpp @@ -0,0 +1,100 @@ + +#include "navigationorbit.hpp" + +#include + +#include + +void CSVRender::NavigationOrbit::rotateCamera (const Ogre::Vector3& diff) +{ + Ogre::Vector3 pos = mCamera->getPosition(); + + float distance = (pos-mCentre).length(); + + Ogre::Vector3 direction = (pos+diff)-mCentre; + direction.normalise(); + + mCamera->setPosition (mCentre + direction*distance); + mCamera->lookAt (mCentre); +} + +CSVRender::NavigationOrbit::NavigationOrbit() : mCamera (0), mCentre (0, 0, 0), mDistance (100) +{} + +bool CSVRender::NavigationOrbit::activate (Ogre::Camera *camera) +{ + mCamera = camera; + mCamera->setFixedYawAxis (false); + + if ((mCamera->getPosition()-mCentre).length()getPosition(); + direction.normalise(); + + if (direction.length()==0) + direction = Ogre::Vector3 (1, 0, 0); + + mCamera->setPosition (mCentre - direction * mDistance); + } + + mCamera->lookAt (mCentre); + + return true; +} + +bool CSVRender::NavigationOrbit::wheelMoved (int delta) +{ + Ogre::Vector3 diff = getFactor (true) * mCamera->getDirection() * delta; + + Ogre::Vector3 pos = mCamera->getPosition(); + + if (delta>0 && diff.length()>=(pos-mCentre).length()-mDistance) + { + pos = mCentre-(mCamera->getDirection() * mDistance); + } + else + { + pos += diff; + } + + mCamera->setPosition (pos); + + return true; +} + +bool CSVRender::NavigationOrbit::mouseMoved (const QPoint& delta, int mode) +{ + Ogre::Vector3 diff = + getFactor (true) * -mCamera->getDerivedRight() * delta.x() + + getFactor (true) * mCamera->getDerivedUp() * delta.y(); + + if (mode==0) + { + rotateCamera (diff); + return true; + } + else if (mode==1) + { + mCamera->move (diff); + mCentre += diff; + return true; + } + + return false; +} + +bool CSVRender::NavigationOrbit::handleMovementKeys (int vertical, int horizontal) +{ + rotateCamera ( + - getFactor (false) * -mCamera->getDerivedRight() * horizontal + + getFactor (false) * mCamera->getDerivedUp() * vertical); + + return true; +} + +bool CSVRender::NavigationOrbit::handleRollKeys (int delta) +{ + mCamera->roll (Ogre::Degree (getFactor (false) * delta)); + return true; +} \ No newline at end of file diff --git a/apps/opencs/view/render/navigationorbit.hpp b/apps/opencs/view/render/navigationorbit.hpp new file mode 100644 index 000000000..7796eb9e7 --- /dev/null +++ b/apps/opencs/view/render/navigationorbit.hpp @@ -0,0 +1,42 @@ +#ifndef OPENCS_VIEW_NAVIGATIONORBIT_H +#define OPENCS_VIEW_NAVIGATIONORBIT_H + +#include "navigation.hpp" + +#include + +namespace CSVRender +{ + /// \brief Orbiting camera controls + class NavigationOrbit : public Navigation + { + Ogre::Camera *mCamera; + Ogre::Vector3 mCentre; + int mDistance; + + void rotateCamera (const Ogre::Vector3& diff); + ///< Rotate camera around centre. + + public: + + NavigationOrbit(); + + virtual bool activate (Ogre::Camera *camera); + ///< \return Update required? + + virtual bool wheelMoved (int delta); + ///< \return Update required? + + virtual bool mouseMoved (const QPoint& delta, int mode); + ///< \param mode: 0: default mouse key, 1: default mouse key and modifier key 1 + /// \return Update required? + + virtual bool handleMovementKeys (int vertical, int horizontal); + ///< \return Update required? + + virtual bool handleRollKeys (int delta); + ///< \return Update required? + }; +} + +#endif diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index fd327b4d1..a23931950 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -85,11 +85,7 @@ void CSVWorld::SceneSubView::selectNavigationMode (const std::string& mode) if (mode=="1st") mScene->setNavigation (&m1st); else if (mode=="free") - { mScene->setNavigation (&mFree); - } else if (mode=="orbit") - { - - } + mScene->setNavigation (&mOrbit); } diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index 8e42540e1..f944e17c3 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -5,6 +5,7 @@ #include "../render/navigation1st.hpp" #include "../render/navigationfree.hpp" +#include "../render/navigationorbit.hpp" class QModelIndex; @@ -32,6 +33,7 @@ namespace CSVWorld CSVRender::SceneWidget *mScene; CSVRender::Navigation1st m1st; CSVRender::NavigationFree mFree; + CSVRender::NavigationOrbit mOrbit; public: From ca30f2af3d16ce80290cb3d12c4fabc71679f701 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 2 Mar 2014 11:03:37 +0100 Subject: [PATCH 054/114] minor fix --- apps/openmw/mwworld/scene.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 10afbc675..508127837 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -43,7 +43,7 @@ namespace mPhysics (physics), mRendering (rendering) {} - bool InsertFunctor::InsertFunctor::operator() (const MWWorld::Ptr& ptr) + bool InsertFunctor::operator() (const MWWorld::Ptr& ptr) { if (mRescale) { From cb3994281c957225a8b93c94b2f56b44f6875572 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 2 Mar 2014 13:11:15 +0100 Subject: [PATCH 055/114] changed UniversalId::Type_Scene from index to string ID argument (argument is the worldspace) --- apps/opencs/model/world/universalid.cpp | 4 ++-- apps/opencs/view/doc/view.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 8301ebfd3..1d1b3c960 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -90,14 +90,14 @@ namespace { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":./weapon.png" }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Reference", 0 }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" }, + { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", 0 }, + { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; static const TypeData sIndexArg[] = { { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, "Verification Results", 0 }, - { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", 0 }, - { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0, 0 } // end marker }; } diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 533ca7f57..b055d34eb 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -431,7 +431,7 @@ void CSVDoc::View::addFiltersSubView() void CSVDoc::View::addSceneSubView() { - addSubView (CSMWorld::UniversalId::Type_Scene); + addSubView (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Scene, "default")); } void CSVDoc::View::addTopicsSubView() From 797f5527ee990ba4d4499371e1ae8dc9a4817d0d Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 2 Mar 2014 13:29:02 +0100 Subject: [PATCH 056/114] added hint parameter for UniversalId viewing requests --- apps/opencs/view/doc/subview.cpp | 4 +++- apps/opencs/view/doc/subview.hpp | 3 +++ apps/opencs/view/doc/view.cpp | 5 ++++- apps/opencs/view/doc/view.hpp | 4 +++- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index 09361a1c0..6160f2673 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -16,4 +16,6 @@ void CSVDoc::SubView::updateEditorSetting (const QString &settingName, const QSt { } -void CSVDoc::SubView::setStatusBar (bool show) {} \ No newline at end of file +void CSVDoc::SubView::setStatusBar (bool show) {} + +void CSVDoc::SubView::useHint (const std::string& hint) {} \ No newline at end of file diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index aa073f81d..127cf91e6 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -40,6 +40,9 @@ namespace CSVDoc virtual void setStatusBar (bool show); ///< Default implementation: ignored + virtual void useHint (const std::string& hint); + ///< Default implementation: ignored + signals: void focusId (const CSMWorld::UniversalId& universalId); diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index b055d34eb..38e2bff0d 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -310,7 +310,7 @@ void CSVDoc::View::updateProgress (int current, int max, int type, int threads) mOperations->setProgress (current, max, type, threads); } -void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id) +void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::string& hint) { /// \todo add an user setting for limiting the number of sub views per top level view. Automatically open a new top level view if this /// number is exceeded @@ -322,6 +322,9 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id) SubView *view = mSubViewFactory.makeSubView (id, *mDocument); + if (!hint.empty()) + view->useHint (hint); + view->setStatusBar (mShowStatusBar->isChecked()); mSubViewWindow.addDockWidget (Qt::TopDockWidgetArea, view); diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 13c15ec9b..a1f6bea71 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -120,7 +120,9 @@ namespace CSVDoc public slots: - void addSubView (const CSMWorld::UniversalId& id); + void addSubView (const CSMWorld::UniversalId& id, const std::string& hint = ""); + ///< \param hint Suggested view point (e.g. coordinates in a 3D scene or a line number + /// in a script). void abortOperation (int type); From 55195f819a432fece462fa32dc90315421532473 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 2 Mar 2014 22:34:41 +0100 Subject: [PATCH 057/114] changed edit requests from row index format to UniversalId/hint format --- apps/opencs/view/doc/subview.hpp | 2 +- apps/opencs/view/doc/view.cpp | 4 ++-- apps/opencs/view/tools/reportsubview.cpp | 2 +- apps/opencs/view/world/table.cpp | 2 +- apps/opencs/view/world/table.hpp | 2 +- apps/opencs/view/world/tablesubview.cpp | 7 ++++--- apps/opencs/view/world/tablesubview.hpp | 2 +- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index 127cf91e6..59781f869 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -45,7 +45,7 @@ namespace CSVDoc signals: - void focusId (const CSMWorld::UniversalId& universalId); + void focusId (const CSMWorld::UniversalId& universalId, const std::string& hint); }; } diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 38e2bff0d..1b271fcfc 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -329,8 +329,8 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin mSubViewWindow.addDockWidget (Qt::TopDockWidgetArea, view); - connect (view, SIGNAL (focusId (const CSMWorld::UniversalId&)), this, - SLOT (addSubView (const CSMWorld::UniversalId&))); + connect (view, SIGNAL (focusId (const CSMWorld::UniversalId&, const std::string&)), this, + SLOT (addSubView (const CSMWorld::UniversalId&, const std::string&))); CSMSettings::UserSettings::instance().updateSettings("Display Format"); diff --git a/apps/opencs/view/tools/reportsubview.cpp b/apps/opencs/view/tools/reportsubview.cpp index 182d1cdd6..d59f0c234 100644 --- a/apps/opencs/view/tools/reportsubview.cpp +++ b/apps/opencs/view/tools/reportsubview.cpp @@ -40,5 +40,5 @@ void CSVTools::ReportSubView::updateEditorSetting (const QString& key, const QSt void CSVTools::ReportSubView::show (const QModelIndex& index) { - focusId (mModel->getUniversalId (index.row())); + focusId (mModel->getUniversalId (index.row()), ""); } \ No newline at end of file diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index edf3bc6de..2c4651dd7 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -306,7 +306,7 @@ void CSVWorld::Table::editRecord() QModelIndexList selectedRows = selectionModel()->selectedRows(); if (selectedRows.size()==1) - emit editRequest (selectedRows.begin()->row()); + emit editRequest (getUniversalId (selectedRows.begin()->row()), ""); } } diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 615a31b4d..995bc0095 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -86,7 +86,7 @@ namespace CSVWorld signals: - void editRequest (int row); + void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); void selectionSizeChanged (int size); diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index e330d4775..30a60536a 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -36,7 +36,8 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D setWidget (widget); - connect (mTable, SIGNAL (editRequest (int)), this, SLOT (editRequest (int))); + connect (mTable, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)), + this, SLOT (editRequest (const CSMWorld::UniversalId&, const std::string&))); connect (mTable, SIGNAL (selectionSizeChanged (int)), mBottom, SLOT (selectionSizeChanged (int))); @@ -81,9 +82,9 @@ void CSVWorld::TableSubView::setEditLock (bool locked) mBottom->setEditLock (locked); } -void CSVWorld::TableSubView::editRequest (int row) +void CSVWorld::TableSubView::editRequest (const CSMWorld::UniversalId& id, const std::string& hint) { - focusId (mTable->getUniversalId (row)); + focusId (id, hint); } void CSVWorld::TableSubView::updateEditorSetting(const QString &settingName, const QString &settingValue) diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index b3c253919..910fec325 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -53,7 +53,7 @@ namespace CSVWorld private slots: - void editRequest (int row); + void editRequest (const CSMWorld::UniversalId& id, const std::string& hint); void cloneRequest (const CSMWorld::UniversalId& toClone); void createFilterRequest(std::vector< CSMWorld::UniversalId >& types, Qt::DropAction action); From 6e8b9c88b2f3030f36fe15b0531a9ce82e51df17 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 2 Mar 2014 22:43:15 +0100 Subject: [PATCH 058/114] changed name of default worldspace (should reduce chance of a name conflict) --- components/esm/loadcell.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index dd7bf3e42..55d043c8a 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -249,7 +249,7 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) if (id.mPaged) { - id.mWorldspace = "default"; + id.mWorldspace = "sys::default"; id.mIndex.mX = mData.mX; id.mIndex.mY = mData.mY; } From 26c2f28879a1f89ddb26ee9ec204388e2cfd0613 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 2 Mar 2014 22:43:44 +0100 Subject: [PATCH 059/114] replaced global world->scene menu item with individual view context menu items in cell and reference tables --- apps/opencs/model/world/collection.hpp | 4 +- apps/opencs/model/world/collectionbase.hpp | 2 +- apps/opencs/model/world/data.cpp | 8 ++-- apps/opencs/model/world/idtable.cpp | 46 +++++++++++++++++++++- apps/opencs/model/world/idtable.hpp | 23 +++++++++-- apps/opencs/view/doc/view.cpp | 9 ----- apps/opencs/view/doc/view.hpp | 2 - apps/opencs/view/world/table.cpp | 25 ++++++++++++ apps/opencs/view/world/table.hpp | 3 ++ 9 files changed, 99 insertions(+), 23 deletions(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index d342e88a4..9d97e36c7 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -98,7 +98,7 @@ namespace CSMWorld UniversalId::Type type = UniversalId::Type_None); ///< \param type Will be ignored, unless the collection supports multiple record types - virtual void cloneRecord(const std::string& origin, + virtual void cloneRecord(const std::string& origin, const std::string& destination, const UniversalId::Type type); @@ -198,7 +198,7 @@ namespace CSMWorld } template - void Collection::cloneRecord(const std::string& origin, + void Collection::cloneRecord(const std::string& origin, const std::string& destination, const UniversalId::Type type) { diff --git a/apps/opencs/model/world/collectionbase.hpp b/apps/opencs/model/world/collectionbase.hpp index 408a89d92..442055d5f 100644 --- a/apps/opencs/model/world/collectionbase.hpp +++ b/apps/opencs/model/world/collectionbase.hpp @@ -74,7 +74,7 @@ namespace CSMWorld UniversalId::Type type = UniversalId::Type_None) = 0; ///< If the record type does not match, an exception is thrown. - virtual void cloneRecord(const std::string& origin, + virtual void cloneRecord(const std::string& origin, const std::string& destination, const UniversalId::Type type) = 0; diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 8d53c4e53..d68b79ff0 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -247,12 +247,12 @@ CSMWorld::Data::Data() : mRefs (mCells) addModel (new IdTable (&mSpells), UniversalId::Type_Spells, UniversalId::Type_Spell); addModel (new IdTable (&mTopics), UniversalId::Type_Topics, UniversalId::Type_Topic); addModel (new IdTable (&mJournals), UniversalId::Type_Journals, UniversalId::Type_Journal); - addModel (new IdTable (&mTopicInfos), UniversalId::Type_TopicInfos, UniversalId::Type_TopicInfo); - addModel (new IdTable (&mJournalInfos), UniversalId::Type_JournalInfos, UniversalId::Type_JournalInfo); - addModel (new IdTable (&mCells), UniversalId::Type_Cells, UniversalId::Type_Cell); + addModel (new IdTable (&mTopicInfos, IdTable::Reordering_WithinTopic), UniversalId::Type_TopicInfos, UniversalId::Type_TopicInfo); + addModel (new IdTable (&mJournalInfos, IdTable::Reordering_WithinTopic), UniversalId::Type_JournalInfos, UniversalId::Type_JournalInfo); + addModel (new IdTable (&mCells, IdTable::Reordering_None, IdTable::Viewing_Id), UniversalId::Type_Cells, UniversalId::Type_Cell); addModel (new IdTable (&mReferenceables), UniversalId::Type_Referenceables, UniversalId::Type_Referenceable); - addModel (new IdTable (&mRefs), UniversalId::Type_References, UniversalId::Type_Reference, false); + addModel (new IdTable (&mRefs, IdTable::Reordering_None, IdTable::Viewing_Cell), UniversalId::Type_References, UniversalId::Type_Reference, false); addModel (new IdTable (&mFilters), UniversalId::Type_Filters, UniversalId::Type_Filter, false); } diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index bea307aa2..453a7da6a 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -4,8 +4,9 @@ #include "collectionbase.hpp" #include "columnbase.hpp" -CSMWorld::IdTable::IdTable (CollectionBase *idCollection, Reordering reordering) -: mIdCollection (idCollection), mReordering (reordering) +CSMWorld::IdTable::IdTable (CollectionBase *idCollection, Reordering reordering, + Viewing viewing) +: mIdCollection (idCollection), mReordering (reordering), mViewing (viewing) {} CSMWorld::IdTable::~IdTable() @@ -188,4 +189,45 @@ void CSMWorld::IdTable::reorderRows (int baseIndex, const std::vector& newO CSMWorld::IdTable::Reordering CSMWorld::IdTable::getReordering() const { return mReordering; +} + +CSMWorld::IdTable::Viewing CSMWorld::IdTable::getViewing() const +{ + return mViewing; +} + +std::pair CSMWorld::IdTable::view (int row) const +{ + std::string id; + std::string hint; + + if (mViewing==Viewing_Cell) + { + int cellColumn = mIdCollection->searchColumnIndex (Columns::ColumnId_Cell); + int idColumn = mIdCollection->searchColumnIndex (Columns::ColumnId_Id); + + if (cellColumn!=-1 && idColumn!=-1) + { + id = mIdCollection->getData (row, cellColumn).toString().toUtf8().constData(); + hint = "r:" + std::string (mIdCollection->getData (row, idColumn).toString().toUtf8().constData()); + } + } + else if (mViewing==Viewing_Id) + { + int column = mIdCollection->searchColumnIndex (Columns::ColumnId_Id); + + if (column!=-1) + { + id = mIdCollection->getData (row, column).toString().toUtf8().constData(); + hint = "c:" + id; + } + } + + if (id.empty()) + return std::make_pair (UniversalId::Type_None, ""); + + if (id[0]=='#') + id = "sys::default"; + + return std::make_pair (UniversalId (UniversalId::Type_Scene, id), hint); } \ No newline at end of file diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 74923867d..5a271de44 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -25,10 +25,20 @@ namespace CSMWorld Reordering_WithinTopic }; + enum Viewing + { + Viewing_None, + Viewing_Id, // use ID column to generate view request (ID is transformed into + // worldspace and original ID is passed as hint with c: prefix) + Viewing_Cell // use cell column to generate view request (cell ID is transformed + // into worldspace and record ID is passed as hint with r: prefix) + }; + private: CollectionBase *mIdCollection; Reordering mReordering; + Viewing mViewing; // not implemented IdTable (const IdTable&); @@ -36,7 +46,8 @@ namespace CSMWorld public: - IdTable (CollectionBase *idCollection, Reordering reordering = Reordering_WithinTopic); + IdTable (CollectionBase *idCollection, Reordering reordering = Reordering_None, + Viewing viewing = Viewing_None); ///< The ownership of \a idCollection is not transferred. virtual ~IdTable(); @@ -63,8 +74,8 @@ namespace CSMWorld void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None); ///< \param type Will be ignored, unless the collection supports multiple record types - void cloneRecord(const std::string& origin, - const std::string& destination, + void cloneRecord(const std::string& origin, + const std::string& destination, UniversalId::Type type = UniversalId::Type_None); QModelIndex getModelIndex (const std::string& id, int column) const; @@ -86,6 +97,12 @@ namespace CSMWorld /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). Reordering getReordering() const; + + Viewing getViewing() const; + + std::pair view (int row) const; + ///< Return the UniversalId and the hint for viewing \a row. If viewing is not + /// supported by this table, return (UniversalId::Type_None, ""). }; } diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 1b271fcfc..de3f476af 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -115,10 +115,6 @@ void CSVDoc::View::setupWorldMenu() world->addSeparator(); // items that don't represent single record lists follow here - QAction *scene = new QAction (tr ("Scene"), this); - connect (scene, SIGNAL (triggered()), this, SLOT (addSceneSubView())); - world->addAction (scene); - QAction *regionMap = new QAction (tr ("Region Map"), this); connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView())); world->addAction (regionMap); @@ -432,11 +428,6 @@ void CSVDoc::View::addFiltersSubView() addSubView (CSMWorld::UniversalId::Type_Filters); } -void CSVDoc::View::addSceneSubView() -{ - addSubView (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Scene, "default")); -} - void CSVDoc::View::addTopicsSubView() { addSubView (CSMWorld::UniversalId::Type_Topics); diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index a1f6bea71..ee7380e2b 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -168,8 +168,6 @@ namespace CSVDoc void addFiltersSubView(); - void addSceneSubView(); - void addTopicsSubView(); void addJournalsSubView(); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 2c4651dd7..95433dbfc 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -35,8 +35,12 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (selectedRows.size()==1) { menu.addAction (mEditAction); + if (mCreateAction) menu.addAction(mCloneAction); + + if (mModel->getViewing()!=CSMWorld::IdTable::Viewing_None) + menu.addAction (mViewAction); } if (mCreateAction) @@ -230,6 +234,10 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); addAction (mMoveDownAction); + mViewAction = new QAction (tr ("View"), this); + connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord())); + addAction (mViewAction); + connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); @@ -381,6 +389,23 @@ void CSVWorld::Table::moveDownRecord() } } +void CSVWorld::Table::viewRecord() +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + if (selectedRows.size()==1) + { + int row =selectedRows.begin()->row(); + + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + + std::pair params = mModel->view (row); + + if (params.first.getType()!=CSMWorld::UniversalId::Type_None) + emit editRequest (params.first, params.second); + } +} + void CSVWorld::Table::updateEditorSetting (const QString &settingName, const QString &settingValue) { int columns = mModel->columnCount(); diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 995bc0095..3bf673dac 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -43,6 +43,7 @@ namespace CSVWorld QAction *mDeleteAction; QAction *mMoveUpAction; QAction *mMoveDownAction; + QAction *mViewAction; CSMWorld::IdTableProxyModel *mProxyModel; CSMWorld::IdTable *mModel; bool mEditLock; @@ -112,6 +113,8 @@ namespace CSVWorld void moveDownRecord(); + void viewRecord(); + public slots: void tableSizeUpdate(); From 0ae9cc0106b2f82e8583cc90815caabf8164930a Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 3 Mar 2014 08:21:16 +0100 Subject: [PATCH 060/114] removed unused function --- apps/openmw/mwworld/scene.cpp | 49 ----------------------------------- 1 file changed, 49 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 508127837..a6493ecf8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -79,55 +79,6 @@ namespace return true; } - - template - void insertCellRefList(MWRender::RenderingManager& rendering, - T& cellRefList, MWWorld::CellStore &cell, MWWorld::PhysicsSystem& physics, bool rescale, Loading::Listener* loadingListener) - { - if (!cellRefList.mList.empty()) - { - const MWWorld::Class& class_ = - MWWorld::Class::get (MWWorld::Ptr (&*cellRefList.mList.begin(), &cell)); - for (typename T::List::iterator it = cellRefList.mList.begin(); - it != cellRefList.mList.end(); it++) - { - if (rescale) - { - if (it->mRef.mScale<0.5) - it->mRef.mScale = 0.5; - else if (it->mRef.mScale>2) - it->mRef.mScale = 2; - } - - if (it->mData.getCount() && it->mData.isEnabled()) - { - MWWorld::Ptr ptr (&*it, &cell); - - try - { - rendering.addObject(ptr); - class_.insertObject(ptr, physics); - - float ax = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[0]).valueDegrees(); - float ay = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[1]).valueDegrees(); - float az = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees(); - MWBase::Environment::get().getWorld()->localRotateObject(ptr, ax, ay, az); - - MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().mScale); - class_.adjustPosition(ptr); - } - catch (const std::exception& e) - { - std::string error ("error during rendering: "); - std::cerr << error + e.what() << std::endl; - } - } - - loadingListener->increaseProgress(1); - } - } - } - } From 6101916b7d63f2afe5ede3d659a46f11ea9df147 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 4 Mar 2014 08:52:48 +0100 Subject: [PATCH 061/114] bumped version number --- CMakeLists.txt | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ae9ec8ac0..07b8ce289 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 28) +set(OPENMW_VERSION_MINOR 29) set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_COMMITHASH "") diff --git a/readme.txt b/readme.txt index 26abdaecc..bb17a68e7 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind OpenMW is an attempt at recreating the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. -Version: 0.28.0 +Version: 0.29.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org From a264e86e1300135866ce7bcf942853a512ca32e2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 4 Mar 2014 14:47:43 +0100 Subject: [PATCH 062/114] subclasses scene widget for worldspace scenes --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/render/worldspacewidget.cpp | 6 ++++++ apps/opencs/view/render/worldspacewidget.hpp | 18 ++++++++++++++++++ apps/opencs/view/world/scenesubview.cpp | 4 ++-- 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 apps/opencs/view/render/worldspacewidget.cpp create mode 100644 apps/opencs/view/render/worldspacewidget.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 119280f13..72757556d 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -64,7 +64,7 @@ opencs_units (view/world ) opencs_units (view/render - scenewidget + scenewidget worldspacewidget ) opencs_units_noqt (view/render diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp new file mode 100644 index 000000000..c88821a62 --- /dev/null +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -0,0 +1,6 @@ + +#include "worldspacewidget.hpp" + +CSVRender::WorldspaceWidget::WorldspaceWidget (QWidget *parent) +: SceneWidget (parent) +{} \ No newline at end of file diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp new file mode 100644 index 000000000..1c122c935 --- /dev/null +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -0,0 +1,18 @@ +#ifndef OPENCS_VIEW_WORLDSPACEWIDGET_H +#define OPENCS_VIEW_WORLDSPACEWIDGET_H + +#include "scenewidget.hpp" + +namespace CSVRender +{ + class WorldspaceWidget : public SceneWidget + { + Q_OBJECT + + public: + + WorldspaceWidget (QWidget *parent = 0); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index a23931950..516a5db80 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -9,7 +9,7 @@ #include "../filter/filterbox.hpp" -#include "../render/scenewidget.hpp" +#include "../render/worldspacewidget.hpp" #include "tablebottombox.hpp" #include "creator.hpp" @@ -44,7 +44,7 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D layout2->addWidget (toolbar, 0); - mScene = new CSVRender::SceneWidget(this); + mScene = new CSVRender::WorldspaceWidget (this); layout2->addWidget (mScene, 1); From ce8d327e8e8e1f528c6dad95f982a5b61f9edf1f Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 5 Mar 2014 22:11:10 +1100 Subject: [PATCH 063/114] Debug MSVC build of openmw needs number of sections beyond 2^16 --- apps/openmw/CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index be583ea74..eb502de38 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -159,3 +159,10 @@ if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw gcov) endif() + +if (MSVC) + # Debug version needs increased number of sections beyond 2^16 + if (CMAKE_CL_64) + set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") + endif (CMAKE_CL_64) +endif (MSVC) From 5539c75ef08949ec76efe62cc9e7dfb7fdfc7906 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 5 Mar 2014 22:12:59 +1100 Subject: [PATCH 064/114] Typo fix to avoid divide by zero. --- extern/oics/ICSInputControlSystem_joystick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/oics/ICSInputControlSystem_joystick.cpp b/extern/oics/ICSInputControlSystem_joystick.cpp index 21adc9f74..35762d2b3 100644 --- a/extern/oics/ICSInputControlSystem_joystick.cpp +++ b/extern/oics/ICSInputControlSystem_joystick.cpp @@ -385,7 +385,7 @@ namespace ICS { ctrl->setIgnoreAutoReverse(true); - float axisRange = SDL_JOY_AXIS_MAX - SDL_JOY_AXIS_MAX; + float axisRange = SDL_JOY_AXIS_MAX - SDL_JOY_AXIS_MIN; float valDisplaced = (float)(evt.value - SDL_JOY_AXIS_MIN); if(joystickBinderItem.direction == Control::INCREASE) From ab224f93c917099134fa95198d3836a6fc13c256 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 5 Mar 2014 17:08:58 +0100 Subject: [PATCH 065/114] remove our stdint.h version that uses boost and force usage of system stdint.h --- CMakeLists.txt | 8 +++++++ apps/openmw/mwbase/dialoguemanager.hpp | 2 +- apps/openmw/mwbase/journal.hpp | 2 +- apps/openmw/mwgui/bookpage.cpp | 2 +- apps/openmw/mwgui/bookpage.hpp | 2 +- apps/openmw/mwgui/journalviewmodel.hpp | 2 +- apps/openmw/mwgui/mapwindow.hpp | 2 +- apps/openmw/mwscript/globalscripts.hpp | 2 +- apps/openmw/mwsound/openal_output.cpp | 2 ++ apps/openmw/mwworld/globals.hpp | 2 +- components/bsa/bsa_file.hpp | 2 +- components/esm/defs.hpp | 2 +- components/esm/esmcommon.hpp | 2 +- components/esm/esmreader.hpp | 2 +- components/esm/loadland.hpp | 2 +- .../files/constrainedfiledatastream.cpp | 2 +- components/nif/niffile.hpp | 2 +- extern/shiny/Main/Preprocessor.hpp | 2 ++ libs/platform/stdint.h | 21 ------------------- 19 files changed, 27 insertions(+), 36 deletions(-) delete mode 100644 libs/platform/stdint.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 07b8ce289..954e161a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -238,6 +238,14 @@ if (UNIX AND NOT APPLE) find_package (Threads) endif() +# Look for stdint.h +include(CheckIncludeFile) +check_include_file(stdint.h HAVE_STDINT_H) +if(NOT HAVE_STDINT_H) + unset(HAVE_STDINT_H CACHE) + message(FATAL_ERROR "stdint.h was not found" ) +endif() + include (CheckIncludeFileCXX) check_include_file_cxx(unordered_map HAVE_UNORDERED_MAP) if (HAVE_UNORDERED_MAP) diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index 33bba07e1..3d70fdc6a 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -3,7 +3,7 @@ #include -#include +#include namespace ESM { diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp index 56d9601fc..8e4e9703f 100644 --- a/apps/openmw/mwbase/journal.hpp +++ b/apps/openmw/mwbase/journal.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include "../mwdialogue/journalentry.hpp" #include "../mwdialogue/topic.hpp" diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 694970e23..52682342f 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -6,7 +6,7 @@ #include "MyGUI_TextureUtility.h" #include "MyGUI_FactoryManager.h" -#include +#include #include #include #include diff --git a/apps/openmw/mwgui/bookpage.hpp b/apps/openmw/mwgui/bookpage.hpp index 28aa371cf..458cf2a19 100644 --- a/apps/openmw/mwgui/bookpage.hpp +++ b/apps/openmw/mwgui/bookpage.hpp @@ -5,7 +5,7 @@ #include "MyGUI_Widget.h" #include -#include +#include #include #include diff --git a/apps/openmw/mwgui/journalviewmodel.hpp b/apps/openmw/mwgui/journalviewmodel.hpp index 21c3a1178..9efdeae54 100644 --- a/apps/openmw/mwgui/journalviewmodel.hpp +++ b/apps/openmw/mwgui/journalviewmodel.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 1e52ff26a..249477551 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -1,7 +1,7 @@ #ifndef MWGUI_MAPWINDOW_H #define MWGUI_MAPWINDOW_H -#include +#include #include "windowpinnablebase.hpp" diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index a4a766226..de63b9906 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include "locals.hpp" diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 7563ad015..9a3dd7342 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include "openal_output.hpp" diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index 8f521c8a6..d8d2cefbf 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 6f3ab3bce..017adf1e3 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -24,7 +24,7 @@ #ifndef BSA_BSA_FILE_H #define BSA_BSA_FILE_H -#include +#include #include #include #include diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index c1f167992..57842796f 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_ESM_DEFS_H #define OPENMW_ESM_DEFS_H -#include +#include namespace ESM { diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index 6f51c767e..d3e6e7fea 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include namespace ESM diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 897c8fe73..b6c0ebc70 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_ESM_READER_H #define OPENMW_ESM_READER_H -#include +#include #include #include #include diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 32abb7799..028341ced 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_ESM_LAND_H #define OPENMW_ESM_LAND_H -#include +#include #include "esmcommon.hpp" diff --git a/components/files/constrainedfiledatastream.cpp b/components/files/constrainedfiledatastream.cpp index 321bcf7c8..66f6fde97 100644 --- a/components/files/constrainedfiledatastream.cpp +++ b/components/files/constrainedfiledatastream.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include namespace { diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 91ae93b40..79e1cc2a5 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -42,7 +42,7 @@ #include #include -#include +#include #include "record.hpp" #include "niftypes.hpp" diff --git a/extern/shiny/Main/Preprocessor.hpp b/extern/shiny/Main/Preprocessor.hpp index 7ee30ae7f..4eb533499 100644 --- a/extern/shiny/Main/Preprocessor.hpp +++ b/extern/shiny/Main/Preprocessor.hpp @@ -1,6 +1,8 @@ #ifndef SH_PREPROCESSOR_H #define SH_PREPROCESSOR_H +#include + #include #include diff --git a/libs/platform/stdint.h b/libs/platform/stdint.h deleted file mode 100644 index 00af741b1..000000000 --- a/libs/platform/stdint.h +++ /dev/null @@ -1,21 +0,0 @@ -// Wrapper for MSVC -#ifndef _STDINT_WRAPPER_H -#define _STDINT_WRAPPER_H - -#if (_MSC_VER >= 1600) - -#include - -#else - -#include - -// Pull the boost names into the global namespace for convenience -using boost::int32_t; -using boost::uint32_t; -using boost::int64_t; -using boost::uint64_t; - -#endif - -#endif From c9e349f60f2823cd7c98fdc13c2d5f4a8ee67c46 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 16 Feb 2014 18:48:35 +0100 Subject: [PATCH 066/114] Terrain: support alternate coordinate systems. Get rid of LoadingListener for now --- apps/openmw/mwrender/renderingmanager.cpp | 7 +-- apps/openmw/mwrender/terrainstorage.cpp | 2 +- apps/openmw/mwrender/terrainstorage.hpp | 2 +- components/terrain/chunk.cpp | 2 +- components/terrain/defs.hpp | 41 ++++++++++++++ components/terrain/material.cpp | 1 - components/terrain/quadtreenode.cpp | 46 ++++++++++++---- components/terrain/quadtreenode.hpp | 6 +-- components/terrain/storage.hpp | 6 +-- components/terrain/world.cpp | 66 +++++++++++++++-------- components/terrain/world.hpp | 25 +++++---- 11 files changed, 147 insertions(+), 57 deletions(-) create mode 100644 components/terrain/defs.hpp diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 928a34107..cf97afa7a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1043,15 +1043,12 @@ void RenderingManager::enableTerrain(bool enable) { if (!mTerrain) { - Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); - Loading::ScopedLoad load(listener); - mTerrain = new Terrain::World(listener, mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + mTerrain = new Terrain::World(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, Settings::Manager::getBool("distant land", "Terrain"), - Settings::Manager::getBool("shader", "Terrain")); + Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64); mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), Settings::Manager::getBool("split", "Shadows")); mTerrain->update(mRendering.getCamera()->getRealPosition()); - mTerrain->setLoadingListener(NULL); } mTerrain->setVisible(true); } diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 750441f6a..2ccf3bd0a 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -167,7 +167,7 @@ namespace MWRender } - void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, + void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, Ogre::HardwareVertexBufferSharedPtr vertexBuffer, Ogre::HardwareVertexBufferSharedPtr normalBuffer, Ogre::HardwareVertexBufferSharedPtr colourBuffer) diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 2ef014aaf..f84da5c5d 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -36,7 +36,7 @@ namespace MWRender /// @param vertexBuffer buffer to write vertices /// @param normalBuffer buffer to write vertex normals /// @param colourBuffer buffer to write vertex colours - virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, + virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, Ogre::HardwareVertexBufferSharedPtr vertexBuffer, Ogre::HardwareVertexBufferSharedPtr normalBuffer, Ogre::HardwareVertexBufferSharedPtr colourBuffer); diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index a5c629088..a05066313 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -52,7 +52,7 @@ namespace Terrain mColourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); - mNode->getTerrain()->getStorage()->fillVertexBuffers(lodLevel, mNode->getSize(), mNode->getCenter(), + mNode->getTerrain()->getStorage()->fillVertexBuffers(lodLevel, mNode->getSize(), mNode->getCenter(), mNode->getTerrain()->getAlign(), mVertexBuffer, mNormalBuffer, mColourBuffer); mVertexData->vertexBufferBinding->setBinding(0, mVertexBuffer); diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp new file mode 100644 index 000000000..9f0b8c09b --- /dev/null +++ b/components/terrain/defs.hpp @@ -0,0 +1,41 @@ +#ifndef COMPONENTS_TERRAIN_DEFS_HPP +#define COMPONENTS_TERRAIN_DEFS_HPP + +namespace Terrain +{ + + /// The alignment of the terrain + enum Alignment + { + /// Terrain is in the X/Z plane + Align_XZ = 0, + /// Terrain is in the X/Y plane + Align_XY = 1, + /// Terrain is in the Y/Z plane. + /// UNTESTED - use at own risk. + /// Besides, X as up axis? What is wrong with you? ;) + Align_YZ = 2 + }; + + inline void convertPosition(Alignment align, float &x, float &y, float &z) + { + switch (align) + { + case Align_XY: + return; + case Align_XZ: + std::swap(y, z); + // This is since -Z should be going *into* the screen + // If not doing this, we'd get wrong vertex winding + z *= -1; + return; + case Align_YZ: + std::swap(x, y); + std::swap(y, z); + return; + } + } + +} + +#endif diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 8e78d2216..31874af51 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -235,7 +235,6 @@ namespace Terrain sh::MaterialInstancePass* p = material->createPass (); - p->setProperty ("vertex_program", sh::makeProperty(new sh::StringValue("terrain_vertex"))); p->setProperty ("fragment_program", sh::makeProperty(new sh::StringValue("terrain_fragment"))); if (layerOffset != 0) diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 82ccc7c89..cb7660fed 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -169,7 +169,11 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const pos = mParent->getCenter(); pos = mCenter - pos; float cellWorldSize = mTerrain->getStorage()->getCellWorldSize(); - mSceneNode->setPosition(Ogre::Vector3(pos.x*cellWorldSize, pos.y*cellWorldSize, 0)); + + Ogre::Vector3 sceneNodePos (pos.x*cellWorldSize, pos.y*cellWorldSize, 0); + mTerrain->convertPosition(sceneNodePos.x, sceneNodePos.y, sceneNodePos.z); + + mSceneNode->setPosition(sceneNodePos); mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled()); } @@ -212,11 +216,31 @@ void QuadTreeNode::initAabb() mChildren[i]->initAabb(); mBounds.merge(mChildren[i]->getBoundingBox()); } - mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*cellWorldSize, -mSize/2*cellWorldSize, mBounds.getMinimum().z), - Ogre::Vector3(mSize/2*cellWorldSize, mSize/2*cellWorldSize, mBounds.getMaximum().z)); + float minH, maxH; + switch (mTerrain->getAlign()) + { + case Terrain::Align_XY: + minH = mBounds.getMinimum().z; + maxH = mBounds.getMaximum().z; + break; + case Terrain::Align_XZ: + minH = mBounds.getMinimum().y; + maxH = mBounds.getMinimum().y; + break; + case Terrain::Align_YZ: + minH = mBounds.getMinimum().x; + maxH = mBounds.getMaximum().x; + break; + } + Ogre::Vector3 min(-mSize/2*cellWorldSize, -mSize/2*cellWorldSize, minH); + Ogre::Vector3 max(Ogre::Vector3(mSize/2*cellWorldSize, mSize/2*cellWorldSize, maxH)); + mBounds = Ogre::AxisAlignedBox (min, max); + mTerrain->convertBounds(mBounds); } - mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + Ogre::Vector3(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0), - mBounds.getMaximum() + Ogre::Vector3(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0)); + Ogre::Vector3 offset(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0); + mTerrain->convertPosition(offset); + mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + offset, + mBounds.getMaximum() + offset); } void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box) @@ -229,7 +253,12 @@ const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox() return mBounds; } -void QuadTreeNode::update(const Ogre::Vector3 &cameraPos, Loading::Listener* loadingListener) +const Ogre::AxisAlignedBox& QuadTreeNode::getWorldBoundingBox() +{ + return mWorldBounds; +} + +void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) { const Ogre::AxisAlignedBox& bounds = getBoundingBox(); if (bounds.isNull()) @@ -263,9 +292,6 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos, Loading::Listener* loa bool hadChunk = hasChunk(); - if (loadingListener) - loadingListener->indicateProgress(); - if (!distantLand && dist > 8192*2) { if (mIsActive) @@ -353,7 +379,7 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos, Loading::Listener* loa } assert(hasChildren() && "Leaf node's LOD needs to be 0"); for (int i=0; i<4; ++i) - mChildren[i]->update(cameraPos, loadingListener); + mChildren[i]->update(cameraPos); } } diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index ea299c096..32c7dd56a 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -5,8 +5,6 @@ #include #include -#include - namespace Ogre { class Rectangle2D; @@ -95,10 +93,12 @@ namespace Terrain /// Get bounding box in local coordinates const Ogre::AxisAlignedBox& getBoundingBox(); + const Ogre::AxisAlignedBox& getWorldBoundingBox(); + World* getTerrain() { return mTerrain; } /// Adjust LODs for the given camera position, possibly splitting up chunks or merging them. - void update (const Ogre::Vector3& cameraPos, Loading::Listener* loadingListener); + void update (const Ogre::Vector3& cameraPos); /// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided. /// Call after QuadTreeNode::update! diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index d8cdab9ec..d7fb78492 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -1,10 +1,10 @@ #ifndef COMPONENTS_TERRAIN_STORAGE_H #define COMPONENTS_TERRAIN_STORAGE_H -#include - #include +#include "defs.hpp" + namespace Terrain { @@ -43,7 +43,7 @@ namespace Terrain /// @param vertexBuffer buffer to write vertices /// @param normalBuffer buffer to write vertex normals /// @param colourBuffer buffer to write vertex colours - virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, + virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, Ogre::HardwareVertexBufferSharedPtr vertexBuffer, Ogre::HardwareVertexBufferSharedPtr normalBuffer, Ogre::HardwareVertexBufferSharedPtr colourBuffer) = 0; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 4273f227d..49a561c0e 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -6,8 +6,6 @@ #include #include -#include - #include "storage.hpp" #include "quadtreenode.hpp" @@ -51,27 +49,25 @@ namespace namespace Terrain { - World::World(Loading::Listener* loadingListener, Ogre::SceneManager* sceneMgr, - Storage* storage, int visibilityFlags, bool distantLand, bool shaders) + World::World(Ogre::SceneManager* sceneMgr, + Storage* storage, int visibilityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize) : mStorage(storage) - , mMinBatchSize(1) - , mMaxBatchSize(64) + , mMinBatchSize(minBatchSize) + , mMaxBatchSize(maxBatchSize) , mSceneMgr(sceneMgr) , mVisibilityFlags(visibilityFlags) , mDistantLand(distantLand) , mShaders(shaders) , mVisible(true) - , mLoadingListener(loadingListener) + , mAlign(align) , mMaxX(0) , mMinX(0) , mMaxY(0) , mMinY(0) { - loadingListener->setLabel("Creating terrain"); - loadingListener->indicateProgress(); - mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); + /// \todo make composite map size configurable Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a"); mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual( "terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, @@ -96,11 +92,11 @@ namespace Terrain mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL); buildQuadTree(mRootNode); - loadingListener->indicateProgress(); + //loadingListener->indicateProgress(); mRootNode->initAabb(); - loadingListener->indicateProgress(); + //loadingListener->indicateProgress(); mRootNode->initNeighbours(); - loadingListener->indicateProgress(); + //loadingListener->indicateProgress(); } World::~World() @@ -120,8 +116,12 @@ namespace Terrain Ogre::Vector2 center = node->getCenter(); float cellWorldSize = getStorage()->getCellWorldSize(); if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ)) - node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ), - Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ))); + { + Ogre::AxisAlignedBox bounds(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ), + Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ)); + convertBounds(bounds); + node->setBoundingBox(bounds); + } else node->markAsDummy(); // no data available for this node, skip it return; @@ -161,7 +161,7 @@ namespace Terrain { if (!mVisible) return; - mRootNode->update(cameraPos, mLoadingListener); + mRootNode->update(cameraPos); mRootNode->updateIndexBuffers(); } @@ -173,11 +173,7 @@ namespace Terrain || center.y < mMinY) return Ogre::AxisAlignedBox::BOX_NULL; QuadTreeNode* node = findNode(center, mRootNode); - Ogre::AxisAlignedBox box = node->getBoundingBox(); - float cellWorldSize = getStorage()->getCellWorldSize(); - box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * cellWorldSize, - box.getMaximum() + Ogre::Vector3(center.x, center.y, 0) * cellWorldSize); - return box; + return node->getWorldBoundingBox(); } Ogre::HardwareVertexBufferSharedPtr World::getVertexBuffer(int numVertsOneSide) @@ -414,5 +410,33 @@ namespace Terrain return mVisible; } + void World::convertPosition(float &x, float &y, float &z) + { + Terrain::convertPosition(mAlign, x, y, z); + } + + void World::convertPosition(Ogre::Vector3 &pos) + { + convertPosition(pos.x, pos.y, pos.z); + } + + void World::convertBounds(Ogre::AxisAlignedBox& bounds) + { + switch (mAlign) + { + case Align_XY: + return; + case Align_XZ: + convertPosition(bounds.getMinimum()); + convertPosition(bounds.getMaximum()); + // Because we changed sign of Z + std::swap(bounds.getMinimum().z, bounds.getMaximum().z); + return; + case Align_YZ: + convertPosition(bounds.getMinimum()); + convertPosition(bounds.getMaximum()); + return; + } + } } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index bf733b889..31bd51ab1 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -6,10 +6,7 @@ #include #include -namespace Loading -{ - class Listener; -} +#include "defs.hpp" namespace Ogre { @@ -33,7 +30,6 @@ namespace Terrain { public: /// @note takes ownership of \a storage - /// @param loadingListener Listener to update with progress /// @param sceneMgr scene manager to use /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) /// @param visbilityFlags visibility flags for the created meshes @@ -41,12 +37,13 @@ namespace Terrain /// This is a temporary option until it can be streamlined. /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually /// faster so this is just here for compatibility. - World(Loading::Listener* loadingListener, Ogre::SceneManager* sceneMgr, - Storage* storage, int visiblityFlags, bool distantLand, bool shaders); + /// @param align The align of the terrain, see Alignment enum + /// @param minBatchSize Minimum size of a terrain batch along one side (in cell units). Used for building the quad tree. + /// @param maxBatchSize Maximum size of a terrain batch along one side (in cell units). Used when traversing the quad tree. + World(Ogre::SceneManager* sceneMgr, + Storage* storage, int visiblityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize); ~World(); - void setLoadingListener(Loading::Listener* loadingListener) { mLoadingListener = loadingListener; } - bool getDistantLandEnabled() { return mDistantLand; } bool getShadersEnabled() { return mShaders; } bool getShadowsEnabled() { return mShadows; } @@ -86,14 +83,15 @@ namespace Terrain void enableSplattingShader(bool enabled); + Alignment getAlign() { return mAlign; } + private: bool mDistantLand; bool mShaders; bool mShadows; bool mSplitShadows; bool mVisible; - - Loading::Listener* mLoadingListener; + Alignment mAlign; QuadTreeNode* mRootNode; Ogre::SceneNode* mRootSceneNode; @@ -138,6 +136,11 @@ namespace Terrain void clearCompositeMapSceneManager(); void renderCompositeMap (Ogre::TexturePtr target); + // Convert the given position from Z-up align, i.e. Align_XY to the wanted align set in mAlign + void convertPosition (float& x, float& y, float& z); + void convertPosition (Ogre::Vector3& pos); + void convertBounds (Ogre::AxisAlignedBox& bounds); + private: // Index buffers are shared across terrain batches where possible. There is one index buffer for each // combination of LOD deltas and index buffer LOD we may need. From 64c99325974deec0fbe0839df488a71e40f4ceac Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 16 Feb 2014 19:04:12 +0100 Subject: [PATCH 067/114] Terrain: remove hard dependency on shiny - can now be compiled without it (fixed function) --- components/CMakeLists.txt | 1 + components/terrain/material.cpp | 6 ++++++ components/terrain/world.cpp | 6 ++++++ 3 files changed, 13 insertions(+) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 831b14057..938ef2e4d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -73,6 +73,7 @@ add_component_dir (translation translation ) +add_definitions(-DTERRAIN_USE_SHADER=1) add_component_dir (terrain quadtreenode chunk world storage material ) diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 31874af51..78b17909a 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -6,7 +6,9 @@ #include +#if TERRAIN_USE_SHADER #include +#endif namespace { @@ -64,7 +66,9 @@ namespace Terrain assert(!renderCompositeMap || !displayCompositeMap); if (!mat.isNull()) { +#if TERRAIN_USE_SHADER sh::Factory::getInstance().destroyMaterialInstance(mat->getName()); +#endif Ogre::MaterialManager::getSingleton().remove(mat->getName()); } @@ -144,6 +148,7 @@ namespace Terrain return mat; } +#if TERRAIN_USE_SHADER else { sh::MaterialInstance* material = sh::Factory::getInstance().createMaterialInstance (name.str()); @@ -343,6 +348,7 @@ namespace Terrain } } } +#endif return Ogre::MaterialManager::getSingleton().getByName(name.str()); } diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 49a561c0e..e633c1a00 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -65,6 +65,12 @@ namespace Terrain , mMaxY(0) , mMinY(0) { +#if TERRAIN_USE_SHADER == 0 + if (mShaders) + std::cerr << "Compiled Terrain without shader support, disabling..." << std::endl; + mShaders = false; +#endif + mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); /// \todo make composite map size configurable From 8730b61362fdee33869a2ac1870fb0aa69a5534c Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 17 Feb 2014 15:00:01 +0100 Subject: [PATCH 068/114] Render maps after *all* cells have finished loading Still not fixing Bug #772, but at least this will allow for background loading of terrain. --- apps/openmw/mwworld/scene.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index a6493ecf8..40004807b 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -162,8 +162,6 @@ namespace MWWorld mRendering.cellAdded (cell); mRendering.configureAmbient(*cell); - mRendering.requestMap(cell); - mRendering.configureAmbient(*cell); } // register local scripts @@ -198,6 +196,9 @@ namespace MWWorld mechMgr->watchActor(player); MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); + + for (CellStoreCollection::iterator active = mActiveCells.begin(); active!=mActiveCells.end(); ++active) + mRendering.requestMap(*active); } void Scene::changeToVoid() From 6c863486e18f08ce40ee185817e2047d705c2cb2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 17 Feb 2014 22:09:07 +0100 Subject: [PATCH 069/114] Terrain: fix an embarrassing copy&paste mistake. --- components/terrain/quadtreenode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index cb7660fed..3969d4761 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -225,7 +225,7 @@ void QuadTreeNode::initAabb() break; case Terrain::Align_XZ: minH = mBounds.getMinimum().y; - maxH = mBounds.getMinimum().y; + maxH = mBounds.getMaximum().y; break; case Terrain::Align_YZ: minH = mBounds.getMinimum().x; From 065b6d3331d7190746cba67d479d8f9e552d6472 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 18 Feb 2014 12:03:35 +0100 Subject: [PATCH 070/114] Terrain: moved ESM::Land load to earlier in the startup procedure Required for background loading as we cannot load ESM::Land data from background threads. --- apps/openmw/mwrender/globalmap.cpp | 7 +++---- apps/openmw/mwrender/terrainstorage.cpp | 7 ------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 018dc082a..76ad1890f 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -75,10 +75,9 @@ namespace MWRender if (land) { - if (!land->isDataLoaded(ESM::Land::DATA_VHGT)) - { - land->loadData(ESM::Land::DATA_VHGT); - } + int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; + if (!land->isDataLoaded(mask)) + land->loadData(mask); } for (int cellY=0; cellYgetStore(); ESM::Land* land = esmStore.get().search(cellX, cellY); - // Load the data we are definitely going to need - int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; - if (land && !land->isDataLoaded(mask)) - land->loadData(mask); return land; } @@ -316,9 +312,6 @@ namespace MWRender ESM::Land* land = getLand(cellX, cellY); if (land) { - if (!land->isDataLoaded(ESM::Land::DATA_VTEX)) - land->loadData(ESM::Land::DATA_VTEX); - int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; if (tex == 0) return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin From b3fed853ae3931655fb094cf0c5efd4ff12e41cc Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 18 Feb 2014 12:27:22 +0100 Subject: [PATCH 071/114] Terrain: take cell world size into account for LOD selection --- components/terrain/quadtreenode.cpp | 30 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 3969d4761..44bf320e4 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -171,7 +171,7 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const float cellWorldSize = mTerrain->getStorage()->getCellWorldSize(); Ogre::Vector3 sceneNodePos (pos.x*cellWorldSize, pos.y*cellWorldSize, 0); - mTerrain->convertPosition(sceneNodePos.x, sceneNodePos.y, sceneNodePos.z); + mTerrain->convertPosition(sceneNodePos); mSceneNode->setPosition(sceneNodePos); @@ -274,21 +274,23 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) mParent->getSceneNode()->addChild(mSceneNode); } - /// \todo implement error metrics or some other means of not using arbitrary values - /// (general quality needs to be user configurable as well) + // Simple LOD selection + /// \todo use error metrics? size_t wantedLod = 0; - if (dist > 8192*1) - wantedLod = 1; - if (dist > 8192*2) - wantedLod = 2; - if (dist > 8192*5) - wantedLod = 3; - if (dist > 8192*12) - wantedLod = 4; - if (dist > 8192*32) - wantedLod = 5; - if (dist > 8192*64) + float cellWorldSize = mTerrain->getStorage()->getCellWorldSize(); + + if (dist > cellWorldSize*64) wantedLod = 6; + else if (dist > cellWorldSize*32) + wantedLod = 5; + else if (dist > cellWorldSize*12) + wantedLod = 4; + else if (dist > cellWorldSize*5) + wantedLod = 3; + else if (dist > cellWorldSize*2) + wantedLod = 2; + else if (dist > cellWorldSize) + wantedLod = 1; bool hadChunk = hasChunk(); From 195071efc7f69359358d7dde50837facc6763884 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 18 Feb 2014 16:44:37 +0100 Subject: [PATCH 072/114] Terrain: geometry is now loaded in background threads. TODO: background load layer textures and blendmaps. "Distant land" setting has been removed for now (i.e. always enabled). --- apps/openmw/mwrender/renderingmanager.cpp | 5 + apps/openmw/mwrender/terrainstorage.cpp | 17 +-- apps/openmw/mwrender/terrainstorage.hpp | 18 +-- apps/openmw/mwworld/store.hpp | 4 + components/terrain/chunk.cpp | 11 +- components/terrain/chunk.hpp | 5 +- components/terrain/defs.hpp | 7 + components/terrain/quadtreenode.cpp | 167 +++++++++++----------- components/terrain/quadtreenode.hpp | 16 +++ components/terrain/storage.hpp | 27 ++-- components/terrain/world.cpp | 69 +++++++++ components/terrain/world.hpp | 42 +++++- 12 files changed, 264 insertions(+), 124 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index cf97afa7a..68bf957c8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -651,6 +651,11 @@ void RenderingManager::setGlare(bool glare) void RenderingManager::requestMap(MWWorld::CellStore* cell) { + // FIXME: move to other method + // TODO: probably not needed when crossing a cell border. Could delay the map render until we are loaded. + if (mTerrain) + mTerrain->syncLoad(); + if (cell->getCell()->isExterior()) { assert(mTerrain); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 8597c762c..ec26b1872 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -164,9 +164,9 @@ namespace MWRender } void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, - Ogre::HardwareVertexBufferSharedPtr vertexBuffer, - Ogre::HardwareVertexBufferSharedPtr normalBuffer, - Ogre::HardwareVertexBufferSharedPtr colourBuffer) + std::vector& positions, + std::vector& normals, + std::vector& colours) { // LOD level n means every 2^n-th vertex is kept size_t increment = 1 << lodLevel; @@ -180,11 +180,8 @@ namespace MWRender size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1; - std::vector colors; - colors.resize(numVerts*numVerts*4); - std::vector positions; + colours.resize(numVerts*numVerts*4); positions.resize(numVerts*numVerts*3); - std::vector normals; normals.resize(numVerts*numVerts*3); Ogre::Vector3 normal; @@ -270,7 +267,7 @@ namespace MWRender color.a = 1; Ogre::uint32 rsColor; Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); - memcpy(&colors[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32)); + memcpy(&colours[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32)); ++vertX; } @@ -283,10 +280,6 @@ namespace MWRender assert(vertX_ == numVerts); // Ensure we covered whole area } assert(vertY_ == numVerts); // Ensure we covered whole area - - vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true); - normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true); - colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colors[0], true); } TerrainStorage::UniqueTextureId TerrainStorage::getVtexIndexAt(int cellX, int cellY, diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index f84da5c5d..b1acdb785 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -19,8 +19,8 @@ namespace MWRender /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); - /// Get the minimum and maximum heights of a terrain chunk. - /// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree. + /// Get the minimum and maximum heights of a terrain region. + /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. /// Larger chunks can simply merge AABB of children. /// @param size size of the chunk in cell units /// @param center center of the chunk in cell units @@ -30,16 +30,18 @@ namespace MWRender virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); /// Fill vertex buffers for a terrain chunk. + /// @note May be called from background threads. Make sure to only call thread-safe functions from here! + /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue. /// @param lodLevel LOD level, 0 = most detailed /// @param size size of the terrain chunk in cell units /// @param center center of the chunk in cell units - /// @param vertexBuffer buffer to write vertices - /// @param normalBuffer buffer to write vertex normals - /// @param colourBuffer buffer to write vertex colours + /// @param positions buffer to write vertices + /// @param normals buffer to write vertex normals + /// @param colours buffer to write vertex colours virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, - Ogre::HardwareVertexBufferSharedPtr vertexBuffer, - Ogre::HardwareVertexBufferSharedPtr normalBuffer, - Ogre::HardwareVertexBufferSharedPtr colourBuffer); + std::vector& positions, + std::vector& normals, + std::vector& colours); /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 7bd00d6bf..6b99c0a0c 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -388,6 +388,8 @@ namespace MWWorld typedef std::vector::const_iterator iterator; + // Must be threadsafe! Called from terrain background loading threads. + // Not a big deal here, since ESM::LandTexture can never be modified or inserted/erased const ESM::LandTexture *search(size_t index, size_t plugin) const { assert(plugin < mStatic.size()); const LandTextureList <exl = mStatic[plugin]; @@ -487,6 +489,8 @@ namespace MWWorld return iterator(mStatic.end()); } + // Must be threadsafe! Called from terrain background loading threads. + // Not a big deal here, since ESM::Land can never be modified or inserted/erased ESM::Land *search(int x, int y) const { ESM::Land land; land.mX = x, land.mY = y; diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index a05066313..139694ee5 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -10,9 +10,9 @@ namespace Terrain { - Chunk::Chunk(QuadTreeNode* node, short lodLevel) + Chunk::Chunk(QuadTreeNode* node, const LoadResponseData& data) : mNode(node) - , mVertexLod(lodLevel) + , mVertexLod(node->getNativeLodLevel()) , mAdditionalLod(0) { mVertexData = OGRE_NEW Ogre::VertexData; @@ -20,6 +20,8 @@ namespace Terrain unsigned int verts = mNode->getTerrain()->getStorage()->getCellVertices(); + size_t lodLevel = mNode->getNativeLodLevel(); + // Set the total number of vertices size_t numVertsOneSide = mNode->getSize() * (verts-1); numVertsOneSide /= 1 << lodLevel; @@ -52,8 +54,9 @@ namespace Terrain mColourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); - mNode->getTerrain()->getStorage()->fillVertexBuffers(lodLevel, mNode->getSize(), mNode->getCenter(), mNode->getTerrain()->getAlign(), - mVertexBuffer, mNormalBuffer, mColourBuffer); + mVertexBuffer->writeData(0, mVertexBuffer->getSizeInBytes(), &data.mPositions[0], true); + mNormalBuffer->writeData(0, mNormalBuffer->getSizeInBytes(), &data.mNormals[0], true); + mColourBuffer->writeData(0, mColourBuffer->getSizeInBytes(), &data.mColours[0], true); mVertexData->vertexBufferBinding->setBinding(0, mVertexBuffer); mVertexData->vertexBufferBinding->setBinding(1, mNormalBuffer); diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp index d74c65ba6..a6477df28 100644 --- a/components/terrain/chunk.hpp +++ b/components/terrain/chunk.hpp @@ -8,6 +8,7 @@ namespace Terrain { class QuadTreeNode; + struct LoadResponseData; /** * @brief Renders a chunk of terrain, either using alpha splatting or a composite map. @@ -15,8 +16,8 @@ namespace Terrain class Chunk : public Ogre::Renderable, public Ogre::MovableObject { public: - /// @param lodLevel LOD level for the vertex buffer. - Chunk (QuadTreeNode* node, short lodLevel); + Chunk (QuadTreeNode* node, const LoadResponseData& data); + virtual ~Chunk(); void setMaterial (const Ogre::MaterialPtr& material); diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp index 9f0b8c09b..c14608e76 100644 --- a/components/terrain/defs.hpp +++ b/components/terrain/defs.hpp @@ -36,6 +36,13 @@ namespace Terrain } } + struct LayerInfo + { + std::string mDiffuseMap; + std::string mNormalMap; + bool mParallax; // Height info in normal map alpha channel? + bool mSpecular; // Specular info in diffuse map alpha channel? + }; } #endif diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 44bf320e4..721771ff3 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -153,6 +153,7 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const , mParent(parent) , mTerrain(terrain) , mChunk(NULL) + , mLoadState(LS_Unloaded) { mBounds.setNull(); for (int i=0; i<4; ++i) @@ -266,8 +267,6 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) float dist = distance(mWorldBounds, cameraPos); - bool distantLand = mTerrain->getDistantLandEnabled(); - // Make sure our scene node is attached if (!mSceneNode->isInSceneGraph()) { @@ -292,100 +291,110 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) else if (dist > cellWorldSize) wantedLod = 1; - bool hadChunk = hasChunk(); - - if (!distantLand && dist > 8192*2) - { - if (mIsActive) - { - destroyChunks(true); - mIsActive = false; - } - return; - } - mIsActive = true; - if (mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod) + bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod; + + if (wantToDisplay) { // Wanted LOD is small enough to render this node in one chunk - if (!mChunk) + if (mLoadState == LS_Unloaded) { - mChunk = new Chunk(this, mLodLevel); - mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); - mChunk->setCastShadows(true); - mSceneNode->attachObject(mChunk); - - mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); - mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled()); + mLoadState = LS_Loading; + mTerrain->queueLoad(this); + } - if (mSize == 1) - { - ensureLayerInfo(); - mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); - } - else + if (mLoadState == LS_Loaded) + { + // Additional (index buffer) LOD is currently disabled. + // This is due to a problem with the LOD selection when a node splits. + // After splitting, the distance is measured from the children's bounding boxes, which are possibly + // further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD + // than the original node. + // In short, we'd sometimes get a switch to a lesser detail when actually moving closer. + // This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour + // node hasn't split yet, and has a higher LOD than our node's child: + // ----- ----- ------------ + // | LOD | LOD | | + // | 1 | 1 | | + // |-----|-----| LOD 0 | + // | LOD | LOD | | + // | 0 | 0 | | + // ----- ----- ------------ + // To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're + // doing here. + // But this "solution" does increase triangle overhead, so eventually we need to find a more clever way. + //mChunk->setAdditionalLod(wantedLod - mLodLevel); + + if (!mChunk->getVisible() && hasChildren()) { - ensureCompositeMap(); - mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); - mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); + // Make sure child scene nodes are detached + mSceneNode->removeAllChildren(); + + // TODO: unload + //for (int i=0; i<4; ++i) + // mChildren[i]->unload(); } + + mChunk->setVisible(true); } + } - // Additional (index buffer) LOD is currently disabled. - // This is due to a problem with the LOD selection when a node splits. - // After splitting, the distance is measured from the children's bounding boxes, which are possibly - // further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD - // than the original node. - // In short, we'd sometimes get a switch to a lesser detail when actually moving closer. - // This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour - // node hasn't split yet, and has a higher LOD than our node's child: - // ----- ----- ------------ - // | LOD | LOD | | - // | 1 | 1 | | - // |-----|-----| LOD 0 | - // | LOD | LOD | | - // | 0 | 0 | | - // ----- ----- ------------ - // To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're - // doing here. - // But this "solution" does increase triangle overhead, so eventually we need to find a more clever way. - //mChunk->setAdditionalLod(wantedLod - mLodLevel); - - mChunk->setVisible(true); - - if (!hadChunk && hasChildren()) + if (wantToDisplay && mLoadState != LS_Loaded) + { + // We want to display, but aren't ready yet. Perhaps our child nodes are ready? + // TODO: this doesn't check child-child nodes... + if (hasChildren()) { - // Make sure child scene nodes are detached - mSceneNode->removeAllChildren(); - - // If distant land is enabled, keep the chunks around in case we need them again, - // otherwise, prefer low memory usage - if (!distantLand) - for (int i=0; i<4; ++i) - mChildren[i]->destroyChunks(true); + for (int i=0; i<4; ++i) + if (mChildren[i]->hasChunk()) + mChildren[i]->update(cameraPos); } } - else + if (!wantToDisplay) { - // Wanted LOD is too detailed to be rendered in one chunk, - // so split it up by delegating to child nodes - if (hadChunk) + // We do not want to display this node - delegate to children + if (mChunk) + mChunk->setVisible(false); + if (hasChildren()) { - // If distant land is enabled, keep the chunks around in case we need them again, - // otherwise, prefer low memory usage - if (!distantLand) - destroyChunks(false); - else if (mChunk) - mChunk->setVisible(false); + for (int i=0; i<4; ++i) + mChildren[i]->update(cameraPos); } - assert(hasChildren() && "Leaf node's LOD needs to be 0"); - for (int i=0; i<4; ++i) - mChildren[i]->update(cameraPos); } } -void QuadTreeNode::destroyChunks(bool children) +void QuadTreeNode::load(const LoadResponseData &data) +{ + assert (!mChunk); + + std::cout << "loading " << std::endl; + mChunk = new Chunk(this, data); + mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); + mChunk->setCastShadows(true); + mSceneNode->attachObject(mChunk); + + mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); + mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled()); + + if (mSize == 1) + { + ensureLayerInfo(); + mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); + } + else + { + ensureCompositeMap(); + mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); + mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); + } + + mChunk->setVisible(false); + + mLoadState = LS_Loaded; +} + +void QuadTreeNode::unload() { if (mChunk) { @@ -411,9 +420,7 @@ void QuadTreeNode::destroyChunks(bool children) mCompositeMap.setNull(); } } - else if (children && hasChildren()) - for (int i=0; i<4; ++i) - mChildren[i]->destroyChunks(true); + mLoadState = LS_Unloaded; } void QuadTreeNode::updateIndexBuffers() diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 32c7dd56a..0bba1024c 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -15,6 +15,7 @@ namespace Terrain class World; class Chunk; class MaterialGenerator; + struct LoadResponseData; enum Direction { @@ -33,6 +34,13 @@ namespace Terrain Root }; + enum LoadState + { + LS_Unloaded, + LS_Loading, + LS_Loaded + }; + /** * @brief A node in the quad tree for our terrain. Depending on LOD, * a node can either choose to render itself in one batch (merging its children), @@ -124,10 +132,18 @@ namespace Terrain /// @param quads collect quads here so they can be deleted later void prepareForCompositeMap(Ogre::TRect area); + /// Create a chunk for this node from the given data. + void load (const LoadResponseData& data); + void unload(); + + LoadState getLoadState() { return mLoadState; } + private: // Stored here for convenience in case we need layer list again MaterialGenerator* mMaterialGenerator; + LoadState mLoadState; + /// Is this node (or any of its child nodes) currently configured to render itself? /// (only relevant when distant land is disabled, otherwise whole terrain is always rendered) bool mIsActive; diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index d7fb78492..1b1968f1a 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -7,15 +7,6 @@ namespace Terrain { - - struct LayerInfo - { - std::string mDiffuseMap; - std::string mNormalMap; - bool mParallax; // Height info in normal map alpha channel? - bool mSpecular; // Specular info in diffuse map alpha channel? - }; - /// We keep storage of terrain data abstract here since we need different implementations for game and editor class Storage { @@ -26,8 +17,8 @@ namespace Terrain /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; - /// Get the minimum and maximum heights of a terrain chunk. - /// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree. + /// Get the minimum and maximum heights of a terrain region. + /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. /// Larger chunks can simply merge AABB of children. /// @param size size of the chunk in cell units /// @param center center of the chunk in cell units @@ -37,16 +28,18 @@ namespace Terrain virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max) = 0; /// Fill vertex buffers for a terrain chunk. + /// @note May be called from background threads. Make sure to only call thread-safe functions from here! + /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue. /// @param lodLevel LOD level, 0 = most detailed /// @param size size of the terrain chunk in cell units /// @param center center of the chunk in cell units - /// @param vertexBuffer buffer to write vertices - /// @param normalBuffer buffer to write vertex normals - /// @param colourBuffer buffer to write vertex colours + /// @param positions buffer to write vertices + /// @param normals buffer to write vertex normals + /// @param colours buffer to write vertex colours virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, Terrain::Alignment align, - Ogre::HardwareVertexBufferSharedPtr vertexBuffer, - Ogre::HardwareVertexBufferSharedPtr normalBuffer, - Ogre::HardwareVertexBufferSharedPtr colourBuffer) = 0; + std::vector& positions, + std::vector& normals, + std::vector& colours) = 0; /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index e633c1a00..87baeff84 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -64,6 +64,8 @@ namespace Terrain , mMinX(0) , mMaxY(0) , mMinY(0) + , mChunksLoading(0) + , mWorkQueueChannel(0) { #if TERRAIN_USE_SHADER == 0 if (mShaders) @@ -103,10 +105,19 @@ namespace Terrain //loadingListener->indicateProgress(); mRootNode->initNeighbours(); //loadingListener->indicateProgress(); + + Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); + mWorkQueueChannel = wq->getChannel("LargeTerrain"); + wq->addRequestHandler(mWorkQueueChannel, this); + wq->addResponseHandler(mWorkQueueChannel, this); } World::~World() { + Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue(); + wq->removeRequestHandler(mWorkQueueChannel, this); + wq->removeResponseHandler(mWorkQueueChannel, this); + delete mRootNode; delete mStorage; } @@ -445,4 +456,62 @@ namespace Terrain } } + void World::syncLoad() + { + while (mChunksLoading) + { + OGRE_THREAD_SLEEP(0); + Ogre::Root::getSingleton().getWorkQueue()->processResponses(); + } + } + + Ogre::WorkQueue::Response* World::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ) + { + const LoadRequestData data = Ogre::any_cast(req->getData()); + + QuadTreeNode* node = data.mNode; + + LoadResponseData* responseData = new LoadResponseData(); + + Ogre::Timer timer; + getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(), + responseData->mPositions, responseData->mNormals, responseData->mColours); + + std::cout << "THREAD" << std::endl; + + responseData->time = timer.getMicroseconds(); + + return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); + } + + void World::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ) + { + static unsigned long time = 0; + if (res->succeeded()) + { + LoadResponseData* data = Ogre::any_cast(res->getData()); + + const LoadRequestData requestData = Ogre::any_cast(res->getRequest()->getData()); + + requestData.mNode->load(*data); + + time += data->time; + + delete data; + + std::cout << "RESPONSE, reqs took ms" << time/1000.f << std::endl; + } + --mChunksLoading; + } + + void World::queueLoad(QuadTreeNode *node) + { + LoadRequestData data; + data.mNode = node; + data.mPack = getShadersEnabled(); + + const Ogre::uint16 loadRequestId = 1; + Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, loadRequestId, Ogre::Any(data)); + ++mChunksLoading; + } } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 31bd51ab1..d552bccb0 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "defs.hpp" @@ -26,7 +27,7 @@ namespace Terrain * Cracks at LOD transitions are avoided using stitching. * @note Multiple cameras are not supported yet */ - class World + class World : public Ogre::WorkQueue::RequestHandler, public Ogre::WorkQueue::ResponseHandler { public: /// @note takes ownership of \a storage @@ -85,7 +86,16 @@ namespace Terrain Alignment getAlign() { return mAlign; } + /// Wait until all background loading is complete. + void syncLoad(); + private: + // Called from a background worker thread + Ogre::WorkQueue::Response* handleRequest(const Ogre::WorkQueue::Request* req, const Ogre::WorkQueue* srcQ); + // Called from the main thread + void handleResponse(const Ogre::WorkQueue::Response* res, const Ogre::WorkQueue* srcQ); + Ogre::uint16 mWorkQueueChannel; + bool mDistantLand; bool mShaders; bool mShadows; @@ -99,6 +109,9 @@ namespace Terrain int mVisibilityFlags; + /// The number of chunks currently loading in a background thread. If 0, we have finished loading! + int mChunksLoading; + Ogre::SceneManager* mSceneMgr; Ogre::SceneManager* mCompositeMapSceneMgr; @@ -141,6 +154,9 @@ namespace Terrain void convertPosition (Ogre::Vector3& pos); void convertBounds (Ogre::AxisAlignedBox& bounds); + // Adds a WorkQueue request to load a chunk for this node in the background. + void queueLoad (QuadTreeNode* node); + private: // Index buffers are shared across terrain batches where possible. There is one index buffer for each // combination of LOD deltas and index buffer LOD we may need. @@ -152,6 +168,30 @@ namespace Terrain Ogre::TexturePtr mCompositeMapRenderTexture; }; + struct LoadRequestData + { + QuadTreeNode* mNode; + bool mPack; + + friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r) + { return o; } + }; + + struct LoadResponseData + { + std::vector mPositions; + std::vector mNormals; + std::vector mColours; + // Since we can't create a texture from a different thread, this only holds the raw texel data + std::vector< std::vector > mBlendmaps; + std::vector mLayerList; + + unsigned long time; + + friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r) + { return o; } + }; + } #endif From edb5a540925f9efc2631b68113a3373aa4da9291 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 19 Feb 2014 18:40:29 +0100 Subject: [PATCH 073/114] Include some more required Ogre headers explicitely. --- apps/opencs/view/render/scenewidget.cpp | 2 ++ apps/openmw/engine.cpp | 1 + apps/openmw/mwgui/enchantingdialog.cpp | 2 ++ apps/openmw/mwgui/loadingscreen.cpp | 6 ++++++ apps/openmw/mwgui/loadingscreen.hpp | 1 + apps/openmw/mwgui/mapwindow.cpp | 1 + apps/openmw/mwgui/repair.cpp | 2 ++ apps/openmw/mwgui/tooltips.cpp | 2 ++ apps/openmw/mwgui/windowmanagerimp.cpp | 2 ++ apps/openmw/mwmechanics/npcstats.cpp | 4 +++- apps/openmw/mwrender/animation.cpp | 2 ++ apps/openmw/mwrender/characterpreview.cpp | 5 ++++- apps/openmw/mwrender/characterpreview.hpp | 1 + apps/openmw/mwrender/debugging.cpp | 2 ++ apps/openmw/mwrender/effectmanager.cpp | 2 ++ apps/openmw/mwrender/localmap.cpp | 2 ++ apps/openmw/mwrender/npcanimation.cpp | 5 +++++ apps/openmw/mwrender/occlusionquery.cpp | 2 ++ apps/openmw/mwrender/refraction.cpp | 2 ++ apps/openmw/mwrender/renderingmanager.cpp | 2 ++ apps/openmw/mwrender/ripplesimulation.cpp | 4 ++++ apps/openmw/mwrender/shadows.cpp | 2 ++ apps/openmw/mwrender/sky.cpp | 1 + apps/openmw/mwrender/videoplayer.cpp | 5 +++++ apps/openmw/mwrender/water.cpp | 7 ++++++- apps/openmw/mwworld/physicssystem.cpp | 1 + components/bsa/bsa_archive.cpp | 1 + components/nifogre/mesh.cpp | 2 ++ components/nifogre/ogrenifloader.cpp | 3 +++ components/ogreinit/ogreinit.cpp | 1 + components/terrain/chunk.cpp | 1 + components/terrain/quadtreenode.cpp | 3 +++ components/terrain/world.cpp | 4 ++++ extern/sdl4ogre/sdlcursormanager.cpp | 1 + extern/sdl4ogre/sdlwindowhelper.cpp | 1 + extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp | 2 ++ libs/openengine/ogre/fader.cpp | 1 + libs/openengine/ogre/imagerotate.cpp | 7 +++++++ libs/openengine/ogre/renderer.cpp | 1 + libs/openengine/ogre/selectionbuffer.cpp | 3 +++ libs/openengine/ogre/selectionbuffer.hpp | 1 + 41 files changed, 97 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 6f07c1b0d..3c389bec2 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include "navigation.hpp" diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index af8d49426..4c3cadb3b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -1,6 +1,7 @@ #include "engine.hpp" #include +#include #include #include diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 92205c3e9..ada004612 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -1,5 +1,7 @@ #include "enchantingdialog.hpp" +#include + #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 868b58209..53a45db09 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -1,6 +1,12 @@ #include "loadingscreen.hpp" #include +#include +#include +#include +#include +#include +#include #include diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 2d1d7431f..e91e5951d 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -2,6 +2,7 @@ #define MWGUI_LOADINGSCREEN_H #include +#include #include "windowbase.hpp" diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 51e160d26..1cc9610df 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index d729ee7fa..de96bcacd 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -1,5 +1,7 @@ #include "repair.hpp" +#include + #include #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 8716c4dea..f941c699b 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -1,5 +1,7 @@ #include "tooltips.hpp" +#include + #include #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index f44078256..1b71157a7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "MyGUI_UString.h" #include "MyGUI_IPointer.h" #include "MyGUI_ResourceImageSetPointer.h" diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 8918bfbe7..74587a626 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include #include @@ -509,4 +511,4 @@ void MWMechanics::NpcStats::readState (const ESM::NpcStats& state) mTimeToStartDrowning = state.mTimeToStartDrowning; mLastDrowningHit = state.mLastDrowningHit; mLevelHealthBonus = state.mLevelHealthBonus; -} \ No newline at end of file +} diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6ba3b1fcd..e62fee6e2 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 280828652..013e3daf4 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -1,10 +1,13 @@ #include "characterpreview.hpp" - #include #include #include #include +#include +#include +#include +#include #include diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index cd30cdf46..16e6ab017 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -3,6 +3,7 @@ #include #include +#include #include diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp index 241f7e470..ba39d10d5 100644 --- a/apps/openmw/mwrender/debugging.cpp +++ b/apps/openmw/mwrender/debugging.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index 7d41525b7..1e6119daa 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include "animation.hpp" #include "renderconst.hpp" diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 772813c73..003f08300 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d07aad31d..60760b7fb 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -4,6 +4,11 @@ #include #include #include +#include +#include +#include +#include +#include #include diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 246103471..67bc75e02 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include "renderconst.hpp" diff --git a/apps/openmw/mwrender/refraction.cpp b/apps/openmw/mwrender/refraction.cpp index d590dbf4c..f22968e9d 100644 --- a/apps/openmw/mwrender/refraction.cpp +++ b/apps/openmw/mwrender/refraction.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 68bf957c8..b67875405 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -5,12 +5,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index e5db8346f..f52deedcc 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -4,6 +4,10 @@ #include #include #include +#include +#include +#include +#include #include diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index 9ebb0ab08..33e337649 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 39f7ccc85..90c08c299 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include diff --git a/apps/openmw/mwrender/videoplayer.cpp b/apps/openmw/mwrender/videoplayer.cpp index adf20dc63..f3c0971e7 100644 --- a/apps/openmw/mwrender/videoplayer.cpp +++ b/apps/openmw/mwrender/videoplayer.cpp @@ -9,6 +9,11 @@ #include #include #include +#include +#include +#include +#include +#include #include diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 9e3105168..1fa5d8834 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -1,11 +1,16 @@ #include "water.hpp" -#include +#include #include #include +#include #include #include #include +#include +#include +#include +#include #include "sky.hpp" #include "renderingmanager.hpp" diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 1a16870da..8ef5797ca 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index eb741fb10..6574f096b 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include "bsa_file.hpp" #include "../files/constrainedfiledatastream.hpp" diff --git a/components/nifogre/mesh.cpp b/components/nifogre/mesh.cpp index 43622cb9a..8bebe0589 100644 --- a/components/nifogre/mesh.cpp +++ b/components/nifogre/mesh.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 43e8934c8..acab419b0 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -38,6 +38,9 @@ #include #include #include +#include +#include +#include #include diff --git a/components/ogreinit/ogreinit.cpp b/components/ogreinit/ogreinit.cpp index 840cf4bb0..1b9a899a0 100644 --- a/components/ogreinit/ogreinit.cpp +++ b/components/ogreinit/ogreinit.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE #include diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index 139694ee5..6f2b9ff73 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "quadtreenode.hpp" #include "world.hpp" diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 721771ff3..1c8553967 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include "world.hpp" #include "chunk.hpp" diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 87baeff84..4d4616511 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -4,7 +4,11 @@ #include #include #include +#include +#include +#include #include +#include // TEMP #include "storage.hpp" #include "quadtreenode.hpp" diff --git a/extern/sdl4ogre/sdlcursormanager.cpp b/extern/sdl4ogre/sdlcursormanager.cpp index 65fb7f98b..5ef274b7e 100644 --- a/extern/sdl4ogre/sdlcursormanager.cpp +++ b/extern/sdl4ogre/sdlcursormanager.cpp @@ -1,6 +1,7 @@ #include "sdlcursormanager.hpp" #include +#include #include #include diff --git a/extern/sdl4ogre/sdlwindowhelper.cpp b/extern/sdl4ogre/sdlwindowhelper.cpp index f819043cf..2a14cc6b4 100644 --- a/extern/sdl4ogre/sdlwindowhelper.cpp +++ b/extern/sdl4ogre/sdlwindowhelper.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include diff --git a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp index f215f4ab7..ad8e6d2b0 100644 --- a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp +++ b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp @@ -1,5 +1,7 @@ #include "OgreTextureUnitState.hpp" +#include + #include #include diff --git a/libs/openengine/ogre/fader.cpp b/libs/openengine/ogre/fader.cpp index 923b0b7e3..20b9296da 100644 --- a/libs/openengine/ogre/fader.cpp +++ b/libs/openengine/ogre/fader.cpp @@ -6,6 +6,7 @@ #include #include #include +#include using namespace Ogre; diff --git a/libs/openengine/ogre/imagerotate.cpp b/libs/openengine/ogre/imagerotate.cpp index 9c32924f1..cc5f572cf 100644 --- a/libs/openengine/ogre/imagerotate.cpp +++ b/libs/openengine/ogre/imagerotate.cpp @@ -8,6 +8,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include using namespace Ogre; using namespace OEngine::Render; diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index c86697497..c816f2060 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include diff --git a/libs/openengine/ogre/selectionbuffer.cpp b/libs/openengine/ogre/selectionbuffer.cpp index 69375b74d..5aeb35c28 100644 --- a/libs/openengine/ogre/selectionbuffer.cpp +++ b/libs/openengine/ogre/selectionbuffer.cpp @@ -5,6 +5,9 @@ #include #include #include +#include +#include + #include #include diff --git a/libs/openengine/ogre/selectionbuffer.hpp b/libs/openengine/ogre/selectionbuffer.hpp index c487b24b0..b9b4cd9d9 100644 --- a/libs/openengine/ogre/selectionbuffer.hpp +++ b/libs/openengine/ogre/selectionbuffer.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace OEngine { From 2a4e99c06949a4f67f96eea0c6bb2ec6fb6c0f74 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 21 Feb 2014 15:11:14 +0100 Subject: [PATCH 074/114] Set StaticGeometry origin Fixes an exception for coordinates far away from (0,0,0). --- apps/openmw/mwrender/objects.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index c97e5279a..9c10ca84b 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -109,6 +109,7 @@ void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh) { uniqueID = uniqueID+1; sg = mRenderer.getScene()->createStaticGeometry("sg" + Ogre::StringConverter::toString(uniqueID)); + sg->setOrigin(ptr.getRefData().getBaseNode()->getPosition()); mStaticGeometrySmall[ptr.getCell()] = sg; sg->setRenderingDistance(Settings::Manager::getInt("small object distance", "Viewing distance")); @@ -122,6 +123,7 @@ void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh) { uniqueID = uniqueID+1; sg = mRenderer.getScene()->createStaticGeometry("sg" + Ogre::StringConverter::toString(uniqueID)); + sg->setOrigin(ptr.getRefData().getBaseNode()->getPosition()); mStaticGeometry[ptr.getCell()] = sg; } else From 97c3efb3ba8dbcdc7ac503ecbdba8163830bb4b1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 26 Feb 2014 19:23:42 +0100 Subject: [PATCH 075/114] Terrain: decoupled Chunk from QuadTreeNode. --- components/CMakeLists.txt | 2 +- components/terrain/buffercache.cpp | 200 ++++++++++++++++++++++++++++ components/terrain/buffercache.hpp | 36 +++++ components/terrain/chunk.cpp | 76 ++--------- components/terrain/chunk.hpp | 17 +-- components/terrain/defs.hpp | 8 ++ components/terrain/quadtreenode.cpp | 39 +++++- components/terrain/quadtreenode.hpp | 10 +- components/terrain/world.cpp | 197 +-------------------------- components/terrain/world.hpp | 28 +--- 10 files changed, 304 insertions(+), 309 deletions(-) create mode 100644 components/terrain/buffercache.cpp create mode 100644 components/terrain/buffercache.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 938ef2e4d..0a99c9528 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -75,7 +75,7 @@ add_component_dir (translation add_definitions(-DTERRAIN_USE_SHADER=1) add_component_dir (terrain - quadtreenode chunk world storage material + quadtreenode chunk world storage material buffercache compositemap defs ) add_component_dir (loadinglistener diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp new file mode 100644 index 000000000..f693c0e40 --- /dev/null +++ b/components/terrain/buffercache.cpp @@ -0,0 +1,200 @@ +#include "buffercache.hpp" + +#include + +#include "defs.hpp" + +namespace Terrain +{ + + Ogre::HardwareVertexBufferSharedPtr BufferCache::getUVBuffer() + { + if (mUvBufferMap.find(mNumVerts) != mUvBufferMap.end()) + { + return mUvBufferMap[mNumVerts]; + } + + int vertexCount = mNumVerts * mNumVerts; + + std::vector uvs; + uvs.reserve(vertexCount*2); + + for (unsigned int col = 0; col < mNumVerts; ++col) + { + for (unsigned int row = 0; row < mNumVerts; ++row) + { + uvs.push_back(col / static_cast(mNumVerts-1)); // U + uvs.push_back(row / static_cast(mNumVerts-1)); // V + } + } + + Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); + Ogre::HardwareVertexBufferSharedPtr buffer = mgr->createVertexBuffer( + Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2), + vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + + buffer->writeData(0, buffer->getSizeInBytes(), &uvs[0], true); + + mUvBufferMap[mNumVerts] = buffer; + return buffer; + } + + Ogre::HardwareIndexBufferSharedPtr BufferCache::getIndexBuffer(int flags) + { + unsigned int verts = mNumVerts; + + if (mIndexBufferMap.find(flags) != mIndexBufferMap.end()) + { + return mIndexBufferMap[flags]; + } + + // LOD level n means every 2^n-th vertex is kept + size_t lodLevel = (flags >> (4*4)); + + size_t lodDeltas[4]; + for (int i=0; i<4; ++i) + lodDeltas[i] = (flags >> (4*i)) & (0xf); + + bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]); + + size_t increment = 1 << lodLevel; + assert(increment < verts); + std::vector indices; + indices.reserve((verts-1)*(verts-1)*2*3 / increment); + + size_t rowStart = 0, colStart = 0, rowEnd = verts-1, colEnd = verts-1; + // If any edge needs stitching we'll skip all edges at this point, + // mainly because stitching one edge would have an effect on corners and on the adjacent edges + if (anyDeltas) + { + colStart += increment; + colEnd -= increment; + rowEnd -= increment; + rowStart += increment; + } + for (size_t row = rowStart; row < rowEnd; row += increment) + { + for (size_t col = colStart; col < colEnd; col += increment) + { + indices.push_back(verts*col+row); + indices.push_back(verts*(col+increment)+row+increment); + indices.push_back(verts*col+row+increment); + + indices.push_back(verts*col+row); + indices.push_back(verts*(col+increment)+row); + indices.push_back(verts*(col+increment)+row+increment); + } + } + + size_t innerStep = increment; + if (anyDeltas) + { + // Now configure LOD transitions at the edges - this is pretty tedious, + // and some very long and boring code, but it works great + + // South + size_t row = 0; + size_t outerStep = 1 << (lodDeltas[South] + lodLevel); + for (size_t col = 0; col < verts-1; col += outerStep) + { + indices.push_back(verts*col+row); + indices.push_back(verts*(col+outerStep)+row); + // Make sure not to touch the right edge + if (col+outerStep == verts-1) + indices.push_back(verts*(col+outerStep-innerStep)+row+innerStep); + else + indices.push_back(verts*(col+outerStep)+row+innerStep); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the left or right edges + if (col+i == 0 || col+i == verts-1-innerStep) + continue; + indices.push_back(verts*(col)+row); + indices.push_back(verts*(col+i+innerStep)+row+innerStep); + indices.push_back(verts*(col+i)+row+innerStep); + } + } + + // North + row = verts-1; + outerStep = 1 << (lodDeltas[North] + lodLevel); + for (size_t col = 0; col < verts-1; col += outerStep) + { + indices.push_back(verts*(col+outerStep)+row); + indices.push_back(verts*col+row); + // Make sure not to touch the left edge + if (col == 0) + indices.push_back(verts*(col+innerStep)+row-innerStep); + else + indices.push_back(verts*col+row-innerStep); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the left or right edges + if (col+i == 0 || col+i == verts-1-innerStep) + continue; + indices.push_back(verts*(col+i)+row-innerStep); + indices.push_back(verts*(col+i+innerStep)+row-innerStep); + indices.push_back(verts*(col+outerStep)+row); + } + } + + // West + size_t col = 0; + outerStep = 1 << (lodDeltas[West] + lodLevel); + for (size_t row = 0; row < verts-1; row += outerStep) + { + indices.push_back(verts*col+row+outerStep); + indices.push_back(verts*col+row); + // Make sure not to touch the top edge + if (row+outerStep == verts-1) + indices.push_back(verts*(col+innerStep)+row+outerStep-innerStep); + else + indices.push_back(verts*(col+innerStep)+row+outerStep); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the top or bottom edges + if (row+i == 0 || row+i == verts-1-innerStep) + continue; + indices.push_back(verts*col+row); + indices.push_back(verts*(col+innerStep)+row+i); + indices.push_back(verts*(col+innerStep)+row+i+innerStep); + } + } + + // East + col = verts-1; + outerStep = 1 << (lodDeltas[East] + lodLevel); + for (size_t row = 0; row < verts-1; row += outerStep) + { + indices.push_back(verts*col+row); + indices.push_back(verts*col+row+outerStep); + // Make sure not to touch the bottom edge + if (row == 0) + indices.push_back(verts*(col-innerStep)+row+innerStep); + else + indices.push_back(verts*(col-innerStep)+row); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the top or bottom edges + if (row+i == 0 || row+i == verts-1-innerStep) + continue; + indices.push_back(verts*col+row+outerStep); + indices.push_back(verts*(col-innerStep)+row+i+innerStep); + indices.push_back(verts*(col-innerStep)+row+i); + } + } + } + + Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); + Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, + indices.size(), Ogre::HardwareBuffer::HBU_STATIC); + buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true); + mIndexBufferMap[flags] = buffer; + return buffer; + } + +} diff --git a/components/terrain/buffercache.hpp b/components/terrain/buffercache.hpp new file mode 100644 index 000000000..f0aea9bfd --- /dev/null +++ b/components/terrain/buffercache.hpp @@ -0,0 +1,36 @@ +#ifndef COMPONENTS_TERRAIN_BUFFERCACHE_H +#define COMPONENTS_TERRAIN_BUFFERCACHE_H + +#include +#include + +#include + +namespace Terrain +{ + + /// @brief Implements creation and caching of vertex buffers for terrain chunks. + class BufferCache + { + public: + BufferCache(unsigned int numVerts) : mNumVerts(numVerts) {} + + /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) + /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices) + Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (int flags); + + Ogre::HardwareVertexBufferSharedPtr getUVBuffer (); + + private: + // Index buffers are shared across terrain batches where possible. There is one index buffer for each + // combination of LOD deltas and index buffer LOD we may need. + std::map mIndexBufferMap; + + std::map mUvBufferMap; + + unsigned int mNumVerts; + }; + +} + +#endif diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index 6f2b9ff73..cf6c99cf4 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -4,31 +4,17 @@ #include #include -#include "quadtreenode.hpp" -#include "world.hpp" -#include "storage.hpp" +#include "world.hpp" // FIXME: for LoadResponseData, move to backgroundloader.hpp namespace Terrain { - Chunk::Chunk(QuadTreeNode* node, const LoadResponseData& data) - : mNode(node) - , mVertexLod(node->getNativeLodLevel()) - , mAdditionalLod(0) + Chunk::Chunk(Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, const LoadResponseData& data) + : mBounds(bounds) { mVertexData = OGRE_NEW Ogre::VertexData; mVertexData->vertexStart = 0; - - unsigned int verts = mNode->getTerrain()->getStorage()->getCellVertices(); - - size_t lodLevel = mNode->getNativeLodLevel(); - - // Set the total number of vertices - size_t numVertsOneSide = mNode->getSize() * (verts-1); - numVertsOneSide /= 1 << lodLevel; - numVertsOneSide += 1; - assert(numVertsOneSide == verts); - mVertexData->vertexCount = numVertsOneSide * numVertsOneSide; + mVertexData->vertexCount = data.mPositions.size()/3; // Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc) Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration; @@ -40,15 +26,16 @@ namespace Terrain vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION); mVertexBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + // Normals vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); mNormalBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + // UV texture coordinates vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, 0); - Ogre::HardwareVertexBufferSharedPtr uvBuf = mNode->getTerrain()->getVertexBuffer(numVertsOneSide); // Colours vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE); @@ -61,54 +48,17 @@ namespace Terrain mVertexData->vertexBufferBinding->setBinding(0, mVertexBuffer); mVertexData->vertexBufferBinding->setBinding(1, mNormalBuffer); - mVertexData->vertexBufferBinding->setBinding(2, uvBuf); + mVertexData->vertexBufferBinding->setBinding(2, uvBuffer); mVertexData->vertexBufferBinding->setBinding(3, mColourBuffer); mIndexData = OGRE_NEW Ogre::IndexData(); mIndexData->indexStart = 0; } - - - void Chunk::updateIndexBuffer() + void Chunk::setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer) { - // Fetch a suitable index buffer (which may be shared) - size_t ourLod = mVertexLod + mAdditionalLod; - - int flags = 0; - - for (int i=0; i<4; ++i) - { - QuadTreeNode* neighbour = mNode->getNeighbour((Direction)i); - - // If the neighbour isn't currently rendering itself, - // go up until we find one. NOTE: We don't need to go down, - // because in that case neighbour's detail would be higher than - // our detail and the neighbour would handle stitching by itself. - while (neighbour && !neighbour->hasChunk()) - neighbour = neighbour->getParent(); - - size_t lod = 0; - if (neighbour) - lod = neighbour->getActualLodLevel(); - - if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are - - lod = 0; // neighbours with more detail will do the stitching themselves - - // Use 4 bits for each LOD delta - if (lod > 0) - { - assert (lod - ourLod < (1 << 4)); - flags |= int(lod - ourLod) << (4*i); - } - } - - flags |= ((int)mAdditionalLod) << (4*4); - - size_t numIndices; - mIndexBuffer = mNode->getTerrain()->getIndexBuffer(flags, numIndices); - mIndexData->indexCount = numIndices; - mIndexData->indexBuffer = mIndexBuffer; + mIndexData->indexBuffer = buffer; + mIndexData->indexCount = buffer->getNumIndexes(); } Chunk::~Chunk() @@ -124,12 +74,12 @@ namespace Terrain const Ogre::AxisAlignedBox& Chunk::getBoundingBox(void) const { - return mNode->getBoundingBox(); + return mBounds; } Ogre::Real Chunk::getBoundingRadius(void) const { - return mNode->getBoundingBox().getHalfSize().length(); + return mBounds.getHalfSize().length(); } void Chunk::_updateRenderQueue(Ogre::RenderQueue* queue) @@ -150,7 +100,7 @@ namespace Terrain void Chunk::getRenderOperation(Ogre::RenderOperation& op) { - assert (!mIndexBuffer.isNull() && "Trying to render, but no index buffer set!"); + assert (!mIndexData->indexBuffer.isNull() && "Trying to render, but no index buffer set!"); op.useIndexes = true; op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST; op.vertexData = mVertexData; diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp index a6477df28..d198de264 100644 --- a/components/terrain/chunk.hpp +++ b/components/terrain/chunk.hpp @@ -7,7 +7,7 @@ namespace Terrain { - class QuadTreeNode; + class BufferCache; struct LoadResponseData; /** @@ -16,18 +16,13 @@ namespace Terrain class Chunk : public Ogre::Renderable, public Ogre::MovableObject { public: - Chunk (QuadTreeNode* node, const LoadResponseData& data); + Chunk (Ogre::HardwareVertexBufferSharedPtr uvBuffer, const Ogre::AxisAlignedBox& bounds, const LoadResponseData& data); virtual ~Chunk(); void setMaterial (const Ogre::MaterialPtr& material); - /// Set additional LOD applied on top of vertex LOD. \n - /// This is achieved by changing the index buffer to omit vertices. - void setAdditionalLod (size_t lod) { mAdditionalLod = lod; } - size_t getAdditionalLod() { return mAdditionalLod; } - - void updateIndexBuffer(); + void setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer); // Inherited from MovableObject virtual const Ogre::String& getMovableType(void) const { static Ogre::String t = "MW_TERRAIN"; return t; } @@ -45,18 +40,14 @@ namespace Terrain virtual const Ogre::LightList& getLights(void) const; private: - QuadTreeNode* mNode; + Ogre::AxisAlignedBox mBounds; Ogre::MaterialPtr mMaterial; - size_t mVertexLod; - size_t mAdditionalLod; - Ogre::VertexData* mVertexData; Ogre::IndexData* mIndexData; Ogre::HardwareVertexBufferSharedPtr mVertexBuffer; Ogre::HardwareVertexBufferSharedPtr mNormalBuffer; Ogre::HardwareVertexBufferSharedPtr mColourBuffer; - Ogre::HardwareIndexBufferSharedPtr mIndexBuffer; }; } diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp index c14608e76..1a081c969 100644 --- a/components/terrain/defs.hpp +++ b/components/terrain/defs.hpp @@ -36,6 +36,14 @@ namespace Terrain } } + enum Direction + { + North = 0, + East = 1, + South = 2, + West = 3 + }; + struct LayerInfo { std::string mDiffuseMap; diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 1c8553967..913970606 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -9,7 +9,7 @@ #include "world.hpp" #include "chunk.hpp" #include "storage.hpp" - +#include "buffercache.hpp" #include "material.hpp" using namespace Terrain; @@ -372,7 +372,7 @@ void QuadTreeNode::load(const LoadResponseData &data) assert (!mChunk); std::cout << "loading " << std::endl; - mChunk = new Chunk(this, data); + mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data); mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); mChunk->setCastShadows(true); mSceneNode->attachObject(mChunk); @@ -429,7 +429,38 @@ void QuadTreeNode::unload() void QuadTreeNode::updateIndexBuffers() { if (hasChunk()) - mChunk->updateIndexBuffer(); + { + // Fetch a suitable index buffer (which may be shared) + size_t ourLod = getActualLodLevel(); + + int flags = 0; + + for (int i=0; i<4; ++i) + { + QuadTreeNode* neighbour = getNeighbour((Direction)i); + + // If the neighbour isn't currently rendering itself, + // go up until we find one. NOTE: We don't need to go down, + // because in that case neighbour's detail would be higher than + // our detail and the neighbour would handle stitching by itself. + while (neighbour && !neighbour->hasChunk()) + neighbour = neighbour->getParent(); + size_t lod = 0; + if (neighbour) + lod = neighbour->getActualLodLevel(); + if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are - + lod = 0; // neighbours with more detail will do the stitching themselves + // Use 4 bits for each LOD delta + if (lod > 0) + { + assert (lod - ourLod < (1 << 4)); + flags |= int(lod - ourLod) << (4*i); + } + } + flags |= 0 /*((int)mAdditionalLod)*/ << (4*4); + + mChunk->setIndexBuffer(mTerrain->getBufferCache().getIndexBuffer(flags)); + } else if (hasChildren()) { for (int i=0; i<4; ++i) @@ -445,7 +476,7 @@ bool QuadTreeNode::hasChunk() size_t QuadTreeNode::getActualLodLevel() { assert(hasChunk() && "Can't get actual LOD level if this node has no render chunk"); - return mLodLevel + mChunk->getAdditionalLod(); + return mLodLevel /* + mChunk->getAdditionalLod() */; } void QuadTreeNode::ensureLayerInfo() diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 0bba1024c..134bc2d34 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -5,6 +5,8 @@ #include #include +#include "defs.hpp" + namespace Ogre { class Rectangle2D; @@ -17,14 +19,6 @@ namespace Terrain class MaterialGenerator; struct LoadResponseData; - enum Direction - { - North = 0, - East = 1, - South = 2, - West = 3 - }; - enum ChildDirection { NW = 0, diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 4d4616511..69748a721 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -70,6 +69,7 @@ namespace Terrain , mMinY(0) , mChunksLoading(0) , mWorkQueueChannel(0) + , mCache(storage->getCellVertices()) { #if TERRAIN_USE_SHADER == 0 if (mShaders) @@ -197,201 +197,6 @@ namespace Terrain return node->getWorldBoundingBox(); } - Ogre::HardwareVertexBufferSharedPtr World::getVertexBuffer(int numVertsOneSide) - { - if (mUvBufferMap.find(numVertsOneSide) != mUvBufferMap.end()) - { - return mUvBufferMap[numVertsOneSide]; - } - - int vertexCount = numVertsOneSide * numVertsOneSide; - - std::vector uvs; - uvs.reserve(vertexCount*2); - - for (int col = 0; col < numVertsOneSide; ++col) - { - for (int row = 0; row < numVertsOneSide; ++row) - { - uvs.push_back(col / static_cast(numVertsOneSide-1)); // U - uvs.push_back(row / static_cast(numVertsOneSide-1)); // V - } - } - - Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); - Ogre::HardwareVertexBufferSharedPtr buffer = mgr->createVertexBuffer( - Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2), - vertexCount, Ogre::HardwareBuffer::HBU_STATIC); - - buffer->writeData(0, buffer->getSizeInBytes(), &uvs[0], true); - - mUvBufferMap[numVertsOneSide] = buffer; - return buffer; - } - - Ogre::HardwareIndexBufferSharedPtr World::getIndexBuffer(int flags, size_t& numIndices) - { - unsigned int verts = mStorage->getCellVertices(); - - if (mIndexBufferMap.find(flags) != mIndexBufferMap.end()) - { - numIndices = mIndexBufferMap[flags]->getNumIndexes(); - return mIndexBufferMap[flags]; - } - - // LOD level n means every 2^n-th vertex is kept - size_t lodLevel = (flags >> (4*4)); - - size_t lodDeltas[4]; - for (int i=0; i<4; ++i) - lodDeltas[i] = (flags >> (4*i)) & (0xf); - - bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]); - - size_t increment = 1 << lodLevel; - assert(increment < verts); - std::vector indices; - indices.reserve((verts-1)*(verts-1)*2*3 / increment); - - size_t rowStart = 0, colStart = 0, rowEnd = verts-1, colEnd = verts-1; - // If any edge needs stitching we'll skip all edges at this point, - // mainly because stitching one edge would have an effect on corners and on the adjacent edges - if (anyDeltas) - { - colStart += increment; - colEnd -= increment; - rowEnd -= increment; - rowStart += increment; - } - for (size_t row = rowStart; row < rowEnd; row += increment) - { - for (size_t col = colStart; col < colEnd; col += increment) - { - indices.push_back(verts*col+row); - indices.push_back(verts*(col+increment)+row+increment); - indices.push_back(verts*col+row+increment); - - indices.push_back(verts*col+row); - indices.push_back(verts*(col+increment)+row); - indices.push_back(verts*(col+increment)+row+increment); - } - } - - size_t innerStep = increment; - if (anyDeltas) - { - // Now configure LOD transitions at the edges - this is pretty tedious, - // and some very long and boring code, but it works great - - // South - size_t row = 0; - size_t outerStep = 1 << (lodDeltas[South] + lodLevel); - for (size_t col = 0; col < verts-1; col += outerStep) - { - indices.push_back(verts*col+row); - indices.push_back(verts*(col+outerStep)+row); - // Make sure not to touch the right edge - if (col+outerStep == verts-1) - indices.push_back(verts*(col+outerStep-innerStep)+row+innerStep); - else - indices.push_back(verts*(col+outerStep)+row+innerStep); - - for (size_t i = 0; i < outerStep; i += innerStep) - { - // Make sure not to touch the left or right edges - if (col+i == 0 || col+i == verts-1-innerStep) - continue; - indices.push_back(verts*(col)+row); - indices.push_back(verts*(col+i+innerStep)+row+innerStep); - indices.push_back(verts*(col+i)+row+innerStep); - } - } - - // North - row = verts-1; - outerStep = 1 << (lodDeltas[North] + lodLevel); - for (size_t col = 0; col < verts-1; col += outerStep) - { - indices.push_back(verts*(col+outerStep)+row); - indices.push_back(verts*col+row); - // Make sure not to touch the left edge - if (col == 0) - indices.push_back(verts*(col+innerStep)+row-innerStep); - else - indices.push_back(verts*col+row-innerStep); - - for (size_t i = 0; i < outerStep; i += innerStep) - { - // Make sure not to touch the left or right edges - if (col+i == 0 || col+i == verts-1-innerStep) - continue; - indices.push_back(verts*(col+i)+row-innerStep); - indices.push_back(verts*(col+i+innerStep)+row-innerStep); - indices.push_back(verts*(col+outerStep)+row); - } - } - - // West - size_t col = 0; - outerStep = 1 << (lodDeltas[West] + lodLevel); - for (size_t row = 0; row < verts-1; row += outerStep) - { - indices.push_back(verts*col+row+outerStep); - indices.push_back(verts*col+row); - // Make sure not to touch the top edge - if (row+outerStep == verts-1) - indices.push_back(verts*(col+innerStep)+row+outerStep-innerStep); - else - indices.push_back(verts*(col+innerStep)+row+outerStep); - - for (size_t i = 0; i < outerStep; i += innerStep) - { - // Make sure not to touch the top or bottom edges - if (row+i == 0 || row+i == verts-1-innerStep) - continue; - indices.push_back(verts*col+row); - indices.push_back(verts*(col+innerStep)+row+i); - indices.push_back(verts*(col+innerStep)+row+i+innerStep); - } - } - - // East - col = verts-1; - outerStep = 1 << (lodDeltas[East] + lodLevel); - for (size_t row = 0; row < verts-1; row += outerStep) - { - indices.push_back(verts*col+row); - indices.push_back(verts*col+row+outerStep); - // Make sure not to touch the bottom edge - if (row == 0) - indices.push_back(verts*(col-innerStep)+row+innerStep); - else - indices.push_back(verts*(col-innerStep)+row); - - for (size_t i = 0; i < outerStep; i += innerStep) - { - // Make sure not to touch the top or bottom edges - if (row+i == 0 || row+i == verts-1-innerStep) - continue; - indices.push_back(verts*col+row+outerStep); - indices.push_back(verts*(col-innerStep)+row+i+innerStep); - indices.push_back(verts*(col-innerStep)+row+i); - } - } - } - - - - numIndices = indices.size(); - - Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); - Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, - numIndices, Ogre::HardwareBuffer::HBU_STATIC); - buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true); - mIndexBufferMap[flags] = buffer; - return buffer; - } - void World::renderCompositeMap(Ogre::TexturePtr target) { mCompositeMapRenderTarget->update(); diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index d552bccb0..354738744 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -1,13 +1,12 @@ #ifndef COMPONENTS_TERRAIN_H #define COMPONENTS_TERRAIN_H -#include -#include #include #include #include #include "defs.hpp" +#include "buffercache.hpp" namespace Ogre { @@ -125,25 +124,12 @@ namespace Terrain void buildQuadTree(QuadTreeNode* node); + BufferCache mCache; + public: // ----INTERNAL---- - - enum IndexBufferFlags - { - IBF_North = 1 << 0, - IBF_East = 1 << 1, - IBF_South = 1 << 2, - IBF_West = 1 << 3 - }; - - /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) - /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices) - /// @param numIndices number of indices that were used will be written here - Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (int flags, size_t& numIndices); - - Ogre::HardwareVertexBufferSharedPtr getVertexBuffer (int numVertsOneSide); - Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; } + BufferCache& getBufferCache() { return mCache; } // Delete all quads void clearCompositeMapSceneManager(); @@ -158,12 +144,6 @@ namespace Terrain void queueLoad (QuadTreeNode* node); private: - // Index buffers are shared across terrain batches where possible. There is one index buffer for each - // combination of LOD deltas and index buffer LOD we may need. - std::map mIndexBufferMap; - - std::map mUvBufferMap; - Ogre::RenderTarget* mCompositeMapRenderTarget; Ogre::TexturePtr mCompositeMapRenderTexture; }; From 6a002d19b3241dddcbfa163ca4ee9674dee4e54e Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 28 Feb 2014 03:15:04 +0100 Subject: [PATCH 076/114] Terrain: destroy no longer used materials --- components/terrain/chunk.cpp | 9 +++++++++ components/terrain/chunk.hpp | 1 + 2 files changed, 10 insertions(+) diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index cf6c99cf4..624809229 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -3,6 +3,10 @@ #include #include #include +#include + +#include + #include "world.hpp" // FIXME: for LoadResponseData, move to backgroundloader.hpp @@ -63,6 +67,11 @@ namespace Terrain Chunk::~Chunk() { +#if TERRAIN_USE_SHADER + sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName()); +#endif + Ogre::MaterialManager::getSingleton().remove(mMaterial->getName()); + OGRE_DELETE mVertexData; OGRE_DELETE mIndexData; } diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp index d198de264..9a7d20fb8 100644 --- a/components/terrain/chunk.hpp +++ b/components/terrain/chunk.hpp @@ -20,6 +20,7 @@ namespace Terrain virtual ~Chunk(); + /// @note Takes ownership of \a material void setMaterial (const Ogre::MaterialPtr& material); void setIndexBuffer(Ogre::HardwareIndexBufferSharedPtr buffer); From 4328e0816229b246bff4da040d7a36f710ac67a8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 28 Feb 2014 03:18:19 +0100 Subject: [PATCH 077/114] Terrain: get rid of unneeded members --- components/terrain/chunk.cpp | 18 +++++++++--------- components/terrain/chunk.hpp | 3 --- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index 624809229..ed640be5f 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -28,12 +28,12 @@ namespace Terrain // Positions vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION); - mVertexBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), + Ogre::HardwareVertexBufferSharedPtr vertexBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); // Normals vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); - mNormalBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), + Ogre::HardwareVertexBufferSharedPtr normalBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); @@ -43,17 +43,17 @@ namespace Terrain // Colours vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE); - mColourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), + Ogre::HardwareVertexBufferSharedPtr colourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); - mVertexBuffer->writeData(0, mVertexBuffer->getSizeInBytes(), &data.mPositions[0], true); - mNormalBuffer->writeData(0, mNormalBuffer->getSizeInBytes(), &data.mNormals[0], true); - mColourBuffer->writeData(0, mColourBuffer->getSizeInBytes(), &data.mColours[0], true); + vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &data.mPositions[0], true); + normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &data.mNormals[0], true); + colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &data.mColours[0], true); - mVertexData->vertexBufferBinding->setBinding(0, mVertexBuffer); - mVertexData->vertexBufferBinding->setBinding(1, mNormalBuffer); + mVertexData->vertexBufferBinding->setBinding(0, vertexBuffer); + mVertexData->vertexBufferBinding->setBinding(1, normalBuffer); mVertexData->vertexBufferBinding->setBinding(2, uvBuffer); - mVertexData->vertexBufferBinding->setBinding(3, mColourBuffer); + mVertexData->vertexBufferBinding->setBinding(3, colourBuffer); mIndexData = OGRE_NEW Ogre::IndexData(); mIndexData->indexStart = 0; diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp index 9a7d20fb8..d037661ae 100644 --- a/components/terrain/chunk.hpp +++ b/components/terrain/chunk.hpp @@ -46,9 +46,6 @@ namespace Terrain Ogre::VertexData* mVertexData; Ogre::IndexData* mIndexData; - Ogre::HardwareVertexBufferSharedPtr mVertexBuffer; - Ogre::HardwareVertexBufferSharedPtr mNormalBuffer; - Ogre::HardwareVertexBufferSharedPtr mColourBuffer; }; } From 1d926816b587c75e43cf6824e29f06efb42e18fb Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 5 Mar 2014 21:45:43 +0100 Subject: [PATCH 078/114] Terrain: background load blendmaps & layer textures. Refactor QuadTree update. --- apps/openmw/mwrender/renderingmanager.cpp | 19 ++- apps/openmw/mwrender/renderingmanager.hpp | 4 + apps/openmw/mwrender/terrainstorage.cpp | 47 ++++-- apps/openmw/mwrender/terrainstorage.hpp | 20 ++- apps/openmw/mwworld/scene.cpp | 4 +- components/terrain/chunk.cpp | 1 + components/terrain/defs.hpp | 9 ++ components/terrain/material.cpp | 4 + components/terrain/quadtreenode.cpp | 181 +++++++++++++--------- components/terrain/quadtreenode.hpp | 17 +- components/terrain/storage.hpp | 15 +- components/terrain/world.cpp | 80 ++++++---- components/terrain/world.hpp | 30 +++- 13 files changed, 292 insertions(+), 139 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b67875405..7c70b17f0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -651,13 +651,20 @@ void RenderingManager::setGlare(bool glare) mSkyManager->setGlare(glare); } -void RenderingManager::requestMap(MWWorld::CellStore* cell) +void RenderingManager::updateTerrain() { - // FIXME: move to other method - // TODO: probably not needed when crossing a cell border. Could delay the map render until we are loaded. if (mTerrain) + { + // Avoid updating with dims.getCenter for each cell. Player position should be good enough + mTerrain->update(mRendering.getCamera()->getRealPosition()); mTerrain->syncLoad(); + // need to update again so the chunks that were just loaded can be made visible + mTerrain->update(mRendering.getCamera()->getRealPosition()); + } +} +void RenderingManager::requestMap(MWWorld::CellStore* cell) +{ if (cell->getCell()->isExterior()) { assert(mTerrain); @@ -666,9 +673,6 @@ void RenderingManager::requestMap(MWWorld::CellStore* cell) Ogre::Vector2 center (cell->getCell()->getGridX() + 0.5, cell->getCell()->getGridY() + 0.5); dims.merge(mTerrain->getWorldBoundingBox(center)); - if (dims.isFinite()) - mTerrain->update(dims.getCenter()); - mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z); } else @@ -1059,8 +1063,7 @@ void RenderingManager::enableTerrain(bool enable) } mTerrain->setVisible(true); } - else - if (mTerrain) + else if (mTerrain) mTerrain->setVisible(false); } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 64ec029ce..115a94786 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -180,6 +180,10 @@ public: void removeWaterRippleEmitter (const MWWorld::Ptr& ptr); void updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); + void updateTerrain (); + ///< update the terrain according to the player position. Usually done automatically, but should be done manually + /// before calling requestMap + void requestMap (MWWorld::CellStore* cell); ///< request the local map for a cell diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index ec26b1872..2558c95c5 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -13,6 +14,8 @@ #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" +#include + namespace MWRender { @@ -329,8 +332,24 @@ namespace MWRender return texture; } + void TerrainStorage::getBlendmaps (const std::vector& nodes, std::vector& out, bool pack) + { + for (std::vector::const_iterator it = nodes.begin(); it != nodes.end(); ++it) + { + out.push_back(Terrain::LayerCollection()); + out.back().mTarget = *it; + getBlendmapsImpl((*it)->getSize(), (*it)->getCenter(), pack, out.back().mBlendmaps, out.back().mLayers); + } + } + void TerrainStorage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) + bool pack, std::vector &blendmaps, std::vector &layerList) + { + getBlendmapsImpl(chunkSize, chunkCenter, pack, blendmaps, layerList); + } + + void TerrainStorage::getBlendmapsImpl(float chunkSize, const Ogre::Vector2 &chunkCenter, + bool pack, std::vector &blendmaps, std::vector &layerList) { // TODO - blending isn't completely right yet; the blending radius appears to be // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap @@ -375,16 +394,14 @@ namespace MWRender // Second iteration - create and fill in the blend maps const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1; - std::vector data; - data.resize(blendmapSize * blendmapSize * channels, 0); for (int i=0; iloadRawData(stream, blendmapSize, blendmapSize, format); - blendmaps.push_back(map); + blendmaps.push_back(Ogre::PixelBox(blendmapSize, blendmapSize, 1, format, pData)); } } @@ -527,6 +540,12 @@ namespace MWRender info.mSpecular = true; } + // This wasn't cached, so the textures are probably not loaded either. + // Background load them so they are hopefully already loaded once we need them! + Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mDiffuseMap, "General"); + if (!info.mNormalMap.empty()) + Ogre::ResourceBackgroundQueue::getSingleton().load("Texture", info.mNormalMap, "General"); + mLayerInfoMap[texture] = info; return info; diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index b1acdb785..b5e6012f3 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -46,6 +46,7 @@ namespace MWRender /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from *one* background thread. /// @param chunkSize size of the terrain chunk in cell units /// @param chunkCenter center of the chunk in cell units /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - @@ -54,9 +55,21 @@ namespace MWRender /// @param blendmaps created blendmaps will be written here /// @param layerList names of the layer textures used will be written here virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, + std::vector& blendmaps, std::vector& layerList); + /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. + /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. + /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from *one* background thread. + /// @param nodes A collection of nodes for which to retrieve the aforementioned data + /// @param out Output vector + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + virtual void getBlendmaps (const std::vector& nodes, std::vector& out, bool pack); + virtual float getHeightAt (const Ogre::Vector3& worldPos); virtual Terrain::LayerInfo getDefaultLayer(); @@ -86,6 +99,11 @@ namespace MWRender std::map mLayerInfoMap; Terrain::LayerInfo getLayerInfo(const std::string& texture); + + // Non-virtual + void getBlendmapsImpl (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, + std::vector& blendmaps, + std::vector& layerList); }; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 40004807b..caae72c13 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -195,10 +195,12 @@ namespace MWWorld mechMgr->updateCell(old, player); mechMgr->watchActor(player); - MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); + mRendering.updateTerrain(); for (CellStoreCollection::iterator active = mActiveCells.begin(); active!=mActiveCells.end(); ++active) mRendering.requestMap(*active); + + MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); } void Scene::changeToVoid() diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index ed640be5f..0820dcc73 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -110,6 +110,7 @@ namespace Terrain void Chunk::getRenderOperation(Ogre::RenderOperation& op) { assert (!mIndexData->indexBuffer.isNull() && "Trying to render, but no index buffer set!"); + assert(!mMaterial.isNull() && "Trying to render, but no material set!"); op.useIndexes = true; op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST; op.vertexData = mVertexData; diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp index 1a081c969..685937653 100644 --- a/components/terrain/defs.hpp +++ b/components/terrain/defs.hpp @@ -3,6 +3,7 @@ namespace Terrain { + class QuadTreeNode; /// The alignment of the terrain enum Alignment @@ -51,6 +52,14 @@ namespace Terrain bool mParallax; // Height info in normal map alpha channel? bool mSpecular; // Specular info in diffuse map alpha channel? }; + + struct LayerCollection + { + QuadTreeNode* mTarget; + // Since we can't create a texture from a different thread, this only holds the raw texel data + std::vector mBlendmaps; + std::vector mLayers; + }; } #endif diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 78b17909a..faa73a986 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -48,11 +48,15 @@ namespace Terrain Ogre::MaterialPtr MaterialGenerator::generate(Ogre::MaterialPtr mat) { + assert(!mLayerList.empty() && "Can't create material with no layers"); + return create(mat, false, false); } Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT(Ogre::MaterialPtr mat) { + assert(!mLayerList.empty() && "Can't create material with no layers"); + return create(mat, true, false); } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 913970606..56cfc2a74 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -144,7 +144,6 @@ namespace QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent) : mMaterialGenerator(NULL) - , mIsActive(false) , mIsDummy(false) , mSize(size) , mLodLevel(Log2(mSize)) @@ -262,11 +261,13 @@ const Ogre::AxisAlignedBox& QuadTreeNode::getWorldBoundingBox() return mWorldBounds; } -void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) +bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos) { - const Ogre::AxisAlignedBox& bounds = getBoundingBox(); - if (bounds.isNull()) - return; + if (isDummy()) + return true; + + if (mBounds.isNull()) + return true; float dist = distance(mWorldBounds, cameraPos); @@ -291,11 +292,9 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) wantedLod = 3; else if (dist > cellWorldSize*2) wantedLod = 2; - else if (dist > cellWorldSize) + else if (dist > cellWorldSize * 1.42) // < sqrt2 so the 3x3 grid around player is always highest lod wantedLod = 1; - mIsActive = true; - bool wantToDisplay = mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod; if (wantToDisplay) @@ -305,6 +304,7 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) { mLoadState = LS_Loading; mTerrain->queueLoad(this); + return false; } if (mLoadState == LS_Loaded) @@ -331,47 +331,60 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) if (!mChunk->getVisible() && hasChildren()) { - // Make sure child scene nodes are detached - mSceneNode->removeAllChildren(); - - // TODO: unload - //for (int i=0; i<4; ++i) - // mChildren[i]->unload(); + for (int i=0; i<4; ++i) + mChildren[i]->unload(true); } - mChunk->setVisible(true); + + return true; } + return false; // LS_Loading } - if (wantToDisplay && mLoadState != LS_Loaded) + // We do not want to display this node - delegate to children if they are already loaded + if (!wantToDisplay && hasChildren()) { - // We want to display, but aren't ready yet. Perhaps our child nodes are ready? - // TODO: this doesn't check child-child nodes... - if (hasChildren()) + if (mChunk) { + // Are children already loaded? + bool childrenLoaded = true; for (int i=0; i<4; ++i) - if (mChildren[i]->hasChunk()) - mChildren[i]->update(cameraPos); + if (!mChildren[i]->update(cameraPos)) + childrenLoaded = false; + + if (!childrenLoaded) + { + // Make sure child scene nodes are detached until all children are loaded + mSceneNode->removeAllChildren(); + } + else + { + // Delegation went well, we can unload now + unload(); + + for (int i=0; i<4; ++i) + { + if (!mChildren[i]->getSceneNode()->isInSceneGraph()) + mSceneNode->addChild(mChildren[i]->getSceneNode()); + } + } + return true; } - } - if (!wantToDisplay) - { - // We do not want to display this node - delegate to children - if (mChunk) - mChunk->setVisible(false); - if (hasChildren()) + else { + bool success = true; for (int i=0; i<4; ++i) - mChildren[i]->update(cameraPos); + success = mChildren[i]->update(cameraPos) & success; + return success; } } + return false; } void QuadTreeNode::load(const LoadResponseData &data) { assert (!mChunk); - std::cout << "loading " << std::endl; mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data); mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); mChunk->setCastShadows(true); @@ -380,50 +393,49 @@ void QuadTreeNode::load(const LoadResponseData &data) mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled()); - if (mSize == 1) - { - ensureLayerInfo(); - mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); - } - else + if (mTerrain->areLayersLoaded()) { - ensureCompositeMap(); - mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); - mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); + if (mSize == 1) + { + mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); + } + else + { + ensureCompositeMap(); + mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); + mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); + } } - + // else: will be loaded in loadMaterials() after background thread has finished loading layers mChunk->setVisible(false); mLoadState = LS_Loaded; } -void QuadTreeNode::unload() +void QuadTreeNode::unload(bool recursive) { if (mChunk) { - Ogre::MaterialManager::getSingleton().remove(mChunk->getMaterial()->getName()); mSceneNode->detachObject(mChunk); delete mChunk; mChunk = NULL; - // destroy blendmaps - if (mMaterialGenerator) - { - const std::vector& list = mMaterialGenerator->getBlendmapList(); - for (std::vector::const_iterator it = list.begin(); it != list.end(); ++it) - Ogre::TextureManager::getSingleton().remove((*it)->getName()); - mMaterialGenerator->setBlendmapList(std::vector()); - mMaterialGenerator->setLayerList(std::vector()); - mMaterialGenerator->setCompositeMap(""); - } if (!mCompositeMap.isNull()) { Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName()); mCompositeMap.setNull(); } + + // Do *not* set this when we are still loading! + mLoadState = LS_Unloaded; + } + + if (recursive && hasChildren()) + { + for (int i=0; i<4; ++i) + mChildren[i]->unload(); } - mLoadState = LS_Unloaded; } void QuadTreeNode::updateIndexBuffers() @@ -479,17 +491,53 @@ size_t QuadTreeNode::getActualLodLevel() return mLodLevel /* + mChunk->getAdditionalLod() */; } -void QuadTreeNode::ensureLayerInfo() +void QuadTreeNode::loadLayers(const LayerCollection& collection) { - if (mMaterialGenerator->hasLayers()) + assert (!mMaterialGenerator->hasLayers()); + + std::vector blendTextures; + for (std::vector::const_iterator it = collection.mBlendmaps.begin(); it != collection.mBlendmaps.end(); ++it) + { + // TODO: clean up blend textures on destruction + static int count=0; + Ogre::TexturePtr map = Ogre::TextureManager::getSingleton().createManual("terrain/blend/" + + Ogre::StringConverter::toString(count++), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, it->getWidth(), it->getHeight(), 0, it->format); + + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(it->data, it->getWidth()*it->getHeight()*Ogre::PixelUtil::getNumElemBytes(it->format), true)); + map->loadRawData(stream, it->getWidth(), it->getHeight(), it->format); + blendTextures.push_back(map); + } + + mMaterialGenerator->setLayerList(collection.mLayers); + mMaterialGenerator->setBlendmapList(blendTextures); +} + +void QuadTreeNode::loadMaterials() +{ + if (isDummy()) return; - std::vector blendmaps; - std::vector layerList; - mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList); + // Load children first since we depend on them when creating a composite map + if (hasChildren()) + { + for (int i=0; i<4; ++i) + mChildren[i]->loadMaterials(); + } - mMaterialGenerator->setLayerList(layerList); - mMaterialGenerator->setBlendmapList(blendmaps); + if (mChunk) + { + if (mSize == 1) + { + mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); + } + else + { + ensureCompositeMap(); + mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); + mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); + } + } } void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) @@ -525,8 +573,7 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) } else { - ensureLayerInfo(); - + // TODO: when to destroy? Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT(Ogre::MaterialPtr()); makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, material); } @@ -570,13 +617,3 @@ void QuadTreeNode::applyMaterials() for (int i=0; i<4; ++i) mChildren[i]->applyMaterials(); } - -void QuadTreeNode::setVisible(bool visible) -{ - if (!visible && mChunk) - mChunk->setVisible(false); - - if (hasChildren()) - for (int i=0; i<4; ++i) - mChildren[i]->setVisible(visible); -} diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 134bc2d34..c57589487 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -51,8 +51,6 @@ namespace Terrain QuadTreeNode (World* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); ~QuadTreeNode(); - void setVisible(bool visible); - /// Rebuild all materials void applyMaterials(); @@ -100,7 +98,9 @@ namespace Terrain World* getTerrain() { return mTerrain; } /// Adjust LODs for the given camera position, possibly splitting up chunks or merging them. - void update (const Ogre::Vector3& cameraPos); + /// @param force Always choose to render this node, even if not the perfect LOD. + /// @return Did we (or all of our children) choose to render? + bool update (const Ogre::Vector3& cameraPos); /// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided. /// Call after QuadTreeNode::update! @@ -122,13 +122,17 @@ namespace Terrain /// Add a textured quad to a specific 2d area in the composite map scenemanager. /// Only nodes with size <= 1 can be rendered with alpha blending, so larger nodes will simply /// call this method on their children. + /// @note Do not call this before World::areLayersLoaded() == true /// @param area area in image space to put the quad /// @param quads collect quads here so they can be deleted later void prepareForCompositeMap(Ogre::TRect area); /// Create a chunk for this node from the given data. void load (const LoadResponseData& data); - void unload(); + void unload(bool recursive=false); + void loadLayers (const LayerCollection& collection); + /// This is recursive! Call it once on the root node after all leafs have loaded layers. + void loadMaterials(); LoadState getLoadState() { return mLoadState; } @@ -138,10 +142,6 @@ namespace Terrain LoadState mLoadState; - /// Is this node (or any of its child nodes) currently configured to render itself? - /// (only relevant when distant land is disabled, otherwise whole terrain is always rendered) - bool mIsActive; - bool mIsDummy; float mSize; size_t mLodLevel; // LOD if we were to render this node in one chunk @@ -162,7 +162,6 @@ namespace Terrain Ogre::TexturePtr mCompositeMap; - void ensureLayerInfo(); void ensureCompositeMap(); }; diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index 1b1968f1a..d3770ea57 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -44,6 +44,7 @@ namespace Terrain /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from background threads. Make sure to only call thread-safe functions from here! /// @param chunkSize size of the terrain chunk in cell units /// @param chunkCenter center of the chunk in cell units /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - @@ -52,9 +53,21 @@ namespace Terrain /// @param blendmaps created blendmaps will be written here /// @param layerList names of the layer textures used will be written here virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, - std::vector& blendmaps, + std::vector& blendmaps, std::vector& layerList) = 0; + /// Retrieve pixel data for textures holding layer blend values for terrain chunks and layer texture information. + /// This variant is provided to eliminate the overhead of virtual function calls when retrieving a large number of blendmaps at once. + /// @note The terrain chunks shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @note May be called from background threads. Make sure to only call thread-safe functions from here! + /// @param nodes A collection of nodes for which to retrieve the aforementioned data + /// @param out Output vector + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + virtual void getBlendmaps (const std::vector& nodes, std::vector& out, bool pack) = 0; + virtual float getHeightAt (const Ogre::Vector3& worldPos) = 0; virtual LayerInfo getDefaultLayer() = 0; diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 69748a721..844144d7c 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -7,7 +7,6 @@ #include #include #include -#include // TEMP #include "storage.hpp" #include "quadtreenode.hpp" @@ -52,6 +51,9 @@ namespace namespace Terrain { + const Ogre::uint REQ_ID_CHUNK = 1; + const Ogre::uint REQ_ID_LAYERS = 2; + World::World(Ogre::SceneManager* sceneMgr, Storage* storage, int visibilityFlags, bool distantLand, bool shaders, Alignment align, float minBatchSize, float maxBatchSize) : mStorage(storage) @@ -70,6 +72,7 @@ namespace Terrain , mChunksLoading(0) , mWorkQueueChannel(0) , mCache(storage->getCellVertices()) + , mLayerLoadPending(true) { #if TERRAIN_USE_SHADER == 0 if (mShaders) @@ -102,8 +105,12 @@ namespace Terrain mRootSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); + // While building the quadtree, remember leaf nodes since we need to load their layers + LayersRequestData data; + data.mPack = getShadersEnabled(); + mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(centerX, centerY), NULL); - buildQuadTree(mRootNode); + buildQuadTree(mRootNode, data.mNodes); //loadingListener->indicateProgress(); mRootNode->initAabb(); //loadingListener->indicateProgress(); @@ -114,6 +121,9 @@ namespace Terrain mWorkQueueChannel = wq->getChannel("LargeTerrain"); wq->addRequestHandler(mWorkQueueChannel, this); wq->addResponseHandler(mWorkQueueChannel, this); + + // Start loading layers in the background (for leaf nodes) + wq->addRequest(mWorkQueueChannel, REQ_ID_LAYERS, Ogre::Any(data)); } World::~World() @@ -126,7 +136,7 @@ namespace Terrain delete mStorage; } - void World::buildQuadTree(QuadTreeNode *node) + void World::buildQuadTree(QuadTreeNode *node, std::vector& leafs) { float halfSize = node->getSize()/2.f; @@ -142,6 +152,7 @@ namespace Terrain Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ)); convertBounds(bounds); node->setBoundingBox(bounds); + leafs.push_back(node); } else node->markAsDummy(); // no data available for this node, skip it @@ -164,10 +175,10 @@ namespace Terrain node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f)); node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f)); node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f); - buildQuadTree(node->getChild(SW)); - buildQuadTree(node->getChild(SE)); - buildQuadTree(node->getChild(NW)); - buildQuadTree(node->getChild(NE)); + buildQuadTree(node->getChild(SW), leafs); + buildQuadTree(node->getChild(SE), leafs); + buildQuadTree(node->getChild(NW), leafs); + buildQuadTree(node->getChild(NE), leafs); // if all children are dummy, we are also dummy for (int i=0; i<4; ++i) @@ -267,7 +278,7 @@ namespace Terrain void World::syncLoad() { - while (mChunksLoading) + while (mChunksLoading || mLayerLoadPending) { OGRE_THREAD_SLEEP(0); Ogre::Root::getSingleton().getWorkQueue()->processResponses(); @@ -276,27 +287,36 @@ namespace Terrain Ogre::WorkQueue::Response* World::handleRequest(const Ogre::WorkQueue::Request *req, const Ogre::WorkQueue *srcQ) { - const LoadRequestData data = Ogre::any_cast(req->getData()); + if (req->getType() == REQ_ID_CHUNK) + { + const LoadRequestData data = Ogre::any_cast(req->getData()); - QuadTreeNode* node = data.mNode; + QuadTreeNode* node = data.mNode; - LoadResponseData* responseData = new LoadResponseData(); + LoadResponseData* responseData = new LoadResponseData(); - Ogre::Timer timer; - getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(), - responseData->mPositions, responseData->mNormals, responseData->mColours); + getStorage()->fillVertexBuffers(node->getNativeLodLevel(), node->getSize(), node->getCenter(), getAlign(), + responseData->mPositions, responseData->mNormals, responseData->mColours); - std::cout << "THREAD" << std::endl; + return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); + } + else // REQ_ID_LAYERS + { + const LayersRequestData data = Ogre::any_cast(req->getData()); + + LayersResponseData* responseData = new LayersResponseData(); - responseData->time = timer.getMicroseconds(); + getStorage()->getBlendmaps(data.mNodes, responseData->mLayerCollections, data.mPack); - return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); + return OGRE_NEW Ogre::WorkQueue::Response(req, true, Ogre::Any(responseData)); + } } void World::handleResponse(const Ogre::WorkQueue::Response *res, const Ogre::WorkQueue *srcQ) { - static unsigned long time = 0; - if (res->succeeded()) + assert(res->succeeded() && "Response failure not handled"); + + if (res->getRequest()->getType() == REQ_ID_CHUNK) { LoadResponseData* data = Ogre::any_cast(res->getData()); @@ -304,23 +324,31 @@ namespace Terrain requestData.mNode->load(*data); - time += data->time; - delete data; - std::cout << "RESPONSE, reqs took ms" << time/1000.f << std::endl; + --mChunksLoading; + } + else // REQ_ID_LAYERS + { + LayersResponseData* data = Ogre::any_cast(res->getData()); + + for (std::vector::iterator it = data->mLayerCollections.begin(); it != data->mLayerCollections.end(); ++it) + { + it->mTarget->loadLayers(*it); + } + + mRootNode->loadMaterials(); + + mLayerLoadPending = false; } - --mChunksLoading; } void World::queueLoad(QuadTreeNode *node) { LoadRequestData data; data.mNode = node; - data.mPack = getShadersEnabled(); - const Ogre::uint16 loadRequestId = 1; - Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, loadRequestId, Ogre::Any(data)); + Ogre::Root::getSingleton().getWorkQueue()->addRequest(mWorkQueueChannel, REQ_ID_CHUNK, Ogre::Any(data)); ++mChunksLoading; } } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 354738744..26a6d034d 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -122,15 +122,20 @@ namespace Terrain /// Maximum size of a terrain batch along one side (in cell units) float mMaxBatchSize; - void buildQuadTree(QuadTreeNode* node); + void buildQuadTree(QuadTreeNode* node, std::vector& leafs); BufferCache mCache; + // Are layers for leaf nodes loaded? This is done once at startup (but in a background thread) + bool mLayerLoadPending; + public: // ----INTERNAL---- Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; } BufferCache& getBufferCache() { return mCache; } + bool areLayersLoaded() { return !mLayerLoadPending; } + // Delete all quads void clearCompositeMapSceneManager(); void renderCompositeMap (Ogre::TexturePtr target); @@ -151,7 +156,6 @@ namespace Terrain struct LoadRequestData { QuadTreeNode* mNode; - bool mPack; friend std::ostream& operator<<(std::ostream& o, const LoadRequestData& r) { return o; } @@ -162,16 +166,28 @@ namespace Terrain std::vector mPositions; std::vector mNormals; std::vector mColours; - // Since we can't create a texture from a different thread, this only holds the raw texel data - std::vector< std::vector > mBlendmaps; - std::vector mLayerList; - - unsigned long time; friend std::ostream& operator<<(std::ostream& o, const LoadResponseData& r) { return o; } }; + struct LayersRequestData + { + std::vector mNodes; + bool mPack; + + friend std::ostream& operator<<(std::ostream& o, const LayersRequestData& r) + { return o; } + }; + + struct LayersResponseData + { + std::vector mLayerCollections; + + friend std::ostream& operator<<(std::ostream& o, const LayersResponseData& r) + { return o; } + }; + } #endif From 83b6fcf22e1d6367eec2182958b3821907b5eb52 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 5 Mar 2014 22:24:05 +0100 Subject: [PATCH 079/114] Bug #416: Workaround for page flipping problem --- apps/openmw/mwgui/loadingscreen.cpp | 13 +++++++++++++ apps/openmw/mwworld/scene.cpp | 7 ++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 53a45db09..46c6aebdc 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -72,6 +72,10 @@ namespace MWGui void LoadingScreen::loadingOn() { + // Early-out if already on + if (mRectangle->getVisible()) + return; + // Temporarily turn off VSync, we want to do actual loading rather than waiting for the screen to sync. // Threaded loading would be even better, of course - especially because some drivers force VSync to on and we can't change it. // In Ogre 1.8, the swapBuffers argument is useless and setVSyncEnabled is bugged with GLX, nothing we can do :/ @@ -80,6 +84,15 @@ namespace MWGui mWindow->setVSyncEnabled(false); #endif + if (!mFirstLoad) + { + // When the loading screen is updated, we will disable render target clearing and only draw the loading bar itself. + // But if using page flipping, this can cause jitteriness as it will alternate between the last two rendered frames. + // Even though we attempt to disable vsync above, this may not work if it's forced on the driver level. + // Therefore render the same frame twice before activating the loading bar. + mWindow->update(); + } + setVisible(true); if (mFirstLoad) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index caae72c13..3d4413a35 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -214,12 +214,13 @@ namespace MWWorld void Scene::changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos) { - mRendering.enableTerrain(true); Nif::NIFFile::CacheLock cachelock; Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); + mRendering.enableTerrain(true); + std::string loadingExteriorText = "#{sLoadingMessage3}"; loadingListener->setLabel(loadingExteriorText); @@ -364,11 +365,11 @@ namespace MWWorld Nif::NIFFile::CacheLock lock; MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(0.5); - mRendering.enableTerrain(false); - Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); + mRendering.enableTerrain(false); + std::string loadingInteriorText = "#{sLoadingMessage2}"; loadingListener->setLabel(loadingInteriorText); From d0f98103e4fb57a609fbe84d8b9865f5dd199a47 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 6 Mar 2014 02:58:46 +0100 Subject: [PATCH 080/114] Terrain: re-added "distant land=off" path Still a hack, but the overhead of building and traversing the quad tree appears negligible. --- components/CMakeLists.txt | 2 +- components/terrain/quadtreenode.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 0a99c9528..db4ecad0b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -75,7 +75,7 @@ add_component_dir (translation add_definitions(-DTERRAIN_USE_SHADER=1) add_component_dir (terrain - quadtreenode chunk world storage material buffercache compositemap defs + quadtreenode chunk world storage material buffercache defs ) add_component_dir (loadinglistener diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 56cfc2a74..40a8baaf0 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -282,6 +282,9 @@ bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos) size_t wantedLod = 0; float cellWorldSize = mTerrain->getStorage()->getCellWorldSize(); + if (!mTerrain->getDistantLandEnabled() && dist > cellWorldSize) + return true; + if (dist > cellWorldSize*64) wantedLod = 6; else if (dist > cellWorldSize*32) From e08f6c9ce3866204fcd6fd5bf3cd83c3c36a2e44 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 6 Mar 2014 04:01:25 +0100 Subject: [PATCH 081/114] Bug #416: Copy framebuffer to a texture instead of not clearing Potentially faster than the previous workaround, and should work for triple buffering too. --- apps/openmw/mwgui/loadingscreen.cpp | 35 +++++++++++++++++------------ 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 46c6aebdc..37e29591b 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -86,11 +87,26 @@ namespace MWGui if (!mFirstLoad) { - // When the loading screen is updated, we will disable render target clearing and only draw the loading bar itself. - // But if using page flipping, this can cause jitteriness as it will alternate between the last two rendered frames. - // Even though we attempt to disable vsync above, this may not work if it's forced on the driver level. - // Therefore render the same frame twice before activating the loading bar. - mWindow->update(); + mBackgroundImage->setImageTexture(""); + int width = mWindow->getWidth(); + int height = mWindow->getHeight(); + const std::string textureName = "@loading_background"; + Ogre::TexturePtr texture; + texture = Ogre::TextureManager::getSingleton().getByName(textureName); + if (texture.isNull()) + { + texture = Ogre::TextureManager::getSingleton().createManual(textureName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + width, height, 0, mWindow->suggestPixelFormat(), Ogre::TU_DYNAMIC_WRITE_ONLY); + } + texture->unload(); + texture->setWidth(width); + texture->setHeight(height); + texture->createInternalResources(); + mWindow->copyContentsToMemory(texture->getBuffer()->lock(Ogre::Image::Box(0,0,width,height), Ogre::HardwareBuffer::HBL_DISCARD)); + texture->getBuffer()->unlock(); + mBackgroundImage->setImageTexture(texture->getName()); } setVisible(true); @@ -99,10 +115,6 @@ namespace MWGui { changeWallpaper(); } - else - { - mBackgroundImage->setImageTexture(""); - } MWBase::Environment::get().getWindowManager()->pushGuiMode(mFirstLoad ? GM_LoadingWallpaper : GM_Loading); } @@ -216,8 +228,6 @@ namespace MWGui MWBase::Environment::get().getInputManager()->update(0, true); - mWindow->getViewport(0)->setClearEveryFrame(false); - // First, swap buffers from last draw, then, queue an update of the // window contents, but don't swap buffers (which would have // caused a sync / flush and would be expensive). @@ -227,9 +237,6 @@ namespace MWGui mWindow->update(false); - mWindow->getViewport(0)->setClearEveryFrame(true); - - mRectangle->setVisible(false); // resume 3d rendering From 7386d3eb0b00a20e48d8c513a3214e56eedba730 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 6 Mar 2014 10:01:23 +0100 Subject: [PATCH 082/114] moved navigation into the WorldspaceWidget --- apps/opencs/view/render/scenewidget.hpp | 2 ++ apps/opencs/view/render/worldspacewidget.cpp | 34 +++++++++++++++++++- apps/opencs/view/render/worldspacewidget.hpp | 24 ++++++++++++++ apps/opencs/view/world/scenesubview.cpp | 24 +++----------- apps/opencs/view/world/scenesubview.hpp | 15 ++------- 5 files changed, 65 insertions(+), 34 deletions(-) diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index 4a18c4244..c0537aa73 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -25,6 +25,8 @@ namespace CSVRender QPaintEngine* paintEngine() const; + protected: + void setNavigation (Navigation *navigation); ///< \attention The ownership of \a navigation is not transferred to *this. diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index c88821a62..dcd152bb3 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -1,6 +1,38 @@ #include "worldspacewidget.hpp" +#include "../world/scenetoolmode.hpp" + CSVRender::WorldspaceWidget::WorldspaceWidget (QWidget *parent) : SceneWidget (parent) -{} \ No newline at end of file +{} + +void CSVRender::WorldspaceWidget::selectNavigationMode (const std::string& mode) +{ + if (mode=="1st") + setNavigation (&m1st); + else if (mode=="free") + setNavigation (&mFree); + else if (mode=="orbit") + setNavigation (&mOrbit); +} + +void CSVRender::WorldspaceWidget::selectDefaultNavigationMode() +{ + setNavigation (&m1st); +} + +CSVWorld::SceneToolMode *CSVRender::WorldspaceWidget::makeNavigationSelector ( + CSVWorld::SceneToolbar *parent) +{ + CSVWorld::SceneToolMode *tool = new CSVWorld::SceneToolMode (parent); + + tool->addButton (":door.png", "1st"); /// \todo replace icons + tool->addButton (":GMST.png", "free"); + tool->addButton (":Info.png", "orbit"); + + connect (tool, SIGNAL (modeChanged (const std::string&)), + this, SLOT (selectNavigationMode (const std::string&))); + + return tool; +} \ No newline at end of file diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 1c122c935..9d9452c17 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -3,15 +3,39 @@ #include "scenewidget.hpp" +#include "navigation1st.hpp" +#include "navigationfree.hpp" +#include "navigationorbit.hpp" + +namespace CSVWorld +{ + class SceneToolMode; + class SceneToolbar; +} + namespace CSVRender { class WorldspaceWidget : public SceneWidget { Q_OBJECT + CSVRender::Navigation1st m1st; + CSVRender::NavigationFree mFree; + CSVRender::NavigationOrbit mOrbit; + public: WorldspaceWidget (QWidget *parent = 0); + + CSVWorld::SceneToolMode *makeNavigationSelector (CSVWorld::SceneToolbar *parent); + ///< \important The created tool is not added to the toolbar (via addTool). Doing that + /// is the responsibility of the calling function. + + void selectDefaultNavigationMode(); + + private slots: + + void selectNavigationMode (const std::string& mode); }; } diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 516a5db80..88b84ace5 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -33,19 +33,13 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SceneToolbar *toolbar = new SceneToolbar (48, this); - // navigation mode - SceneToolMode *tool = new SceneToolMode (toolbar); - tool->addButton (":door.png", "1st"); /// \todo replace icons - tool->addButton (":GMST.png", "free"); - tool->addButton (":Info.png", "orbit"); + mScene = new CSVRender::WorldspaceWidget (this); + + SceneToolMode *tool = mScene->makeNavigationSelector (toolbar); toolbar->addTool (tool); - connect (tool, SIGNAL (modeChanged (const std::string&)), - this, SLOT (selectNavigationMode (const std::string&))); layout2->addWidget (toolbar, 0); - mScene = new CSVRender::WorldspaceWidget (this); - layout2->addWidget (mScene, 1); layout->insertLayout (0, layout2, 1); @@ -60,7 +54,7 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D setWidget (widget); - mScene->setNavigation (&m1st); + mScene->selectDefaultNavigationMode(); } void CSVWorld::SceneSubView::setEditLock (bool locked) @@ -79,13 +73,3 @@ void CSVWorld::SceneSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); } - -void CSVWorld::SceneSubView::selectNavigationMode (const std::string& mode) -{ - if (mode=="1st") - mScene->setNavigation (&m1st); - else if (mode=="free") - mScene->setNavigation (&mFree); - else if (mode=="orbit") - mScene->setNavigation (&mOrbit); -} diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index f944e17c3..21b3dc204 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -3,10 +3,6 @@ #include "../doc/subview.hpp" -#include "../render/navigation1st.hpp" -#include "../render/navigationfree.hpp" -#include "../render/navigationorbit.hpp" - class QModelIndex; namespace CSMDoc @@ -16,7 +12,7 @@ namespace CSMDoc namespace CSVRender { - class SceneWidget; + class WorldspaceWidget; } namespace CSVWorld @@ -30,10 +26,7 @@ namespace CSVWorld Q_OBJECT TableBottomBox *mBottom; - CSVRender::SceneWidget *mScene; - CSVRender::Navigation1st m1st; - CSVRender::NavigationFree mFree; - CSVRender::NavigationOrbit mOrbit; + CSVRender::WorldspaceWidget *mScene; public: @@ -44,10 +37,6 @@ namespace CSVWorld virtual void updateEditorSetting (const QString& key, const QString& value); virtual void setStatusBar (bool show); - - private slots: - - void selectNavigationMode (const std::string& mode); }; } From 397921e45766f7f8c99591264fd2310a2a99c9e6 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 6 Mar 2014 10:13:39 +0100 Subject: [PATCH 083/114] specialising WorldspaceWdiget into paged and unpaged subclasses --- apps/opencs/CMakeLists.txt | 2 +- .../view/render/pagedworldspacewidget.cpp | 6 ++++++ .../view/render/pagedworldspacewidget.hpp | 18 ++++++++++++++++++ .../view/render/unpagedworldspacewidget.cpp | 6 ++++++ .../view/render/unpagedworldspacewidget.hpp | 18 ++++++++++++++++++ apps/opencs/view/world/scenesubview.cpp | 8 ++++++-- 6 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 apps/opencs/view/render/pagedworldspacewidget.cpp create mode 100644 apps/opencs/view/render/pagedworldspacewidget.hpp create mode 100644 apps/opencs/view/render/unpagedworldspacewidget.cpp create mode 100644 apps/opencs/view/render/unpagedworldspacewidget.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 72757556d..621b9bf86 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -64,7 +64,7 @@ opencs_units (view/world ) opencs_units (view/render - scenewidget worldspacewidget + scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget ) opencs_units_noqt (view/render diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp new file mode 100644 index 000000000..fa32e3959 --- /dev/null +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -0,0 +1,6 @@ + +#include "pagedworldspacewidget.hpp" + +CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget *parent) +: WorldspaceWidget (parent) +{} \ No newline at end of file diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp new file mode 100644 index 000000000..172e2477a --- /dev/null +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -0,0 +1,18 @@ +#ifndef OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H +#define OPENCS_VIEW_PAGEDWORLDSPACEWIDGET_H + +#include "worldspacewidget.hpp" + +namespace CSVRender +{ + class PagedWorldspaceWidget : public WorldspaceWidget + { + Q_OBJECT + + public: + + PagedWorldspaceWidget (QWidget *parent); + }; +} + +#endif diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp new file mode 100644 index 000000000..6ccb1b99b --- /dev/null +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -0,0 +1,6 @@ + +#include "unpagedworldspacewidget.hpp" + +CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (QWidget *parent) +: WorldspaceWidget (parent) +{} \ No newline at end of file diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp new file mode 100644 index 000000000..7f9c895bc --- /dev/null +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -0,0 +1,18 @@ +#ifndef OPENCS_VIEW_UNPAGEDWORLDSPACEWIDGET_H +#define OPENCS_VIEW_UNPAGEDWORLDSPACEWIDGET_H + +#include "worldspacewidget.hpp" + +namespace CSVRender +{ + class UnpagedWorldspaceWidget : public WorldspaceWidget + { + Q_OBJECT + + public: + + UnpagedWorldspaceWidget (QWidget *parent); + }; +} + +#endif diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 88b84ace5..091e78ec4 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -9,7 +9,8 @@ #include "../filter/filterbox.hpp" -#include "../render/worldspacewidget.hpp" +#include "../render/pagedworldspacewidget.hpp" +#include "../render/unpagedworldspacewidget.hpp" #include "tablebottombox.hpp" #include "creator.hpp" @@ -33,7 +34,10 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D SceneToolbar *toolbar = new SceneToolbar (48, this); - mScene = new CSVRender::WorldspaceWidget (this); + if (id.getId()[0]=='#') + mScene = new CSVRender::PagedWorldspaceWidget (this); + else + mScene = new CSVRender::UnpagedWorldspaceWidget (this); SceneToolMode *tool = mScene->makeNavigationSelector (toolbar); toolbar->addTool (tool); From 7e0f0c8402b16637674c862c00093b09743734d9 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 6 Mar 2014 12:42:09 +0100 Subject: [PATCH 084/114] fixed SceneWidget destructor --- apps/opencs/view/render/scenewidget.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 6f07c1b0d..f71329c61 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -94,7 +94,11 @@ namespace CSVRender SceneWidget::~SceneWidget() { - Ogre::Root::getSingleton().destroyRenderTarget(mWindow); + if (mWindow) + Ogre::Root::getSingleton().destroyRenderTarget (mWindow); + + if (mSceneMgr) + Ogre::Root::getSingleton().destroySceneManager (mSceneMgr); } void SceneWidget::setNavigation (Navigation *navigation) From 4a119c8f465264cd9894c772f8d6e843699e3570 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 6 Mar 2014 13:02:21 +0100 Subject: [PATCH 085/114] ambient lighting in interior cells --- apps/opencs/view/render/scenewidget.cpp | 5 ++ apps/opencs/view/render/scenewidget.hpp | 4 ++ .../view/render/unpagedworldspacewidget.cpp | 60 ++++++++++++++++++- .../view/render/unpagedworldspacewidget.hpp | 28 ++++++++- apps/opencs/view/world/scenesubview.cpp | 2 +- 5 files changed, 94 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index f71329c61..2c98b60cf 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -59,6 +59,11 @@ namespace CSVRender timer->start (20); /// \todo make this configurable } + void SceneWidget::setAmbient (const Ogre::ColourValue& colour) + { + mSceneMgr->setAmbientLight (colour); + } + void SceneWidget::updateOgreWindow() { if (mWindow) diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp index c0537aa73..ad68897ac 100644 --- a/apps/opencs/view/render/scenewidget.hpp +++ b/apps/opencs/view/render/scenewidget.hpp @@ -8,6 +8,7 @@ namespace Ogre class Camera; class SceneManager; class RenderWindow; + class ColourValue; } namespace CSVRender @@ -25,6 +26,9 @@ namespace CSVRender QPaintEngine* paintEngine() const; + void setAmbient (const Ogre::ColourValue& colour); + ///< \note The actual ambient colour may differ based on lighting settings. + protected: void setNavigation (Navigation *navigation); diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 6ccb1b99b..812a3b165 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -1,6 +1,60 @@ #include "unpagedworldspacewidget.hpp" -CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (QWidget *parent) -: WorldspaceWidget (parent) -{} \ No newline at end of file +#include + +#include "../../model/doc/document.hpp" + +#include "../../model/world/data.hpp" +#include "../../model/world/idtable.hpp" + +void CSVRender::UnpagedWorldspaceWidget::update() +{ + const CSMWorld::Record& record = + dynamic_cast&> (mCellsModel->getRecord (mCellId)); + + Ogre::ColourValue colour; + colour.setAsABGR (record.get().mAmbi.mAmbient); + setAmbient (colour); + + /// \todo deal with mSunlight and mFog/mForDensity +} + +CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& cellId, + CSMDoc::Document& document, QWidget *parent) +: WorldspaceWidget (parent), mCellId (cellId) +{ + mCellsModel = &dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + + connect (mCellsModel, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (cellDataChanged (const QModelIndex&, const QModelIndex&))); + connect (mCellsModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (cellRowsAboutToBeRemoved (const QModelIndex&, int, int))); + + update(); +} + +void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + QModelIndex cellIndex = mCellsModel->getModelIndex (mCellId, 0); + + if (cellIndex.row()>=topLeft.row() && cellIndex.row()getModelIndex (mCellId, 0); + + if (cellIndex.row()>=start && cellIndex.row()<=end) + { + + } +} \ No newline at end of file diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 7f9c895bc..17dc46918 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -1,17 +1,43 @@ #ifndef OPENCS_VIEW_UNPAGEDWORLDSPACEWIDGET_H #define OPENCS_VIEW_UNPAGEDWORLDSPACEWIDGET_H +#include + #include "worldspacewidget.hpp" +class QModelIndex; + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class IdTable; +} + namespace CSVRender { class UnpagedWorldspaceWidget : public WorldspaceWidget { Q_OBJECT + std::string mCellId; + CSMWorld::IdTable *mCellsModel; + + void update(); + public: - UnpagedWorldspaceWidget (QWidget *parent); + UnpagedWorldspaceWidget (const std::string& cellId, CSMDoc::Document& document, + QWidget *parent); + + private slots: + + void cellDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void cellRowsAboutToBeRemoved (const QModelIndex& parent, int start, int end); }; } diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index 091e78ec4..d04cc4c15 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -37,7 +37,7 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D if (id.getId()[0]=='#') mScene = new CSVRender::PagedWorldspaceWidget (this); else - mScene = new CSVRender::UnpagedWorldspaceWidget (this); + mScene = new CSVRender::UnpagedWorldspaceWidget (id.getId(), document, this); SceneToolMode *tool = mScene->makeNavigationSelector (toolbar); toolbar->addTool (tool); From f04348fb8e3208b32e286acc4f0031c67e1f39c2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 6 Mar 2014 13:43:06 +0100 Subject: [PATCH 086/114] do not offer view action for cells flagged as deleted --- apps/opencs/view/world/table.cpp | 13 +++++++++++-- apps/opencs/view/world/table.hpp | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 95433dbfc..4d469fd20 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -40,7 +40,16 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) menu.addAction(mCloneAction); if (mModel->getViewing()!=CSMWorld::IdTable::Viewing_None) - menu.addAction (mViewAction); + { + int row = selectedRows.begin()->row(); + + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + + CSMWorld::UniversalId id = mModel->view (row).first; + + if (!mData.getCells().getRecord (id.getId()).isDeleted()) + menu.addAction (mViewAction); + } } if (mCreateAction) @@ -168,7 +177,7 @@ std::vector CSVWorld::Table::listDeletableSelectedIds() const CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete, bool sorting, const CSMDoc::Document& document) - : mUndoStack (undoStack), mCreateAction (0), mCloneAction(0), mEditLock (false), mRecordStatusDisplay (0), mDocument(document) + : mUndoStack (undoStack), mCreateAction (0), mCloneAction(0), mEditLock (false), mRecordStatusDisplay (0), mDocument(document), mData (data) { mModel = &dynamic_cast (*data.getTableModel (id)); diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 3bf673dac..3791bf4ac 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -48,6 +48,7 @@ namespace CSVWorld CSMWorld::IdTable *mModel; bool mEditLock; int mRecordStatusDisplay; + CSMWorld::Data& mData; /// \brief This variable is used exclusivly for checking if dropEvents came from the same document. Most likely you /// should NOT use it for anything else. From 0254a209f6cd1f8fe604b4e104f45d9719f87024 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 6 Mar 2014 13:51:21 +0100 Subject: [PATCH 087/114] some general cleanup --- apps/opencs/view/world/table.cpp | 34 +++++++++++++------------ apps/opencs/view/world/table.hpp | 21 ++++++--------- apps/opencs/view/world/tablesubview.cpp | 2 +- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 4d469fd20..4bb9955e6 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -2,7 +2,6 @@ #include "table.hpp" #include - #include #include #include @@ -10,6 +9,8 @@ #include #include +#include "../../model/doc/document.hpp" + #include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/idtableproxymodel.hpp" @@ -47,7 +48,7 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) CSMWorld::UniversalId id = mModel->view (row).first; - if (!mData.getCells().getRecord (id.getId()).isDeleted()) + if (!mDocument.getData().getCells().getRecord (id.getId()).isDeleted()) menu.addAction (mViewAction); } } @@ -175,11 +176,12 @@ std::vector CSVWorld::Table::listDeletableSelectedIds() const return deletableIds; } -CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, - bool createAndDelete, bool sorting, const CSMDoc::Document& document) - : mUndoStack (undoStack), mCreateAction (0), mCloneAction(0), mEditLock (false), mRecordStatusDisplay (0), mDocument(document), mData (data) +CSVWorld::Table::Table (const CSMWorld::UniversalId& id, + bool createAndDelete, bool sorting, CSMDoc::Document& document) +: mCreateAction (0), mCloneAction(0), mEditLock (false), mRecordStatusDisplay (0), + mDocument (document) { - mModel = &dynamic_cast (*data.getTableModel (id)); + mModel = &dynamic_cast (*mDocument.getData().getTableModel (id)); mProxyModel = new CSMWorld::IdTableProxyModel (this); mProxyModel->setSourceModel (mModel); @@ -203,7 +205,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q mModel->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); CommandDelegate *delegate = CommandDelegateFactoryCollection::get().makeDelegate (display, - undoStack, this); + mDocument.getUndoStack(), this); mDelegates.push_back (delegate); setItemDelegateForColumn (i, delegate); @@ -285,13 +287,13 @@ void CSVWorld::Table::revertRecord() if (revertableIds.size()>0) { if (revertableIds.size()>1) - mUndoStack.beginMacro (tr ("Revert multiple records")); + mDocument.getUndoStack().beginMacro (tr ("Revert multiple records")); for (std::vector::const_iterator iter (revertableIds.begin()); iter!=revertableIds.end(); ++iter) - mUndoStack.push (new CSMWorld::RevertCommand (*mModel, *iter)); + mDocument.getUndoStack().push (new CSMWorld::RevertCommand (*mModel, *iter)); if (revertableIds.size()>1) - mUndoStack.endMacro(); + mDocument.getUndoStack().endMacro(); } } } @@ -305,13 +307,13 @@ void CSVWorld::Table::deleteRecord() if (deletableIds.size()>0) { if (deletableIds.size()>1) - mUndoStack.beginMacro (tr ("Delete multiple records")); + mDocument.getUndoStack().beginMacro (tr ("Delete multiple records")); for (std::vector::const_iterator iter (deletableIds.begin()); iter!=deletableIds.end(); ++iter) - mUndoStack.push (new CSMWorld::DeleteCommand (*mModel, *iter)); + mDocument.getUndoStack().push (new CSMWorld::DeleteCommand (*mModel, *iter)); if (deletableIds.size()>1) - mUndoStack.endMacro(); + mDocument.getUndoStack().endMacro(); } } } @@ -364,7 +366,7 @@ void CSVWorld::Table::moveUpRecord() for (int i=1; i command (new CSMWorld::ModifyCommand (*mProxyModel, index, QVariant (QString::fromUtf8 (record.getId().c_str())))); - mUndoStack.push (command.release()); + mDocument.getUndoStack().push (command.release()); } } //TODO handle drops from different document } diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 3791bf4ac..e8d5648d1 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -10,13 +10,14 @@ #include "../../model/filter/node.hpp" #include "../../model/world/columnbase.hpp" -namespace CSMDoc { - class Document; -} - class QUndoStack; class QAction; +namespace CSMDoc +{ + class Document; +} + namespace CSMWorld { class Data; @@ -35,7 +36,6 @@ namespace CSVWorld Q_OBJECT std::vector mDelegates; - QUndoStack& mUndoStack; QAction *mEditAction; QAction *mCreateAction; QAction *mCloneAction; @@ -48,11 +48,7 @@ namespace CSVWorld CSMWorld::IdTable *mModel; bool mEditLock; int mRecordStatusDisplay; - CSMWorld::Data& mData; - - /// \brief This variable is used exclusivly for checking if dropEvents came from the same document. Most likely you - /// should NOT use it for anything else. - const CSMDoc::Document& mDocument; + CSMDoc::Document& mDocument; private: @@ -72,9 +68,8 @@ namespace CSVWorld public: - Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete, - bool sorting, const CSMDoc::Document& document); - + Table (const CSMWorld::UniversalId& id, bool createAndDelete, + bool sorting, CSMDoc::Document& document); ///< \param createAndDelete Allow creation and deletion of records. /// \param sorting Allow changing order of rows in the view via column headers. diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 30a60536a..2d08d186e 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -24,7 +24,7 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this), 0); layout->insertWidget (0, mTable = - new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete(), sorting, document), 2); + new Table (id, mBottom->canCreateAndDelete(), sorting, document), 2); CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); From 516a3b9abd27c126bc8864f02bcb2e50303a8939 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 6 Mar 2014 16:40:08 +0100 Subject: [PATCH 088/114] close interior cell view when cell is deleted --- .../view/render/unpagedworldspacewidget.cpp | 22 ++++++++++++------- apps/opencs/view/render/worldspacewidget.hpp | 4 ++++ apps/opencs/view/world/scenesubview.cpp | 7 ++++++ apps/opencs/view/world/scenesubview.hpp | 4 ++++ 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 812a3b165..c7edbe79b 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -38,13 +38,21 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& void CSVRender::UnpagedWorldspaceWidget::cellDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { - QModelIndex cellIndex = mCellsModel->getModelIndex (mCellId, 0); + int index = mCellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Modification); + QModelIndex cellIndex = mCellsModel->getModelIndex (mCellId, index); - if (cellIndex.row()>=topLeft.row() && cellIndex.row()=topLeft.row() && cellIndex.row()<=bottomRight.row()) { - /// \todo possible optimisation: check columns and update only if relevant columns have - /// changed - update(); + if (mCellsModel->data (cellIndex).toInt()==CSMWorld::RecordBase::State_Deleted) + { + emit closeRequest(); + } + else + { + /// \todo possible optimisation: check columns and update only if relevant columns have + /// changed + update(); + } } } @@ -54,7 +62,5 @@ void CSVRender::UnpagedWorldspaceWidget::cellRowsAboutToBeRemoved (const QModelI QModelIndex cellIndex = mCellsModel->getModelIndex (mCellId, 0); if (cellIndex.row()>=start && cellIndex.row()<=end) - { - - } + emit closeRequest(); } \ No newline at end of file diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 9d9452c17..2eccca3bf 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -36,6 +36,10 @@ namespace CSVRender private slots: void selectNavigationMode (const std::string& mode); + + signals: + + void closeRequest(); }; } diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index d04cc4c15..3601ae094 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -59,6 +59,8 @@ CSVWorld::SceneSubView::SceneSubView (const CSMWorld::UniversalId& id, CSMDoc::D setWidget (widget); mScene->selectDefaultNavigationMode(); + + connect (mScene, SIGNAL (closeRequest()), this, SLOT (closeRequest())); } void CSVWorld::SceneSubView::setEditLock (bool locked) @@ -77,3 +79,8 @@ void CSVWorld::SceneSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); } + +void CSVWorld::SceneSubView::closeRequest() +{ + deleteLater(); +} \ No newline at end of file diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index 21b3dc204..ecf3fe4e4 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -37,6 +37,10 @@ namespace CSVWorld virtual void updateEditorSetting (const QString& key, const QString& value); virtual void setStatusBar (bool show); + + private slots: + + void closeRequest(); }; } From f36bea03ab40acdd265f4bab73eb3e89d4c095b4 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 7 Mar 2014 19:33:46 +1100 Subject: [PATCH 089/114] Bug #1189 fix by scrawl --- apps/openmw/mwworld/worldimp.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b51e224f8..f154f3b24 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1873,6 +1873,8 @@ namespace MWWorld bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) { + if (!targetNpc.getRefData().isEnabled() || !npc.getRefData().isEnabled()) + return false; // cannot get LOS unless both NPC's are enabled Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(npc.getRefData().getHandle())->getHalfExtents(); float* pos1 = npc.getRefData().getPosition().pos; Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents(); From 12de0afb03a382332f2da3d31b073fe9d49bb7ff Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 7 Mar 2014 06:11:00 +0100 Subject: [PATCH 090/114] Feature #50: Spawn projectiles Fix a bug in copyObjectToCell. Make actor rotations more consistent. --- apps/openmw/mwbase/world.hpp | 8 +- apps/openmw/mwclass/creaturelevlist.cpp | 2 +- apps/openmw/mwinput/inputmanagerimp.cpp | 4 +- apps/openmw/mwmechanics/actors.cpp | 4 +- apps/openmw/mwmechanics/character.cpp | 12 +- apps/openmw/mwmechanics/spellcasting.cpp | 4 +- apps/openmw/mwrender/camera.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 48 +++- apps/openmw/mwrender/renderingmanager.cpp | 9 +- .../mwscript/transformationextensions.cpp | 34 +-- apps/openmw/mwstate/statemanagerimp.cpp | 4 +- apps/openmw/mwworld/actionteleport.cpp | 4 +- apps/openmw/mwworld/physicssystem.cpp | 16 +- apps/openmw/mwworld/worldimp.cpp | 239 +++++++++++++----- apps/openmw/mwworld/worldimp.hpp | 24 +- libs/openengine/bullet/physic.cpp | 2 + 16 files changed, 292 insertions(+), 124 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index fd3978943..5c2912a8f 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -274,7 +274,7 @@ namespace MWBase virtual void moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; virtual void - moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore &newCell, float x, float y, float z) = 0; + moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z) = 0; virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0; @@ -282,7 +282,7 @@ namespace MWBase virtual void localRotateObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; - virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) = 0; + virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0; ///< place an object in a "safe" location (ie not in the void, etc). virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) @@ -464,8 +464,10 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor) = 0; - virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, + virtual void launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName) = 0; + virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, + const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed) = 0; virtual const std::vector& getContentFiles() const = 0; diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index ea586e5b6..caef521af 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -57,7 +57,7 @@ namespace MWClass MWWorld::ManualRef ref(store, id); ref.getPtr().getCellRef().mPos = ptr.getCellRef().mPos; // TODO: hold on to this for respawn purposes later - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), *ptr.getCell() , ptr.getCellRef().mPos); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), ptr.getCell() , ptr.getCellRef().mPos); } ptr.getRefData().setCustomData(data.release()); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index b5d83427b..30cefe2df 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -578,13 +578,13 @@ namespace MWInput float rot[3]; rot[0] = -y; rot[1] = 0.0f; - rot[2] = x; + rot[2] = -x; // Only actually turn player when we're not in vanity mode if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot)) { mPlayer->yaw(x); - mPlayer->pitch(-y); + mPlayer->pitch(y); } if (arg.zrel && mControlSwitch["playerviewswitch"]) //Check to make sure you are allowed to zoomout and there is a change diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 1fb22ce63..92be89f2f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -210,7 +210,7 @@ namespace MWMechanics && LOS ) { - creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayer().getPlayer())); + creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr())); creatureStats.setHostile(true); } } @@ -541,7 +541,7 @@ namespace MWMechanics // TODO: Add AI to follow player and fight for him // TODO: VFX_SummonStart, VFX_SummonEnd creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos).getRefData().getHandle())); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos).getRefData().getHandle())); } } else diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index d19f32507..04d909b49 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -313,6 +313,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat mAnimation->disable(mCurrentMovement); mCurrentMovement = movement; + mMovementAnimVelocity = 0.0f; if(!mCurrentMovement.empty()) { float vel, speedmult = 1.0f; @@ -320,7 +321,10 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run); if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 1.0f) + { + mMovementAnimVelocity = vel; speedmult = mMovementSpeed / vel; + } else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed else if (mMovementSpeed > 0.0f) @@ -330,10 +334,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f); mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); - - mMovementAnimVelocity = vel; } - else mMovementAnimVelocity = 0.0f; } } @@ -1194,7 +1195,10 @@ void CharacterController::update(float duration) : (sneak ? CharState_SneakBack : (isrunning ? CharState_RunBack : CharState_WalkBack))); } - else if(rot.z != 0.0f && !inwater && !sneak) + // Don't play turning animations during attack. It would break positioning of the arrow bone when releasing a shot. + // Actually, in vanilla the turning animation is not even played when merely having equipped the weapon, + // but I don't think we need to go as far as that. + else if(rot.z != 0.0f && !inwater && !sneak && mUpperBodyState < UpperCharState_StartToMinAttack) { if(rot.z > 0.0f) movestate = CharState_TurnRight; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 9745cdebe..7f5a7fac5 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -587,7 +587,7 @@ namespace MWMechanics inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); } - MWBase::Environment::get().getWorld()->launchProjectile(mId, false, enchantment->mEffects, mCaster, mSourceName); + MWBase::Environment::get().getWorld()->launchMagicBolt(mId, false, enchantment->mEffects, mCaster, mSourceName); return true; } @@ -666,7 +666,7 @@ namespace MWMechanics } } - MWBase::Environment::get().getWorld()->launchProjectile(mId, false, spell->mEffects, mCaster, mSourceName); + MWBase::Environment::get().getWorld()->launchMagicBolt(mId, false, spell->mEffects, mCaster, mSourceName); return true; } diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 9ae9c5878..294264951 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -70,7 +70,7 @@ namespace MWRender if (!mVanity.enabled && !mPreviewMode) { mCamera->getParentNode()->setOrientation(xr); } else { - Ogre::Quaternion zr(Ogre::Radian(getYaw()), Ogre::Vector3::NEGATIVE_UNIT_Z); + Ogre::Quaternion zr(Ogre::Radian(getYaw()), Ogre::Vector3::UNIT_Z); mCamera->getParentNode()->setOrientation(zr * xr); } } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 60760b7fb..6169b6ddb 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -532,7 +532,7 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) { float pitch = mPtr.getRefData().getPosition().rot[0]; Ogre::Node *node = baseinst->getBone("Bip01 Neck"); - node->pitch(Ogre::Radian(pitch), Ogre::Node::TS_WORLD); + node->pitch(Ogre::Radian(-pitch), Ogre::Node::TS_WORLD); // This has to be done before this function ends; // updateSkeletonInstance, below, touches the hands. @@ -543,9 +543,9 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) // In third person mode we may still need pitch for ranged weapon targeting float pitch = mPtr.getRefData().getPosition().rot[0] * mPitchFactor; Ogre::Node *node = baseinst->getBone("Bip01 Spine2"); - node->pitch(Ogre::Radian(pitch/2), Ogre::Node::TS_WORLD); + node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); node = baseinst->getBone("Bip01 Spine1"); - node->pitch(Ogre::Radian(pitch/2), Ogre::Node::TS_WORLD); + node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); } mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame. @@ -770,12 +770,35 @@ void NpcAnimation::attachArrow() void NpcAnimation::releaseArrow() { - // Thrown weapons get detached now MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weapon != inv.end() && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + if (weapon == inv.end()) + return; + + // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise. + Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(mPtr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(mPtr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + + if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) { + // Thrown weapons get detached now + NifOgre::ObjectScenePtr objects = mObjectParts[ESM::PRT_Weapon]; + + Ogre::Vector3 launchPos(0,0,0); + if (objects->mSkelBase) + { + launchPos = objects->mSkelBase->getParentNode()->_getDerivedPosition(); + } + else if (objects->mEntities.size()) + { + objects->mEntities[0]->getParentNode()->needUpdate(true); + launchPos = objects->mEntities[0]->getParentNode()->_getDerivedPosition(); + } + + MWBase::Environment::get().getWorld()->launchProjectile(mPtr, *weapon, launchPos, orient, *weapon, 400); + showWeapons(false); + inv.remove(*weapon, 1, mPtr); } else @@ -784,6 +807,21 @@ void NpcAnimation::releaseArrow() MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if (ammo == inv.end()) return; + + Ogre::Vector3 launchPos(0,0,0); + if (mAmmunition->mSkelBase) + { + launchPos = mAmmunition->mSkelBase->getParentNode()->_getDerivedPosition(); + } + else if (mAmmunition->mEntities.size()) + { + mAmmunition->mEntities[0]->getParentNode()->needUpdate(true); + launchPos = mAmmunition->mEntities[0]->getParentNode()->_getDerivedPosition(); + } + + /// \todo speed + MWBase::Environment::get().getWorld()->launchProjectile(mPtr, *ammo, launchPos, orient, *weapon, 400); + inv.remove(*ammo, 1, mPtr); mAmmunition.setNull(); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 7c70b17f0..a0d06f068 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -280,13 +280,12 @@ void RenderingManager::rotateObject(const MWWorld::Ptr &ptr) if(ptr.getRefData().getHandle() == mCamera->getHandle() && !mCamera->isVanityOrPreviewModeEnabled()) - mCamera->rotateCamera(rot, false); + mCamera->rotateCamera(-rot, false); - Ogre::Quaternion newo = Ogre::Quaternion(Ogre::Radian(-rot.z), Ogre::Vector3::UNIT_Z); + Ogre::Quaternion newo = Ogre::Quaternion(Ogre::Radian(rot.z), Ogre::Vector3::NEGATIVE_UNIT_Z); if(!MWWorld::Class::get(ptr).isActor()) - newo = Ogre::Quaternion(Ogre::Radian(-rot.x), Ogre::Vector3::UNIT_X) * - Ogre::Quaternion(Ogre::Radian(-rot.y), Ogre::Vector3::UNIT_Y) * newo; - + newo = Ogre::Quaternion(Ogre::Radian(rot.x), Ogre::Vector3::NEGATIVE_UNIT_X) * + Ogre::Quaternion(Ogre::Radian(rot.y), Ogre::Vector3::NEGATIVE_UNIT_Y) * newo; ptr.getRefData().getBaseNode()->setOrientation(newo); } diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 43a0fedce..9a9175f19 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -82,27 +82,21 @@ namespace MWScript Interpreter::Type_Float angle = runtime[0].mFloat; runtime.pop(); - float ax = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[0]).valueDegrees(); - float ay = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[1]).valueDegrees(); - float az = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees(); - - float *objRot = ptr.getRefData().getPosition().rot; - - float lx = Ogre::Radian(objRot[0]).valueDegrees(); - float ly = Ogre::Radian(objRot[1]).valueDegrees(); - float lz = Ogre::Radian(objRot[2]).valueDegrees(); + float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); + float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); + float az = Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees(); if (axis == "x") { - MWBase::Environment::get().getWorld()->localRotateObject(ptr,angle-lx,ay,az); + MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az); } else if (axis == "y") { - MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax,angle-ly,az); + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az); } else if (axis == "z") { - MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax,ay,angle-lz); + MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle); } else throw std::runtime_error ("invalid ration axis: " + axis); @@ -152,15 +146,15 @@ namespace MWScript if (axis=="x") { - runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[0]).valueDegrees()+Ogre::Radian(ptr.getRefData().getLocalRotation().rot[0]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees()); } else if (axis=="y") { - runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[1]).valueDegrees()+Ogre::Radian(ptr.getRefData().getLocalRotation().rot[1]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees()); } else if (axis=="z") { - runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[2]).valueDegrees()+Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees()); + runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees()); } else throw std::runtime_error ("invalid ration axis: " + axis); @@ -313,7 +307,7 @@ namespace MWScript } if(store) { - MWBase::Environment::get().getWorld()->moveObject(ptr,*store,x,y,z); + MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z); float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity @@ -361,7 +355,7 @@ namespace MWScript int cx,cy; MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy); MWBase::Environment::get().getWorld()->moveObject(ptr, - *MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z); + MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z); float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity @@ -421,7 +415,7 @@ namespace MWScript pos.rot[2] = zRot; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().mPos = pos; - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,pos); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); } else { @@ -462,7 +456,7 @@ namespace MWScript pos.rot[2] = zRot; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().mPos = pos; - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,pos); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); } else { @@ -530,7 +524,7 @@ namespace MWScript MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), itemID, count); ref.getPtr().getCellRef().mPos = ipos; - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); } }; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 3807a67b6..8b01bacdf 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -148,7 +148,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot MWBase::World& world = *MWBase::Environment::get().getWorld(); - MWWorld::Ptr player = world.getPlayer().getPlayer(); + MWWorld::Ptr player = world.getPlayerPtr(); profile.mContentFiles = world.getContentFiles(); @@ -300,7 +300,7 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl MWBase::Environment::get().getWindowManager()->updatePlayer(); MWBase::Environment::get().getMechanicsManager()->playerLoaded(); - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); ESM::CellId cellId = ptr.getCell()->getCell()->getCellId(); diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index b4c572ba9..150f0bed2 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -41,11 +41,11 @@ namespace MWWorld int cellX; int cellY; world->positionToIndex(mPosition.pos[0],mPosition.pos[1],cellX,cellY); - world->moveObject(actor,*world->getExterior(cellX,cellY), + world->moveObject(actor,world->getExterior(cellX,cellY), mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); } else - world->moveObject(actor,*world->getInterior(mCellName),mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); + world->moveObject(actor,world->getInterior(mCellName),mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); } } } diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 8ef5797ca..1657cf267 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -120,11 +120,9 @@ namespace MWWorld OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle()); if(!physicActor || !physicActor->getCollisionMode()) { - // FIXME: This works, but it's inconcsistent with how the rotations are applied elsewhere. Why? - return position + (Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)* - Ogre::Quaternion(Ogre::Radian(-refpos.rot[1]), Ogre::Vector3::UNIT_Y)* - Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) * - movement * time; + return position + (Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(refpos.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)) + * movement * time; } btCollisionObject *colobj = physicActor->getCollisionBody(); @@ -140,14 +138,12 @@ namespace MWWorld Ogre::Vector3 velocity; if(position.z < waterlevel || isFlying) { - velocity = (Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)* - Ogre::Quaternion(Ogre::Radian(-refpos.rot[1]), Ogre::Vector3::UNIT_Y)* - Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) * - movement; + velocity = (Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z)* + Ogre::Quaternion(Ogre::Radian(refpos.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)) * movement; } else { - velocity = Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z) * movement; + velocity = Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * movement; if(!physicActor->getOnGround()) { // If falling, add part of the incoming velocity with the current inertia diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f154f3b24..0ef50c582 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -228,6 +228,7 @@ namespace MWWorld mCells.clear(); + mMagicBolts.clear(); mProjectiles.clear(); mDoorStates.clear(); @@ -821,7 +822,7 @@ namespace MWWorld const ESM::Position &posdata = ptr.getRefData().getPosition(); Ogre::Vector3 pos(posdata.pos); Ogre::Quaternion rot = Ogre::Quaternion(Ogre::Radian(posdata.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(posdata.rot[0]), Ogre::Vector3::UNIT_X); + Ogre::Quaternion(Ogre::Radian(posdata.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); MWRender::Animation *anim = mRendering->getAnimation(ptr); if(anim != NULL) @@ -858,7 +859,7 @@ namespace MWWorld } } - void World::moveObject(const Ptr &ptr, CellStore &newCell, float x, float y, float z) + void World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z) { ESM::Position &pos = ptr.getRefData().getPosition(); @@ -872,27 +873,27 @@ namespace MWWorld bool isPlayer = ptr == mPlayer->getPlayer(); bool haveToMove = isPlayer || mWorldScene->isCellActive(*currCell); - if (*currCell != newCell) + if (currCell != newCell) { removeContainerScripts(ptr); if (isPlayer) { - if (!newCell.isExterior()) - changeToInteriorCell(Misc::StringUtils::lowerCase(newCell.getCell()->mName), pos); + if (!newCell->isExterior()) + changeToInteriorCell(Misc::StringUtils::lowerCase(newCell->getCell()->mName), pos); else { - int cellX = newCell.getCell()->getGridX(); - int cellY = newCell.getCell()->getGridY(); + int cellX = newCell->getCell()->getGridX(); + int cellY = newCell->getCell()->getGridY(); mWorldScene->changeCell(cellX, cellY, pos, false); } - addContainerScripts (getPlayerPtr(), &newCell); + addContainerScripts (getPlayerPtr(), newCell); } else { if (!mWorldScene->isCellActive(*currCell)) - copyObjectToCell(ptr, newCell, pos); - else if (!mWorldScene->isCellActive(newCell)) + ptr.getClass().copyToCell(ptr, *newCell, pos); + else if (!mWorldScene->isCellActive(*newCell)) { mWorldScene->removeObjectFromScene(ptr); mLocalScripts.remove(ptr); @@ -900,7 +901,7 @@ namespace MWWorld haveToMove = false; MWWorld::Ptr newPtr = MWWorld::Class::get(ptr) - .copyToCell(ptr, newCell); + .copyToCell(ptr, *newCell); newPtr.getRefData().setBaseNode(0); objectLeftActiveCell(ptr, newPtr); @@ -908,7 +909,7 @@ namespace MWWorld else { MWWorld::Ptr copy = - MWWorld::Class::get(ptr).copyToCell(ptr, newCell, pos); + MWWorld::Class::get(ptr).copyToCell(ptr, *newCell, pos); mRendering->updateObjectCell(ptr, copy); MWBase::Environment::get().getSoundManager()->updatePtr (ptr, copy); @@ -923,7 +924,7 @@ namespace MWWorld mLocalScripts.remove(ptr); removeContainerScripts (ptr); mLocalScripts.add(script, copy); - addContainerScripts (copy, &newCell); + addContainerScripts (copy, newCell); } } ptr.getRefData().setCount(0); @@ -947,7 +948,7 @@ namespace MWWorld cell = getExterior(cellX, cellY); } - moveObject(ptr, *cell, x, y, z); + moveObject(ptr, cell, x, y, z); return cell != ptr.getCell(); } @@ -1041,15 +1042,13 @@ namespace MWWorld while(ptr.getRefData().getLocalRotation().rot[2]<=-fullRotateRad) ptr.getRefData().getLocalRotation().rot[2]+=fullRotateRad; - float *worldRot = ptr.getRefData().getPosition().rot; + Ogre::Quaternion worldRotQuat(Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)* + Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[1]), Ogre::Vector3::NEGATIVE_UNIT_Y)* + Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z)); - Ogre::Quaternion worldRotQuat(Ogre::Quaternion(Ogre::Radian(-worldRot[0]), Ogre::Vector3::UNIT_X)* - Ogre::Quaternion(Ogre::Radian(-worldRot[1]), Ogre::Vector3::UNIT_Y)* - Ogre::Quaternion(Ogre::Radian(-worldRot[2]), Ogre::Vector3::UNIT_Z)); - - Ogre::Quaternion rot(Ogre::Quaternion(Ogre::Radian(Ogre::Degree(-x).valueRadians()), Ogre::Vector3::UNIT_X)* - Ogre::Quaternion(Ogre::Radian(Ogre::Degree(-y).valueRadians()), Ogre::Vector3::UNIT_Y)* - Ogre::Quaternion(Ogre::Radian(Ogre::Degree(-z).valueRadians()), Ogre::Vector3::UNIT_Z)); + Ogre::Quaternion rot(Ogre::Quaternion(Ogre::Degree(x), Ogre::Vector3::NEGATIVE_UNIT_X)* + Ogre::Quaternion(Ogre::Degree(y), Ogre::Vector3::NEGATIVE_UNIT_Y)* + Ogre::Quaternion(Ogre::Degree(z), Ogre::Vector3::NEGATIVE_UNIT_Z)); ptr.getRefData().getBaseNode()->setOrientation(worldRotQuat*rot); mPhysics->rotateObject(ptr); @@ -1080,7 +1079,7 @@ namespace MWWorld pos.z = traced.z; } - moveObject(ptr, *ptr.getCell(), pos.x, pos.y, pos.z); + moveObject(ptr, ptr.getCell(), pos.x, pos.y, pos.z); } void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust) @@ -1091,9 +1090,9 @@ namespace MWWorld adjust); } - MWWorld::Ptr World::safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) + MWWorld::Ptr World::safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos) { - return copyObjectToCell(ptr,Cell,pos,false); + return copyObjectToCell(ptr,cell,pos,false); } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const @@ -1127,6 +1126,7 @@ namespace MWWorld { processDoors(duration); + moveMagicBolts(duration); moveProjectiles(duration); const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration); @@ -1506,15 +1506,7 @@ namespace MWWorld if (!result.first) return false; - CellStore* cell; - if (isCellExterior()) - { - int cellX, cellY; - positionToIndex(result.second[0], result.second[1], cellX, cellY); - cell = mCells.getExterior(cellX, cellY); - } - else - cell = getPlayerPtr().getCell(); + CellStore* cell = getPlayerPtr().getCell(); ESM::Position pos = getPlayerPtr().getRefData().getPosition(); pos.pos[0] = result.second[0]; @@ -1527,7 +1519,7 @@ namespace MWWorld // copy the object and set its count int origCount = object.getRefData().getCount(); object.getRefData().setCount(amount); - Ptr dropped = copyObjectToCell(object, *cell, pos, true); + Ptr dropped = copyObjectToCell(object, cell, pos, true); object.getRefData().setCount(origCount); // only the player place items in the world, so no need to check actor @@ -1548,7 +1540,7 @@ namespace MWWorld } - Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, ESM::Position pos, bool adjustPos) + Ptr World::copyObjectToCell(const Ptr &object, CellStore* cell, ESM::Position pos, bool adjustPos) { if (object.getClass().isActor() || adjustPos) { @@ -1560,17 +1552,17 @@ namespace MWWorld } } - if (cell.isExterior()) + if (cell->isExterior()) { int cellX, cellY; positionToIndex(pos.pos[0], pos.pos[1], cellX, cellY); - cell = *mCells.getExterior(cellX, cellY); + cell = mCells.getExterior(cellX, cellY); } MWWorld::Ptr dropped = - MWWorld::Class::get(object).copyToCell(object, cell, pos); + MWWorld::Class::get(object).copyToCell(object, *cell, pos); - if (mWorldScene->isCellActive(cell)) { + if (mWorldScene->isCellActive(*cell)) { if (dropped.getRefData().isEnabled()) { mWorldScene->addObjectToScene(dropped); } @@ -1578,7 +1570,7 @@ namespace MWWorld if (!script.empty()) { mLocalScripts.add(script, dropped); } - addContainerScripts(dropped, &cell); + addContainerScripts(dropped, cell); } return dropped; @@ -1608,7 +1600,7 @@ namespace MWWorld // copy the object and set its count int origCount = object.getRefData().getCount(); object.getRefData().setCount(amount); - Ptr dropped = copyObjectToCell(object, *cell, pos); + Ptr dropped = copyObjectToCell(object, cell, pos); object.getRefData().setCount(origCount); if(actor == mPlayer->getPlayer()) // Only call if dropped by player @@ -2143,7 +2135,37 @@ namespace MWWorld } } - void World::launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, + void World::launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, + const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed) + { + ProjectileState state; + state.mActorHandle = actor.getRefData().getHandle(); + state.mBow = bow; + state.mSpeed = speed; + + MWWorld::ManualRef ref(getStore(), projectile.getCellRef().mRefID); + + ESM::Position pos; + pos.pos[0] = worldPos.x; + pos.pos[1] = worldPos.y; + pos.pos[2] = worldPos.z; + + // Do NOT copy actor rotation! actors use a different rotation order, and this will not produce the same facing direction. + Ogre::Matrix3 mat; + orient.ToRotationMatrix(mat); + Ogre::Radian xr,yr,zr; + mat.ToEulerAnglesXYZ(xr, yr, zr); + pos.rot[0] = -xr.valueRadians(); + pos.rot[1] = -yr.valueRadians(); + pos.rot[2] = -zr.valueRadians(); + + MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), actor.getCell(), pos, false); + ptr.getRefData().setCount(1); + + mProjectiles[ptr] = state; + } + + void World::launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName) { std::string projectileModel; @@ -2185,13 +2207,23 @@ namespace MWWorld pos.pos[0] = actor.getRefData().getPosition().pos[0]; pos.pos[1] = actor.getRefData().getPosition().pos[1]; pos.pos[2] = actor.getRefData().getPosition().pos[2] + height; - pos.rot[0] = actor.getRefData().getPosition().rot[0]; - pos.rot[1] = actor.getRefData().getPosition().rot[1]; - pos.rot[2] = actor.getRefData().getPosition().rot[2]; + + // Do NOT copy rotation directly! actors use a different rotation order, and this will not produce the same facing direction. + Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + Ogre::Matrix3 mat; + orient.ToRotationMatrix(mat); + Ogre::Radian xr,yr,zr; + mat.ToEulerAnglesXYZ(xr, yr, zr); + pos.rot[0] = -xr.valueRadians(); + pos.rot[1] = -yr.valueRadians(); + pos.rot[2] = -zr.valueRadians(); + ref.getPtr().getCellRef().mPos = pos; - MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), *actor.getCell(), pos); + CellStore* cell = actor.getCell(); + MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), cell, pos); - ProjectileState state; + MagicBoltState state; state.mSourceName = sourceName; state.mId = id; state.mActorHandle = actor.getRefData().getHandle(); @@ -2209,7 +2241,7 @@ namespace MWWorld MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - mProjectiles[ptr] = state; + mMagicBolts[ptr] = state; } void World::moveProjectiles(float duration) @@ -2226,13 +2258,100 @@ namespace MWWorld MWWorld::Ptr ptr = it->first; - Ogre::Vector3 rot(ptr.getRefData().getPosition().rot); + Ogre::Quaternion orient = ptr.getRefData().getBaseNode()->getOrientation(); - // TODO: Why -rot.z, but not -rot.x? (note: same issue in MovementSolver::move) - Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(-rot.z), Ogre::Vector3::UNIT_Z); - orient = orient * Ogre::Quaternion(Ogre::Radian(rot.x), Ogre::Vector3::UNIT_X); + float speed = it->second.mSpeed; + Ogre::Vector3 direction = orient.yAxis(); + direction.normalise(); + Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); + Ogre::Vector3 newPos = pos + direction * duration * speed; + + // Check for impact + btVector3 from(pos.x, pos.y, pos.z); + btVector3 to(newPos.x, newPos.y, newPos.z); + std::vector > collisions = mPhysEngine->rayTest2(from, to); + bool hit=false; + + // HACK: query against the shape as well, since the ray does not take the volume into account + // really, this should be a convex cast, but the whole physics system needs a rewrite + std::vector col2 = mPhysEngine->getCollisions(ptr.getRefData().getHandle()); + for (std::vector::iterator ci = col2.begin(); ci != col2.end(); ++ci) + collisions.push_back(std::make_pair(0.f,*ci)); + + for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end() && !hit; ++cIt) + { + MWWorld::Ptr obstacle = searchPtrViaHandle(cIt->second); + if (obstacle == ptr) + continue; + MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); + + // Arrow intersects with player immediately after shooting :/ + if (obstacle == caster) + continue; + + if (caster.isEmpty()) + caster = obstacle; + + if (obstacle.isEmpty()) + { + // Terrain + } + else if (obstacle.getClass().isActor()) + { + // Fargoth + obstacle.getClass().getCreatureStats(obstacle).setHealth(0); + } + hit = true; + } + if (hit) + { + mProjectiles.erase(it++); + continue; + } + + std::string handle = ptr.getRefData().getHandle(); + + moveObject(ptr, newPos.x, newPos.y, newPos.z); + + // HACK: Re-fetch Ptrs if necessary, since the cell might have changed + if (!ptr.getRefData().getCount()) + { + moved[handle] = it->second; + mProjectiles.erase(it++); + } + else + ++it; + } + + // HACK: Re-fetch Ptrs if necessary, since the cell might have changed + for (std::map::iterator it = moved.begin(); it != moved.end(); ++it) + { + MWWorld::Ptr newPtr = searchPtrViaHandle(it->first); + if (newPtr.isEmpty()) // The projectile went into an inactive cell and was deleted + continue; + mProjectiles[getPtrViaHandle(it->first)] = it->second; + } + } + + void World::moveMagicBolts(float duration) + { + std::map moved; + for (std::map::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) + { + if (!mWorldScene->isCellActive(*it->first.getCell())) + { + deleteObject(it->first); + mMagicBolts.erase(it++); + continue; + } + + MWWorld::Ptr ptr = it->first; + + Ogre::Vector3 rot(ptr.getRefData().getPosition().rot); + + Ogre::Quaternion orient = ptr.getRefData().getBaseNode()->getOrientation(); static float fTargetSpellMaxSpeed = getStore().get().find("fTargetSpellMaxSpeed")->getFloat(); float speed = fTargetSpellMaxSpeed * it->second.mSpeed; @@ -2279,7 +2398,7 @@ namespace MWWorld explodeSpell(Ogre::Vector3(ptr.getRefData().getPosition().pos), ptr, it->second.mEffects, caster, it->second.mId, it->second.mSourceName); deleteObject(ptr); - mProjectiles.erase(it++); + mMagicBolts.erase(it++); continue; } @@ -2291,29 +2410,29 @@ namespace MWWorld if (!ptr.getRefData().getCount()) { moved[handle] = it->second; - mProjectiles.erase(it++); + mMagicBolts.erase(it++); } else ++it; } // HACK: Re-fetch Ptrs if necessary, since the cell might have changed - for (std::map::iterator it = moved.begin(); it != moved.end(); ++it) + for (std::map::iterator it = moved.begin(); it != moved.end(); ++it) { MWWorld::Ptr newPtr = searchPtrViaHandle(it->first); if (newPtr.isEmpty()) // The projectile went into an inactive cell and was deleted continue; - mProjectiles[getPtrViaHandle(it->first)] = it->second; + mMagicBolts[getPtrViaHandle(it->first)] = it->second; } } void World::objectLeftActiveCell(Ptr object, Ptr movedPtr) { // For now, projectiles moved to an inactive cell are just deleted, because there's no reliable way to hold on to the meta information + if (mMagicBolts.find(object) != mMagicBolts.end()) + deleteObject(movedPtr); if (mProjectiles.find(object) != mProjectiles.end()) - { deleteObject(movedPtr); - } } const std::vector& World::getContentFiles() const @@ -2650,7 +2769,7 @@ namespace MWWorld MWWorld::ManualRef ref(getStore(), selectedCreature, 1); ref.getPtr().getCellRef().mPos = ipos; - safePlaceObject(ref.getPtr(),*cell,ipos); + safePlaceObject(ref.getPtr(), cell, ipos); } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 036cafe2d..40fe4c96a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -90,7 +90,7 @@ namespace MWWorld std::map mDoorStates; ///< only holds doors that are currently moving. 0 means closing, 1 opening - struct ProjectileState + struct MagicBoltState { // Id of spell or enchantment to apply when it hits std::string mId; @@ -108,6 +108,17 @@ namespace MWWorld bool mStack; }; + struct ProjectileState + { + // Actor who shot this projectile + std::string mActorHandle; + + MWWorld::Ptr mBow; // bow or crossbow the projectile was fired from + + float mSpeed; + }; + + std::map mMagicBolts; std::map mProjectiles; void updateWeather(float duration); int getDaysPerMonth (int month) const; @@ -117,7 +128,7 @@ namespace MWWorld bool moveObjectImp (const Ptr& ptr, float x, float y, float z); ///< @return true if the active cell (cell player is in) changed - Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, ESM::Position pos, bool adjustPos=true); + Ptr copyObjectToCell(const Ptr &ptr, CellStore* cell, ESM::Position pos, bool adjustPos=true); void updateWindowManager (); void performUpdateSceneQueries (); @@ -134,6 +145,7 @@ namespace MWWorld void processDoors(float duration); ///< Run physics simulation and modify \a world accordingly. + void moveMagicBolts(float duration); void moveProjectiles(float duration); void doPhysics(float duration); @@ -341,7 +353,7 @@ namespace MWWorld virtual void deleteObject (const Ptr& ptr); virtual void moveObject (const Ptr& ptr, float x, float y, float z); - virtual void moveObject (const Ptr& ptr, CellStore &newCell, float x, float y, float z); + virtual void moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z); virtual void scaleObject (const Ptr& ptr, float scale); @@ -351,7 +363,7 @@ namespace MWWorld virtual void localRotateObject (const Ptr& ptr, float x, float y, float z); - virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos); + virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos); ///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr. virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) @@ -552,8 +564,10 @@ namespace MWWorld */ virtual void castSpell (const MWWorld::Ptr& actor); - virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, + virtual void launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName); + virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile, + const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed); virtual const std::vector& getContentFiles() const; diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index f124abb99..4484d9862 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -597,6 +597,8 @@ namespace Physic std::vector PhysicEngine::getCollisions(const std::string& name) { RigidBody* body = getRigidBody(name); + if (!body) // fall back to raycasting body if there is no collision body + body = getRigidBody(name, true); ContactTestResultCallback callback; dynamicsWorld->contactTest(body, callback); return callback.mResult; From 072dc6d4383638421fbb271fa5b8fafffd36fc13 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 8 Mar 2014 05:51:47 +0100 Subject: [PATCH 091/114] Feature #50: Implement marksman mechanics. --- apps/openmw/mwclass/creature.cpp | 13 +-- apps/openmw/mwclass/npc.cpp | 16 +--- apps/openmw/mwmechanics/combat.cpp | 113 +++++++++++++++++++++++++- apps/openmw/mwmechanics/combat.hpp | 8 ++ apps/openmw/mwrender/npcanimation.cpp | 31 ++++++- apps/openmw/mwworld/worldimp.cpp | 25 +++--- apps/openmw/mwworld/worldimp.hpp | 2 +- 7 files changed, 171 insertions(+), 37 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 2939a5431..33c4390a0 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -7,6 +7,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/movement.hpp" +#include "../mwmechanics/disease.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwbase/environment.hpp" @@ -243,15 +244,7 @@ namespace MWClass Ogre::Vector3 hitPosition = result.second; - MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim); - const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); - float hitchance = ref->mBase->mData.mCombat + - (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + - (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); - hitchance *= stats.getFatigueTerm(); - hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude - - mageffects.get(ESM::MagicEffect::Blind).mMagnitude; - hitchance -= otherstats.getEvasion(); + float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat); if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) { @@ -334,6 +327,8 @@ namespace MWClass if (damage > 0) MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); + MWMechanics::diseaseContact(victim, ptr); + victim.getClass().onHit(victim, damage, true, weapon, ptr, true); } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 20c7d9a0e..50e002113 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -498,15 +498,7 @@ namespace MWClass if(!weapon.isEmpty()) weapskill = get(weapon).getEquipmentSkill(weapon); - MWMechanics::NpcStats &stats = getNpcStats(ptr); - const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); - float hitchance = stats.getSkill(weapskill).getModified() + - (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + - (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); - hitchance *= stats.getFatigueTerm(); - hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude - - mageffects.get(ESM::MagicEffect::Blind).mMagnitude; - hitchance -= otherstats.getEvasion(); + float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill)); if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f) { @@ -516,6 +508,7 @@ namespace MWClass bool healthdmg; float damage = 0.0f; + MWMechanics::NpcStats &stats = getNpcStats(ptr); if(!weapon.isEmpty()) { const bool weaphashealth = get(weapon).hasItemHealth(weapon); @@ -615,6 +608,8 @@ namespace MWClass if (healthdmg && damage > 0) MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); + MWMechanics::diseaseContact(victim, ptr); + othercls.onHit(victim, damage, healthdmg, weapon, ptr, true); } @@ -648,9 +643,6 @@ namespace MWClass ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); } - if (!attacker.isEmpty()) - MWMechanics::diseaseContact(ptr, attacker); - if (damage > 0.0f && !object.isEmpty()) MWMechanics::resistNormalWeapon(ptr, attacker, object, damage); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 6aeb90dad..cdc12e210 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -4,9 +4,12 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/movement.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" @@ -17,14 +20,30 @@ namespace { -Ogre::Radian signedAngle(Ogre::Vector3 v1, Ogre::Vector3 v2, Ogre::Vector3 n) +Ogre::Radian signedAngle(Ogre::Vector3 v1, Ogre::Vector3 v2, Ogre::Vector3 normal) { return Ogre::Math::ATan2( - n.dotProduct( v1.crossProduct(v2) ), + normal.dotProduct( v1.crossProduct(v2) ), v1.dotProduct(v2) ); } +void applyEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const Ogre::Vector3& hitPosition) +{ + std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : ""; + if (!enchantmentName.empty()) + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + enchantmentName); + if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + { + MWMechanics::CastSpell cast(attacker, victim); + cast.mHitPosition = hitPosition; + cast.cast(object); + } + } +} + } namespace MWMechanics @@ -135,4 +154,94 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}"); } + void projectileHit(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, MWWorld::Ptr weapon, const MWWorld::Ptr &projectile, + const Ogre::Vector3& hitPosition) + { + MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Store &gmst = world->getStore().get(); + + MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); + + const MWWorld::Class &othercls = victim.getClass(); + if(!othercls.isActor()) // Can't hit non-actors + return; + MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim); + if(otherstats.isDead()) // Can't hit dead actors + return; + + if(attacker.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->setEnemy(victim); + + int weapskill = ESM::Skill::Marksman; + if(!weapon.isEmpty()) + weapskill = weapon.getClass().getEquipmentSkill(weapon); + + float skillValue = attacker.getClass().getSkill(attacker, + weapon.getClass().getEquipmentSkill(weapon)); + + if((::rand()/(RAND_MAX+1.0)) > getHitChance(attacker, victim, skillValue)/100.0f) + { + victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false); + return; + } + + float damage = 0.0f; + + float fDamageStrengthBase = gmst.find("fDamageStrengthBase")->getFloat(); + float fDamageStrengthMult = gmst.find("fDamageStrengthMult")->getFloat(); + + const unsigned char* attack = weapon.get()->mBase->mData.mChop; + damage = attack[0] + ((attack[1]-attack[0])*attackerStats.getAttackStrength()); // Bow/crossbow damage + if (weapon != projectile) + { + // Arrow/bolt damage + attack = projectile.get()->mBase->mData.mChop; + damage += attack[0] + ((attack[1]-attack[0])*attackerStats.getAttackStrength()); + } + + damage *= fDamageStrengthBase + + (attackerStats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult * 0.1); + + if(attacker.getRefData().getHandle() == "player") + attacker.getClass().skillUsageSucceeded(attacker, weapskill, 0); + + bool detected = MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim); + if(!detected) + { + damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat(); + MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); + MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); + } + if (victim.getClass().getCreatureStats(victim).getKnockedDown()) + damage *= gmst.find("fCombatKODamageMult")->getFloat(); + + // Apply "On hit" effect of the weapon + applyEnchantment(attacker, victim, weapon, hitPosition); + if (weapon != projectile) + applyEnchantment(attacker, victim, projectile, hitPosition); + + if (damage > 0) + MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); + + float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); + if ((::rand()/(RAND_MAX+1.0)) < fProjectileThrownStoreChance/100.f) + victim.getClass().getContainerStore(victim).add(projectile, 1, victim); + + victim.getClass().onHit(victim, damage, true, projectile, attacker, true); + } + + float getHitChance(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, int skillValue) + { + MWMechanics::CreatureStats &stats = attacker.getClass().getCreatureStats(attacker); + const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects(); + float hitchance = skillValue + + (stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + + (stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f); + hitchance *= stats.getFatigueTerm(); + hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude - + mageffects.get(ESM::MagicEffect::Blind).mMagnitude; + hitchance -= victim.getClass().getCreatureStats(victim).getEvasion(); + return hitchance; + } + } diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index 7f2415697..bc58227bf 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -2,6 +2,7 @@ #define OPENMW_MECHANICS_COMBAT_H #include "../mwworld/ptr.hpp" +#include namespace MWMechanics { @@ -11,6 +12,13 @@ bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker void resistNormalWeapon (const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage); +/// @note for a thrown weapon, \a weapon == \a projectile, for bows/crossbows, \a projectile is the arrow/bolt +void projectileHit (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile, + const Ogre::Vector3& hitPosition); + +/// Get the chance (in percent) for \a attacker to successfully hit \a victim with a given weapon skill value +float getHitChance (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, int skillValue); + } #endif diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 6169b6ddb..8f8e65cd0 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -779,6 +779,23 @@ void NpcAnimation::releaseArrow() Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(mPtr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * Ogre::Quaternion(Ogre::Radian(mPtr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + + // Reduce fatigue + // somewhat of a guess, but using the weapon weight makes sense + const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat(); + const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat(); + const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat(); + MWMechanics::CreatureStats& attackerStats = mPtr.getClass().getCreatureStats(mPtr); + MWMechanics::DynamicStat fatigue = attackerStats.getFatigue(); + const float normalizedEncumbrance = mPtr.getClass().getEncumbrance(mPtr) / mPtr.getClass().getCapacity(mPtr); + float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; + if (!weapon->isEmpty()) + fatigueLoss += weapon->getClass().getWeight(*weapon) * attackerStats.getAttackStrength() * fWeaponFatigueMult; + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); + attackerStats.setFatigue(fatigue); + if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) { // Thrown weapons get detached now @@ -795,7 +812,12 @@ void NpcAnimation::releaseArrow() launchPos = objects->mEntities[0]->getParentNode()->_getDerivedPosition(); } - MWBase::Environment::get().getWorld()->launchProjectile(mPtr, *weapon, launchPos, orient, *weapon, 400); + float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->getFloat(); + float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat(); + float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * + mPtr.getClass().getCreatureStats(mPtr).getAttackStrength(); + + MWBase::Environment::get().getWorld()->launchProjectile(mPtr, *weapon, launchPos, orient, *weapon, speed); showWeapons(false); @@ -819,8 +841,11 @@ void NpcAnimation::releaseArrow() launchPos = mAmmunition->mEntities[0]->getParentNode()->_getDerivedPosition(); } - /// \todo speed - MWBase::Environment::get().getWorld()->launchProjectile(mPtr, *ammo, launchPos, orient, *weapon, 400); + float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->getFloat(); + float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); + float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * mPtr.getClass().getCreatureStats(mPtr).getAttackStrength(); + + MWBase::Environment::get().getWorld()->launchProjectile(mPtr, *ammo, launchPos, orient, *weapon, speed); inv.remove(*ammo, 1, mPtr); mAmmunition.setNull(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0ef50c582..62ef09ae4 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -28,7 +28,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/levelledlist.hpp" - +#include "../mwmechanics/combat.hpp" #include "../mwrender/sky.hpp" #include "../mwrender/animation.hpp" @@ -2141,7 +2141,7 @@ namespace MWWorld ProjectileState state; state.mActorHandle = actor.getRefData().getHandle(); state.mBow = bow; - state.mSpeed = speed; + state.mVelocity = orient.yAxis() * speed; MWWorld::ManualRef ref(getStore(), projectile.getCellRef().mRefID); @@ -2258,14 +2258,19 @@ namespace MWWorld MWWorld::Ptr ptr = it->first; - Ogre::Quaternion orient = ptr.getRefData().getBaseNode()->getOrientation(); + // gravity constant - must be way lower than the gravity affecting actors, since we're not + // simulating aerodynamics at all + it->second.mVelocity -= Ogre::Vector3(0, 0, 627.2f * 0.1f) * duration; - float speed = it->second.mSpeed; - - Ogre::Vector3 direction = orient.yAxis(); - direction.normalise(); Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); - Ogre::Vector3 newPos = pos + direction * duration * speed; + Ogre::Vector3 newPos = pos + it->second.mVelocity * duration; + + Ogre::Quaternion orient = Ogre::Vector3::UNIT_Y.getRotationTo(it->second.mVelocity); + Ogre::Matrix3 mat; + orient.ToRotationMatrix(mat); + Ogre::Radian xr,yr,zr; + mat.ToEulerAnglesXYZ(xr, yr, zr); + rotateObject(ptr, -xr.valueDegrees(), -yr.valueDegrees(), -zr.valueDegrees()); // Check for impact btVector3 from(pos.x, pos.y, pos.z); @@ -2300,13 +2305,13 @@ namespace MWWorld } else if (obstacle.getClass().isActor()) { - // Fargoth - obstacle.getClass().getCreatureStats(obstacle).setHealth(0); + MWMechanics::projectileHit(caster, obstacle, it->second.mBow, ptr, pos + (newPos - pos) * cIt->first); } hit = true; } if (hit) { + deleteObject(ptr); mProjectiles.erase(it++); continue; } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 40fe4c96a..b694e00f7 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -115,7 +115,7 @@ namespace MWWorld MWWorld::Ptr mBow; // bow or crossbow the projectile was fired from - float mSpeed; + Ogre::Vector3 mVelocity; }; std::map mMagicBolts; From 72a3c50eb87914bee5794f30fb58a41060401c34 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sat, 8 Mar 2014 13:45:54 -0500 Subject: [PATCH 092/114] (#1191) Disallow picking up if inventory disabled Check if window manager has allowed the inventory window if not, then items should not be possible to pick up --- apps/openmw/mwgui/inventorywindow.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 2ea09db61..5fa1d3bb1 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -514,6 +514,9 @@ namespace MWGui void InventoryWindow::pickUpObject (MWWorld::Ptr object) { + // If the inventory is not yet enabled, don't pick anything up + if (!MWBase::Environment::get().getWindowManager()->isAllowed(GW_Inventory)) + return; // make sure the object is of a type that can be picked up std::string type = object.getTypeName(); if ( (type != typeid(ESM::Apparatus).name()) From b8ca067730907cb31e69cee23c7546dcac84ed64 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 9 Mar 2014 03:21:34 +0100 Subject: [PATCH 093/114] Small fix for terrain --- components/terrain/quadtreenode.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 40a8baaf0..4461e2b02 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -357,6 +357,7 @@ bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos) if (!childrenLoaded) { + mChunk->setVisible(true); // Make sure child scene nodes are detached until all children are loaded mSceneNode->removeAllChildren(); } From 6eaa7553f8367233526e41be709e62070e0f1c85 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 9 Mar 2014 03:34:49 +0100 Subject: [PATCH 094/114] Fixes #1181: Enable controls when loading a savegame --- apps/openmw/mwbase/inputmanager.hpp | 3 +++ apps/openmw/mwinput/inputmanagerimp.cpp | 7 +++++++ apps/openmw/mwinput/inputmanagerimp.hpp | 3 +++ apps/openmw/mwstate/statemanagerimp.cpp | 2 ++ 4 files changed, 15 insertions(+) diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index 8293cbfa7..42922a5b3 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -20,6 +20,9 @@ namespace MWBase InputManager() {} + /// Clear all savegame-specific data + virtual void clear() = 0; + virtual ~InputManager() {} virtual void update(float dt, bool loading) = 0; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 30cefe2df..4bfd3f465 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -143,6 +143,13 @@ namespace MWInput mControlSwitch["vanitymode"] = true; } + void InputManager::clear() + { + // Enable all controls + for (std::map::iterator it = mControlSwitch.begin(); it != mControlSwitch.end(); ++it) + it->second = true; + } + InputManager::~InputManager() { mInputBinder->save (mUserFile); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index bd3f4954b..2eab03a34 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -65,6 +65,9 @@ namespace MWInput virtual ~InputManager(); + /// Clear all savegame-specific data + virtual void clear(); + virtual void update(float dt, bool loading); void setPlayer (MWWorld::Player* player) { mPlayer = player; } diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 8b01bacdf..05e928937 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -20,6 +20,7 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/inputmanager.hpp" #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" @@ -39,6 +40,7 @@ void MWState::StateManager::cleanup (bool force) MWBase::Environment::get().getScriptManager()->getGlobalScripts().clear(); MWBase::Environment::get().getWorld()->clear(); MWBase::Environment::get().getWindowManager()->clear(); + MWBase::Environment::get().getInputManager()->clear(); mState = State_NoGame; mCharacterManager.clearCurrentCharacter(); From a9dcc9097060da54a140616dc673763b46971059 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 9 Mar 2014 11:59:23 +0100 Subject: [PATCH 095/114] Another terrain fix --- components/terrain/quadtreenode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 4461e2b02..21c1becb0 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -438,7 +438,7 @@ void QuadTreeNode::unload(bool recursive) if (recursive && hasChildren()) { for (int i=0; i<4; ++i) - mChildren[i]->unload(); + mChildren[i]->unload(true); } } From 06e02ed77f231036192baddff9d79cc602ac8dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Sedej?= Date: Wed, 12 Mar 2014 10:53:56 +0100 Subject: [PATCH 096/114] Added version and revision number to mainmenu --- apps/openmw/mwgui/mainmenu.cpp | 10 ++++++++++ apps/openmw/mwgui/mainmenu.hpp | 1 + files/mygui/openmw_mainmenu.layout | 6 +++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index da1992474..2788c8f5c 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -1,5 +1,7 @@ #include "mainmenu.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -20,6 +22,14 @@ namespace MWGui , mButtonBox(0), mWidth (w), mHeight (h) , mSaveGameDialog(NULL) { + getWidget(mVersionText, "VersionText"); + std::string rev = OPENMW_VERSION_COMMITHASH; + rev = rev.substr(0,10); + std::stringstream sstream; + sstream << "OpenMW version: " << OPENMW_VERSION << "\nrevision: " << rev; + std::string output = sstream.str(); + mVersionText->setCaptionWithReplacing(output); + updateMenu(); } diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index 6d52f26d5..a9453b2c8 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -24,6 +24,7 @@ namespace MWGui private: MyGUI::Widget* mButtonBox; + MyGUI::TextBox* mVersionText; std::map mButtons; diff --git a/files/mygui/openmw_mainmenu.layout b/files/mygui/openmw_mainmenu.layout index 4479a121f..2dc43f1b7 100644 --- a/files/mygui/openmw_mainmenu.layout +++ b/files/mygui/openmw_mainmenu.layout @@ -2,5 +2,9 @@ - + + + + + From 0cd40294a24cff680eb695c23d54021bd8be807f Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 12 Mar 2014 11:30:44 +0100 Subject: [PATCH 097/114] Fixed ranged combat for creatures --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/creatureanimation.cpp | 51 ++++++- apps/openmw/mwrender/creatureanimation.hpp | 21 ++- apps/openmw/mwrender/npcanimation.cpp | 146 +++---------------- apps/openmw/mwrender/npcanimation.hpp | 27 +--- apps/openmw/mwrender/weaponanimation.cpp | 159 +++++++++++++++++++++ apps/openmw/mwrender/weaponanimation.hpp | 56 ++++++++ 7 files changed, 306 insertions(+), 156 deletions(-) create mode 100644 apps/openmw/mwrender/weaponanimation.cpp create mode 100644 apps/openmw/mwrender/weaponanimation.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index eb502de38..5e108edaf 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,7 +15,7 @@ add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation actors objects renderinginterface localmap occlusionquery water shadows characterpreview globalmap videoplayer ripplesimulation refraction - terrainstorage renderconst effectmanager + terrainstorage renderconst effectmanager weaponanimation ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 8ef584154..0eb883953 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -55,6 +55,8 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr) updateParts(); } + + mWeaponAnimationTime = Ogre::SharedPtr(new WeaponAnimationTime(this)); } void CreatureWeaponAnimation::showWeapons(bool showWeapon) @@ -110,6 +112,20 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo setRenderProperties(scene, RV_Actors, RQG_Main, RQG_Alpha, 0, !item.getClass().getEnchantment(item).empty(), &glowColor); + // Crossbows start out with a bolt attached + if (slot == MWWorld::InventoryStore::Slot_CarriedRight && + item.getTypeName() == typeid(ESM::Weapon).name() && + item.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) + { + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) + attachArrow(); + else + mAmmunition.setNull(); + } + else + mAmmunition.setNull(); + if(scene->mSkelBase) { Ogre::SkeletonInstance *skel = scene->mSkelBase->getSkeleton(); @@ -133,15 +149,42 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo updateSkeletonInstance(mSkelBase->getSkeleton(), skel); } - // TODO: - // type == ESM::PRT_Weapon should get an animation source based on the current offset - // of the weapon attack animation (from its beginning, or start marker?) std::vector >::iterator ctrl(scene->mControllers.begin()); for(;ctrl != scene->mControllers.end();ctrl++) { if(ctrl->getSource().isNull()) - ctrl->setSource(Ogre::SharedPtr(new NullAnimationTime())); + { + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + ctrl->setSource(mWeaponAnimationTime); + else + ctrl->setSource(Ogre::SharedPtr(new NullAnimationTime())); + } } } +void CreatureWeaponAnimation::configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) +{ + Ogre::Vector3 glowColor = getEnchantmentColor(ptr); + + setRenderProperties(object, RV_Actors, RQG_Main, RQG_Alpha, 0, + !ptr.getClass().getEnchantment(ptr).empty(), &glowColor); +} + +void CreatureWeaponAnimation::attachArrow() +{ + WeaponAnimation::attachArrow(mPtr); +} + +void CreatureWeaponAnimation::releaseArrow() +{ + WeaponAnimation::releaseArrow(mPtr); +} + +Ogre::Vector3 CreatureWeaponAnimation::runAnimation(float duration) +{ + Ogre::Vector3 ret = Animation::runAnimation(duration); + pitchSkeleton(mPtr.getRefData().getPosition().rot[0], mSkelBase->getSkeleton()); + return ret; +} + } diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index 37826673d..d6cd8a517 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -2,6 +2,7 @@ #define GAME_RENDER_CREATUREANIMATION_H #include "animation.hpp" +#include "weaponanimation.hpp" #include "../mwworld/inventorystore.hpp" namespace MWWorld @@ -21,7 +22,7 @@ namespace MWRender // For creatures with weapons and shields // Animation is already virtual anyway, so might as well make a separate class. // Most creatures don't need weapons/shields, so this will save some memory. - class CreatureWeaponAnimation : public Animation, public MWWorld::InventoryStoreListener + class CreatureWeaponAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: CreatureWeaponAnimation(const MWWorld::Ptr& ptr); @@ -36,11 +37,29 @@ namespace MWRender void updatePart(NifOgre::ObjectScenePtr& scene, int slot); + virtual void attachArrow(); + virtual void releaseArrow(); + + virtual Ogre::Vector3 runAnimation(float duration); + + /// A relative factor (0-1) that decides if and how much the skeleton should be pitched + /// to indicate the facing orientation of the character. + virtual void setPitchFactor(float factor) { mPitchFactor = factor; } + + virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); } + + // WeaponAnimation + virtual NifOgre::ObjectScenePtr getWeapon() { return mWeapon; } + virtual void showWeapon(bool show) { showWeapons(show); } + virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot); + private: NifOgre::ObjectScenePtr mWeapon; NifOgre::ObjectScenePtr mShield; bool mShowWeapons; bool mShowCarriedLeft; + + Ogre::SharedPtr mWeaponAnimationTime; }; } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 8f8e65cd0..a09a58191 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -76,27 +76,6 @@ float HeadAnimationTime::getValue() const return 1; } -float WeaponAnimationTime::getValue() const -{ - if (mWeaponGroup.empty()) - return 0; - float current = mAnimation->getCurrentTime(mWeaponGroup); - if (current == -1) - return 0; - return current - mStartTime; -} - -void WeaponAnimationTime::setGroup(const std::string &group) -{ - mWeaponGroup = group; - mStartTime = mAnimation->getStartTime(mWeaponGroup); -} - -void WeaponAnimationTime::updateStartTime() -{ - setGroup(mWeaponGroup); -} - static NpcAnimation::PartBoneMap createPartListMap() { NpcAnimation::PartBoneMap result; @@ -147,8 +126,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v mShowCarriedLeft(true), mFirstPersonOffset(0.f, 0.f, 0.f), mAlpha(1.f), - mNpcType(Type_Normal), - mPitchFactor(0) + mNpcType(Type_Normal) { mNpc = mPtr.get()->mBase; @@ -538,14 +516,10 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) // updateSkeletonInstance, below, touches the hands. node->translate(mFirstPersonOffset, Ogre::Node::TS_WORLD); } - else if (mPitchFactor > 0) + else { // In third person mode we may still need pitch for ranged weapon targeting - float pitch = mPtr.getRefData().getPosition().rot[0] * mPitchFactor; - Ogre::Node *node = baseinst->getBone("Bip01 Spine2"); - node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); - node = baseinst->getBone("Bip01 Spine1"); - node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); + pitchSkeleton(mPtr.getRefData().getPosition().rot[0], baseinst); } mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame. @@ -695,13 +669,14 @@ void NpcAnimation::showWeapons(bool showWeapon) { MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon != inv.end()) // special case for weapons + if(weapon != inv.end()) { Ogre::Vector3 glowColor = getEnchantmentColor(*weapon); std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon); addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); + // Crossbows start out with a bolt attached if (weapon->getTypeName() == typeid(ESM::Weapon).name() && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { @@ -743,113 +718,24 @@ void NpcAnimation::showCarriedLeft(bool show) removeIndividualPart(ESM::PRT_Shield); } -void NpcAnimation::attachArrow() +void NpcAnimation::configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) { - MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weaponSlot != inv.end() && weaponSlot->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) - showWeapons(true); - else - { - NifOgre::ObjectScenePtr weapon = mObjectParts[ESM::PRT_Weapon]; - - MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo == inv.end()) - return; - std::string model = ammo->getClass().getModel(*ammo); + Ogre::Vector3 glowColor = getEnchantmentColor(ptr); + setRenderProperties(object, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, + !ptr.getClass().getEnchantment(ptr).empty(), &glowColor); - mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", mInsert, model); - Ogre::Vector3 glowColor = getEnchantmentColor(*ammo); - setRenderProperties(mAmmunition, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, - !ammo->getClass().getEnchantment(*ammo).empty(), &glowColor); + std::for_each(object->mEntities.begin(), object->mEntities.end(), SetObjectGroup(slot)); + std::for_each(object->mParticles.begin(), object->mParticles.end(), SetObjectGroup(slot)); +} - std::for_each(mAmmunition->mEntities.begin(), mAmmunition->mEntities.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition)); - std::for_each(mAmmunition->mParticles.begin(), mAmmunition->mParticles.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition)); - } +void NpcAnimation::attachArrow() +{ + WeaponAnimation::attachArrow(mPtr); } void NpcAnimation::releaseArrow() { - MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weapon == inv.end()) - return; - - // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise. - Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(mPtr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(mPtr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); - - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); - - // Reduce fatigue - // somewhat of a guess, but using the weapon weight makes sense - const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat(); - const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat(); - const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat(); - MWMechanics::CreatureStats& attackerStats = mPtr.getClass().getCreatureStats(mPtr); - MWMechanics::DynamicStat fatigue = attackerStats.getFatigue(); - const float normalizedEncumbrance = mPtr.getClass().getEncumbrance(mPtr) / mPtr.getClass().getCapacity(mPtr); - float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; - if (!weapon->isEmpty()) - fatigueLoss += weapon->getClass().getWeight(*weapon) * attackerStats.getAttackStrength() * fWeaponFatigueMult; - fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); - attackerStats.setFatigue(fatigue); - - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) - { - // Thrown weapons get detached now - NifOgre::ObjectScenePtr objects = mObjectParts[ESM::PRT_Weapon]; - - Ogre::Vector3 launchPos(0,0,0); - if (objects->mSkelBase) - { - launchPos = objects->mSkelBase->getParentNode()->_getDerivedPosition(); - } - else if (objects->mEntities.size()) - { - objects->mEntities[0]->getParentNode()->needUpdate(true); - launchPos = objects->mEntities[0]->getParentNode()->_getDerivedPosition(); - } - - float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->getFloat(); - float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat(); - float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * - mPtr.getClass().getCreatureStats(mPtr).getAttackStrength(); - - MWBase::Environment::get().getWorld()->launchProjectile(mPtr, *weapon, launchPos, orient, *weapon, speed); - - showWeapons(false); - - inv.remove(*weapon, 1, mPtr); - } - else - { - // With bows and crossbows only the used arrow/bolt gets detached - MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo == inv.end()) - return; - - Ogre::Vector3 launchPos(0,0,0); - if (mAmmunition->mSkelBase) - { - launchPos = mAmmunition->mSkelBase->getParentNode()->_getDerivedPosition(); - } - else if (mAmmunition->mEntities.size()) - { - mAmmunition->mEntities[0]->getParentNode()->needUpdate(true); - launchPos = mAmmunition->mEntities[0]->getParentNode()->_getDerivedPosition(); - } - - float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->getFloat(); - float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); - float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * mPtr.getClass().getCreatureStats(mPtr).getAttackStrength(); - - MWBase::Environment::get().getWorld()->launchProjectile(mPtr, *ammo, launchPos, orient, *weapon, speed); - - inv.remove(*ammo, 1, mPtr); - mAmmunition.setNull(); - } + WeaponAnimation::releaseArrow(mPtr); } void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 725fde01d..8ec46facd 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -5,6 +5,8 @@ #include "../mwworld/inventorystore.hpp" +#include "weaponanimation.hpp" + namespace ESM { struct NPC; @@ -25,24 +27,7 @@ public: { } }; -class WeaponAnimationTime : public Ogre::ControllerValue -{ -private: - Animation* mAnimation; - std::string mWeaponGroup; - float mStartTime; -public: - WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {} - void setGroup(const std::string& group); - void updateStartTime(); - - virtual Ogre::Real getValue() const; - virtual void setValue(Ogre::Real value) - { } -}; - - -class NpcAnimation : public Animation, public MWWorld::InventoryStoreListener +class NpcAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: virtual void equipmentChanged() { updateParts(); } @@ -91,7 +76,6 @@ private: Ogre::SharedPtr mWeaponAnimationTime; float mAlpha; - float mPitchFactor; void updateNpcBase(); @@ -138,7 +122,10 @@ public: virtual void attachArrow(); virtual void releaseArrow(); - NifOgre::ObjectScenePtr mAmmunition; + // WeaponAnimation + virtual NifOgre::ObjectScenePtr getWeapon() { return mObjectParts[ESM::PRT_Weapon]; } + virtual void showWeapon(bool show) { showWeapons(show); } + virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot); void setViewMode(ViewMode viewMode); diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp new file mode 100644 index 000000000..5f953bd83 --- /dev/null +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -0,0 +1,159 @@ +#include "weaponanimation.hpp" + +#include +#include +#include +#include + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "../mwmechanics/creaturestats.hpp" + +#include "animation.hpp" + +namespace MWRender +{ + +float WeaponAnimationTime::getValue() const +{ + if (mWeaponGroup.empty()) + return 0; + float current = mAnimation->getCurrentTime(mWeaponGroup); + if (current == -1) + return 0; + return current - mStartTime; +} + +void WeaponAnimationTime::setGroup(const std::string &group) +{ + mWeaponGroup = group; + mStartTime = mAnimation->getStartTime(mWeaponGroup); +} + +void WeaponAnimationTime::updateStartTime() +{ + setGroup(mWeaponGroup); +} + +void WeaponAnimation::attachArrow(MWWorld::Ptr actor) +{ + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weaponSlot != inv.end() && weaponSlot->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + showWeapon(true); + else + { + NifOgre::ObjectScenePtr weapon = getWeapon(); + + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + std::string model = ammo->getClass().getModel(*ammo); + + mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", weapon->mSkelBase->getParentSceneNode(), model); + configureAddedObject(mAmmunition, *ammo, MWWorld::InventoryStore::Slot_Ammunition); + } +} + +void WeaponAnimation::releaseArrow(MWWorld::Ptr actor) +{ + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon == inv.end()) + return; + + // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise. + Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + + // Reduce fatigue + // somewhat of a guess, but using the weapon weight makes sense + const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat(); + const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat(); + const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat(); + MWMechanics::CreatureStats& attackerStats = actor.getClass().getCreatureStats(actor); + MWMechanics::DynamicStat fatigue = attackerStats.getFatigue(); + const float normalizedEncumbrance = actor.getClass().getEncumbrance(actor) / actor.getClass().getCapacity(actor); + float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; + if (!weapon->isEmpty()) + fatigueLoss += weapon->getClass().getWeight(*weapon) * attackerStats.getAttackStrength() * fWeaponFatigueMult; + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); + attackerStats.setFatigue(fatigue); + + if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + { + // Thrown weapons get detached now + NifOgre::ObjectScenePtr objects = getWeapon(); + + Ogre::Vector3 launchPos(0,0,0); + if (objects->mSkelBase) + { + launchPos = objects->mSkelBase->getParentNode()->_getDerivedPosition(); + } + else if (objects->mEntities.size()) + { + objects->mEntities[0]->getParentNode()->needUpdate(true); + launchPos = objects->mEntities[0]->getParentNode()->_getDerivedPosition(); + } + + float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->getFloat(); + float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat(); + float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * + actor.getClass().getCreatureStats(actor).getAttackStrength(); + + MWBase::Environment::get().getWorld()->launchProjectile(actor, *weapon, launchPos, orient, *weapon, speed); + + showWeapon(false); + + inv.remove(*weapon, 1, actor); + } + else + { + // With bows and crossbows only the used arrow/bolt gets detached + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + + Ogre::Vector3 launchPos(0,0,0); + if (mAmmunition->mSkelBase) + { + launchPos = mAmmunition->mSkelBase->getParentNode()->_getDerivedPosition(); + } + else if (mAmmunition->mEntities.size()) + { + mAmmunition->mEntities[0]->getParentNode()->needUpdate(true); + launchPos = mAmmunition->mEntities[0]->getParentNode()->_getDerivedPosition(); + } + + float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->getFloat(); + float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); + float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * actor.getClass().getCreatureStats(actor).getAttackStrength(); + + MWBase::Environment::get().getWorld()->launchProjectile(actor, *ammo, launchPos, orient, *weapon, speed); + + inv.remove(*ammo, 1, actor); + mAmmunition.setNull(); + } +} + +void WeaponAnimation::pitchSkeleton(float xrot, Ogre::SkeletonInstance* skel) +{ + if (mPitchFactor == 0) + return; + + float pitch = xrot * mPitchFactor; + Ogre::Node *node = skel->getBone("Bip01 Spine2"); + node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); + node = skel->getBone("Bip01 Spine1"); + node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); +} + +} diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp new file mode 100644 index 000000000..c09aa65d9 --- /dev/null +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -0,0 +1,56 @@ +#ifndef OPENMW_MWRENDER_WEAPONANIMATION_H +#define OPENMW_MWRENDER_WEAPONANIMATION_H + +#include + +#include + +#include "../mwworld/ptr.hpp" + +namespace MWRender +{ + + class Animation; + + class WeaponAnimationTime : public Ogre::ControllerValue + { + private: + Animation* mAnimation; + std::string mWeaponGroup; + float mStartTime; + public: + WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {} + void setGroup(const std::string& group); + void updateStartTime(); + + virtual Ogre::Real getValue() const; + virtual void setValue(Ogre::Real value) + { } + }; + + /// Handles attach & release of projectiles for ranged weapons + class WeaponAnimation + { + public: + WeaponAnimation() : mPitchFactor(0) {} + + virtual void attachArrow(MWWorld::Ptr actor); + virtual void releaseArrow(MWWorld::Ptr actor); + + protected: + NifOgre::ObjectScenePtr mAmmunition; + + virtual NifOgre::ObjectScenePtr getWeapon() = 0; + virtual void showWeapon(bool show) = 0; + virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) = 0; + + /// A relative factor (0-1) that decides if and how much the skeleton should be pitched + /// to indicate the facing orientation of the character, for ranged weapon aiming. + float mPitchFactor; + + void pitchSkeleton(float xrot, Ogre::SkeletonInstance* skel); + }; + +} + +#endif From d72a2f1ffb85d4e416c5bad19b974403c8f6c3ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Sedej?= Date: Wed, 12 Mar 2014 12:33:42 +0100 Subject: [PATCH 098/114] Added code to test if git hash is availible --- apps/openmw/mwgui/mainmenu.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 2788c8f5c..4ad260fd9 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -23,12 +23,20 @@ namespace MWGui , mSaveGameDialog(NULL) { getWidget(mVersionText, "VersionText"); - std::string rev = OPENMW_VERSION_COMMITHASH; - rev = rev.substr(0,10); std::stringstream sstream; - sstream << "OpenMW version: " << OPENMW_VERSION << "\nrevision: " << rev; + sstream << "OpenMW version: " << OPENMW_VERSION; + + // adding info about git hash if availible + std::string rev = OPENMW_VERSION_COMMITHASH; + std::string tag = OPENMW_VERSION_TAGHASH; + if (!rev.empty() && !tag.empty()) + { + rev = rev.substr(0,10); + sstream << "\nrevision: " << rev; + } + std::string output = sstream.str(); - mVersionText->setCaptionWithReplacing(output); + mVersionText->setCaption(output); updateMenu(); } From 43a12fffd58369726bb51bc5a0dfcc349e933b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C5=A1per=20Sedej?= Date: Wed, 12 Mar 2014 14:37:44 +0100 Subject: [PATCH 099/114] indentation issue... --- apps/openmw/mwgui/mainmenu.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index a9453b2c8..48b515d65 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -24,7 +24,7 @@ namespace MWGui private: MyGUI::Widget* mButtonBox; - MyGUI::TextBox* mVersionText; + MyGUI::TextBox* mVersionText; std::map mButtons; From 7bc97fb8b85de38df4d6d15f450a4069137512b2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 13 Mar 2014 13:19:32 +0100 Subject: [PATCH 100/114] reworked code for player positioning on startup and new game --- apps/openmw/engine.cpp | 33 ++----------- apps/openmw/mwbase/world.hpp | 3 +- apps/openmw/mwstate/statemanagerimp.cpp | 5 +- apps/openmw/mwworld/worldimp.cpp | 61 +++++++++++++++++-------- apps/openmw/mwworld/worldimp.hpp | 9 +++- 5 files changed, 57 insertions(+), 54 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 4c3cadb3b..7d0626913 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -404,7 +404,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create the world mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mContentFiles, mResDir, mCfgMgr.getCachePath(), mEncoder, mFallbackMap, - mActivationDistanceOverride)); + mActivationDistanceOverride, mCellName)); MWBase::Environment::get().getWorld()->setupPlayer(); input->setPlayer(&mEnvironment.getWorld()->getPlayer()); @@ -440,31 +440,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mechanics->buildPlayer(); window->updatePlayer(); - // load cell - ESM::Position pos; - MWBase::World *world = MWBase::Environment::get().getWorld(); - - if (!mCellName.empty()) - { - if (world->findExteriorPosition(mCellName, pos)) { - world->changeToExteriorCell (pos); - } - else { - world->findInteriorPosition(mCellName, pos); - world->changeToInteriorCell (mCellName, pos); - } - } - else - { - pos.pos[0] = pos.pos[1] = pos.pos[2] = 0; - pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; - world->changeToExteriorCell (pos); - } - - Ogre::FrameEvent event; - event.timeSinceLastEvent = 0; - event.timeSinceLastFrame = 0; - frameRenderingQueued(event); mOgre->getRoot()->addFrameListener (this); // scripts @@ -502,15 +477,15 @@ void OMW::Engine::go() // Play some good 'ol tunes MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); - if (!mStartupScript.empty()) - MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript); - // start in main menu if (!mSkipMenu) MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); else MWBase::Environment::get().getStateManager()->newGame (true); + if (!mStartupScript.empty()) + MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript); + // Start the main rendering loop while (!mEnvironment.get().getStateManager()->hasQuitRequest()) Ogre::Root::getSingleton().renderOneFrame(); diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 5c2912a8f..bb6f5741d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -101,7 +101,8 @@ namespace MWBase virtual ~World() {} - virtual void startNewGame() = 0; + virtual void startNewGame (bool bypass) = 0; + ///< \param bypass Bypass regular game start. virtual void clear() = 0; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 05e928937..d6309c1c9 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -125,11 +125,10 @@ void MWState::StateManager::newGame (bool bypass) { cleanup(); + MWBase::Environment::get().getWorld()->startNewGame (bypass); + if (!bypass) - { - MWBase::Environment::get().getWorld()->startNewGame(); MWBase::Environment::get().getWindowManager()->setNewGame (true); - } else MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 62ef09ae4..992ecc960 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -121,13 +121,15 @@ namespace MWWorld const Files::Collections& fileCollections, const std::vector& contentFiles, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, - ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, int mActivationDistanceOverride) + ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, + int activationDistanceOverride, const std::string& startCell) : mPlayer (0), mLocalScripts (mStore), mSky (true), mCells (mStore, mEsm), - mActivationDistanceOverride (mActivationDistanceOverride), + mActivationDistanceOverride (activationDistanceOverride), mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), mLevitationEnabled(true), mFacedDistance(FLT_MAX), mGodMode(false), mContentFiles (contentFiles), - mGoToJail(false) + mGoToJail(false), + mStartCell (startCell) { mPhysics = new PhysicsSystem(renderer); mPhysEngine = mPhysics->getEngine(); @@ -168,7 +170,7 @@ namespace MWWorld mWorldScene = new Scene(*mRendering, mPhysics); } - void World::startNewGame() + void World::startNewGame (bool bypass) { mGoToJail = false; mLevitationEnabled = true; @@ -181,23 +183,44 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->updatePlayer(); - // FIXME: this will add cell 0,0 as visible on the global map - ESM::Position pos; - const int cellSize = 8192; - pos.pos[0] = cellSize/2; - pos.pos[1] = cellSize/2; - pos.pos[2] = 0; - pos.rot[0] = 0; - pos.rot[1] = 0; - pos.rot[2] = 0; - mWorldScene->changeToExteriorCell(pos); + if (bypass && !mStartCell.empty()) + { + ESM::Position pos; + + if (findExteriorPosition (mStartCell, pos)) + { + changeToExteriorCell (pos); + } + else + { + findInteriorPosition (mStartCell, pos); + changeToInteriorCell (mStartCell, pos); + } + } + else + { + /// \todo if !bypass, do not add player location to global map for the duration of one + /// frame + ESM::Position pos; + const int cellSize = 8192; + pos.pos[0] = cellSize/2; + pos.pos[1] = cellSize/2; + pos.pos[2] = 0; + pos.rot[0] = 0; + pos.rot[1] = 0; + pos.rot[2] = 0; + mWorldScene->changeToExteriorCell(pos); + } - // FIXME: should be set to 1, but the sound manager won't pause newly started sounds - mPlayIntro = 2; + if (!bypass) + { + // FIXME: should be set to 1, but the sound manager won't pause newly started sounds + mPlayIntro = 2; - // set new game mark - mGlobalVariables["chargenstate"].setInteger (1); - mGlobalVariables["pcrace"].setInteger (3); + // set new game mark + mGlobalVariables["chargenstate"].setInteger (1); + mGlobalVariables["pcrace"].setInteger (3); + } // we don't want old weather to persist on a new game delete mWeatherManager; diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index b694e00f7..42f52cb61 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -120,6 +120,9 @@ namespace MWWorld std::map mMagicBolts; std::map mProjectiles; + + std::string mStartCell; + void updateWeather(float duration); int getDaysPerMonth (int month) const; @@ -179,11 +182,13 @@ namespace MWWorld const Files::Collections& fileCollections, const std::vector& contentFiles, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, - ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, int mActivationDistanceOverride); + ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, + int activationDistanceOverride, const std::string& startCell); virtual ~World(); - virtual void startNewGame(); + virtual void startNewGame (bool bypass); + ///< \param bypass Bypass regular game start. virtual void clear(); From d92740efc9db3ab1569cd89c0e905942ab2f0ba7 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Thu, 13 Mar 2014 23:44:52 +1100 Subject: [PATCH 101/114] Bug #900 fix - only fixed AiWonder, AiCombat, AiTravel and others may need a different strategy to this. --- apps/openmw/mwmechanics/aiwander.cpp | 96 +++++++++++++++++++++++++++- apps/openmw/mwmechanics/aiwander.hpp | 14 ++++ 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 9a95822f5..844eaf490 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -27,6 +27,11 @@ namespace namespace MWMechanics { + // NOTE: determined empherically but probably need further tweaking + static const int COUNT_BEFORE_STUCK = 20; + static const int COUNT_BEFORE_RESET = 200; + static const int COUNT_EVADE = 7; + AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) , mCellX(std::numeric_limits::max()) @@ -36,6 +41,11 @@ namespace MWMechanics , mX(0) , mY(0) , mZ(0) + , mPrevX(0) + , mPrevY(0) + , mWalkState(State_Norm) + , mStuckCount(0) + , mEvadeCount(0) , mSaidGreeting(false) { for(unsigned short counter = 0; counter < mIdle.size(); counter++) @@ -298,9 +308,91 @@ namespace MWMechanics } else { - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + /* 1 n + * State_Norm <---> State_CheckStuck --> State_Evade + * ^ ^ | ^ | ^ | | + * | | | | | | | | + * | +---+ +---+ +---+ | m + * | any < n < m | + * +--------------------------------------------+ + */ + bool samePosition = (abs(pos.pos[0] - mPrevX) < 1) && (abs(pos.pos[1] - mPrevY) < 1); + switch(mWalkState) + { + case State_Norm: + { + if(!samePosition) + break; + else + mWalkState = State_CheckStuck; + } + /* FALL THROUGH */ + case State_CheckStuck: + { + if(!samePosition) + { + mWalkState = State_Norm; + // to do this properly need yet another variable, simply don't clear for now + //mStuckCount = 0; + break; + } + else + { + // consider stuck only if position unchanges consequitively + if((mStuckCount++ % COUNT_BEFORE_STUCK) == 0) + mWalkState = State_Evade; + // NOTE: mStuckCount is purposely not cleared here + else + break; // still in the same state, but counter got incremented + } + } + /* FALL THROUGH */ + case State_Evade: + { + if(mEvadeCount++ < COUNT_EVADE) + break; + else + { + mWalkState = State_Norm; // tried to evade, assume all is ok and start again + // NOTE: mStuckCount is purposely not cleared here + mEvadeCount = 0; + } + } + /* NO DEFAULT CASE */ + } + + if(mWalkState == State_Evade) + { + //std::cout << "Stuck \""<= COUNT_BEFORE_RESET) // something has gone wrong, reset + { + //std::cout << "Reset \""< Date: Fri, 14 Mar 2014 00:09:03 +1100 Subject: [PATCH 102/114] Bug #900 fix - minor update to comments --- apps/openmw/mwmechanics/aiwander.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index a893e257e..6de0b8181 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -44,6 +44,9 @@ namespace MWMechanics float mYCell; // for checking if we're stuck (but don't check Z axis) + float mPrevX; + float mPrevY; + enum WalkState { State_Norm, @@ -52,8 +55,6 @@ namespace MWMechanics }; WalkState mWalkState; - float mPrevX; - float mPrevY; int mStuckCount; int mEvadeCount; From b2e3fa70c2e860dfba3061e6cf4d36e72add3a09 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Fri, 14 Mar 2014 07:04:39 +1100 Subject: [PATCH 103/114] Fix spelling errors in comments. --- apps/openmw/mwmechanics/aiwander.cpp | 4 ++-- apps/openmw/mwscript/transformationextensions.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 844eaf490..a51092fea 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -27,7 +27,7 @@ namespace namespace MWMechanics { - // NOTE: determined empherically but probably need further tweaking + // NOTE: determined empirically but probably need further tweaking static const int COUNT_BEFORE_STUCK = 20; static const int COUNT_BEFORE_RESET = 200; static const int COUNT_EVADE = 7; @@ -338,7 +338,7 @@ namespace MWMechanics } else { - // consider stuck only if position unchanges consequitively + // consider stuck only if position unchanges consecutively if((mStuckCount++ % COUNT_BEFORE_STUCK) == 0) mWalkState = State_Evade; // NOTE: mStuckCount is purposely not cleared here diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 9a9175f19..1efc79643 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -99,7 +99,7 @@ namespace MWScript MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle); } else - throw std::runtime_error ("invalid ration axis: " + axis); + throw std::runtime_error ("invalid rotation axis: " + axis); } }; @@ -128,7 +128,7 @@ namespace MWScript runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[2]).valueDegrees()); } else - throw std::runtime_error ("invalid ration axis: " + axis); + throw std::runtime_error ("invalid rotation axis: " + axis); } }; @@ -157,7 +157,7 @@ namespace MWScript runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees()); } else - throw std::runtime_error ("invalid ration axis: " + axis); + throw std::runtime_error ("invalid rotation axis: " + axis); } }; @@ -186,7 +186,7 @@ namespace MWScript runtime.push(ptr.getRefData().getPosition().pos[2]); } else - throw std::runtime_error ("invalid rotation axis: " + axis); + throw std::runtime_error ("invalid axis: " + axis); } }; From e6977d00e812840dd6d8d8f9dba420db198999c7 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 15 Mar 2014 08:16:35 +1100 Subject: [PATCH 104/114] Oops. Fix typo picked up by Zini. --- apps/openmw/mwmechanics/aiwander.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index a51092fea..4da325abd 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -391,8 +391,8 @@ namespace MWMechanics // update position ESM::Position updatedPos = actor.getRefData().getPosition(); - mPrevX = pos.pos[0]; - mPrevY = pos.pos[1]; + mPrevX = updatedPos.pos[0]; + mPrevY = updatedPos.pos[1]; } } From cd0c283795ae576db6d2f01fed3229576afba9fd Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 15 Mar 2014 15:14:59 +0100 Subject: [PATCH 105/114] Add text shadow to version text for better readability --- files/mygui/openmw_mainmenu.layout | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/mygui/openmw_mainmenu.layout b/files/mygui/openmw_mainmenu.layout index 2dc43f1b7..e8cb23b77 100644 --- a/files/mygui/openmw_mainmenu.layout +++ b/files/mygui/openmw_mainmenu.layout @@ -5,6 +5,8 @@ + + From 3880247017dfb88aa286e32154125d5734068e9c Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 15 Mar 2014 20:28:48 +0100 Subject: [PATCH 106/114] Fixes #1206: effect texture override was not accounted for --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a0d06f068..15d56b8a9 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1073,7 +1073,7 @@ float RenderingManager::getCameraDistance() const void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const Vector3 &worldPosition, float scale) { - mEffectManager->addEffect(model, "", worldPosition, scale); + mEffectManager->addEffect(model, texture, worldPosition, scale); } } // namespace From 93c21b5ef22ab3543b427775bf17a1f0010d6a3b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 15 Mar 2014 20:34:12 +0100 Subject: [PATCH 107/114] Fixes #1197: incorrect mouse wheel step --- apps/openmw/mwgui/race.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 3dff1b7e4..499c1e191 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -41,9 +41,13 @@ namespace MWGui getWidget(mPreviewImage, "PreviewImage"); getWidget(mHeadRotate, "HeadRotate"); - mHeadRotate->setScrollRange(50); - mHeadRotate->setScrollPosition(25); - mHeadRotate->setScrollViewPage(10); + + // Mouse wheel step is hardcoded to 50 in MyGUI 3.2 ("FIXME"). + // Give other steps the same value to accomodate. + mHeadRotate->setScrollRange(1000); + mHeadRotate->setScrollPosition(500); + mHeadRotate->setScrollViewPage(50); + mHeadRotate->setScrollPage(50); mHeadRotate->eventScrollChangePosition += MyGUI::newDelegate(this, &RaceDialog::onHeadRotate); // Set up next/previous buttons @@ -171,9 +175,9 @@ namespace MWGui eventBack(); } - void RaceDialog::onHeadRotate(MyGUI::ScrollBar*, size_t _position) + void RaceDialog::onHeadRotate(MyGUI::ScrollBar* scroll, size_t _position) { - float angle = (float(_position) / 49.f - 0.5) * 3.14 * 2; + float angle = (float(_position) / (scroll->getScrollRange()-1) - 0.5) * 3.14 * 2; float diff = angle - mCurrentAngle; mPreview->update (diff); mPreviewDirty = true; From 61955111f1f6e10af7dc4d45de61cd90a0489548 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 15 Mar 2014 20:48:54 +0100 Subject: [PATCH 108/114] Fixes #1204: Any health value < 1 should show as empty life bar. --- apps/openmw/mwgui/hud.cpp | 17 +++++++++++------ apps/openmw/mwgui/hud.hpp | 2 ++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index a6ad43ce5..06a228a1f 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -604,15 +604,22 @@ namespace MWGui mEffectBox->setPosition((viewSize.width - mEffectBoxBaseRight) - mEffectBox->getWidth() + effectsDx, mEffectBox->getTop()); } + void HUD::updateEnemyHealthBar() + { + MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy); + mEnemyHealth->setProgressRange(100); + // Health is usually cast to int before displaying. Actors die whenever they are < 1 health. + // Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :) + mEnemyHealth->setProgressPosition(int(stats.getHealth().getCurrent()) / stats.getHealth().getModified() * 100); + } + void HUD::update() { mSpellIcons->updateWidgets(mEffectBox, true); if (!mEnemy.isEmpty() && mEnemyHealth->getVisible()) { - MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy); - mEnemyHealth->setProgressRange(100); - mEnemyHealth->setProgressPosition(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100); + updateEnemyHealthBar(); } if (mIsDrowning) @@ -629,9 +636,7 @@ namespace MWGui if (!mEnemyHealth->getVisible()) mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() - MyGUI::IntPoint(0,20)); mEnemyHealth->setVisible(true); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy); - mEnemyHealth->setProgressRange(100); - mEnemyHealth->setProgressPosition(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100); + updateEnemyHealthBar(); } } diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 04206fbc8..6d1ffd03c 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -112,6 +112,8 @@ namespace MWGui void onMagicClicked(MyGUI::Widget* _sender); void onMapClicked(MyGUI::Widget* _sender); + void updateEnemyHealthBar(); + void updatePositions(); }; } From d08394bf787a411e82dd58ca97a0538f52a737dc Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 15 Mar 2014 21:00:25 +0100 Subject: [PATCH 109/114] Fixes #1205: apparently creature and hand-to-hand (health) attacks should also level up armor skill (tested in vanilla) --- apps/openmw/mwclass/npc.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 50e002113..a1d2ef848 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -673,12 +673,7 @@ namespace MWClass else getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? - if(object.isEmpty()) - { - if(ishealth) - damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f); - } - else if(ishealth) + if(ishealth) { // Hit percentages: // cuirass = 30% From 43757efdc4af643aeedc99c43e170b324c062830 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 16 Mar 2014 08:09:14 +1100 Subject: [PATCH 110/114] Feature #1030 - partial fix to stop creatures unable to walk/fly to come out of water. Does not necessarily handle situations where they are already out of water, however. --- apps/openmw/mwclass/creature.cpp | 28 +++++++- apps/openmw/mwclass/creature.hpp | 5 +- apps/openmw/mwworld/class.cpp | 17 ++++- apps/openmw/mwworld/class.hpp | 5 +- apps/openmw/mwworld/physicssystem.cpp | 97 ++++++++++++++++++++------- apps/openmw/mwworld/worldimp.cpp | 2 +- 6 files changed, 122 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 33c4390a0..c66bda09f 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -525,7 +525,7 @@ namespace MWClass float moveSpeed; if(normalizedEncumbrance >= 1.0f) moveSpeed = 0.0f; - else if(isFlying(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 && + else if(canFly(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 && world->isLevitationEnabled())) { float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() + @@ -678,7 +678,15 @@ namespace MWClass return MWWorld::Ptr(&cell.get().insert(*ref), &cell); } - bool Creature::isFlying(const MWWorld::Ptr &ptr) const + bool Creature::isBipedal(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return ref->mBase->mFlags & ESM::Creature::Bipedal; + } + + bool Creature::canFly(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -686,6 +694,22 @@ namespace MWClass return ref->mBase->mFlags & ESM::Creature::Flies; } + bool Creature::canSwim(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return ref->mBase->mFlags & ESM::Creature::Swims; + } + + bool Creature::canWalk(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return ref->mBase->mFlags & ESM::Creature::Walks; + } + int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name) { if(name == "left") diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 6df6db297..c1bcb8739 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -124,7 +124,10 @@ namespace MWClass return true; } - virtual bool isFlying (const MWWorld::Ptr &ptr) const; + virtual bool isBipedal (const MWWorld::Ptr &ptr) const; + virtual bool canFly (const MWWorld::Ptr &ptr) const; + virtual bool canSwim (const MWWorld::Ptr &ptr) const; + virtual bool canWalk (const MWWorld::Ptr &ptr) const; virtual int getSkill(const MWWorld::Ptr &ptr, int skill) const; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 2110086d3..39d48f95b 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -363,7 +363,22 @@ namespace MWWorld return newPtr; } - bool Class::isFlying(const Ptr &ptr) const + bool Class::isBipedal(const Ptr &ptr) const + { + return false; + } + + bool Class::canFly(const Ptr &ptr) const + { + return false; + } + + bool Class::canSwim(const Ptr &ptr) const + { + return false; + } + + bool Class::canWalk(const Ptr &ptr) const { return false; } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index ad2cc3af4..739fd5942 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -307,7 +307,10 @@ namespace MWWorld return false; } - virtual bool isFlying(const MWWorld::Ptr& ptr) const; + virtual bool isBipedal(const MWWorld::Ptr& ptr) const; + virtual bool canFly(const MWWorld::Ptr& ptr) const; + virtual bool canSwim(const MWWorld::Ptr& ptr) const; + virtual bool canWalk(const MWWorld::Ptr& ptr) const; virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const; diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 1657cf267..1c6838939 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -116,8 +116,9 @@ namespace MWWorld const ESM::Position &refpos = ptr.getRefData().getPosition(); Ogre::Vector3 position(refpos.pos); - /* Anything to collide with? */ OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle()); + + // If no need to check for collision simply return the new position. if(!physicActor || !physicActor->getCollisionMode()) { return position + (Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * @@ -125,36 +126,67 @@ namespace MWWorld * movement * time; } + /* Anything to collide with? */ btCollisionObject *colobj = physicActor->getCollisionBody(); Ogre::Vector3 halfExtents = physicActor->getHalfExtents(); - position.z += halfExtents.z; - - waterlevel -= halfExtents.z * 0.5; + Ogre::Vector3 newPosition = position; // FIXME: not sure if a copy is needed + newPosition.z += halfExtents.z; // NOTE: remember to restore before returning + float actorWaterlevel = waterlevel - halfExtents.z * 0.5; + + /* + * A 3/4 submerged example + * + * +---+ + * | | + * | | <- waterlevel + * | | + * | | <- newPosition <- actorWaterlevel + * | | + * | | + * | | + * +---+ <- position + */ OEngine::Physic::ActorTracer tracer; bool wasOnGround = false; bool isOnGround = false; Ogre::Vector3 inertia(0.0f); Ogre::Vector3 velocity; - if(position.z < waterlevel || isFlying) + + bool canWalk = ptr.getClass().canWalk(ptr); + bool isBipedal = ptr.getClass().isBipedal(ptr); + bool isNpc = ptr.getClass().isNpc(); + + //if(!canWalk && !isBipedal && !isNpc && (position.z >= waterlevel)) + //{ + //std::cout << "Swim Creature \""<