Merge branch 'master' into new-script-api

new-script-api
Koncord 7 years ago
commit 045dc566ea

@ -34,6 +34,7 @@ Programmers
Ben Shealy (bentsherman)
Bret Curtis (psi29a)
Britt Mathis (galdor557)
Capostrophic
cc9cii
Chris Boyce (slothlife)
Chris Robinson (KittyCat)

@ -1,3 +1,43 @@
# Apps and tools
option(BUILD_OPENMW "build OpenMW" ON)
option(BUILD_OPENMW_MP "build OpenMW-MP" ON)
option(BUILD_MASTER "build tes3mp master server" OFF)
option(BUILD_BSATOOL "build BSA extractor" ON)
option(BUILD_ESMTOOL "build ESM inspector" ON)
option(BUILD_LAUNCHER "build Launcher" ON)
option(BUILD_BROWSER "build tes3mp Server Browser" ON)
option(BUILD_MWINIIMPORTER "build MWiniImporter" ON)
option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON)
option(BUILD_OPENCS "build OpenMW Construction Set" ON)
option(BUILD_WIZARD "build Installation Wizard" ON)
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
option(BUILD_NIFTEST "build nif file tester" OFF)
option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON)
option(BUILD_DOCS "build documentation." OFF )
if (NOT BUILD_LAUNCHER AND NOT BUILD_BROWSER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD)
set(USE_QT FALSE)
else()
set(USE_QT TRUE)
endif()
if (USE_QT)
set(DESIRED_QT_VERSION 4 CACHE STRING "The QT version OpenMW should use (4 or 5)")
set_property(CACHE DESIRED_QT_VERSION PROPERTY STRINGS 4 5)
endif()
if (APPLE)
# OS X build process relies on this fix: https://github.com/Kitware/CMake/commit/3df5147043d83aa09acd5c9ce31d5c602efb99db
cmake_minimum_required(VERSION 3.1.0)
elseif (USE_QT AND DESIRED_QT_VERSION MATCHES 5)
# 2.8.11+ is required to make Qt5 happy and allow linking QtMain on Windows.
cmake_minimum_required(VERSION 2.8.11)
else()
# We probably support older versions than this.
cmake_minimum_required(VERSION 2.6)
endif()
project(OpenMW)
# If the user doesn't supply a CMAKE_BUILD_TYPE via command line, choose one for them.
@ -59,24 +99,6 @@ option(QT_STATIC "Link static build of QT into the binaries" FALSE)
option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE)
# Apps and tools
option(BUILD_OPENMW "build OpenMW" ON)
option(BUILD_OPENMW_MP "build OpenMW-MP" ON)
option(BUILD_MASTER "build tes3mp master server" OFF)
option(BUILD_BSATOOL "build BSA extractor" ON)
option(BUILD_ESMTOOL "build ESM inspector" ON)
option(BUILD_LAUNCHER "build Launcher" ON)
option(BUILD_BROWSER "build tes3mp Server Browser" ON)
option(BUILD_MWINIIMPORTER "build MWiniImporter" ON)
option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON)
option(BUILD_OPENCS "build OpenMW Construction Set" ON)
option(BUILD_WIZARD "build Installation Wizard" ON)
option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF)
option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF)
option(BUILD_NIFTEST "build nif file tester" OFF)
option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON)
option(BUILD_DOCS "build documentation." OFF )
# what is necessary to build documentation
IF( BUILD_DOCS )
# Builds the documentation.
@ -126,16 +148,8 @@ endif()
find_package(RakNet REQUIRED)
include_directories(${RakNet_INCLUDES})
if (NOT BUILD_LAUNCHER AND NOT BUILD_BROWSER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD)
set(USE_QT FALSE)
else()
set(USE_QT TRUE)
endif()
# Dependencies
if (USE_QT)
set(DESIRED_QT_VERSION 5 CACHE STRING "The QT version OpenMW should use (4 or 5)")
set_property(CACHE DESIRED_QT_VERSION PROPERTY STRINGS 4 5)
message(STATUS "Using Qt${DESIRED_QT_VERSION}")
if (DESIRED_QT_VERSION MATCHES 4)
@ -150,17 +164,6 @@ if (USE_QT)
endif()
endif()
if (APPLE)
# OS X build process relies on this fix: https://github.com/Kitware/CMake/commit/3df5147043d83aa09acd5c9ce31d5c602efb99db
cmake_minimum_required(VERSION 3.1.0)
elseif (USE_QT AND DESIRED_QT_VERSION MATCHES 5)
# 2.8.11+ is required to make Qt5 happy and allow linking QtMain on Windows.
cmake_minimum_required(VERSION 2.8.11)
else()
# We probably support older versions than this.
cmake_minimum_required(VERSION 2.6)
endif()
IF(BUILD_OPENMW OR BUILD_OPENCS)
# Sound setup
find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE)
@ -217,7 +220,8 @@ endif()
IF(BUILD_OPENMW OR BUILD_OPENCS)
find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgAnimation osgParticle osgUtil osgFX)
find_package(OpenSceneGraph 3.3.4 REQUIRED osgDB osgViewer osgText osgGA osgParticle osgUtil osgFX)
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
set(USED_OSG_PLUGINS
osgdb_bmp
@ -261,8 +265,6 @@ IF(BUILD_OPENMW OR BUILD_OPENCS)
ENDIF(BUILD_OPENMW OR BUILD_OPENCS)
include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS})
set(BOOST_COMPONENTS system filesystem program_options)
if(WIN32)
@ -357,6 +359,8 @@ if (NOT WIN32 AND NOT APPLE)
"${OpenMW_BINARY_DIR}/openmw.desktop")
configure_file(${OpenMW_SOURCE_DIR}/files/openmw.appdata.xml
"${OpenMW_BINARY_DIR}/openmw.appdata.xml")
configure_file(${OpenMW_SOURCE_DIR}/files/tes3mp-browser.desktop
"${OpenMW_BINARY_DIR}/tes3mp-browser.desktop")
configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.desktop
"${OpenMW_BINARY_DIR}/openmw-cs.desktop")
endif()
@ -437,6 +441,9 @@ IF(NOT WIN32 AND NOT APPLE)
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw")
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.appdata.xml" DESTINATION "${DATAROOTDIR}/metainfo" COMPONENT "openmw")
IF(BUILD_BROWSER)
INSTALL(FILES "${OpenMW_BINARY_DIR}/tes3mp-browser.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "browser")
ENDIF(BUILD_BROWSER)
IF(BUILD_OPENCS)
INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs")
INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs")

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

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

@ -28,7 +28,7 @@ set(ESSIMPORTER_FILES
convertplayer.cpp
)
add_executable(openmw-essimporter
openmw_add_executable(openmw-essimporter
${ESSIMPORTER_FILES}
)

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

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

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

@ -174,7 +174,7 @@ else()
set (OPENCS_OPENMW_CFG "")
endif(APPLE)
add_executable(openmw-cs
openmw_add_executable(openmw-cs
MACOSX_BUNDLE
${OPENCS_SRC}
${OPENCS_UI_HDR}
@ -199,7 +199,7 @@ if(APPLE)
RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}"
OUTPUT_NAME ${OPENCS_BUNDLE_NAME}
MACOSX_BUNDLE_ICON_FILE "openmw-cs.icns"
MACOSX_BUNDLE_BUNDLE_NAME "OpenCS"
MACOSX_BUNDLE_BUNDLE_NAME "OpenMW-CS"
MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs"
MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION}
MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION}

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

@ -131,7 +131,7 @@ add_openmw_dir (mwmp/processors/world BaseObjectProcessor ProcessorConsoleComman
# Main executable
if (NOT ANDROID)
add_executable(tes3mp
openmw_add_executable(tes3mp
${OPENMW_FILES}
${GAME} ${GAME_HEADER}
${APPLE_BUNDLE_RESOURCES}

@ -257,6 +257,7 @@ namespace MWBase
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0;
virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr) = 0;
virtual bool isRunning(const MWWorld::Ptr& ptr) = 0;
virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0;
};

@ -380,7 +380,7 @@ namespace MWBase
// In WindowManager for now since there isn't a VFS singleton
virtual std::string correctIconPath(const std::string& path) = 0;
virtual std::string correctBookartPath(const std::string& path, int width, int height) = 0;
virtual std::string correctBookartPath(const std::string& path, int width, int height, bool* exists = nullptr) = 0;
virtual std::string correctTexturePath(const std::string& path) = 0;
virtual bool textureExists(const std::string& path) = 0;

@ -3,6 +3,7 @@
#include <components/esm/loadlock.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
@ -155,6 +156,16 @@ namespace MWClass
return MWWorld::Ptr(cell.insert(ref), &cell);
}
std::pair<int, std::string> Lockpick::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const
{
// Do not allow equip tools from inventory during attack
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)
&& MWBase::Environment::get().getWindowManager()->isGuiMode())
return std::make_pair(0, "#{sCantEquipWeapWarning}");
return std::make_pair(1, "");
}
bool Lockpick::canSell (const MWWorld::ConstPtr& item, int npcServices) const
{
return (npcServices & ESM::NPC::Picks) != 0;

@ -51,6 +51,8 @@ namespace MWClass
virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const;
///< Return name of inventory icon.
virtual std::pair<int, std::string> canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const;
///< Generate action for using via inventory menu

@ -3,6 +3,7 @@
#include <components/esm/loadprob.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
@ -155,6 +156,16 @@ namespace MWClass
return MWWorld::Ptr(cell.insert(ref), &cell);
}
std::pair<int, std::string> Probe::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const
{
// Do not allow equip tools from inventory during attack
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)
&& MWBase::Environment::get().getWindowManager()->isGuiMode())
return std::make_pair(0, "#{sCantEquipWeapWarning}");
return std::make_pair(1, "");
}
bool Probe::canSell (const MWWorld::ConstPtr& item, int npcServices) const
{
return (npcServices & ESM::NPC::Probes) != 0;

@ -51,6 +51,8 @@ namespace MWClass
virtual std::string getInventoryIcon (const MWWorld::ConstPtr& ptr) const;
///< Return name of inventory icon.
virtual std::pair<int, std::string> canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const;
virtual std::shared_ptr<MWWorld::Action> use (const MWWorld::Ptr& ptr)
const;
///< Generate action for using via inventory menu

@ -374,7 +374,9 @@ namespace MWClass
if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0)
return std::make_pair(0, "#{sInventoryMessage1}");
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc))
// Do not allow equip weapons from inventory during attack
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)
&& MWBase::Environment::get().getWindowManager()->isGuiMode())
return std::make_pair(0, "#{sCantEquipWeapWarning}");
std::pair<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr);

@ -634,7 +634,7 @@ namespace MWGui
void DialogueWindow::onFrame()
{
if(mMainWidget->getVisible() && mEnabled && mPtr.getTypeName() == typeid(ESM::NPC).name())
if(mMainWidget->getVisible() && mPtr.getTypeName() == typeid(ESM::NPC).name())
{
int disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr);
mDispositionBar->setProgressRange(100);

@ -275,8 +275,6 @@ namespace MWGui
{
case BookTextParser::Event_ImgTag:
{
pag.setIgnoreLeadingEmptyLines(false);
const BookTextParser::Attributes & attr = parser.getAttributes();
if (attr.find("src") == attr.end() || attr.find("width") == attr.end() || attr.find("height") == attr.end())
@ -286,8 +284,19 @@ namespace MWGui
int width = MyGUI::utility::parseInt(attr.at("width"));
int height = MyGUI::utility::parseInt(attr.at("height"));
bool exists;
std::string correctedSrc = MWBase::Environment::get().getWindowManager()->correctBookartPath(src, width, height, &exists);
if (!exists)
{
std::cerr << "Warning: Could not find \"" << src << "\" referenced by an <img> tag." << std::endl;
break;
}
pag.setIgnoreLeadingEmptyLines(false);
ImageElement elem(paper, pag, mBlockStyle,
src, width, height);
correctedSrc, width, height);
elem.paginate();
break;
}
@ -471,8 +480,7 @@ namespace MWGui
MyGUI::IntCoord(left, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top,
parent->getName() + MyGUI::utility::toString(parent->getChildCount()));
std::string image = MWBase::Environment::get().getWindowManager()->correctBookartPath(src, width, mImageHeight);
mImageBox->setImageTexture(image);
mImageBox->setImageTexture(src);
mImageBox->setProperty("NeedMouse", "false");
}

@ -269,6 +269,19 @@ namespace MWGui
}
}
// If we unequip weapon during attack, it can lead to unexpected behaviour
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPtr))
{
bool isWeapon = item.mBase.getTypeName() == typeid(ESM::Weapon).name();
MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr);
if (isWeapon && invStore.isEquipped(item.mBase))
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sCantEquipWeapWarning}");
return;
}
}
if (count > 1 && !shift)
{
CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog();

@ -129,7 +129,7 @@ namespace MWGui
Disable increases for Security and Sneak when using ignoreJailSkillIncreases
*/
if (mwmp::Main::get().getLocalPlayer()->ignoreJailSkillIncreases)
value.setBase(value.getBase() - 1);
value.setBase(std::max(0, value.getBase()-1));
else if (skill == ESM::Skill::Security || skill == ESM::Skill::Sneak)
/*
End of tes3mp change (minor)

@ -143,10 +143,10 @@ namespace MWGui
mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level));
std::string levelupdescription;
if(level > 20)
levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+MyGUI::utility::toString(level));
if (levelupdescription == "")
levelupdescription=world->getFallback()->getFallbackString("Level_Up_Default");
else
levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+MyGUI::utility::toString(level));
mLevelDescription->setCaption (levelupdescription);

@ -344,16 +344,17 @@ namespace MWGui
{
MWWorld::Ptr item = *button->getUserData<MWWorld::Ptr>();
bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name();
bool isTool = item.getTypeName() == typeid(ESM::Probe).name() || item.getTypeName() == typeid(ESM::Lockpick).name();
// delay weapon switching if player is busy
if (isDelayNeeded && isWeapon)
if (isDelayNeeded && (isWeapon || isTool))
{
mActivatedIndex = index;
return;
}
// disable weapon switching if player is dead or paralyzed
if (isReturnNeeded && isWeapon)
if (isReturnNeeded && (isWeapon || isTool))
{
return;
}

@ -2128,9 +2128,12 @@ namespace MWGui
return Misc::ResourceHelpers::correctIconPath(path, mResourceSystem->getVFS());
}
std::string WindowManager::correctBookartPath(const std::string& path, int width, int height)
std::string WindowManager::correctBookartPath(const std::string& path, int width, int height, bool* exists)
{
return Misc::ResourceHelpers::correctBookartPath(path, width, height, mResourceSystem->getVFS());
std::string corrected = Misc::ResourceHelpers::correctBookartPath(path, width, height, mResourceSystem->getVFS());
if (exists)
*exists = mResourceSystem->getVFS()->exists(corrected);
return corrected;
}
std::string WindowManager::correctTexturePath(const std::string& path)

@ -408,7 +408,7 @@ namespace MWGui
// In WindowManager for now since there isn't a VFS singleton
virtual std::string correctIconPath(const std::string& path);
virtual std::string correctBookartPath(const std::string& path, int width, int height);
virtual std::string correctBookartPath(const std::string& path, int width, int height, bool* exists = nullptr);
virtual std::string correctTexturePath(const std::string& path);
virtual bool textureExists(const std::string& path);

@ -985,7 +985,11 @@ namespace MWInput
if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"])
return;
if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer()))
// We want to interrupt animation only if attack is prepairing, but still is not triggered
// Otherwise we will get a "speedshooting" exploit, when player can skip reload animation by hitting "Toggle Weapon" key twice
if (MWBase::Environment::get().getMechanicsManager()->isAttackPrepairing(mPlayer->getPlayer()))
mPlayer->setAttackingOrSpell(false);
else if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer()))
return;
MWMechanics::DrawState_ state = mPlayer->getDrawState();

@ -443,10 +443,15 @@ namespace MWMechanics
End of tes3mp addition
*/
}
// Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter
if (actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc())
{
// Check if the creature is too far
static const float fAlarmRadius = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fAlarmRadius")->getFloat();
if (sqrDist > fAlarmRadius * fAlarmRadius)
return;
bool followerOrEscorter = false;
for (std::list<MWMechanics::AiPackage*>::const_iterator it = creatureStats2.getAiSequence().begin(); it != creatureStats2.getAiSequence().end(); ++it)
{
@ -851,6 +856,16 @@ namespace MWMechanics
}
}
bool Actors::isAttackPrepairing(const MWWorld::Ptr& ptr)
{
PtrActorMap::iterator it = mActors.find(ptr);
if (it == mActors.end())
return false;
CharacterController* ctrl = it->second->getCharacterController();
return ctrl->isAttackPrepairing();
}
bool Actors::isRunning(const MWWorld::Ptr& ptr)
{
PtrActorMap::iterator it = mActors.find(ptr);
@ -1065,7 +1080,18 @@ namespace MWMechanics
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, ptr))
{
static const int iCrimeThresholdMultiplier = esmStore.get<ESM::GameSetting>().find("iCrimeThresholdMultiplier")->getInt();
if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier)
/*
Start of tes3mp change (major)
Only attack players based on their high bounty if they haven't died since the last
time an attempt was made to arrest them
*/
if (player.getClass().getNpcStats(player).getBounty() >= cutoff * iCrimeThresholdMultiplier
&& !mwmp::Main::get().getLocalPlayer()->diedSinceArrestAttempt)
/*
End of tes3mp change (major)
*/
{
MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, player);
creatureStats.setHitAttemptActorId(player.getClass().getCreatureStats(player).getActorId()); // Stops the guard from quitting combat if player is unreachable
@ -1096,6 +1122,24 @@ namespace MWMechanics
// Update witness crime id
npcStats.setCrimeId(-1);
}
/*
Start of tes3mp addition
If the player has died, stop combat with them as though they had
paid their bounty
*/
else if (mwmp::Main::get().getLocalPlayer()->diedSinceArrestAttempt)
{
if (creatureStats.getAiSequence().isInCombat(player))
{
creatureStats.getAiSequence().stopCombat();
creatureStats.setAttacked(false);
creatureStats.setAlarmed(false);
}
}
/*
End of tes3mp addition
*/
}
}
}

@ -117,6 +117,7 @@ namespace MWMechanics
End of tes3mp addition
*/
bool isAttackPrepairing(const MWWorld::Ptr& ptr);
bool isRunning(const MWWorld::Ptr& ptr);
bool isSneaking(const MWWorld::Ptr& ptr);

@ -13,7 +13,10 @@
Include additional headers for multiplayer purposes
*/
#include <components/openmw-mp/Log.hpp>
#include "../mwgui/windowmanagerimp.hpp"
#include "../mwmp/Main.hpp"
#include "../mwmp/LocalPlayer.hpp"
/*
End of tes3mp addition
*/
@ -81,6 +84,18 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte
if (pathTo(actor, dest, duration, 100)) {
target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached
/*
Start of tes3mp addition
Record that the player has not died since the last attempt to arrest them
*/
LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "After being pursued by %s, diedSinceArrestAttempt is now false", actor.getCellRef().getRefId().c_str());
mwmp::Main::get().getLocalPlayer()->diedSinceArrestAttempt = false;
/*
End of tes3mp addition
*/
return true;
}

@ -262,22 +262,16 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co
void MWMechanics::Alchemy::removeIngredients()
{
bool needsUpdate = false;
for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter)
if (!iter->isEmpty())
{
iter->getContainerStore()->remove(*iter, 1, mAlchemist);
if (iter->getRefData().getCount()<1)
{
needsUpdate = true;
*iter = MWWorld::Ptr();
}
}
if (needsUpdate)
updateEffects();
updateEffects();
}
void MWMechanics::Alchemy::addPotion (const std::string& name)

@ -2357,6 +2357,12 @@ void CharacterController::setAttackTypeBasedOnMovement()
mAttackType = "chop";
}
bool CharacterController::isAttackPrepairing() const
{
return mUpperBodyState == UpperCharState_StartToMinAttack ||
mUpperBodyState == UpperCharState_MinAttackToMaxAttack;
}
bool CharacterController::isReadyToBlock() const
{
return updateCarriedLeftVisible(mWeaponType);

@ -263,6 +263,7 @@ public:
void forceStateUpdate();
bool isAttackPrepairing() const;
bool isReadyToBlock() const;
bool isKnockedOut() const;
bool isSneaking() const;

@ -436,6 +436,11 @@ namespace MWMechanics
mObjects.update(duration, paused);
}
bool MechanicsManager::isAttackPrepairing(const MWWorld::Ptr& ptr)
{
return mActors.isAttackPrepairing(ptr);
}
bool MechanicsManager::isRunning(const MWWorld::Ptr& ptr)
{
return mActors.isRunning(ptr);

@ -220,8 +220,10 @@ namespace MWMechanics
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count);
virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr);
virtual bool isRunning(const MWWorld::Ptr& ptr);
virtual bool isSneaking(const MWWorld::Ptr& ptr);
private:
void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
OffenseType type, int arg=0);

@ -422,7 +422,7 @@ bool MWMechanics::NpcStats::hasSkillsForRank (const std::string& factionId, int
for (int i=0; i<7; ++i)
{
if (faction.mData.mSkills[i] != -1)
skills.push_back (static_cast<int> (getSkill (faction.mData.mSkills[i]).getModified()));
skills.push_back (static_cast<int> (getSkill (faction.mData.mSkills[i]).getBase()));
}
if (skills.empty())

@ -61,6 +61,10 @@ LocalPlayer::LocalPlayer()
jailProgressText = "";
jailEndText = "";
isWerewolf = false;
diedSinceArrestAttempt = false;
}
LocalPlayer::~LocalPlayer()
@ -93,15 +97,8 @@ void LocalPlayer::update()
updateDeadState();
updateEquipment();
updateStatsDynamic();
// Only send attributes and skills if we are not a werewolf, or they will be
// overwritten by the werewolf ones
if (!isWerewolf)
{
updateAttributes();
updateSkills();
}
updateAttributes();
updateSkills();
updateLevel();
updateBounty();
}
@ -230,6 +227,10 @@ void LocalPlayer::updateStatsDynamic(bool forceUpdate)
void LocalPlayer::updateAttributes(bool forceUpdate)
{
// Only send attributes if we are not a werewolf, or they will be
// overwritten by the werewolf ones
if (isWerewolf) return;
MWWorld::Ptr ptrPlayer = getPlayerPtr();
const MWMechanics::NpcStats &ptrNpcStats = ptrPlayer.getClass().getNpcStats(ptrPlayer);
bool attributesChanged = false;
@ -252,6 +253,10 @@ void LocalPlayer::updateAttributes(bool forceUpdate)
void LocalPlayer::updateSkills(bool forceUpdate)
{
// Only send skills if we are not a werewolf, or they will be
// overwritten by the werewolf ones
if (isWerewolf) return;
MWWorld::Ptr ptrPlayer = getPlayerPtr();
const MWMechanics::NpcStats &ptrNpcStats = ptrPlayer.getClass().getNpcStats(ptrPlayer);

@ -52,6 +52,12 @@ namespace mwmp
// readied but be unable to use it unless we clear it here
playerPtr.getClass().getNpcStats(playerPtr).setDrawState(MWMechanics::DrawState_Nothing);
// Record that the player has died since the last attempt was made to arrest them,
// used to make guards lenient enough to attempt an arrest again
player->diedSinceArrestAttempt = true;
LOG_APPEND(Log::LOG_INFO, "- diedSinceArrestAttempt is now true");
packet.setPlayer(player);
packet.Send(serverAddr);

@ -18,7 +18,7 @@ namespace MWPhysics
Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape> shape, btCollisionWorld* world)
: mCanWaterWalk(false), mWalkingOnWater(false)
, mCollisionObject(nullptr), mForce(0.f, 0.f, 0.f), mOnGround(false), mOnSlope(false)
, mCollisionObject(nullptr), mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
, mInternalCollisionMode(true)
, mExternalCollisionMode(true)
, mCollisionWorld(world)

@ -182,7 +182,10 @@ namespace
void remove()
{
for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it)
it->second->removeChild(it->first);
{
if (!it->second->removeChild(it->first))
std::cerr << "error removing " << it->first->getName() << std::endl;
}
}
protected:
@ -1192,6 +1195,9 @@ namespace MWRender
mObjectRoot->addChild(created);
mInsert->addChild(mObjectRoot);
}
osg::ref_ptr<SceneUtil::Skeleton> skel = dynamic_cast<SceneUtil::Skeleton*>(mObjectRoot.get());
if (skel)
mSkeleton = skel.get();
}
else
{

@ -118,7 +118,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
osg::ref_ptr<osg::Node> node = mResourceSystem->getSceneManager()->getInstance(item.getClass().getModel(item));
const NodeMap& nodeMap = getNodeMap();
NodeMap::const_iterator found = getNodeMap().find(Misc::StringUtils::lowerCase(bonename));
NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename));
if (found == nodeMap.end())
throw std::runtime_error("Can't find attachment node " + bonename);
osg::ref_ptr<osg::Node> attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get());

@ -23,6 +23,7 @@
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/player.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/actorutil.hpp"
@ -919,6 +920,9 @@ void NpcAnimation::showWeapons(bool showWeapon)
else
{
removeIndividualPart(ESM::PRT_Weapon);
// If we remove/hide weapon from player, we should reset attack animation as well
if (mPtr == MWMechanics::getPlayer())
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
}
}

@ -1310,6 +1310,14 @@ namespace MWScript
if (mNegativeEffect != -1)
currentValue -= effects.get(mNegativeEffect).getMagnitude();
// GetResist* should take in account elemental shields
if (mPositiveEffect == ESM::MagicEffect::ResistFire)
currentValue += effects.get(ESM::MagicEffect::FireShield).getMagnitude();
if (mPositiveEffect == ESM::MagicEffect::ResistShock)
currentValue += effects.get(ESM::MagicEffect::LightningShield).getMagnitude();
if (mPositiveEffect == ESM::MagicEffect::ResistFrost)
currentValue += effects.get(ESM::MagicEffect::FrostShield).getMagnitude();
int ret = static_cast<int>(currentValue);
runtime.push(ret);
}
@ -1336,6 +1344,14 @@ namespace MWScript
if (mNegativeEffect != -1)
currentValue -= effects.get(mNegativeEffect).getMagnitude();
// SetResist* should take in account elemental shields
if (mPositiveEffect == ESM::MagicEffect::ResistFire)
currentValue += effects.get(ESM::MagicEffect::FireShield).getMagnitude();
if (mPositiveEffect == ESM::MagicEffect::ResistShock)
currentValue += effects.get(ESM::MagicEffect::LightningShield).getMagnitude();
if (mPositiveEffect == ESM::MagicEffect::ResistFrost)
currentValue += effects.get(ESM::MagicEffect::FrostShield).getMagnitude();
int arg = runtime[0].mInteger;
runtime.pop();
effects.modifyBase(mPositiveEffect, (arg - static_cast<int>(currentValue)));

@ -17,7 +17,7 @@ if (GTEST_FOUND)
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
target_link_libraries(openmw_test_suite ${GTEST_BOTH_LIBRARIES} components)
# Fix for not visible pthreads functions for linker with glibc 2.15

@ -96,7 +96,7 @@ if (OPENMW_USE_UNSHIELD)
include_directories(${LIBUNSHIELD_INCLUDE_DIRS})
endif()
add_executable(openmw-wizard
openmw_add_executable(openmw-wizard
${GUI_TYPE}
${WIZARD}
${WIZARD_HEADER}

@ -145,3 +145,32 @@ foreach (u ${ARGN})
add_hdr (OPENCS ${dir} ${u})
endforeach (u)
endmacro (opencs_hdrs_noqt)
include(CMakeParseArguments)
macro (openmw_add_executable target)
set(OMW_ADD_EXE_OPTIONS WIN32 MACOSX_BUNDLE EXCLUDE_FROM_ALL)
set(OMW_ADD_EXE_VALUES)
set(OMW_ADD_EXE_MULTI_VALUES)
cmake_parse_arguments(OMW_ADD_EXE "${OMW_ADD_EXE_OPTIONS}" "${OMW_ADD_EXE_VALUES}" "${OMW_ADD_EXE_MULTI_VALUES}" ${ARGN})
if (OMW_ADD_EXE_WIN32)
set(OMW_ADD_EXE_WIN32_VALUE WIN32)
endif (OMW_ADD_EXE_WIN32)
if (OMW_ADD_EXE_MACOSX_BUNDLE)
set(OMW_ADD_EXE_MACOSX_BUNDLE_VALUE MACOSX_BUNDLE)
endif (OMW_ADD_EXE_MACOSX_BUNDLE)
if (OMW_ADD_EXE_EXCLUDE_FROM_ALL)
set(OMW_ADD_EXE_EXCLUDE_FROM_ALL_VALUE EXCLUDE_FROM_ALL)
endif (OMW_ADD_EXE_EXCLUDE_FROM_ALL)
add_executable(${target} ${OMW_ADD_EXE_WIN32_VALUE} ${OMW_ADD_EXE_MACOSX_BUNDLE_VALUE} ${OMW_ADD_EXE_EXCLUDE_FROM_ALL_VALUE} ${OMW_ADD_EXE_UNPARSED_ARGUMENTS})
if (MSVC)
if (CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8)
set_target_properties(${target} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "$(TargetDir)")
endif (CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8)
endif (MSVC)
endmacro (openmw_add_executable)

@ -52,7 +52,7 @@ add_component_dir (shader
)
add_component_dir (sceneutil
clone attach visitor util statesetupdater controller skeleton riggeometry lightcontroller
clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller
lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer
)

@ -6,11 +6,10 @@
#include <osg/Texture2D>
#include <osg/UserDataContainer>
#include <osgAnimation/MorphGeometry>
#include <osgParticle/Emitter>
#include <components/nif/data.hpp>
#include <components/sceneutil/morphgeometry.hpp>
#include "userdata.hpp"
@ -188,7 +187,7 @@ GeomMorpherController::GeomMorpherController(const Nif::NiMorphData *data)
void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable)
{
osgAnimation::MorphGeometry* morphGeom = static_cast<osgAnimation::MorphGeometry*>(drawable);
SceneUtil::MorphGeometry* morphGeom = static_cast<SceneUtil::MorphGeometry*>(drawable);
if (hasInput())
{
if (mKeyFrames.size() <= 1)
@ -202,7 +201,7 @@ void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable
val = it->interpKey(input);
val = std::max(0.f, std::min(1.f, val));
osgAnimation::MorphGeometry::MorphTarget& target = morphGeom->getMorphTarget(i);
SceneUtil::MorphGeometry::MorphTarget& target = morphGeom->getMorphTarget(i);
if (target.getWeight() != val)
{
target.setWeight(val);
@ -210,8 +209,6 @@ void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable
}
}
}
// morphGeometry::transformSoftwareMethod() done in cull callback i.e. only for visible morph geometries
}
UVController::UVController()

@ -31,11 +31,6 @@ namespace osgParticle
class Emitter;
}
namespace osgAnimation
{
class MorphGeometry;
}
namespace NifOsg
{
@ -172,7 +167,7 @@ namespace NifOsg
virtual float getMaximum() const;
};
/// Must be set on an osgAnimation::MorphGeometry.
/// Must be set on a SceneUtil::MorphGeometry.
class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller
{
public:

@ -13,9 +13,6 @@
#include <components/misc/resourcehelpers.hpp>
#include <components/resource/imagemanager.hpp>
// skel
#include <osgAnimation/MorphGeometry>
// particle
#include <osgParticle/ParticleSystem>
#include <osgParticle/ParticleSystemUpdater>
@ -39,6 +36,7 @@
#include <components/nif/effect.hpp>
#include <components/sceneutil/skeleton.hpp>
#include <components/sceneutil/riggeometry.hpp>
#include <components/sceneutil/morphgeometry.hpp>
#include "particle.hpp"
#include "userdata.hpp"
@ -83,35 +81,6 @@ namespace
collectDrawableProperties(nifNode->parent, out);
}
class FrameSwitch : public osg::Group
{
public:
FrameSwitch()
{
}
FrameSwitch(const FrameSwitch& copy, const osg::CopyOp& copyop)
: osg::Group(copy, copyop)
{
}
META_Object(NifOsg, FrameSwitch)
virtual void traverse(osg::NodeVisitor& nv)
{
if (nv.getTraversalMode() != osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN && nv.getVisitorType() != osg::NodeVisitor::UPDATE_VISITOR)
osg::Group::traverse(nv);
else
{
for (unsigned int i=0; i<getNumChildren(); ++i)
{
if (i%2 == nv.getTraversalNumber()%2)
getChild(i)->accept(nv);
}
}
}
};
// NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale
// set just like a regular MatrixTransform, but the rotation set will be overridden in order to face the camera.
// Must be set as a cull callback.
@ -154,70 +123,6 @@ namespace
}
};
struct UpdateMorphGeometry : public osg::Drawable::CullCallback
{
UpdateMorphGeometry()
: mLastFrameNumber(0)
{
}
UpdateMorphGeometry(const UpdateMorphGeometry& copy, const osg::CopyOp& copyop)
: osg::Drawable::CullCallback(copy, copyop)
, mLastFrameNumber(0)
{
}
META_Object(NifOsg, UpdateMorphGeometry)
virtual bool cull(osg::NodeVisitor* nv, osg::Drawable * drw, osg::State *) const
{
osgAnimation::MorphGeometry* geom = static_cast<osgAnimation::MorphGeometry*>(drw);
if (!geom)
return false;
if (mLastFrameNumber == nv->getTraversalNumber())
return false;
mLastFrameNumber = nv->getTraversalNumber();
geom->transformSoftwareMethod();
return false;
}
private:
mutable unsigned int mLastFrameNumber;
};
// Callback to return a static bounding box for a MorphGeometry. The idea is to not recalculate the bounding box
// every time the morph weights change. To do so we return a maximum containing box that is big enough for all possible combinations of morph targets.
class StaticBoundingBoxCallback : public osg::Drawable::ComputeBoundingBoxCallback
{
public:
StaticBoundingBoxCallback()
{
}
StaticBoundingBoxCallback(const osg::BoundingBox& bounds)
: mBoundingBox(bounds)
{
}
StaticBoundingBoxCallback(const StaticBoundingBoxCallback& copy, const osg::CopyOp& copyop)
: osg::Drawable::ComputeBoundingBoxCallback(copy, copyop)
, mBoundingBox(copy.mBoundingBox)
{
}
META_Object(NifOsg, StaticBoundingBoxCallback)
virtual osg::BoundingBox computeBound(const osg::Drawable&) const
{
return mBoundingBox;
}
private:
osg::BoundingBox mBoundingBox;
};
void extractTextKeys(const Nif::NiTextKeyExtraData *tk, NifOsg::TextKeyMap &textkeys)
{
for(size_t i = 0;i < tk->list.size();i++)
@ -1107,106 +1012,49 @@ namespace NifOsg
void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
{
osg::ref_ptr<osg::Geometry> geometry;
osg::ref_ptr<osg::Drawable> drawable;
for (Nif::ControllerPtr ctrl = triShape->controller; !ctrl.empty(); ctrl = ctrl->next)
{
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
continue;
if(ctrl->recType == Nif::RC_NiGeomMorpherController)
{
geometry = handleMorphGeometry(static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags);
drawable = handleMorphGeometry(static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags);
osg::ref_ptr<GeomMorpherController> morphctrl = new GeomMorpherController(
static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr())->data.getPtr());
setupController(ctrl.getPtr(), morphctrl, animflags);
geometry->setUpdateCallback(morphctrl);
drawable->setUpdateCallback(morphctrl);
break;
}
}
if (!geometry.get())
if (!drawable.get())
{
geometry = new osg::Geometry;
triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags);
osg::ref_ptr<osg::Geometry> geom (new osg::Geometry);
drawable = geom;
triShapeToGeometry(triShape, geom, parentNode, composite, boundTextures, animflags);
}
if (geometry->getDataVariance() == osg::Object::DYNAMIC)
{
// Add a copy, we will alternate between the two copies every other frame using the FrameSwitch
// This is so we can set the DataVariance as STATIC, giving a huge performance boost
geometry->setDataVariance(osg::Object::STATIC);
osg::ref_ptr<FrameSwitch> frameswitch = new FrameSwitch;
drawable->setName(triShape->name);
osg::ref_ptr<osg::Geometry> geom2 = osg::clone(geometry.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES);
frameswitch->addChild(geometry);
frameswitch->addChild(geom2);
parentNode->addChild(frameswitch);
}
else
parentNode->addChild(geometry);
parentNode->addChild(drawable);
}
osg::ref_ptr<osg::Geometry> handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
osg::ref_ptr<osg::Drawable> handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
{
osg::ref_ptr<osgAnimation::MorphGeometry> morphGeom = new osgAnimation::MorphGeometry;
morphGeom->setMethod(osgAnimation::MorphGeometry::RELATIVE);
// No normals available in the MorphData
morphGeom->setMorphNormals(false);
morphGeom->setUpdateCallback(NULL);
morphGeom->setCullCallback(new UpdateMorphGeometry);
morphGeom->setUseVertexBufferObjects(true);
triShapeToGeometry(triShape, morphGeom, parentNode, composite, boundTextures, animflags);
osg::ref_ptr<SceneUtil::MorphGeometry> morphGeom = new SceneUtil::MorphGeometry;
morphGeom->getOrCreateVertexBufferObject()->setUsage(GL_DYNAMIC_DRAW_ARB);
osg::ref_ptr<osg::Geometry> sourceGeometry (new osg::Geometry);
triShapeToGeometry(triShape, sourceGeometry, parentNode, composite, boundTextures, animflags);
morphGeom->setSourceGeometry(sourceGeometry);
const std::vector<Nif::NiMorphData::MorphData>& morphs = morpher->data.getPtr()->mMorphs;
if (morphs.empty())
return morphGeom;
// Note we are not interested in morph 0, which just contains the original vertices
for (unsigned int i = 1; i < morphs.size(); ++i)
{
osg::ref_ptr<osg::Geometry> morphTarget = new osg::Geometry;
morphTarget->setVertexArray(new osg::Vec3Array(morphs[i].mVertices.size(), &morphs[i].mVertices[0]));
morphGeom->addMorphTarget(morphTarget, 0.f);
}
// build the bounding box containing all possible morph combinations
std::vector<osg::BoundingBox> vertBounds(morphs[0].mVertices.size());
// Since we don't know what combinations of morphs are being applied we need to keep track of a bounding box for each vertex.
// The minimum/maximum of the box is the minimum/maximum offset the vertex can have from its starting position.
// Start with zero offsets which will happen when no morphs are applied.
for (unsigned int i=0; i<vertBounds.size(); ++i)
vertBounds[i].set(osg::Vec3f(0,0,0), osg::Vec3f(0,0,0));
for (unsigned int i = 1; i < morphs.size(); ++i)
{
for (unsigned int j=0; j<morphs[i].mVertices.size() && vertBounds.size(); ++j)
{
osg::BoundingBox& bounds = vertBounds[j];
bounds.expandBy(bounds._max + morphs[i].mVertices[j]);
bounds.expandBy(bounds._min + morphs[i].mVertices[j]);
}
}
osg::BoundingBox box;
for (unsigned int i=0; i<vertBounds.size(); ++i)
{
vertBounds[i]._max += morphs[0].mVertices[i];
vertBounds[i]._min += morphs[0].mVertices[i];
box.expandBy(vertBounds[i]);
}
// For the initial bounding box (used for object placement) use the default pose, fire off a bounding compute to set this initial box
morphGeom->getBound();
// Now set up the callback so that we get properly enlarged bounds if/when the mesh starts animating
morphGeom->setComputeBoundingBoxCallback(new StaticBoundingBoxCallback(box));
morphGeom->addMorphTarget(new osg::Vec3Array(morphs[i].mVertices.size(), &morphs[i].mVertices[0]), 0.f);
return morphGeom;
}
@ -1219,6 +1067,7 @@ namespace NifOsg
osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry);
rig->setSourceGeometry(geometry);
rig->setName(triShape->name);
const Nif::NiSkinInstance *skin = triShape->skin.getPtr();
@ -1233,7 +1082,6 @@ namespace NifOsg
SceneUtil::RigGeometry::BoneInfluence influence;
const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights;
//influence.mWeights.reserve(weights.size());
for(size_t j = 0;j < weights.size();j++)
{
std::pair<unsigned short, float> indexWeight = std::make_pair(weights[j].vertex, weights[j].weight);
@ -1246,17 +1094,7 @@ namespace NifOsg
}
rig->setInfluenceMap(map);
// Add a copy, we will alternate between the two copies every other frame using the FrameSwitch
// This is so we can set the DataVariance as STATIC, giving a huge performance boost
rig->setDataVariance(osg::Object::STATIC);
osg::ref_ptr<FrameSwitch> frameswitch = new FrameSwitch;
SceneUtil::RigGeometry* rig2 = osg::clone(rig.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES);
frameswitch->addChild(rig);
frameswitch->addChild(rig2);
parentNode->addChild(frameswitch);
parentNode->addChild(rig);
}
osg::BlendFunc::BlendFuncMode getBlendMode(int mode)

@ -243,6 +243,8 @@ namespace mwmp
std::string jailEndText;
unsigned int resurrectType;
bool diedSinceArrestAttempt;
};
}

@ -80,11 +80,14 @@ namespace mwmp
{
if (write)
{
RakNet::RakString rstr("%s", str.c_str());
if (compress)
rstr.SerializeCompressed(bs);
RakNet::RakString::SerializeCompressed(str.c_str(), bs);
else
{
RakNet::RakString rstr;
rstr = str.c_str();
bs->Write(rstr);
}
}
else
{

@ -32,29 +32,29 @@ namespace SceneUtil
virtual void apply(osg::MatrixTransform& node)
{
applyNode(node);
}
virtual void apply(osg::Geometry& node)
{
applyNode(node);
traverse(node);
}
virtual void apply(osg::Node& node)
{
applyNode(node);
traverse(node);
}
virtual void apply(osg::Group& node)
{
applyNode(node);
traverse(node);
}
void applyNode(osg::Node& node)
virtual void apply(osg::Drawable& drawable)
{
std::string lowerName = Misc::StringUtils::lowerCase(node.getName());
std::string lowerName = Misc::StringUtils::lowerCase(drawable.getName());
if ((lowerName.size() >= mFilter.size() && lowerName.compare(0, mFilter.size(), mFilter) == 0)
|| (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0))
mToCopy.push_back(&node);
else
traverse(node);
{
osg::Node* node = &drawable;
while (node && node->getNumParents() && !node->getStateSet())
node = node->getParent(0);
if (node)
mToCopy.push_back(node);
}
}
void doCopy()

@ -8,7 +8,7 @@
#include <osgParticle/Emitter>
#include <osgParticle/Program>
#include <osgAnimation/MorphGeometry>
#include <components/sceneutil/morphgeometry.hpp>
#include <components/sceneutil/riggeometry.hpp>
@ -49,46 +49,12 @@ namespace SceneUtil
{
if (const osgParticle::ParticleSystem* partsys = dynamic_cast<const osgParticle::ParticleSystem*>(drawable))
return operator()(partsys);
if (dynamic_cast<const osgAnimation::MorphGeometry*>(drawable))
{
osg::CopyOp copyop = *this;
copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_ARRAYS);
#if OSG_VERSION_LESS_THAN(3,5,0)
/*
Deep copy of primitives required to work around the following (bad?) code in osg::Geometry copy constructor:
if ((copyop.getCopyFlags() & osg::CopyOp::DEEP_COPY_ARRAYS))
{
if (_useVertexBufferObjects)
{
// copying of arrays doesn't set up buffer objects so we'll need to force
// Geometry to assign these, we'll do this by switching off VBO's then renabling them.
setUseVertexBufferObjects(false);
setUseVertexBufferObjects(true);
}
}
In case of DEEP_COPY_PRIMITIVES=Off, DEEP_COPY_ARRAYS=On, the above code makes a modification to the original const Geometry& we copied from,
causing problems if we relied on the original Geometry to remain static such as when it was added to an osgUtil::IncrementalCompileOperation.
Fixed in OSG 3.5 ( http://forum.openscenegraph.org/viewtopic.php?t=15217 ).
*/
copyop.setCopyFlags(copyop.getCopyFlags()|osg::CopyOp::DEEP_COPY_PRIMITIVES);
#endif
osg::Drawable* cloned = osg::clone(drawable, copyop);
return cloned;
}
if (dynamic_cast<const SceneUtil::RigGeometry*>(drawable))
if (dynamic_cast<const SceneUtil::RigGeometry*>(drawable) || dynamic_cast<const SceneUtil::MorphGeometry*>(drawable))
{
return osg::clone(drawable, *this);
}
return osg::CopyOp::operator()(drawable);
}

@ -0,0 +1,190 @@
#include "morphgeometry.hpp"
#include <cassert>
namespace SceneUtil
{
MorphGeometry::MorphGeometry()
: mLastFrameNumber(0)
, mDirty(true)
, mMorphedBoundingBox(false)
{
}
MorphGeometry::MorphGeometry(const MorphGeometry &copy, const osg::CopyOp &copyop)
: osg::Drawable(copy, copyop)
, mMorphTargets(copy.mMorphTargets)
, mLastFrameNumber(0)
, mDirty(true)
, mMorphedBoundingBox(false)
{
setSourceGeometry(copy.getSourceGeometry());
}
void MorphGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeom)
{
mSourceGeometry = sourceGeom;
for (unsigned int i=0; i<2; ++i)
{
mGeometry[i] = new osg::Geometry(*mSourceGeometry, osg::CopyOp::SHALLOW_COPY);
const osg::Geometry& from = *mSourceGeometry;
osg::Geometry& to = *mGeometry[i];
to.setSupportsDisplayList(false);
to.setUseVertexBufferObjects(true);
to.setCullingActive(false); // make sure to disable culling since that's handled by this class
// vertices are modified every frame, so we need to deep copy them.
// assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO.
osg::ref_ptr<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
vbo->setUsage(GL_DYNAMIC_DRAW_ARB);
osg::ref_ptr<osg::Array> vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL);
if (vertexArray)
{
vertexArray->setVertexBufferObject(vbo);
to.setVertexArray(vertexArray);
}
}
}
void MorphGeometry::addMorphTarget(osg::Vec3Array *offsets, float weight)
{
mMorphTargets.push_back(MorphTarget(offsets, weight));
mMorphedBoundingBox = false;
dirty();
}
void MorphGeometry::dirty()
{
mDirty = true;
if (!mMorphedBoundingBox)
dirtyBound();
}
osg::ref_ptr<osg::Geometry> MorphGeometry::getSourceGeometry() const
{
return mSourceGeometry;
}
void MorphGeometry::accept(osg::NodeVisitor &nv)
{
if (!nv.validNodeMask(*this))
return;
nv.pushOntoNodePath(this);
if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
cull(&nv);
else
nv.apply(*this);
nv.popFromNodePath();
}
void MorphGeometry::accept(osg::PrimitiveFunctor& func) const
{
getGeometry(mLastFrameNumber)->accept(func);
}
osg::BoundingBox MorphGeometry::computeBoundingBox() const
{
bool anyMorphTarget = false;
for (unsigned int i=0; i<mMorphTargets.size(); ++i)
if (mMorphTargets[i].getWeight() > 0)
{
anyMorphTarget = true;
break;
}
// before the MorphGeometry has started animating, we will use a regular bounding box (this is required
// for correct object placements, which uses the bounding box)
if (!mMorphedBoundingBox && !anyMorphTarget)
{
return mSourceGeometry->getBoundingBox();
}
// once it animates, use a bounding box that encompasses all possible animations so as to avoid recalculating
else
{
mMorphedBoundingBox = true;
osg::Vec3Array& sourceVerts = *static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
std::vector<osg::BoundingBox> vertBounds(sourceVerts.size());
// Since we don't know what combinations of morphs are being applied we need to keep track of a bounding box for each vertex.
// The minimum/maximum of the box is the minimum/maximum offset the vertex can have from its starting position.
// Start with zero offsets which will happen when no morphs are applied.
for (unsigned int i=0; i<vertBounds.size(); ++i)
vertBounds[i].set(osg::Vec3f(0,0,0), osg::Vec3f(0,0,0));
for (unsigned int i = 0; i < mMorphTargets.size(); ++i)
{
const osg::Vec3Array& offsets = *mMorphTargets[i].getOffsets();
for (unsigned int j=0; j<offsets.size() && j<vertBounds.size(); ++j)
{
osg::BoundingBox& bounds = vertBounds[j];
bounds.expandBy(bounds._max + offsets[j]);
bounds.expandBy(bounds._min + offsets[j]);
}
}
osg::BoundingBox box;
for (unsigned int i=0; i<vertBounds.size(); ++i)
{
vertBounds[i]._max += sourceVerts[i];
vertBounds[i]._min += sourceVerts[i];
box.expandBy(vertBounds[i]);
}
return box;
}
}
void MorphGeometry::cull(osg::NodeVisitor *nv)
{
if (mLastFrameNumber == nv->getTraversalNumber() || !mDirty)
{
osg::Geometry& geom = *getGeometry(mLastFrameNumber);
nv->pushOntoNodePath(&geom);
nv->apply(geom);
nv->popFromNodePath();
return;
}
mDirty = false;
mLastFrameNumber = nv->getTraversalNumber();
osg::Geometry& geom = *getGeometry(mLastFrameNumber);
const osg::Vec3Array* positionSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(geom.getVertexArray());
assert(positionSrc->size() == positionDst->size());
for (unsigned int vertex=0; vertex<positionSrc->size(); ++vertex)
(*positionDst)[vertex] = (*positionSrc)[vertex];
for (unsigned int i=0; i<mMorphTargets.size(); ++i)
{
float weight = mMorphTargets[i].getWeight();
if (weight == 0.f)
continue;
const osg::Vec3Array* offsets = mMorphTargets[i].getOffsets();
for (unsigned int vertex=0; vertex<positionSrc->size(); ++vertex)
(*positionDst)[vertex] += (*offsets)[vertex] * weight;
}
positionDst->dirty();
nv->pushOntoNodePath(&geom);
nv->apply(geom);
nv->popFromNodePath();
}
osg::Geometry* MorphGeometry::getGeometry(unsigned int frame) const
{
return mGeometry[frame%2];
}
}

@ -0,0 +1,83 @@
#ifndef OPENMW_COMPONENTS_MORPHGEOMETRY_H
#define OPENMW_COMPONENTS_MORPHGEOMETRY_H
#include <osg/Geometry>
namespace SceneUtil
{
/// @brief Vertex morphing implementation.
/// @note The internal Geometry used for rendering is double buffered, this allows updates to be done in a thread safe way while
/// not compromising rendering performance. This is crucial when using osg's default threading model of DrawThreadPerContext.
class MorphGeometry : public osg::Drawable
{
public:
MorphGeometry();
MorphGeometry(const MorphGeometry& copy, const osg::CopyOp& copyop);
META_Object(SceneUtil, MorphGeometry)
/// Initialize this geometry from the source geometry.
/// @note The source geometry will not be modified.
void setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeom);
class MorphTarget
{
protected:
osg::ref_ptr<osg::Vec3Array> mOffsets;
float mWeight;
public:
MorphTarget(osg::Vec3Array* offsets, float w = 1.0) : mOffsets(offsets), mWeight(w) {}
void setWeight(float weight) { mWeight = weight; }
float getWeight() const { return mWeight; }
osg::Vec3Array* getOffsets() { return mOffsets.get(); }
const osg::Vec3Array* getOffsets() const { return mOffsets.get(); }
void setOffsets(osg::Vec3Array* offsets) { mOffsets = offsets; }
};
typedef std::vector<MorphTarget> MorphTargetList;
virtual void addMorphTarget( osg::Vec3Array* offsets, float weight = 1.0 );
/** Set the MorphGeometry dirty.*/
void dirty();
/** Get the list of MorphTargets.*/
const MorphTargetList& getMorphTargetList() const { return mMorphTargets; }
/** Get the list of MorphTargets. Warning if you modify this array you will have to call dirty() */
MorphTargetList& getMorphTargetList() { return mMorphTargets; }
/** Return the \c MorphTarget at position \c i.*/
inline const MorphTarget& getMorphTarget( unsigned int i ) const { return mMorphTargets[i]; }
/** Return the \c MorphTarget at position \c i.*/
inline MorphTarget& getMorphTarget( unsigned int i ) { return mMorphTargets[i]; }
osg::ref_ptr<osg::Geometry> getSourceGeometry() const;
virtual void accept(osg::NodeVisitor &nv);
virtual bool supports(const osg::PrimitiveFunctor&) const { return true; }
virtual void accept(osg::PrimitiveFunctor&) const;
virtual osg::BoundingBox computeBoundingBox() const;
private:
void cull(osg::NodeVisitor* nv);
MorphTargetList mMorphTargets;
osg::ref_ptr<osg::Geometry> mSourceGeometry;
osg::ref_ptr<osg::Geometry> mGeometry[2];
osg::Geometry* getGeometry(unsigned int frame) const;
unsigned int mLastFrameNumber;
bool mDirty; // Have any morph targets changed?
mutable bool mMorphedBoundingBox;
};
}
#endif

@ -10,73 +10,17 @@
namespace SceneUtil
{
class UpdateRigBounds : public osg::Drawable::UpdateCallback
{
public:
UpdateRigBounds()
{
}
UpdateRigBounds(const UpdateRigBounds& copy, const osg::CopyOp& copyop)
: osg::Drawable::UpdateCallback(copy, copyop)
{
}
META_Object(SceneUtil, UpdateRigBounds)
void update(osg::NodeVisitor* nv, osg::Drawable* drw)
{
RigGeometry* rig = static_cast<RigGeometry*>(drw);
rig->updateBounds(nv);
}
};
// TODO: make threadsafe for multiple cull threads
class UpdateRigGeometry : public osg::Drawable::CullCallback
{
public:
UpdateRigGeometry()
{
}
UpdateRigGeometry(const UpdateRigGeometry& copy, const osg::CopyOp& copyop)
: osg::Drawable::CullCallback(copy, copyop)
{
}
META_Object(SceneUtil, UpdateRigGeometry)
virtual bool cull(osg::NodeVisitor* nv, osg::Drawable* drw, osg::State*) const
{
RigGeometry* geom = static_cast<RigGeometry*>(drw);
geom->update(nv);
return false;
}
};
// We can't compute the bounds without a NodeVisitor, since we need the current geomToSkelMatrix.
// So we return nothing. Bounds are updated every frame in the UpdateCallback.
class DummyComputeBoundCallback : public osg::Drawable::ComputeBoundingBoxCallback
{
public:
virtual osg::BoundingBox computeBound(const osg::Drawable&) const { return osg::BoundingBox(); }
};
RigGeometry::RigGeometry()
: mSkeleton(NULL)
, mLastFrameNumber(0)
, mBoundsFirstFrame(true)
{
setCullCallback(new UpdateRigGeometry);
setUpdateCallback(new UpdateRigBounds);
setSupportsDisplayList(false);
setUseVertexBufferObjects(true);
setComputeBoundingBoxCallback(new DummyComputeBoundCallback);
setUpdateCallback(new osg::Callback); // dummy to make sure getNumChildrenRequiringUpdateTraversal() is correct
// update done in accept(NodeVisitor&)
}
RigGeometry::RigGeometry(const RigGeometry &copy, const osg::CopyOp &copyop)
: osg::Geometry(copy, copyop)
: Drawable(copy, copyop)
, mSkeleton(NULL)
, mInfluenceMap(copy.mInfluenceMap)
, mLastFrameNumber(0)
@ -89,57 +33,47 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeometry)
{
mSourceGeometry = sourceGeometry;
osg::Geometry& from = *sourceGeometry;
if (from.getStateSet())
setStateSet(from.getStateSet());
// shallow copy primitive sets & vertex attributes that we will not modify
setPrimitiveSetList(from.getPrimitiveSetList());
setColorArray(from.getColorArray());
setSecondaryColorArray(from.getSecondaryColorArray());
setFogCoordArray(from.getFogCoordArray());
// need to copy over texcoord list manually due to a missing null pointer check in setTexCoordArrayList(), this has been fixed in OSG 3.5
osg::Geometry::ArrayList& texCoordList = from.getTexCoordArrayList();
for (unsigned int i=0; i<texCoordList.size(); ++i)
if (texCoordList[i])
setTexCoordArray(i, texCoordList[i], osg::Array::BIND_PER_VERTEX);
setVertexAttribArrayList(from.getVertexAttribArrayList());
// vertices and normals are modified every frame, so we need to deep copy them.
// assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO.
osg::ref_ptr<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
vbo->setUsage(GL_DYNAMIC_DRAW_ARB);
osg::ref_ptr<osg::Array> vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL);
if (vertexArray)
for (unsigned int i=0; i<2; ++i)
{
vertexArray->setVertexBufferObject(vbo);
setVertexArray(vertexArray);
}
if (osg::Array* normals = from.getNormalArray())
{
osg::ref_ptr<osg::Array> normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL);
if (normalArray)
const osg::Geometry& from = *sourceGeometry;
mGeometry[i] = new osg::Geometry(from, osg::CopyOp::SHALLOW_COPY);
osg::Geometry& to = *mGeometry[i];
to.setSupportsDisplayList(false);
to.setUseVertexBufferObjects(true);
to.setCullingActive(false); // make sure to disable culling since that's handled by this class
// vertices and normals are modified every frame, so we need to deep copy them.
// assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO.
osg::ref_ptr<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
vbo->setUsage(GL_DYNAMIC_DRAW_ARB);
osg::ref_ptr<osg::Array> vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL);
if (vertexArray)
{
normalArray->setVertexBufferObject(vbo);
setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX);
vertexArray->setVertexBufferObject(vbo);
to.setVertexArray(vertexArray);
}
}
if (const osg::Array* normals = from.getNormalArray())
{
osg::ref_ptr<osg::Array> normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL);
if (normalArray)
{
normalArray->setVertexBufferObject(vbo);
to.setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX);
}
}
if (osg::Vec4Array* tangents = dynamic_cast<osg::Vec4Array*>(from.getTexCoordArray(7)))
{
mSourceTangents = tangents;
osg::ref_ptr<osg::Array> tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL);
tangentArray->setVertexBufferObject(vbo);
setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX);
if (const osg::Vec4Array* tangents = dynamic_cast<const osg::Vec4Array*>(from.getTexCoordArray(7)))
{
mSourceTangents = tangents;
osg::ref_ptr<osg::Array> tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL);
tangentArray->setVertexBufferObject(vbo);
to.setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX);
}
else
mSourceTangents = NULL;
}
else
mSourceTangents = NULL;
}
osg::ref_ptr<osg::Geometry> RigGeometry::getSourceGeometry()
@ -228,7 +162,7 @@ void accumulateMatrix(const osg::Matrixf& invBindMatrix, const osg::Matrixf& mat
ptrresult[14] += ptr[14] * weight;
}
void RigGeometry::update(osg::NodeVisitor* nv)
void RigGeometry::cull(osg::NodeVisitor* nv)
{
if (!mSkeleton)
{
@ -238,23 +172,27 @@ void RigGeometry::update(osg::NodeVisitor* nv)
return;
}
if (!mSkeleton->getActive() && mLastFrameNumber != 0)
return;
if (mLastFrameNumber == nv->getTraversalNumber())
if ((!mSkeleton->getActive() && mLastFrameNumber != 0) || mLastFrameNumber == nv->getTraversalNumber())
{
osg::Geometry& geom = *getGeometry(mLastFrameNumber);
nv->pushOntoNodePath(&geom);
nv->apply(geom);
nv->popFromNodePath();
return;
}
mLastFrameNumber = nv->getTraversalNumber();
osg::Geometry& geom = *getGeometry(mLastFrameNumber);
mSkeleton->updateBoneMatrices(nv->getTraversalNumber());
// skinning
osg::Vec3Array* positionSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
osg::Vec3Array* normalSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getNormalArray());
osg::Vec4Array* tangentSrc = mSourceTangents;
const osg::Vec3Array* positionSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
const osg::Vec3Array* normalSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getNormalArray());
const osg::Vec4Array* tangentSrc = mSourceTangents;
osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(getVertexArray());
osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(getNormalArray());
osg::Vec4Array* tangentDst = static_cast<osg::Vec4Array*>(getTexCoordArray(7));
osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(geom.getVertexArray());
osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(geom.getNormalArray());
osg::Vec4Array* tangentDst = static_cast<osg::Vec4Array*>(geom.getTexCoordArray(7));
for (Bone2VertexMap::const_iterator it = mBone2VertexMap.begin(); it != mBone2VertexMap.end(); ++it)
{
@ -294,6 +232,10 @@ void RigGeometry::update(osg::NodeVisitor* nv)
normalDst->dirty();
if (tangentDst)
tangentDst->dirty();
nv->pushOntoNodePath(&geom);
nv->apply(geom);
nv->popFromNodePath();
}
void RigGeometry::updateBounds(osg::NodeVisitor *nv)
@ -365,5 +307,32 @@ void RigGeometry::setInfluenceMap(osg::ref_ptr<InfluenceMap> influenceMap)
mInfluenceMap = influenceMap;
}
void RigGeometry::accept(osg::NodeVisitor &nv)
{
if (!nv.validNodeMask(*this))
return;
nv.pushOntoNodePath(this);
if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
cull(&nv);
else if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR)
updateBounds(&nv);
else
nv.apply(*this);
nv.popFromNodePath();
}
void RigGeometry::accept(osg::PrimitiveFunctor& func) const
{
getGeometry(mLastFrameNumber)->accept(func);
}
osg::Geometry* RigGeometry::getGeometry(unsigned int frame) const
{
return mGeometry[frame%2].get();
}
}

@ -13,10 +13,9 @@ namespace SceneUtil
/// @brief Mesh skinning implementation.
/// @note A RigGeometry may be attached directly to a Skeleton, or somewhere below a Skeleton.
/// Note though that the RigGeometry ignores any transforms below the Skeleton, so the attachment point is not that important.
/// @note To avoid race conditions, the rig geometry needs to be double buffered. This can be done
/// using a FrameSwitch node that has two RigGeometry children. In the future we may want to consider implementing
/// the double buffering inside RigGeometry.
class RigGeometry : public osg::Geometry
/// @note The internal Geometry used for rendering is double buffered, this allows updates to be done in a thread safe way while
/// not compromising rendering performance. This is crucial when using osg's default threading model of DrawThreadPerContext.
class RigGeometry : public osg::Drawable
{
public:
RigGeometry();
@ -24,6 +23,9 @@ namespace SceneUtil
META_Object(SceneUtil, RigGeometry)
// At this point compileGLObjects() remains unimplemented, hard to avoid race conditions
// and there is limited value in compiling anyway since the data will change again for the next frame
struct BoneInfluence
{
osg::Matrixf mInvBindMatrix;
@ -45,15 +47,19 @@ namespace SceneUtil
osg::ref_ptr<osg::Geometry> getSourceGeometry();
// Called automatically by our CullCallback
void update(osg::NodeVisitor* nv);
virtual void accept(osg::NodeVisitor &nv);
virtual bool supports(const osg::PrimitiveFunctor&) const { return true; }
virtual void accept(osg::PrimitiveFunctor&) const;
// Called automatically by our UpdateCallback
private:
void cull(osg::NodeVisitor* nv);
void updateBounds(osg::NodeVisitor* nv);
private:
osg::ref_ptr<osg::Geometry> mGeometry[2];
osg::Geometry* getGeometry(unsigned int frame) const;
osg::ref_ptr<osg::Geometry> mSourceGeometry;
osg::ref_ptr<osg::Vec4Array> mSourceTangents;
osg::ref_ptr<const osg::Vec4Array> mSourceTangents;
Skeleton* mSkeleton;
osg::ref_ptr<osg::RefMatrix> mGeomToSkelMatrix;

@ -6,6 +6,7 @@
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/sceneutil/skeleton.hpp>
#include <components/sceneutil/riggeometry.hpp>
#include <components/sceneutil/morphgeometry.hpp>
namespace SceneUtil
{
@ -37,20 +38,20 @@ public:
}
};
class FrameSwitchSerializer : public osgDB::ObjectWrapper
class RigGeometrySerializer : public osgDB::ObjectWrapper
{
public:
FrameSwitchSerializer()
: osgDB::ObjectWrapper(createInstanceFunc<osg::Group>, "NifOsg::FrameSwitch", "osg::Object osg::Node osg::Group NifOsg::FrameSwitch")
RigGeometrySerializer()
: osgDB::ObjectWrapper(createInstanceFunc<SceneUtil::RigGeometry>, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::RigGeometry")
{
}
};
class RigGeometrySerializer : public osgDB::ObjectWrapper
class MorphGeometrySerializer : public osgDB::ObjectWrapper
{
public:
RigGeometrySerializer()
: osgDB::ObjectWrapper(createInstanceFunc<SceneUtil::RigGeometry>, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable osg::Geometry SceneUtil::RigGeometry")
MorphGeometrySerializer()
: osgDB::ObjectWrapper(createInstanceFunc<SceneUtil::MorphGeometry>, "SceneUtil::MorphGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::MorphGeometry")
{
}
};
@ -95,8 +96,8 @@ void registerSerializers()
osgDB::ObjectWrapperManager* mgr = osgDB::Registry::instance()->getObjectWrapperManager();
mgr->addWrapper(new PositionAttitudeTransformSerializer);
mgr->addWrapper(new SkeletonSerializer);
mgr->addWrapper(new FrameSwitchSerializer);
mgr->addWrapper(new RigGeometrySerializer);
mgr->addWrapper(new MorphGeometrySerializer);
mgr->addWrapper(new LightManagerSerializer);
mgr->addWrapper(new CameraRelativeTransformSerializer);

@ -38,8 +38,6 @@ Skeleton::Skeleton()
, mNeedToUpdateBoneMatrices(true)
, mActive(true)
, mLastFrameNumber(0)
, mTraversedEvenFrame(false)
, mTraversedOddFrame(false)
{
}
@ -50,8 +48,6 @@ Skeleton::Skeleton(const Skeleton &copy, const osg::CopyOp &copyop)
, mNeedToUpdateBoneMatrices(true)
, mActive(copy.mActive)
, mLastFrameNumber(0)
, mTraversedEvenFrame(false)
, mTraversedOddFrame(false)
{
}
@ -115,11 +111,6 @@ void Skeleton::updateBoneMatrices(unsigned int traversalNumber)
mLastFrameNumber = traversalNumber;
if (mLastFrameNumber % 2 == 0)
mTraversedEvenFrame = true;
else
mTraversedOddFrame = true;
if (mNeedToUpdateBoneMatrices)
{
if (mRootBone.get())
@ -144,18 +135,14 @@ bool Skeleton::getActive() const
void Skeleton::markDirty()
{
mTraversedEvenFrame = false;
mTraversedOddFrame = false;
mLastFrameNumber = 0;
mBoneCache.clear();
mBoneCacheInit = false;
}
void Skeleton::traverse(osg::NodeVisitor& nv)
{
if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR
// need to process at least 2 frames before shutting off update, since we need to have both frame-alternating RigGeometries initialized
// this would be more naturally handled if the double-buffering was implemented in RigGeometry itself rather than in a FrameSwitch decorator node
&& mLastFrameNumber != 0 && mTraversedEvenFrame && mTraversedOddFrame)
if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR && mLastFrameNumber != 0)
return;
osg::Group::traverse(nv);
}

@ -74,8 +74,6 @@ namespace SceneUtil
bool mActive;
unsigned int mLastFrameNumber;
bool mTraversedEvenFrame;
bool mTraversedOddFrame;
};
}

@ -13,6 +13,7 @@
#include <components/resource/imagemanager.hpp>
#include <components/vfs/manager.hpp>
#include <components/sceneutil/riggeometry.hpp>
#include <components/sceneutil/morphgeometry.hpp>
#include "shadermanager.hpp"
@ -26,6 +27,7 @@ namespace Shader
, mMaterialOverridden(false)
, mNormalHeight(false)
, mTexStageRequiringTangents(-1)
, mNode(NULL)
{
}
@ -69,7 +71,7 @@ namespace Shader
{
if (node.getStateSet())
{
pushRequirements();
pushRequirements(node);
applyStateSet(node.getStateSet(), node);
traverse(node);
popRequirements();
@ -234,9 +236,10 @@ namespace Shader
}
}
void ShaderVisitor::pushRequirements()
void ShaderVisitor::pushRequirements(osg::Node& node)
{
mRequirements.push_back(mRequirements.back());
mRequirements.back().mNode = &node;
}
void ShaderVisitor::popRequirements()
@ -244,8 +247,12 @@ namespace Shader
mRequirements.pop_back();
}
void ShaderVisitor::createProgram(const ShaderRequirements &reqs, osg::Node& node)
void ShaderVisitor::createProgram(const ShaderRequirements &reqs)
{
if (!reqs.mShaderRequired && !mForceShaders)
return;
osg::Node& node = *reqs.mNode;
osg::StateSet* writableStateSet = NULL;
if (mAllowedToModifyStateSets)
writableStateSet = node.getOrCreateStateSet();
@ -302,12 +309,42 @@ namespace Shader
}
}
bool ShaderVisitor::adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs)
{
bool useShader = reqs.mShaderRequired || mForceShaders;
bool generateTangents = reqs.mTexStageRequiringTangents != -1;
bool changed = false;
if (mAllowedToModifyStateSets && (useShader || generateTangents))
{
// make sure that all UV sets are there
for (std::map<int, std::string>::const_iterator it = reqs.mTextures.begin(); it != reqs.mTextures.end(); ++it)
{
if (sourceGeometry.getTexCoordArray(it->first) == NULL)
{
sourceGeometry.setTexCoordArray(it->first, sourceGeometry.getTexCoordArray(0));
changed = true;
}
}
if (generateTangents)
{
osg::ref_ptr<osgUtil::TangentSpaceGenerator> generator (new osgUtil::TangentSpaceGenerator);
generator->generate(&sourceGeometry, reqs.mTexStageRequiringTangents);
sourceGeometry.setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX);
changed = true;
}
}
return changed;
}
void ShaderVisitor::apply(osg::Geometry& geometry)
{
bool needPop = (geometry.getStateSet() != NULL);
if (geometry.getStateSet())
if (geometry.getStateSet()) // TODO: check if stateset affects shader permutation before pushing it
{
pushRequirements();
pushRequirements(geometry);
applyStateSet(geometry.getStateSet(), geometry);
}
@ -315,44 +352,9 @@ namespace Shader
{
const ShaderRequirements& reqs = mRequirements.back();
bool useShader = reqs.mShaderRequired || mForceShaders;
bool generateTangents = reqs.mTexStageRequiringTangents != -1;
if (mAllowedToModifyStateSets && (useShader || generateTangents))
{
osg::ref_ptr<osg::Geometry> sourceGeometry = &geometry;
SceneUtil::RigGeometry* rig = dynamic_cast<SceneUtil::RigGeometry*>(&geometry);
if (rig)
sourceGeometry = rig->getSourceGeometry();
bool requiresSetGeometry = false;
adjustGeometry(geometry, reqs);
// make sure that all UV sets are there
for (std::map<int, std::string>::const_iterator it = reqs.mTextures.begin(); it != reqs.mTextures.end(); ++it)
{
if (sourceGeometry->getTexCoordArray(it->first) == NULL)
{
sourceGeometry->setTexCoordArray(it->first, sourceGeometry->getTexCoordArray(0));
requiresSetGeometry = true;
}
}
if (generateTangents)
{
osg::ref_ptr<osgUtil::TangentSpaceGenerator> generator (new osgUtil::TangentSpaceGenerator);
generator->generate(sourceGeometry, reqs.mTexStageRequiringTangents);
sourceGeometry->setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX);
requiresSetGeometry = true;
}
if (rig && requiresSetGeometry)
rig->setSourceGeometry(sourceGeometry);
}
// TODO: find a better place for the stateset
if (useShader)
createProgram(reqs, geometry);
createProgram(reqs);
}
if (needPop)
@ -366,16 +368,27 @@ namespace Shader
if (drawable.getStateSet())
{
pushRequirements();
pushRequirements(drawable);
applyStateSet(drawable.getStateSet(), drawable);
}
if (!mRequirements.empty())
{
const ShaderRequirements& reqs = mRequirements.back();
// TODO: find a better place for the stateset
if (reqs.mShaderRequired || mForceShaders)
createProgram(reqs, drawable);
createProgram(reqs);
if (auto rig = dynamic_cast<SceneUtil::RigGeometry*>(&drawable))
{
osg::ref_ptr<osg::Geometry> sourceGeometry = rig->getSourceGeometry();
if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs))
rig->setSourceGeometry(sourceGeometry);
}
else if (auto morph = dynamic_cast<SceneUtil::MorphGeometry*>(&drawable))
{
osg::ref_ptr<osg::Geometry> sourceGeometry = morph->getSourceGeometry();
if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs))
morph->setSourceGeometry(sourceGeometry);
}
}
if (needPop)

@ -52,7 +52,7 @@ namespace Shader
void applyStateSet(osg::ref_ptr<osg::StateSet> stateset, osg::Node& node);
void pushRequirements();
void pushRequirements(osg::Node& node);
void popRequirements();
private:
@ -89,13 +89,17 @@ namespace Shader
// -1 == no tangents required
int mTexStageRequiringTangents;
// the Node that requested these requirements
osg::Node* mNode;
};
std::vector<ShaderRequirements> mRequirements;
std::string mDefaultVsTemplate;
std::string mDefaultFsTemplate;
void createProgram(const ShaderRequirements& reqs, osg::Node& node);
void createProgram(const ShaderRequirements& reqs);
bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs);
};
}

@ -23,6 +23,7 @@ namespace Gui
private:
MyGUI::Widget* mLeft;
MyGUI::Widget* mRight;
MyGUI::Widget* mClient;
void align();
};

@ -56,7 +56,7 @@ Enabling this feature results in better visuals, and a marginally lower frame ra
This setting has no effect if the shader setting is false.
This setting can be toggled with the Refraction button in the Water tab of the Video panel of the Options menu.
This setting can be toggled with the 'Refraction' button in the Water tab of the Video panel of the Options menu.
reflect actors
--------------
@ -68,6 +68,8 @@ reflect actors
This setting controls whether or not NPCs and creatures are drawn in water reflections.
Setting this to true will enable actors in reflections and increase realism with a likely decrease in performance.
This setting can be toggled with the 'Reflect actors' button in the Water tab of the Video panel of the Options menu.
small feature culling pixel size
--------------------------------

@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Name=TES3MP Server Browser
GenericName=Server Browser
Comment=Multiplayer extension for TES3.
Keywords=Morrowind;Multiplayer;Server Browser;TES;openmw;
TryExec=tes3mp-browser
Exec=tes3mp-browser
Icon=openmw
Categories=Game;RolePlaying;
Loading…
Cancel
Save