Merge branch 'master' into script

actorid
Marc Zinnschlag 11 years ago
commit d6e212a02b

1
.gitignore vendored

@ -40,6 +40,7 @@ resources
## generated objects ## generated objects
apps/openmw/config.hpp apps/openmw/config.hpp
components/version/version.hpp
Docs/mainpage.hpp Docs/mainpage.hpp
moc_*.cxx moc_*.cxx
*.cxx_parameters *.cxx_parameters

@ -4,17 +4,17 @@ compiler:
branches: branches:
only: only:
- master - master
- next - /openmw-.*$/
before_install: before_install:
- pwd - pwd
- git submodule update --init --recursive - 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 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 - echo "yes" | sudo apt-add-repository ppa:openmw/openmw
- sudo apt-get update -qq - sudo apt-get update -qq
- sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev uuid-dev - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock
- sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev - sudo apt-get install -qq libqt4-dev
- sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev - sudo apt-get install -qq libopenal-dev
- sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev - sudo apt-get install -qq libavcodec-dev libavformat-dev libavutil-dev libswscale-dev
- sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev - sudo apt-get install -qq libbullet-dev libogre-1.9-dev libmygui-dev libsdl2-dev libunshield-dev
- sudo mkdir /usr/src/gtest/build - sudo mkdir /usr/src/gtest/build
- cd /usr/src/gtest/build - cd /usr/src/gtest/build
@ -37,3 +37,9 @@ notifications:
email: email:
on_success: change on_success: change
on_failure: always on_failure: always
irc:
channels:
- "chat.freenode.net#openmw"
on_success: change
on_failure: always

@ -14,15 +14,30 @@ endif (APPLE)
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/)
include (OpenMWMacros) include(OpenMWMacros)
# Version # Version
set (OPENMW_VERSION_MAJOR 0) include(GetGitRevisionDescription)
set (OPENMW_VERSION_MINOR 27)
set (OPENMW_VERSION_RELEASE 0)
set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") 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)
# doxygen main page # doxygen main page

@ -1,5 +1,10 @@
#include "maindialog.hpp" #include "maindialog.hpp"
#include <components/version/version.hpp>
#include <QLabel>
#include <QDate>
#include <QTime>
#include <QPushButton> #include <QPushButton>
#include <QFontDatabase> #include <QFontDatabase>
#include <QInputDialog> #include <QInputDialog>
@ -67,6 +72,22 @@ Launcher::MainDialog::MainDialog(QWidget *parent)
// Remove what's this? button // Remove what's this? button
setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
// Add version information to bottom of the window
QString revision(OPENMW_VERSION_COMMITHASH);
QString tag(OPENMW_VERSION_TAGHASH);
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)));
createIcons(); createIcons();
} }

@ -33,7 +33,9 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow (int sourceRow, const QModelI
CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent) CSMWorld::IdTableProxyModel::IdTableProxyModel (QObject *parent)
: QSortFilterProxyModel (parent) : QSortFilterProxyModel (parent)
{} {
setSortCaseSensitivity (Qt::CaseInsensitive);
}
QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const QModelIndex CSMWorld::IdTableProxyModel::getModelIndex (const std::string& id, int column) const
{ {

@ -1,7 +1,3 @@
# config file
configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/config.hpp")
# local files # local files
set(GAME set(GAME
main.cpp main.cpp
@ -12,7 +8,6 @@ if(NOT WIN32)
endif() endif()
set(GAME_HEADER set(GAME_HEADER
engine.hpp engine.hpp
config.hpp
) )
source_group(game FILES ${GAME} ${GAME_HEADER}) source_group(game FILES ${GAME} ${GAME_HEADER})

@ -1,6 +1,7 @@
#include <iostream> #include <iostream>
#include <cstdio> #include <cstdio>
#include <components/version/version.hpp>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
#include <SDL.h> #include <SDL.h>
@ -30,8 +31,6 @@ extern int is_debugger_attached(void);
#include <OSX/macUtils.h> #include <OSX/macUtils.h>
#endif #endif
#include "config.hpp"
#include <boost/version.hpp> #include <boost/version.hpp>
/** /**
* Workaround for problems with whitespaces in paths in older versions of Boost library * Workaround for problems with whitespaces in paths in older versions of Boost library

@ -57,6 +57,12 @@ namespace MWGui
class InventoryWindow; class InventoryWindow;
class ContainerWindow; class ContainerWindow;
class DialogueWindow; class DialogueWindow;
enum ShowInDialogueMode {
ShowInDialogueMode_IfPossible,
ShowInDialogueMode_Only,
ShowInDialogueMode_Never
};
} }
namespace SFO namespace SFO
@ -226,7 +232,7 @@ namespace MWBase
virtual void removeDialog(OEngine::GUI::Layout* dialog) = 0; virtual void removeDialog(OEngine::GUI::Layout* dialog) = 0;
///< Hides dialog and schedules dialog to be deleted. ///< Hides dialog and schedules dialog to be deleted.
virtual void messageBox (const std::string& message, const std::vector<std::string>& buttons = std::vector<std::string>(), bool showInDialogueModeOnly = false) = 0; virtual void messageBox (const std::string& message, const std::vector<std::string>& buttons = std::vector<std::string>(), enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0;
virtual void staticMessageBox(const std::string& message) = 0; virtual void staticMessageBox(const std::string& message) = 0;
virtual void removeStaticMessageBox() = 0; virtual void removeStaticMessageBox() = 0;
virtual int readPressedButton() = 0; virtual int readPressedButton() = 0;

@ -529,8 +529,8 @@ namespace MWClass
float moveSpeed; float moveSpeed;
if(normalizedEncumbrance >= 1.0f) if(normalizedEncumbrance >= 1.0f)
moveSpeed = 0.0f; moveSpeed = 0.0f;
else if(mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 && else if(isFlying(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 &&
world->isLevitationEnabled()) world->isLevitationEnabled()))
{ {
float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() + float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() +
mageffects.get(ESM::MagicEffect::Levitate).mMagnitude); mageffects.get(ESM::MagicEffect::Levitate).mMagnitude);

@ -55,7 +55,7 @@ namespace MWGui
getWidget(mRightPane, "RightPane"); getWidget(mRightPane, "RightPane");
getWidget(mArmorRating, "ArmorRating"); getWidget(mArmorRating, "ArmorRating");
mAvatar->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked); mAvatarImage->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked);
getWidget(mItemView, "ItemView"); getWidget(mItemView, "ItemView");
mItemView->eventItemClicked += MyGUI::newDelegate(this, &InventoryWindow::onItemSelected); mItemView->eventItemClicked += MyGUI::newDelegate(this, &InventoryWindow::onItemSelected);
@ -76,11 +76,12 @@ namespace MWGui
void InventoryWindow::adjustPanes() void InventoryWindow::adjustPanes()
{ {
const float aspect = 0.5; // fixed aspect ratio for the left pane const float aspect = 0.5; // fixed aspect ratio for the avatar image
mLeftPane->setSize( (mMainWidget->getSize().height-44) * aspect, mMainWidget->getSize().height-44 ); float leftPaneWidth = (mMainWidget->getSize().height-44-mArmorRating->getHeight()) * aspect;
mRightPane->setCoord( mLeftPane->getPosition().left + (mMainWidget->getSize().height-44) * aspect + 4, mLeftPane->setSize( leftPaneWidth, mMainWidget->getSize().height-44 );
mRightPane->setCoord( mLeftPane->getPosition().left + leftPaneWidth + 4,
mRightPane->getPosition().top, mRightPane->getPosition().top,
mMainWidget->getSize().width - 12 - (mMainWidget->getSize().height-44) * aspect - 15, mMainWidget->getSize().width - 12 - leftPaneWidth - 15,
mMainWidget->getSize().height-44 ); mMainWidget->getSize().height-44 );
} }
@ -418,9 +419,9 @@ namespace MWGui
else else
{ {
MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance ().getLastPressedPosition (MyGUI::MouseButton::Left); MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance ().getLastPressedPosition (MyGUI::MouseButton::Left);
MyGUI::IntPoint relPos = mousePos - mAvatar->getAbsolutePosition (); MyGUI::IntPoint relPos = mousePos - mAvatarImage->getAbsolutePosition ();
int realX = int(float(relPos.left) / float(mAvatar->getSize().width) * 512.f ); int realX = int(float(relPos.left) / float(mAvatarImage->getSize().width) * 512.f );
int realY = int(float(relPos.top) / float(mAvatar->getSize().height) * 1024.f ); int realY = int(float(relPos.top) / float(mAvatarImage->getSize().height) * 1024.f );
MWWorld::Ptr itemSelected = getAvatarSelectedItem (realX, realY); MWWorld::Ptr itemSelected = getAvatarSelectedItem (realX, realY);
if (itemSelected.isEmpty ()) if (itemSelected.isEmpty ())
@ -487,11 +488,18 @@ namespace MWGui
if (mPreviewDirty) if (mPreviewDirty)
{ {
mPreviewDirty = false; mPreviewDirty = false;
MyGUI::IntSize size = mAvatar->getSize(); MyGUI::IntSize size = mAvatarImage->getSize();
mPreview.update (size.width, size.height); mPreview.update (size.width, size.height);
mAvatarImage->setSize(MyGUI::IntSize(std::max(mAvatar->getSize().width, 512), std::max(mAvatar->getSize().height, 1024)));
mAvatarImage->setImageTexture("CharacterPreview"); mAvatarImage->setImageTexture("CharacterPreview");
mAvatarImage->setImageCoord(MyGUI::IntCoord(0, 0, std::min(512, size.width), std::min(1024, size.height)));
mAvatarImage->setImageTile(MyGUI::IntSize(std::min(512, size.width), std::min(1024, size.height)));
mArmorRating->setCaptionWithReplacing ("#{sArmor}: "
+ boost::lexical_cast<std::string>(static_cast<int>(MWWorld::Class::get(mPtr).getArmorRating(mPtr))));
if (mArmorRating->getTextSize().width > mArmorRating->getSize().width)
mArmorRating->setCaptionWithReplacing (boost::lexical_cast<std::string>(static_cast<int>(MWWorld::Class::get(mPtr).getArmorRating(mPtr))));
} }
} }
@ -502,9 +510,6 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->getSpellWindow()->updateSpells(); MWBase::Environment::get().getWindowManager()->getSpellWindow()->updateSpells();
mPreviewDirty = true; mPreviewDirty = true;
mArmorRating->setCaptionWithReplacing ("#{sArmor}: "
+ boost::lexical_cast<std::string>(static_cast<int>(MWWorld::Class::get(mPtr).getArmorRating(mPtr))));
} }
void InventoryWindow::pickUpObject (MWWorld::Ptr object) void InventoryWindow::pickUpObject (MWWorld::Ptr object)
@ -551,9 +556,4 @@ namespace MWGui
MWBase::Environment::get().getMechanicsManager()->itemTaken(player, newObject, count); MWBase::Environment::get().getMechanicsManager()->itemTaken(player, newObject, count);
} }
MyGUI::IntCoord InventoryWindow::getAvatarScreenCoord ()
{
return mAvatar->getAbsoluteCoord ();
}
} }

@ -31,8 +31,6 @@ namespace MWGui
void pickUpObject (MWWorld::Ptr object); void pickUpObject (MWWorld::Ptr object);
MyGUI::IntCoord getAvatarScreenCoord();
MWWorld::Ptr getAvatarSelectedItem(int x, int y); MWWorld::Ptr getAvatarSelectedItem(int x, int y);
void rebuildAvatar() { void rebuildAvatar() {

@ -1,6 +1,8 @@
#ifndef MWGUI_MAPWINDOW_H #ifndef MWGUI_MAPWINDOW_H
#define MWGUI_MAPWINDOW_H #define MWGUI_MAPWINDOW_H
#include <libs/platform/stdint.h>
#include "windowpinnablebase.hpp" #include "windowpinnablebase.hpp"
namespace MWRender namespace MWRender

@ -245,11 +245,11 @@ namespace MWGui
} }
mainWidgetSize.height = textSize.height + textButtonPadding + buttonHeight + buttonMainPadding; mainWidgetSize.height = textSize.height + textButtonPadding + buttonHeight + buttonMainPadding;
MyGUI::IntCoord absCoord; MyGUI::IntPoint absPos;
absCoord.left = (gameWindowSize.width - mainWidgetSize.width)/2; absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2;
absCoord.top = (gameWindowSize.height - mainWidgetSize.height)/2; absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2;
mMainWidget->setCoord(absCoord); mMainWidget->setPosition(absPos);
mMainWidget->setSize(mainWidgetSize); mMainWidget->setSize(mainWidgetSize);
MyGUI::IntCoord messageWidgetCoord; MyGUI::IntCoord messageWidgetCoord;

@ -182,7 +182,7 @@ namespace MWGui
} }
else if (type == "AvatarItemSelection") else if (type == "AvatarItemSelection")
{ {
MyGUI::IntCoord avatarPos = MWBase::Environment::get().getWindowManager()->getInventoryWindow ()->getAvatarScreenCoord (); MyGUI::IntCoord avatarPos = focus->getAbsoluteCoord();
MyGUI::IntPoint relMousePos = MyGUI::InputManager::getInstance ().getMousePosition () - MyGUI::IntPoint(avatarPos.left, avatarPos.top); MyGUI::IntPoint relMousePos = MyGUI::InputManager::getInstance ().getMousePosition () - MyGUI::IntPoint(avatarPos.left, avatarPos.top);
int realX = int(float(relMousePos.left) / float(avatarPos.width) * 512.f ); int realX = int(float(relMousePos.left) / float(avatarPos.width) * 512.f );
int realY = int(float(relMousePos.top) / float(avatarPos.height) * 1024.f ); int realY = int(float(relMousePos.top) / float(avatarPos.height) * 1024.f );

@ -648,20 +648,15 @@ namespace MWGui
mGarbageDialogs.push_back(dialog); mGarbageDialogs.push_back(dialog);
} }
void WindowManager::messageBox (const std::string& message, const std::vector<std::string>& buttons, bool showInDialogueModeOnly) void WindowManager::messageBox (const std::string& message, const std::vector<std::string>& buttons, enum MWGui::ShowInDialogueMode showInDialogueMode)
{ {
if (buttons.empty()) { if (buttons.empty()) {
/* If there are no buttons, and there is a dialogue window open, messagebox goes to the dialogue window */ /* If there are no buttons, and there is a dialogue window open, messagebox goes to the dialogue window */
if (getMode() == GM_Dialogue) { if (getMode() == GM_Dialogue && showInDialogueMode != MWGui::ShowInDialogueMode_Never) {
mDialogueWindow->addMessageBox(MyGUI::LanguageManager::getInstance().replaceTags(message)); mDialogueWindow->addMessageBox(MyGUI::LanguageManager::getInstance().replaceTags(message));
} else { } else if (showInDialogueMode != MWGui::ShowInDialogueMode_Only) {
if (showInDialogueModeOnly) {
if (getMode() == GM_Dialogue)
mMessageBoxManager->createMessageBox(message);
} else {
mMessageBoxManager->createMessageBox(message); mMessageBoxManager->createMessageBox(message);
} }
}
} else { } else {
mMessageBoxManager->createInteractiveMessageBox(message, buttons); mMessageBoxManager->createInteractiveMessageBox(message, buttons);
MWBase::Environment::get().getInputManager()->changeInputMode(isGuiMode()); MWBase::Environment::get().getInputManager()->changeInputMode(isGuiMode());
@ -1035,6 +1030,11 @@ namespace MWGui
{ {
mSelectedSpell = ""; mSelectedSpell = "";
mHud->unsetSelectedSpell(); mHud->unsetSelectedSpell();
MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer();
if (player->getDrawState() == MWMechanics::DrawState_Spell)
player->setDrawState(MWMechanics::DrawState_Nothing);
mSpellWindow->setTitle("#{sNone}"); mSpellWindow->setTitle("#{sNone}");
} }

@ -220,7 +220,7 @@ namespace MWGui
virtual void removeDialog(OEngine::GUI::Layout* dialog); ///< Hides dialog and schedules dialog to be deleted. virtual void removeDialog(OEngine::GUI::Layout* dialog); ///< Hides dialog and schedules dialog to be deleted.
virtual void messageBox (const std::string& message, const std::vector<std::string>& buttons = std::vector<std::string>(), bool showInDialogueModeOnly = false); virtual void messageBox (const std::string& message, const std::vector<std::string>& buttons = std::vector<std::string>(), enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible);
virtual void staticMessageBox(const std::string& message); virtual void staticMessageBox(const std::string& message);
virtual void removeStaticMessageBox(); virtual void removeStaticMessageBox();
virtual int readPressedButton (); ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) virtual int readPressedButton (); ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox)

@ -646,6 +646,10 @@ namespace MWInput
if (!mControlSwitch["playermagic"]) if (!mControlSwitch["playermagic"])
return; return;
// Not allowed if no spell selected
if (MWBase::Environment::get().getWindowManager()->getSelectedSpell().empty())
return;
MWMechanics::DrawState_ state = mPlayer->getDrawState(); MWMechanics::DrawState_ state = mPlayer->getDrawState();
if (state == MWMechanics::DrawState_Weapon || state == MWMechanics::DrawState_Nothing) if (state == MWMechanics::DrawState_Weapon || state == MWMechanics::DrawState_Nothing)
mPlayer->setDrawState(MWMechanics::DrawState_Spell); mPlayer->setDrawState(MWMechanics::DrawState_Spell);
@ -748,7 +752,8 @@ namespace MWInput
void InputManager::showQuickKeysMenu() void InputManager::showQuickKeysMenu()
{ {
if (!MWBase::Environment::get().getWindowManager()->isGuiMode ()) if (!MWBase::Environment::get().getWindowManager()->isGuiMode ()
&& MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1)
MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_QuickKeysMenu); MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_QuickKeysMenu);
else if (MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_QuickKeysMenu) else if (MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_QuickKeysMenu)
MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_QuickKeysMenu); MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_QuickKeysMenu);

@ -776,6 +776,27 @@ namespace MWMechanics
{ {
if(!paused) if(!paused)
{ {
// Reset data from previous frame
for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
{
// Reset last hit object, which is only valid for one frame
// Note, the new hit object for this frame may be set by CharacterController::update -> Animation::runAnimation
// (below)
iter->first.getClass().getCreatureStats(iter->first).setLastHitObject(std::string());
}
// AI and magic effects update
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
{
if (!iter->first.getClass().getCreatureStats(iter->first).isDead())
{
updateActor(iter->first, duration);
if(iter->first.getTypeName() == typeid(ESM::NPC).name())
updateNpc(iter->first, duration, paused);
}
}
// Looping magic VFX update
// Note: we need to do this before any of the animations are updated. // Note: we need to do this before any of the animations are updated.
// Reaching the text keys may trigger Hit / Spellcast (and as such, particles), // Reaching the text keys may trigger Hit / Spellcast (and as such, particles),
// so updating VFX immediately after that would just remove the particle effects instantly. // so updating VFX immediately after that would just remove the particle effects instantly.
@ -783,6 +804,7 @@ namespace MWMechanics
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
iter->second->updateContinuousVfx(); iter->second->updateContinuousVfx();
// Animation/movement update
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
{ {
if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects().get( if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects().get(
@ -790,18 +812,8 @@ namespace MWMechanics
iter->second->skipAnim(); iter->second->skipAnim();
iter->second->update(duration); iter->second->update(duration);
} }
}
if (!paused)
{
for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
{
// Reset last hit object, which is only valid for one frame
// Note, the new hit object for this frame may be set by CharacterController::update -> Animation::runAnimation
// (below)
iter->first.getClass().getCreatureStats(iter->first).setLastHitObject(std::string());
}
// Kill dead actors
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++) for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++)
{ {
const MWWorld::Class &cls = MWWorld::Class::get(iter->first); const MWWorld::Class &cls = MWWorld::Class::get(iter->first);
@ -812,10 +824,6 @@ namespace MWMechanics
if(iter->second->isDead()) if(iter->second->isDead())
iter->second->resurrect(); iter->second->resurrect();
updateActor(iter->first, duration);
if(iter->first.getTypeName() == typeid(ESM::NPC).name())
updateNpc(iter->first, duration, paused);
if(!stats.isDead()) if(!stats.isDead())
continue; continue;
} }

@ -168,7 +168,7 @@ namespace MWMechanics
float rangeMelee; float rangeMelee;
float rangeCloseUp; float rangeCloseUp;
bool distantCombat = false; bool distantCombat = false;
if (weaptype==WeapType_BowAndArrow || weaptype==WeapType_Crossbow || weaptype==WeapType_ThowWeapon) if (weaptype==WeapType_BowAndArrow || weaptype==WeapType_Crossbow || weaptype==WeapType_Thrown)
{ {
rangeMelee = 1000; // TODO: should depend on archer skill rangeMelee = 1000; // TODO: should depend on archer skill
rangeCloseUp = 0; //doesn't needed when attacking from distance rangeCloseUp = 0; //doesn't needed when attacking from distance

@ -119,7 +119,7 @@ static const struct WeaponInfo {
{ WeapType_TwoWide, "2w", "weapontwowide" }, { WeapType_TwoWide, "2w", "weapontwowide" },
{ WeapType_BowAndArrow, "1h", "bowandarrow" }, { WeapType_BowAndArrow, "1h", "bowandarrow" },
{ WeapType_Crossbow, "crossbow", "crossbow" }, { WeapType_Crossbow, "crossbow", "crossbow" },
{ WeapType_ThowWeapon, "1h", "throwweapon" }, { WeapType_Thrown, "1h", "throwweapon" },
{ WeapType_PickProbe, "1h", "pickprobe" }, { WeapType_PickProbe, "1h", "pickprobe" },
{ WeapType_Spell, "spell", "spellcast" }, { WeapType_Spell, "spell", "spellcast" },
}; };
@ -157,7 +157,14 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock();
if(mHitState == CharState_None) if(mHitState == CharState_None)
{ {
if(knockdown) if (mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0)
{
mHitState = CharState_KnockOut;
mCurrentHit = "knockout";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, false, 1, "start", "stop", 0.0f, ~0ul);
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true);
}
else if(knockdown)
{ {
mHitState = CharState_KnockDown; mHitState = CharState_KnockDown;
mCurrentHit = "knockdown"; mCurrentHit = "knockdown";
@ -187,6 +194,12 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
mPtr.getClass().getCreatureStats(mPtr).setBlock(false); mPtr.getClass().getCreatureStats(mPtr).setBlock(false);
mHitState = CharState_None; mHitState = CharState_None;
} }
else if (mHitState == CharState_KnockOut && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0)
{
mHitState = CharState_KnockDown;
mAnimation->disable(mCurrentHit);
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "loop stop", "stop", 0.0f, 0);
}
} }
const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType));
@ -302,11 +315,24 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
if(!mCurrentMovement.empty()) if(!mCurrentMovement.empty())
{ {
float vel, speedmult = 1.0f; float vel, speedmult = 1.0f;
bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run);
if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 1.0f) if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 1.0f)
speedmult = mMovementSpeed / 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)
// The first person anims don't have any velocity to calculate a speed multiplier from.
// We use the third person velocities instead.
// FIXME: should be pulled from the actual animation, but it is not presently loaded.
speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f);
mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false,
speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
mMovementAnimVelocity = vel;
} }
else mMovementAnimVelocity = 0.0f;
} }
} }
@ -367,7 +393,7 @@ MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::I
*weaptype = WeapType_Crossbow; *weaptype = WeapType_Crossbow;
break; break;
case ESM::Weapon::MarksmanThrown: case ESM::Weapon::MarksmanThrown:
*weaptype = WeapType_ThowWeapon; *weaptype = WeapType_Thrown;
break; break;
} }
} }
@ -403,6 +429,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
, mIdleState(CharState_None) , mIdleState(CharState_None)
, mMovementState(CharState_None) , mMovementState(CharState_None)
, mMovementSpeed(0.0f) , mMovementSpeed(0.0f)
, mMovementAnimVelocity(0.0f)
, mDeathState(CharState_None) , mDeathState(CharState_None)
, mHitState(CharState_None) , mHitState(CharState_None)
, mUpperBodyState(UpperCharState_Nothing) , mUpperBodyState(UpperCharState_Nothing)
@ -534,6 +561,7 @@ bool CharacterController::updateWeaponState()
{ {
getWeaponGroup(weaptype, weapgroup); getWeaponGroup(weaptype, weapgroup);
mAnimation->showWeapons(false); mAnimation->showWeapons(false);
mAnimation->setWeaponGroup(weapgroup);
mAnimation->play(weapgroup, Priority_Weapon, mAnimation->play(weapgroup, Priority_Weapon,
MWRender::Animation::Group_UpperBody, true, MWRender::Animation::Group_UpperBody, true,
@ -588,6 +616,19 @@ bool CharacterController::updateWeaponState()
if(isWeapon) if(isWeapon)
weapSpeed = weapon->get<ESM::Weapon>()->mBase->mData.mSpeed; weapSpeed = weapon->get<ESM::Weapon>()->mBase->mData.mSpeed;
// Cancel attack if we no longer have ammunition
bool ammunition = true;
MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
if (mWeaponType == WeapType_Crossbow)
ammunition = (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt);
else if (mWeaponType == WeapType_BowAndArrow)
ammunition = (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Arrow);
if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped)
{
mAnimation->disable(mCurrentWeapon);
mUpperBodyState = UpperCharState_WeapEquiped;
}
float complete; float complete;
bool animPlaying; bool animPlaying;
if(stats.getAttackingOrSpell()) if(stats.getAttackingOrSpell())
@ -692,10 +733,10 @@ bool CharacterController::updateWeaponState()
if(item.getRefData().getCount()) if(item.getRefData().getCount())
MWBase::Environment::get().getWindowManager()->setSelectedWeapon(item); MWBase::Environment::get().getWindowManager()->setSelectedWeapon(item);
} }
else else if (ammunition)
{ {
if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow ||
mWeaponType == WeapType_ThowWeapon) mWeaponType == WeapType_Thrown)
mAttackType = "shoot"; mAttackType = "shoot";
else else
{ {
@ -759,12 +800,59 @@ bool CharacterController::updateWeaponState()
} }
} }
mAnimation->setPitchFactor(0.f);
if (mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown)
{
switch (mUpperBodyState)
{
case UpperCharState_StartToMinAttack:
mAnimation->setPitchFactor(complete);
break;
case UpperCharState_MinAttackToMaxAttack:
case UpperCharState_MaxAttackToMinHit:
case UpperCharState_MinHitToHit:
mAnimation->setPitchFactor(1.f);
break;
case UpperCharState_FollowStartToFollowStop:
if (animPlaying)
mAnimation->setPitchFactor(1.f-complete);
break;
default:
break;
}
}
else if (mWeaponType == WeapType_Crossbow)
{
switch (mUpperBodyState)
{
case UpperCharState_EquipingWeap:
mAnimation->setPitchFactor(complete);
break;
case UpperCharState_UnEquipingWeap:
mAnimation->setPitchFactor(1.f-complete);
break;
case UpperCharState_WeapEquiped:
case UpperCharState_StartToMinAttack:
case UpperCharState_MinAttackToMaxAttack:
case UpperCharState_MaxAttackToMinHit:
case UpperCharState_MinHitToHit:
case UpperCharState_FollowStartToFollowStop:
mAnimation->setPitchFactor(1.f);
break;
default:
break;
}
}
if(!animPlaying) if(!animPlaying)
{ {
if(mUpperBodyState == UpperCharState_EquipingWeap || if(mUpperBodyState == UpperCharState_EquipingWeap ||
mUpperBodyState == UpperCharState_FollowStartToFollowStop || mUpperBodyState == UpperCharState_FollowStartToFollowStop ||
mUpperBodyState == UpperCharState_CastingSpell) mUpperBodyState == UpperCharState_CastingSpell)
{ {
if (ammunition && mWeaponType == WeapType_Crossbow)
mAnimation->attachArrow();
mUpperBodyState = UpperCharState_WeapEquiped; mUpperBodyState = UpperCharState_WeapEquiped;
//don't allow to continue playing hit animation on UpperBody after actor had attacked during it //don't allow to continue playing hit animation on UpperBody after actor had attacked during it
if(mHitState == CharState_Hit) if(mHitState == CharState_Hit)
@ -773,6 +861,7 @@ bool CharacterController::updateWeaponState()
//commenting out following 2 lines will give a bit different combat dynamics(slower) //commenting out following 2 lines will give a bit different combat dynamics(slower)
mHitState = CharState_None; mHitState = CharState_None;
mCurrentHit.clear(); mCurrentHit.clear();
mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false);
} }
} }
else if(mUpperBodyState == UpperCharState_UnEquipingWeap) else if(mUpperBodyState == UpperCharState_UnEquipingWeap)
@ -788,6 +877,13 @@ bool CharacterController::updateWeaponState()
stop = mAttackType+" max attack"; stop = mAttackType+" max attack";
mUpperBodyState = UpperCharState_MinAttackToMaxAttack; mUpperBodyState = UpperCharState_MinAttackToMaxAttack;
break; break;
case UpperCharState_MinAttackToMaxAttack:
//hack to avoid body pos desync when jumping/sneaking in 'max attack' state
if(!mAnimation->isPlaying(mCurrentWeapon))
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, false,
0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0);
break;
case UpperCharState_MaxAttackToMinHit: case UpperCharState_MaxAttackToMinHit:
if(mAttackType == "shoot") if(mAttackType == "shoot")
{ {
@ -837,6 +933,22 @@ bool CharacterController::updateWeaponState()
} }
} }
//if playing combat animation and lowerbody is not busy switch to whole body animation
if((weaptype != WeapType_None || UpperCharState_UnEquipingWeap) && animPlaying)
{
if( mMovementState != CharState_None ||
mJumpState != JumpState_None ||
mHitState != CharState_None ||
MWBase::Environment::get().getWorld()->isSwimming(mPtr) ||
cls.getCreatureStats(mPtr).getMovementFlag(CreatureStats::Flag_Sneak))
{
mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_UpperBody);
}
else
{
mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_All);
}
}
MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name()
@ -1121,15 +1233,20 @@ void CharacterController::update(float duration)
if (!mSkipAnim) if (!mSkipAnim)
{ {
rot *= Ogre::Math::RadiansToDegrees(1.0f); rot *= Ogre::Math::RadiansToDegrees(1.0f);
if(mHitState != CharState_KnockDown) if(mHitState != CharState_KnockDown && mHitState != CharState_KnockOut)
{ {
world->rotateObject(mPtr, rot.x, rot.y, rot.z, true); world->rotateObject(mPtr, rot.x, rot.y, rot.z, true);
} }
else //avoid z-rotating for knockdown else //avoid z-rotating for knockdown
world->rotateObject(mPtr, rot.x, rot.y, 0.0f, true); world->rotateObject(mPtr, rot.x, rot.y, 0.0f, true);
// always control actual movement by animation unless this:
// FIXME: actor falling/landing should be controlled by physics engine
if(mMovementAnimVelocity == 0.0f && (vec.length() > 0.0f || mJumpState != JumpState_None))
{
world->queueMovement(mPtr, vec); world->queueMovement(mPtr, vec);
} }
}
movement = vec; movement = vec;
cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = cls.getMovementSettings(mPtr).mPosition[2] = 0; cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = cls.getMovementSettings(mPtr).mPosition[2] = 0;

@ -93,6 +93,7 @@ enum CharacterState {
CharState_Hit, CharState_Hit,
CharState_KnockDown, CharState_KnockDown,
CharState_KnockOut,
CharState_Block CharState_Block
}; };
@ -105,7 +106,7 @@ enum WeaponType {
WeapType_TwoWide, WeapType_TwoWide,
WeapType_BowAndArrow, WeapType_BowAndArrow,
WeapType_Crossbow, WeapType_Crossbow,
WeapType_ThowWeapon, WeapType_Thrown,
WeapType_PickProbe, WeapType_PickProbe,
WeapType_Spell WeapType_Spell
@ -144,6 +145,7 @@ class CharacterController
CharacterState mMovementState; CharacterState mMovementState;
std::string mCurrentMovement; std::string mCurrentMovement;
float mMovementSpeed; float mMovementSpeed;
float mMovementAnimVelocity;
CharacterState mDeathState; CharacterState mDeathState;
std::string mCurrentDeath; std::string mCurrentDeath;

@ -179,9 +179,6 @@ namespace MWMechanics
mDynamic[index] = value; mDynamic[index] = value;
if (index == 2 && value.getCurrent() < 0)
setKnockedDown(true);
if (index==0 && mDynamic[index].getCurrent()<1) if (index==0 && mDynamic[index].getCurrent()<1)
{ {
if (!mDead) if (!mDead)

@ -220,16 +220,18 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas
/// \todo check if character is the player, if levelling is ever implemented for NPCs /// \todo check if character is the player, if levelling is ever implemented for NPCs
MWBase::Environment::get().getSoundManager ()->playSound ("skillraise", 1, 1); MWBase::Environment::get().getSoundManager ()->playSound ("skillraise", 1, 1);
std::vector <std::string> noButtons;
std::stringstream message; std::stringstream message;
message << boost::format(MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", "")) message << boost::format(MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", ""))
% std::string("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}") % std::string("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}")
% static_cast<int> (base); % static_cast<int> (base);
MWBase::Environment::get().getWindowManager ()->messageBox(message.str()); MWBase::Environment::get().getWindowManager ()->messageBox(message.str(), noButtons, MWGui::ShowInDialogueMode_Never);
if (mLevelProgress >= gmst.find("iLevelUpTotal")->getInt()) if (mLevelProgress >= gmst.find("iLevelUpTotal")->getInt())
{ {
// levelup is possible now // levelup is possible now
MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}"); MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}", noButtons, MWGui::ShowInDialogueMode_Never);
} }
getSkill (skillIndex).setBase (base); getSkill (skillIndex).setBase (base);

@ -158,7 +158,9 @@ namespace
namespace MWMechanics namespace MWMechanics
{ {
PathFinder::PathFinder() PathFinder::PathFinder()
:mIsPathConstructed(false),mIsGraphConstructed(false) : mIsPathConstructed(false),
mIsGraphConstructed(false),
mCell(NULL)
{ {
} }

@ -232,6 +232,24 @@ namespace MWMechanics
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find ( MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
effectIt->mEffectID); effectIt->mEffectID);
if (!MWBase::Environment::get().getWorld()->isLevitationEnabled() && effectIt->mEffectID == ESM::MagicEffect::Levitate)
{
if (caster.getRefData().getHandle() == "player")
MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}");
continue;
}
if (!MWBase::Environment::get().getWorld()->isTeleportingEnabled() &&
(effectIt->mEffectID == ESM::MagicEffect::AlmsiviIntervention ||
effectIt->mEffectID == ESM::MagicEffect::DivineIntervention ||
effectIt->mEffectID == ESM::MagicEffect::Mark ||
effectIt->mEffectID == ESM::MagicEffect::Recall))
{
if (caster.getRefData().getHandle() == "player")
MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
continue;
}
// If player is healing someone, show the target's HP bar // If player is healing someone, show the target's HP bar
if (caster.getRefData().getHandle() == "player" && target != caster if (caster.getRefData().getHandle() == "player" && target != caster
&& effectIt->mEffectID == ESM::MagicEffect::RestoreHealth && effectIt->mEffectID == ESM::MagicEffect::RestoreHealth

@ -53,7 +53,6 @@ void Animation::EffectAnimationTime::setValue(Ogre::Real)
Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node)
: mPtr(ptr) : mPtr(ptr)
, mCamera(NULL)
, mInsert(node) , mInsert(node)
, mSkelBase(NULL) , mSkelBase(NULL)
, mAccumRoot(NULL) , mAccumRoot(NULL)
@ -696,6 +695,12 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co
else else
mPtr.getClass().hit(mPtr); mPtr.getClass().hit(mPtr);
} }
else if (evt.compare(off, len, "shoot attach") == 0)
attachArrow();
else if (evt.compare(off, len, "shoot release") == 0)
releaseArrow();
else if (evt.compare(off, len, "shoot follow attach") == 0)
attachArrow();
else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release") else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release")
MWBase::Environment::get().getWorld()->castSpell(mPtr); MWBase::Environment::get().getWorld()->castSpell(mPtr);
@ -870,6 +875,27 @@ bool Animation::getInfo(const std::string &groupname, float *complete, float *sp
return true; return true;
} }
float Animation::getStartTime(const std::string &groupname) const
{
AnimSourceList::const_iterator iter(mAnimSources.begin());
for(;iter != mAnimSources.end();iter++)
{
const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys;
NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname);
if(found != keys.end())
return found->first;
}
return -1.f;
}
float Animation::getCurrentTime(const std::string &groupname) const
{
AnimStateMap::const_iterator iter = mStates.find(groupname);
if(iter == mStates.end())
return -1.f;
return iter->second.mTime;
}
void Animation::disable(const std::string &groupname) void Animation::disable(const std::string &groupname)
{ {
@ -1044,9 +1070,8 @@ bool Animation::allowSwitchViewMode() const
{ {
for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter) for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter)
{ {
if(stateiter->second.mGroups == Group_UpperBody if(stateiter->second.mPriority > MWMechanics::Priority_Movement
|| (stateiter->first.size()==4 && stateiter->first.find("hit") != std::string::npos) && stateiter->second.mPriority < MWMechanics::Priority_Torch)
|| (stateiter->first.find("knock") != std::string::npos) )
return false; return false;
} }
return true; return true;

@ -121,7 +121,6 @@ protected:
std::vector<EffectParams> mEffects; std::vector<EffectParams> mEffects;
MWWorld::Ptr mPtr; MWWorld::Ptr mPtr;
Camera *mCamera;
Ogre::SceneNode *mInsert; Ogre::SceneNode *mInsert;
Ogre::Entity *mSkelBase; Ogre::Entity *mSkelBase;
@ -271,27 +270,37 @@ public:
*/ */
bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const; bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const;
/// Get the absolute position in the animation track of the first text key with the given group.
float getStartTime(const std::string &groupname) const;
/// Get the current absolute position in the animation track for the animation that is currently playing from the given group.
float getCurrentTime(const std::string& groupname) const;
/** Disables the specified animation group; /** Disables the specified animation group;
* \param groupname Animation group to disable. * \param groupname Animation group to disable.
*/ */
void disable(const std::string &groupname); void disable(const std::string &groupname);
void changeGroups(const std::string &groupname, int group); void changeGroups(const std::string &groupname, int group);
virtual void setWeaponGroup(const std::string& group) {}
/** Retrieves the velocity (in units per second) that the animation will move. */ /** Retrieves the velocity (in units per second) that the animation will move. */
float getVelocity(const std::string &groupname) const; float getVelocity(const std::string &groupname) const;
/// 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) {}
virtual Ogre::Vector3 runAnimation(float duration); virtual Ogre::Vector3 runAnimation(float duration);
virtual void showWeapons(bool showWeapon); virtual void showWeapons(bool showWeapon);
virtual void showCarriedLeft(bool show) {} virtual void showCarriedLeft(bool show) {}
virtual void attachArrow() {}
virtual void releaseArrow() {}
void enableLights(bool enable); void enableLights(bool enable);
Ogre::AxisAlignedBox getWorldBounds(); Ogre::AxisAlignedBox getWorldBounds();
void setCamera(Camera *cam)
{ mCamera = cam; }
Ogre::Node *getNode(const std::string &name); Ogre::Node *getNode(const std::string &name);
// Attaches the given object to a bone on this object's base skeleton. If the bone doesn't // Attaches the given object to a bone on this object's base skeleton. If the bone doesn't

@ -338,11 +338,9 @@ namespace MWRender
if(mAnimation && mAnimation != anim) if(mAnimation && mAnimation != anim)
{ {
mAnimation->setViewMode(NpcAnimation::VM_Normal); mAnimation->setViewMode(NpcAnimation::VM_Normal);
mAnimation->setCamera(NULL);
mAnimation->detachObjectFromBone(mCamera); mAnimation->detachObjectFromBone(mCamera);
} }
mAnimation = anim; mAnimation = anim;
mAnimation->setCamera(this);
processViewChange(); processViewChange();
} }

@ -128,7 +128,7 @@ namespace MWRender
InventoryPreview::InventoryPreview(MWWorld::Ptr character) InventoryPreview::InventoryPreview(MWWorld::Ptr character)
: CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 62, -200), Ogre::Vector3(0, 62, 0)) : CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 65, -180), Ogre::Vector3(0,65,0))
, mSelectionBuffer(NULL) , mSelectionBuffer(NULL)
{ {
} }
@ -161,13 +161,13 @@ namespace MWRender
type == ESM::Weapon::LongBladeOneHand || type == ESM::Weapon::LongBladeOneHand ||
type == ESM::Weapon::BluntOneHand || type == ESM::Weapon::BluntOneHand ||
type == ESM::Weapon::AxeOneHand || type == ESM::Weapon::AxeOneHand ||
type == ESM::Weapon::MarksmanThrown) type == ESM::Weapon::MarksmanThrown ||
type == ESM::Weapon::MarksmanCrossbow ||
type == ESM::Weapon::MarksmanBow)
groupname = "inventoryweapononehand"; groupname = "inventoryweapononehand";
else if(type == ESM::Weapon::LongBladeTwoHand || else if(type == ESM::Weapon::LongBladeTwoHand ||
type == ESM::Weapon::BluntTwoClose || type == ESM::Weapon::BluntTwoClose ||
type == ESM::Weapon::AxeTwoHand || type == ESM::Weapon::AxeTwoHand)
type == ESM::Weapon::MarksmanCrossbow ||
type == ESM::Weapon::MarksmanBow)
groupname = "inventoryweapontwohand"; groupname = "inventoryweapontwohand";
else if(type == ESM::Weapon::BluntTwoWide || else if(type == ESM::Weapon::BluntTwoWide ||
type == ESM::Weapon::SpearTwoWide) type == ESM::Weapon::SpearTwoWide)

@ -291,7 +291,7 @@ namespace MWRender
unsigned int imageY = (yLength - (y + 1)) * cellImageSizeSrc; unsigned int imageY = (yLength - (y + 1)) * cellImageSizeSrc;
assert(imageX < image.getWidth()); assert(imageX < image.getWidth());
assert(imageY < image.getWidth()); assert(imageY < image.getHeight());
if (image.getColourAt(imageX, imageY, 0).a > 0) if (image.getColourAt(imageX, imageY, 0).a > 0)
exploredCells.push_back(std::make_pair(x+bounds.mMinX,y+bounds.mMinY)); exploredCells.push_back(std::make_pair(x+bounds.mMinX,y+bounds.mMinY));

@ -71,6 +71,27 @@ float HeadAnimationTime::getValue() const
return 1; 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() static NpcAnimation::PartBoneMap createPartListMap()
{ {
NpcAnimation::PartBoneMap result; NpcAnimation::PartBoneMap result;
@ -121,11 +142,13 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v
mShowCarriedLeft(true), mShowCarriedLeft(true),
mFirstPersonOffset(0.f, 0.f, 0.f), mFirstPersonOffset(0.f, 0.f, 0.f),
mAlpha(1.f), mAlpha(1.f),
mNpcType(Type_Normal) mNpcType(Type_Normal),
mPitchFactor(0)
{ {
mNpc = mPtr.get<ESM::NPC>()->mBase; mNpc = mPtr.get<ESM::NPC>()->mBase;
mHeadAnimationTime = Ogre::SharedPtr<HeadAnimationTime>(new HeadAnimationTime(mPtr)); mHeadAnimationTime = Ogre::SharedPtr<HeadAnimationTime>(new HeadAnimationTime(mPtr));
mWeaponAnimationTime = Ogre::SharedPtr<WeaponAnimationTime>(new WeaponAnimationTime(this));
for(size_t i = 0;i < ESM::PRT_Count;i++) for(size_t i = 0;i < ESM::PRT_Count;i++)
{ {
@ -223,6 +246,8 @@ void NpcAnimation::updateNpcBase()
for(size_t i = 0;i < ESM::PRT_Count;i++) for(size_t i = 0;i < ESM::PRT_Count;i++)
removeIndividualPart((ESM::PartReferenceType)i); removeIndividualPart((ESM::PartReferenceType)i);
updateParts(); updateParts();
mWeaponAnimationTime->updateStartTime();
} }
void NpcAnimation::updateParts() void NpcAnimation::updateParts()
@ -498,16 +523,25 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed)
Ogre::Vector3 ret = Animation::runAnimation(timepassed); Ogre::Vector3 ret = Animation::runAnimation(timepassed);
Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton();
if(mViewMode == VM_FirstPerson && mCamera) if(mViewMode == VM_FirstPerson)
{ {
float pitch = mCamera->getPitch(); float pitch = mPtr.getRefData().getPosition().rot[0];
Ogre::Node *node = baseinst->getBone("Bip01 Neck"); Ogre::Node *node = baseinst->getBone("Bip01 Neck");
node->pitch(Ogre::Radian(pitch*0.75f), Ogre::Node::TS_WORLD); node->pitch(Ogre::Radian(pitch), Ogre::Node::TS_WORLD);
// This has to be done before this function ends; // This has to be done before this function ends;
// updateSkeletonInstance, below, touches the hands. // updateSkeletonInstance, below, touches the hands.
node->translate(mFirstPersonOffset, Ogre::Node::TS_WORLD); node->translate(mFirstPersonOffset, Ogre::Node::TS_WORLD);
} }
else if (mPitchFactor > 0)
{
// 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);
}
mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame. mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame.
for(size_t i = 0;i < ESM::PRT_Count;i++) for(size_t i = 0;i < ESM::PRT_Count;i++)
@ -588,9 +622,6 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
updateSkeletonInstance(mSkelBase->getSkeleton(), skel); 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<Ogre::Controller<Ogre::Real> >::iterator ctrl(mObjectParts[type]->mControllers.begin()); std::vector<Ogre::Controller<Ogre::Real> >::iterator ctrl(mObjectParts[type]->mControllers.begin());
for(;ctrl != mObjectParts[type]->mControllers.end();ctrl++) for(;ctrl != mObjectParts[type]->mControllers.end();ctrl++)
{ {
@ -600,6 +631,8 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
if (type == ESM::PRT_Head) if (type == ESM::PRT_Head)
ctrl->setSource(mHeadAnimationTime); ctrl->setSource(mHeadAnimationTime);
else if (type == ESM::PRT_Weapon)
ctrl->setSource(mWeaponAnimationTime);
} }
} }
@ -663,6 +696,18 @@ void NpcAnimation::showWeapons(bool showWeapon)
std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon); std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon);
addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1,
mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor);
if (weapon->getTypeName() == typeid(ESM::Weapon).name() &&
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
{
MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
if (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt)
attachArrow();
else
mAmmunition.setNull();
}
else
mAmmunition.setNull();
} }
} }
else else
@ -693,6 +738,52 @@ void NpcAnimation::showCarriedLeft(bool show)
removeIndividualPart(ESM::PRT_Shield); removeIndividualPart(ESM::PRT_Shield);
} }
void NpcAnimation::attachArrow()
{
MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weaponSlot != inv.end() && weaponSlot->get<ESM::Weapon>()->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);
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(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::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<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
{
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;
inv.remove(*ammo, 1, mPtr);
mAmmunition.setNull();
}
}
void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound)
{ {
// During first auto equip, we don't play any sounds. // During first auto equip, we don't play any sounds.

@ -25,6 +25,23 @@ public:
{ } { }
}; };
class WeaponAnimationTime : public Ogre::ControllerValue<Ogre::Real>
{
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 MWWorld::InventoryStoreListener
{ {
public: public:
@ -71,8 +88,10 @@ private:
Ogre::Vector3 mFirstPersonOffset; Ogre::Vector3 mFirstPersonOffset;
Ogre::SharedPtr<HeadAnimationTime> mHeadAnimationTime; Ogre::SharedPtr<HeadAnimationTime> mHeadAnimationTime;
Ogre::SharedPtr<WeaponAnimationTime> mWeaponAnimationTime;
float mAlpha; float mAlpha;
float mPitchFactor;
void updateNpcBase(); void updateNpcBase();
@ -105,11 +124,22 @@ public:
ViewMode viewMode=VM_Normal); ViewMode viewMode=VM_Normal);
virtual ~NpcAnimation(); virtual ~NpcAnimation();
virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); }
virtual Ogre::Vector3 runAnimation(float timepassed); virtual Ogre::Vector3 runAnimation(float timepassed);
/// 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 showWeapons(bool showWeapon); virtual void showWeapons(bool showWeapon);
virtual void showCarriedLeft(bool showa); virtual void showCarriedLeft(bool showa);
virtual void attachArrow();
virtual void releaseArrow();
NifOgre::ObjectScenePtr mAmmunition;
void setViewMode(ViewMode viewMode); void setViewMode(ViewMode viewMode);
void updateParts(); void updateParts();

@ -1,5 +1,14 @@
#include "terrainstorage.hpp" #include "terrainstorage.hpp"
#include <OgreVector2.h>
#include <OgreTextureManager.h>
#include <OgreStringConverter.h>
#include <OgreRenderSystem.h>
#include <OgreResourceGroupManager.h>
#include <OgreRoot.h>
#include <boost/algorithm/string.hpp>
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
@ -53,4 +62,509 @@ namespace MWRender
return esmStore.get<ESM::LandTexture>().find(index, plugin); return esmStore.get<ESM::LandTexture>().find(index, plugin);
} }
bool TerrainStorage::getMinMaxHeights(float size, const Ogre::Vector2 &center, float &min, float &max)
{
assert (size <= 1 && "TerrainStorage::getMinMaxHeights, chunk size should be <= 1 cell");
/// \todo investigate if min/max heights should be stored at load time in ESM::Land instead
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
assert(origin.x == (int) origin.x);
assert(origin.y == (int) origin.y);
int cellX = origin.x;
int cellY = origin.y;
const ESM::Land* land = getLand(cellX, cellY);
if (!land)
return false;
min = std::numeric_limits<float>().max();
max = -std::numeric_limits<float>().max();
for (int row=0; row<ESM::Land::LAND_SIZE; ++row)
{
for (int col=0; col<ESM::Land::LAND_SIZE; ++col)
{
float h = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
if (h > max)
max = h;
if (h < min)
min = h;
}
}
return true;
}
void TerrainStorage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row)
{
while (col >= ESM::Land::LAND_SIZE-1)
{
++cellY;
col -= ESM::Land::LAND_SIZE-1;
}
while (row >= ESM::Land::LAND_SIZE-1)
{
++cellX;
row -= ESM::Land::LAND_SIZE-1;
}
while (col < 0)
{
--cellY;
col += ESM::Land::LAND_SIZE-1;
}
while (row < 0)
{
--cellX;
row += ESM::Land::LAND_SIZE-1;
}
ESM::Land* land = getLand(cellX, cellY);
if (land && land->mHasData)
{
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
normal.normalise();
}
else
normal = Ogre::Vector3(0,0,1);
}
void TerrainStorage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row)
{
Ogre::Vector3 n1,n2,n3,n4;
fixNormal(n1, cellX, cellY, col+1, row);
fixNormal(n2, cellX, cellY, col-1, row);
fixNormal(n3, cellX, cellY, col, row+1);
fixNormal(n4, cellX, cellY, col, row-1);
normal = (n1+n2+n3+n4);
normal.normalise();
}
void TerrainStorage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row)
{
if (col == ESM::Land::LAND_SIZE-1)
{
++cellY;
col = 0;
}
if (row == ESM::Land::LAND_SIZE-1)
{
++cellX;
row = 0;
}
ESM::Land* land = getLand(cellX, cellY);
if (land && land->mLandData->mUsingColours)
{
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
}
else
{
color.r = 1;
color.g = 1;
color.b = 1;
}
}
void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center,
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
Ogre::HardwareVertexBufferSharedPtr colourBuffer)
{
// LOD level n means every 2^n-th vertex is kept
size_t increment = 1 << lodLevel;
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
assert(origin.x == (int) origin.x);
assert(origin.y == (int) origin.y);
int startX = origin.x;
int startY = origin.y;
size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1;
std::vector<uint8_t> colors;
colors.resize(numVerts*numVerts*4);
std::vector<float> positions;
positions.resize(numVerts*numVerts*3);
std::vector<float> normals;
normals.resize(numVerts*numVerts*3);
Ogre::Vector3 normal;
Ogre::ColourValue color;
float vertY;
float vertX;
float vertY_ = 0; // of current cell corner
for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY)
{
float vertX_ = 0; // of current cell corner
for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX)
{
ESM::Land* land = getLand(cellX, cellY);
if (land && !land->mHasData)
land = NULL;
bool hasColors = land && land->mLandData->mUsingColours;
int rowStart = 0;
int colStart = 0;
// Skip the first row / column unless we're at a chunk edge,
// since this row / column is already contained in a previous cell
if (colStart == 0 && vertY_ != 0)
colStart += increment;
if (rowStart == 0 && vertX_ != 0)
rowStart += increment;
vertY = vertY_;
for (int col=colStart; col<ESM::Land::LAND_SIZE; col += increment)
{
vertX = vertX_;
for (int row=rowStart; row<ESM::Land::LAND_SIZE; row += increment)
{
positions[vertX*numVerts*3 + vertY*3] = ((vertX/float(numVerts-1)-0.5) * size * 8192);
positions[vertX*numVerts*3 + vertY*3 + 1] = ((vertY/float(numVerts-1)-0.5) * size * 8192);
if (land)
positions[vertX*numVerts*3 + vertY*3 + 2] = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
else
positions[vertX*numVerts*3 + vertY*3 + 2] = -2048;
if (land)
{
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
normal.normalise();
}
else
normal = Ogre::Vector3(0,0,1);
// Normals apparently don't connect seamlessly between cells
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixNormal(normal, cellX, cellY, col, row);
// some corner normals appear to be complete garbage (z < 0)
if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1))
averageNormal(normal, cellX, cellY, col, row);
assert(normal.z > 0);
normals[vertX*numVerts*3 + vertY*3] = normal.x;
normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y;
normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z;
if (hasColors)
{
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
}
else
{
color.r = 1;
color.g = 1;
color.b = 1;
}
// Unlike normals, colors mostly connect seamlessly between cells, but not always...
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixColour(color, cellX, cellY, col, row);
color.a = 1;
Ogre::uint32 rsColor;
Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor);
memcpy(&colors[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32));
++vertX;
}
++vertY;
}
vertX_ = vertX;
}
vertY_ = vertY;
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,
int x, int y)
{
// For the first/last row/column, we need to get the texture from the neighbour cell
// to get consistent blending at the borders
--x;
if (x < 0)
{
--cellX;
x += ESM::Land::LAND_TEXTURE_SIZE;
}
if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not?
{
++cellY;
y -= ESM::Land::LAND_TEXTURE_SIZE;
}
assert(x<ESM::Land::LAND_TEXTURE_SIZE);
assert(y<ESM::Land::LAND_TEXTURE_SIZE);
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
return std::make_pair(tex, land->mPlugin);
}
else
return std::make_pair(0,0);
}
std::string TerrainStorage::getTextureName(UniqueTextureId id)
{
if (id.first == 0)
return "_land_default.dds"; // Not sure if the default texture floatly is hardcoded?
// NB: All vtex ids are +1 compared to the ltex ids
const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second);
std::string texture = ltex->mTexture;
//TODO this is needed due to MWs messed up texture handling
texture = texture.substr(0, texture.rfind(".")) + ".dds";
return texture;
}
void TerrainStorage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
bool pack, std::vector<Ogre::TexturePtr> &blendmaps, std::vector<Terrain::LayerInfo> &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
// and interpolate the rest of the cell by hand? :/
Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f);
int cellX = origin.x;
int cellY = origin.y;
// Save the used texture indices so we know the total number of textures
// and number of required blend maps
std::set<UniqueTextureId> textureIndices;
// Due to the way the blending works, the base layer will always shine through in between
// blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible).
// To get a consistent look, we need to make sure to use the same base layer in all cells.
// So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell.
textureIndices.insert(std::make_pair(0,0));
for (int y=0; y<ESM::Land::LAND_TEXTURE_SIZE+1; ++y)
for (int x=0; x<ESM::Land::LAND_TEXTURE_SIZE+1; ++x)
{
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
textureIndices.insert(id);
}
// Makes sure the indices are sorted, or rather,
// retrieved as sorted. This is important to keep the splatting order
// consistent across cells.
std::map<UniqueTextureId, int> textureIndicesMap;
for (std::set<UniqueTextureId>::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it)
{
int size = textureIndicesMap.size();
textureIndicesMap[*it] = size;
layerList.push_back(getLayerInfo(getTextureName(*it)));
}
int numTextures = textureIndices.size();
// numTextures-1 since the base layer doesn't need blending
int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1);
int channels = pack ? 4 : 1;
// Second iteration - create and fill in the blend maps
const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1;
std::vector<Ogre::uchar> data;
data.resize(blendmapSize * blendmapSize * channels, 0);
for (int i=0; i<numBlendmaps; ++i)
{
Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8;
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, blendmapSize, blendmapSize, 0, format);
for (int y=0; y<blendmapSize; ++y)
{
for (int x=0; x<blendmapSize; ++x)
{
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
int layerIndex = textureIndicesMap.find(id)->second;
int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1);
int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0;
if (blendIndex == i)
data[y*blendmapSize*channels + x*channels + channel] = 255;
else
data[y*blendmapSize*channels + x*channels + channel] = 0;
}
}
// All done, upload to GPU
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
map->loadRawData(stream, blendmapSize, blendmapSize, format);
blendmaps.push_back(map);
}
}
float TerrainStorage::getHeightAt(const Ogre::Vector3 &worldPos)
{
int cellX = std::floor(worldPos.x / 8192.f);
int cellY = std::floor(worldPos.y / 8192.f);
ESM::Land* land = getLand(cellX, cellY);
if (!land)
return -2048;
// Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition
// Normalized position in the cell
float nX = (worldPos.x - (cellX * 8192))/8192.f;
float nY = (worldPos.y - (cellY * 8192))/8192.f;
// get left / bottom points (rounded down)
float factor = ESM::Land::LAND_SIZE - 1.0f;
float invFactor = 1.0f / factor;
int startX = static_cast<int>(nX * factor);
int startY = static_cast<int>(nY * factor);
int endX = startX + 1;
int endY = startY + 1;
assert(endX < ESM::Land::LAND_SIZE);
assert(endY < ESM::Land::LAND_SIZE);
// now get points in terrain space (effectively rounding them to boundaries)
float startXTS = startX * invFactor;
float startYTS = startY * invFactor;
float endXTS = endX * invFactor;
float endYTS = endY * invFactor;
// get parametric from start coord to next point
float xParam = (nX - startXTS) * factor;
float yParam = (nY - startYTS) * factor;
/* For even / odd tri strip rows, triangles are this shape:
even odd
3---2 3---2
| / | | \ |
0---1 0---1
*/
// Build all 4 positions in normalized cell space, using point-sampled height
Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f);
Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f);
Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f);
Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f);
// define this plane in terrain space
Ogre::Plane plane;
// (At the moment, all rows have the same triangle alignment)
if (true)
{
// odd row
bool secondTri = ((1.0 - yParam) > xParam);
if (secondTri)
plane.redefine(v0, v1, v3);
else
plane.redefine(v1, v2, v3);
}
else
{
// even row
bool secondTri = (yParam > xParam);
if (secondTri)
plane.redefine(v0, v2, v3);
else
plane.redefine(v0, v1, v2);
}
// Solve plane equation for z
return (-plane.normal.x * nX
-plane.normal.y * nY
- plane.d) / plane.normal.z * 8192;
}
float TerrainStorage::getVertexHeight(const ESM::Land *land, int x, int y)
{
assert(x < ESM::Land::LAND_SIZE);
assert(y < ESM::Land::LAND_SIZE);
return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x];
}
Terrain::LayerInfo TerrainStorage::getLayerInfo(const std::string& texture)
{
// Already have this cached?
if (mLayerInfoMap.find(texture) != mLayerInfoMap.end())
return mLayerInfoMap[texture];
Terrain::LayerInfo info;
info.mParallax = false;
info.mSpecular = false;
info.mDiffuseMap = "textures\\" + texture;
std::string texture_ = texture;
boost::replace_last(texture_, ".", "_nh.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
{
info.mNormalMap = "textures\\" + texture_;
info.mParallax = true;
}
else
{
texture_ = texture;
boost::replace_last(texture_, ".", "_n.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
info.mNormalMap = "textures\\" + texture_;
}
texture_ = texture;
boost::replace_last(texture_, ".", "_diffusespec.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
{
info.mDiffuseMap = "textures\\" + texture_;
info.mSpecular = true;
}
mLayerInfoMap[texture] = info;
return info;
}
Terrain::LayerInfo TerrainStorage::getDefaultLayer()
{
Terrain::LayerInfo info;
info.mDiffuseMap = "textures\\_land_default.dds";
info.mParallax = false;
info.mSpecular = false;
return info;
}
float TerrainStorage::getCellWorldSize()
{
return ESM::Land::REAL_SIZE;
}
int TerrainStorage::getCellVertices()
{
return ESM::Land::LAND_SIZE;
}
} }

@ -12,8 +12,75 @@ namespace MWRender
virtual ESM::Land* getLand (int cellX, int cellY); virtual ESM::Land* getLand (int cellX, int cellY);
virtual const ESM::LandTexture* getLandTexture(int index, short plugin); virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
public: public:
/// Get bounds of the whole terrain in cell units
virtual Ogre::AxisAlignedBox getBounds(); virtual Ogre::AxisAlignedBox getBounds();
///< Get bounds in cell units
/// 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.
/// 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
/// @param min min height will be stored here
/// @param max max height will be stored here
/// @return true if there was data available for this terrain chunk
virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max);
/// Fill vertex buffers for a terrain chunk.
/// @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
virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center,
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
Ogre::HardwareVertexBufferSharedPtr colourBuffer);
/// 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.
/// @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) -
/// otherwise, each texture contains blend values for one layer only. Shader-based rendering
/// can utilize packing, FFP can't.
/// @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<Ogre::TexturePtr>& blendmaps,
std::vector<Terrain::LayerInfo>& layerList);
virtual float getHeightAt (const Ogre::Vector3& worldPos);
virtual Terrain::LayerInfo getDefaultLayer();
/// Get the transformation factor for mapping cell units to world units.
virtual float getCellWorldSize();
/// Get the number of vertices on one side for each cell. Should be (power of two)+1
virtual int getCellVertices();
private:
void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row);
void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
float getVertexHeight (const ESM::Land* land, int x, int y);
// Since plugins can define new texture palettes, we need to know the plugin index too
// in order to retrieve the correct texture name.
// pair <texture id, plugin id>
typedef std::pair<short, short> UniqueTextureId;
UniqueTextureId getVtexIndexAt(int cellX, int cellY,
int x, int y);
std::string getTextureName (UniqueTextureId id);
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;
Terrain::LayerInfo getLayerInfo(const std::string& texture);
}; };
} }

@ -70,7 +70,7 @@ namespace MWScript
msgBox = boost::str(boost::format(msgBox) % count % itemName); msgBox = boost::str(boost::format(msgBox) % count % itemName);
} }
std::vector <std::string> noButtons; std::vector <std::string> noButtons;
MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, /*showInDialogueModeOnly*/ true); MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, MWGui::ShowInDialogueMode_Only);
} }
} }
}; };
@ -142,7 +142,7 @@ namespace MWScript
msgBox = boost::str (boost::format(msgBox) % itemName); msgBox = boost::str (boost::format(msgBox) % itemName);
} }
std::vector <std::string> noButtons; std::vector <std::string> noButtons;
MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, /*showInDialogueModeOnly*/ true); MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, MWGui::ShowInDialogueMode_Only);
} }
} }
}; };

@ -17,6 +17,7 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
@ -451,6 +452,14 @@ namespace MWScript
runtime.pop(); runtime.pop();
MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().remove (id); MWWorld::Class::get (ptr).getCreatureStats (ptr).getSpells().remove (id);
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr() &&
id == wm->getSelectedSpell())
{
wm->unsetSelectedSpell();
}
} }
}; };

@ -462,6 +462,10 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem(
int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor) int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor)
{ {
int retCount = ContainerStore::remove(item, count, actor);
if (!item.getRefData().getCount())
{
for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot)
{ {
if (mSlots[slot] == end()) if (mSlots[slot] == end())
@ -469,13 +473,11 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor
if (*mSlots[slot] == item) if (*mSlots[slot] == item)
{ {
// restacking is disabled cause it may break removal unequipSlot(slot, actor);
unequipSlot(slot, actor, false);
break; break;
} }
} }
}
int retCount = ContainerStore::remove(item, count, actor);
// If an armor/clothing item is removed, try to find a replacement, // If an armor/clothing item is removed, try to find a replacement,
// but not for the player nor werewolves. // but not for the player nor werewolves.
@ -500,9 +502,9 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor
return retCount; return retCount;
} }
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool restack) MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor)
{ {
ContainerStoreIterator it = getSlot(slot); ContainerStoreIterator it = mSlots[slot];
if (it != end()) if (it != end())
{ {
@ -511,8 +513,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c
// empty this slot // empty this slot
mSlots[slot] = end(); mSlots[slot] = end();
if (restack) { // restack the previously equipped item with other (non-equipped) items
// restack item previously in this slot
for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter)
{ {
if (stacks(*iter, *it)) if (stacks(*iter, *it))
@ -523,7 +524,6 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c
break; break;
} }
} }
}
if (actor.getRefData().getHandle() == "player") if (actor.getRefData().getHandle() == "player")
{ {

@ -168,12 +168,10 @@ namespace MWWorld
/// ///
/// @return the number of items actually removed /// @return the number of items actually removed
ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool restack = true); ContainerStoreIterator unequipSlot(int slot, const Ptr& actor);
///< Unequip \a slot. ///< Unequip \a slot.
/// ///
/// @return an iterator to the item that was previously in the slot /// @return an iterator to the item that was previously in the slot
/// (if \a restack is true, the item can be re-stacked so its count
/// may differ from when it was equipped).
ContainerStoreIterator unequipItem(const Ptr& item, const Ptr& actor); ContainerStoreIterator unequipItem(const Ptr& item, const Ptr& actor);
///< Unequip an item identified by its Ptr. An exception is thrown ///< Unequip an item identified by its Ptr. An exception is thrown

@ -9,7 +9,7 @@
namespace ESM namespace ESM
{ {
class ObjectState; struct ObjectState;
} }
namespace MWWorld namespace MWWorld

@ -14,7 +14,7 @@ namespace ESM
{ {
class Script; class Script;
class CellRef; class CellRef;
class ObjectState; struct ObjectState;
} }
namespace MWWorld namespace MWWorld

@ -0,0 +1,157 @@
# - Returns a version string from Git
#
# These functions force a re-configure on each git commit so that you can
# trust the values of the variables in your build system.
#
# get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])
#
# Returns the refspec and sha hash of the current head revision
#
# git_describe(<var> [<additional arguments to git describe> ...])
#
# Returns the results of git describe on the source tree, and adjusting
# the output so that it tests false if an error occurs.
#
# git_get_exact_tag(<var> [<additional arguments to git describe> ...])
#
# Returns the results of git describe --exact-match on the source tree,
# and adjusting the output so that it tests false if there was no exact
# matching tag.
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
if(__get_git_revision_description)
return()
endif()
set(__get_git_revision_description YES)
# We must run the following at "include" time, not at function call time,
# to find the path to this module rather than the path to a calling list file
get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
function(get_git_head_revision _refspecvar _hashvar)
set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories
set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}")
get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)
if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)
# We have reached the root directory, we are not in git
set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
return()
endif()
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
endwhile()
# check if this is a submodule
if(NOT IS_DIRECTORY ${GIT_DIR})
file(READ ${GIT_DIR} submodule)
string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule})
get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)
endif()
set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
if(NOT EXISTS "${GIT_DATA}")
file(MAKE_DIRECTORY "${GIT_DATA}")
endif()
if(NOT EXISTS "${GIT_DIR}/HEAD")
return()
endif()
set(HEAD_FILE "${GIT_DATA}/HEAD")
configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY)
configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in"
"${GIT_DATA}/grabRef.cmake" @ONLY)
include("${GIT_DATA}/grabRef.cmake")
set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
endfunction()
function(git_describe _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
#get_git_head_revision(refspec hash)
if(NOT GIT_FOUND)
set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
return()
endif()
#if(NOT hash)
# set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE)
# return()
#endif()
# TODO sanitize
#if((${ARGN}" MATCHES "&&") OR
# (ARGN MATCHES "||") OR
# (ARGN MATCHES "\\;"))
# message("Please report the following error to the project!")
# message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}")
#endif()
#message(STATUS "Arguments to execute_process: ${ARGN}")
execute_process(COMMAND
"${GIT_EXECUTABLE}"
describe
#${hash}
${ARGN}
WORKING_DIRECTORY
"${CMAKE_SOURCE_DIR}"
RESULT_VARIABLE
res
OUTPUT_VARIABLE
out
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT res EQUAL 0)
set(out "${out}-${res}-NOTFOUND")
endif()
set(${_var} "${out}" PARENT_SCOPE)
endfunction()
function(get_git_tag_revision _var)
if(NOT GIT_FOUND)
find_package(Git QUIET)
endif()
execute_process(COMMAND
"${GIT_EXECUTABLE}"
rev-list
${ARGN}
WORKING_DIRECTORY
"${CMAKE_SOURCE_DIR}"
RESULT_VARIABLE
res
OUTPUT_VARIABLE
out
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT res EQUAL 0)
set(out "${out}-${res}-NOTFOUND")
endif()
set(${_var} "${out}" PARENT_SCOPE)
endfunction()

@ -0,0 +1,38 @@
#
# Internal file for GetGitRevisionDescription.cmake
#
# Requires CMake 2.6 or newer (uses the 'function' command)
#
# Original Author:
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
# http://academic.cleardefinition.com
# Iowa State University HCI Graduate Program/VRAC
#
# Copyright Iowa State University 2009-2010.
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
set(HEAD_HASH)
file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
if(HEAD_CONTENTS MATCHES "ref")
# named branch
string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
if(EXISTS "@GIT_DIR@/${HEAD_REF}")
configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}")
configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
set(HEAD_HASH "${HEAD_REF}")
endif()
else()
# detached HEAD
configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
endif()
if(NOT HEAD_HASH)
file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
string(STRIP "${HEAD_HASH}" HEAD_HASH)
endif()

@ -1,5 +1,9 @@
project (Components) project (Components)
set (CMAKE_BUILD_TYPE DEBUG) set (CMAKE_BUILD_TYPE DEBUG)
# Version file
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp")
# source files # source files
add_component_dir (settings add_component_dir (settings
@ -79,6 +83,10 @@ add_component_dir (ogreinit
ogreinit ogreplugin ogreinit ogreplugin
) )
add_component_dir (version
version
)
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
) )

@ -1,5 +1,5 @@
#ifndef COMPILER_EXTENSIONS_H_INCLUDED #ifndef COMPILER_EXTENSIONS_H_INCLUDED
#define COMPILER_EXTENSINOS_H_INCLUDED #define COMPILER_EXTENSIONS_H_INCLUDED
#include <string> #include <string>
#include <map> #include <map>

@ -48,9 +48,10 @@ namespace Interpreter
} }
else if (c=='f' || c=='F' || c=='.') else if (c=='f' || c=='F' || c=='.')
{ {
while (c!='f' && i<message.size()) while (c!='f' && i+1<message.size())
{ {
++i; ++i;
c = message[i];
} }
float value = runtime[0].mFloat; float value = runtime[0].mFloat;

@ -749,8 +749,8 @@ class NIFObjectLoader
emitter->setParticleVelocity(partctrl->velocity - partctrl->velocityRandom*0.5f, emitter->setParticleVelocity(partctrl->velocity - partctrl->velocityRandom*0.5f,
partctrl->velocity + partctrl->velocityRandom*0.5f); partctrl->velocity + partctrl->velocityRandom*0.5f);
emitter->setEmissionRate(partctrl->emitRate); emitter->setEmissionRate(partctrl->emitRate);
emitter->setTimeToLive(partctrl->lifetime - partctrl->lifetimeRandom*0.5f, emitter->setTimeToLive(partctrl->lifetime,
partctrl->lifetime + partctrl->lifetimeRandom*0.5f); partctrl->lifetime + partctrl->lifetimeRandom);
emitter->setParameter("width", Ogre::StringConverter::toString(partctrl->offsetRandom.x)); emitter->setParameter("width", Ogre::StringConverter::toString(partctrl->offsetRandom.x));
emitter->setParameter("height", Ogre::StringConverter::toString(partctrl->offsetRandom.y)); emitter->setParameter("height", Ogre::StringConverter::toString(partctrl->offsetRandom.y));
emitter->setParameter("depth", Ogre::StringConverter::toString(partctrl->offsetRandom.z)); emitter->setParameter("depth", Ogre::StringConverter::toString(partctrl->offsetRandom.z));
@ -882,6 +882,9 @@ class NIFObjectLoader
Ogre::ControllerFunctionRealPtr func(function); Ogre::ControllerFunctionRealPtr func(function);
scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func)); scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
if (partflags&Nif::NiNode::ParticleFlag_AutoPlay)
partsys->fastForward(1, 0.1);
} }
ctrl = ctrl->next; ctrl = ctrl->next;
} }

@ -18,11 +18,13 @@ namespace Terrain
mVertexData = OGRE_NEW Ogre::VertexData; mVertexData = OGRE_NEW Ogre::VertexData;
mVertexData->vertexStart = 0; mVertexData->vertexStart = 0;
unsigned int verts = mNode->getTerrain()->getStorage()->getCellVertices();
// Set the total number of vertices // Set the total number of vertices
size_t numVertsOneSide = mNode->getSize() * (ESM::Land::LAND_SIZE-1); size_t numVertsOneSide = mNode->getSize() * (verts-1);
numVertsOneSide /= 1 << lodLevel; numVertsOneSide /= 1 << lodLevel;
numVertsOneSide += 1; numVertsOneSide += 1;
assert((int)numVertsOneSide == ESM::Land::LAND_SIZE); assert(numVertsOneSide == verts);
mVertexData->vertexCount = numVertsOneSide * numVertsOneSide; mVertexData->vertexCount = numVertsOneSide * numVertsOneSide;
// Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc) // Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc)

@ -168,7 +168,8 @@ QuadTreeNode::QuadTreeNode(World* terrain, ChildDirection dir, float size, const
if (mParent) if (mParent)
pos = mParent->getCenter(); pos = mParent->getCenter();
pos = mCenter - pos; pos = mCenter - pos;
mSceneNode->setPosition(Ogre::Vector3(pos.x*8192, pos.y*8192, 0)); float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
mSceneNode->setPosition(Ogre::Vector3(pos.x*cellWorldSize, pos.y*cellWorldSize, 0));
mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled()); mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled());
} }
@ -203,6 +204,7 @@ void QuadTreeNode::initNeighbours()
void QuadTreeNode::initAabb() void QuadTreeNode::initAabb()
{ {
float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
if (hasChildren()) if (hasChildren())
{ {
for (int i=0; i<4; ++i) for (int i=0; i<4; ++i)
@ -210,11 +212,11 @@ void QuadTreeNode::initAabb()
mChildren[i]->initAabb(); mChildren[i]->initAabb();
mBounds.merge(mChildren[i]->getBoundingBox()); mBounds.merge(mChildren[i]->getBoundingBox());
} }
mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*8192, -mSize/2*8192, mBounds.getMinimum().z), mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*cellWorldSize, -mSize/2*cellWorldSize, mBounds.getMinimum().z),
Ogre::Vector3(mSize/2*8192, mSize/2*8192, mBounds.getMaximum().z)); Ogre::Vector3(mSize/2*cellWorldSize, mSize/2*cellWorldSize, mBounds.getMaximum().z));
} }
mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0), mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + Ogre::Vector3(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0),
mBounds.getMaximum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0)); mBounds.getMaximum() + Ogre::Vector3(mCenter.x*cellWorldSize, mCenter.y*cellWorldSize, 0));
} }
void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box) void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box)
@ -430,11 +432,7 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> area)
// TODO - store this default material somewhere instead of creating one for each empty cell // TODO - store this default material somewhere instead of creating one for each empty cell
MaterialGenerator matGen(mTerrain->getShadersEnabled()); MaterialGenerator matGen(mTerrain->getShadersEnabled());
std::vector<LayerInfo> layer; std::vector<LayerInfo> layer;
LayerInfo info; layer.push_back(mTerrain->getStorage()->getDefaultLayer());
info.mDiffuseMap = "textures\\_land_default.dds";
info.mParallax = false;
info.mSpecular = false;
layer.push_back(info);
matGen.setLayerList(layer); matGen.setLayerList(layer);
makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr())); makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr()));
return; return;

@ -108,7 +108,7 @@ namespace Terrain
void destroyChunks(bool children); void destroyChunks(bool children);
/// Get the effective LOD level if this node was rendered in one chunk /// Get the effective LOD level if this node was rendered in one chunk
/// with ESM::Land::LAND_SIZE^2 vertices /// with Storage::getCellVertices^2 vertices
size_t getNativeLodLevel() { return mLodLevel; } size_t getNativeLodLevel() { return mLodLevel; }
/// Get the effective current LOD level used by the chunk rendering this node /// Get the effective current LOD level used by the chunk rendering this node

@ -1,509 +0,0 @@
#include "storage.hpp"
#include <OgreVector2.h>
#include <OgreTextureManager.h>
#include <OgreStringConverter.h>
#include <OgreRenderSystem.h>
#include <OgreResourceGroupManager.h>
#include <OgreRoot.h>
#include <boost/algorithm/string.hpp>
namespace Terrain
{
struct VertexElement
{
Ogre::Vector3 pos;
Ogre::Vector3 normal;
Ogre::ColourValue colour;
};
bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 &center, float &min, float &max)
{
assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell");
/// \todo investigate if min/max heights should be stored at load time in ESM::Land instead
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
assert(origin.x == (int) origin.x);
assert(origin.y == (int) origin.y);
int cellX = origin.x;
int cellY = origin.y;
const ESM::Land* land = getLand(cellX, cellY);
if (!land)
return false;
min = std::numeric_limits<float>().max();
max = -std::numeric_limits<float>().max();
for (int row=0; row<ESM::Land::LAND_SIZE; ++row)
{
for (int col=0; col<ESM::Land::LAND_SIZE; ++col)
{
float h = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
if (h > max)
max = h;
if (h < min)
min = h;
}
}
return true;
}
void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row)
{
while (col >= ESM::Land::LAND_SIZE-1)
{
++cellY;
col -= ESM::Land::LAND_SIZE-1;
}
while (row >= ESM::Land::LAND_SIZE-1)
{
++cellX;
row -= ESM::Land::LAND_SIZE-1;
}
while (col < 0)
{
--cellY;
col += ESM::Land::LAND_SIZE-1;
}
while (row < 0)
{
--cellX;
row += ESM::Land::LAND_SIZE-1;
}
ESM::Land* land = getLand(cellX, cellY);
if (land && land->mHasData)
{
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
normal.normalise();
}
else
normal = Ogre::Vector3(0,0,1);
}
void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row)
{
Ogre::Vector3 n1,n2,n3,n4;
fixNormal(n1, cellX, cellY, col+1, row);
fixNormal(n2, cellX, cellY, col-1, row);
fixNormal(n3, cellX, cellY, col, row+1);
fixNormal(n4, cellX, cellY, col, row-1);
normal = (n1+n2+n3+n4);
normal.normalise();
}
void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row)
{
if (col == ESM::Land::LAND_SIZE-1)
{
++cellY;
col = 0;
}
if (row == ESM::Land::LAND_SIZE-1)
{
++cellX;
row = 0;
}
ESM::Land* land = getLand(cellX, cellY);
if (land && land->mLandData->mUsingColours)
{
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
}
else
{
color.r = 1;
color.g = 1;
color.b = 1;
}
}
void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center,
Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
Ogre::HardwareVertexBufferSharedPtr normalBuffer,
Ogre::HardwareVertexBufferSharedPtr colourBuffer)
{
// LOD level n means every 2^n-th vertex is kept
size_t increment = 1 << lodLevel;
Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f);
assert(origin.x == (int) origin.x);
assert(origin.y == (int) origin.y);
int startX = origin.x;
int startY = origin.y;
size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1;
std::vector<uint8_t> colors;
colors.resize(numVerts*numVerts*4);
std::vector<float> positions;
positions.resize(numVerts*numVerts*3);
std::vector<float> normals;
normals.resize(numVerts*numVerts*3);
Ogre::Vector3 normal;
Ogre::ColourValue color;
float vertY;
float vertX;
float vertY_ = 0; // of current cell corner
for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY)
{
float vertX_ = 0; // of current cell corner
for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX)
{
ESM::Land* land = getLand(cellX, cellY);
if (land && !land->mHasData)
land = NULL;
bool hasColors = land && land->mLandData->mUsingColours;
int rowStart = 0;
int colStart = 0;
// Skip the first row / column unless we're at a chunk edge,
// since this row / column is already contained in a previous cell
if (colStart == 0 && vertY_ != 0)
colStart += increment;
if (rowStart == 0 && vertX_ != 0)
rowStart += increment;
vertY = vertY_;
for (int col=colStart; col<ESM::Land::LAND_SIZE; col += increment)
{
vertX = vertX_;
for (int row=rowStart; row<ESM::Land::LAND_SIZE; row += increment)
{
positions[vertX*numVerts*3 + vertY*3] = ((vertX/float(numVerts-1)-0.5) * size * 8192);
positions[vertX*numVerts*3 + vertY*3 + 1] = ((vertY/float(numVerts-1)-0.5) * size * 8192);
if (land)
positions[vertX*numVerts*3 + vertY*3 + 2] = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
else
positions[vertX*numVerts*3 + vertY*3 + 2] = -2048;
if (land)
{
normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
normal.normalise();
}
else
normal = Ogre::Vector3(0,0,1);
// Normals apparently don't connect seamlessly between cells
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixNormal(normal, cellX, cellY, col, row);
// some corner normals appear to be complete garbage (z < 0)
if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1))
averageNormal(normal, cellX, cellY, col, row);
assert(normal.z > 0);
normals[vertX*numVerts*3 + vertY*3] = normal.x;
normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y;
normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z;
if (hasColors)
{
color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
}
else
{
color.r = 1;
color.g = 1;
color.b = 1;
}
// Unlike normals, colors mostly connect seamlessly between cells, but not always...
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixColour(color, cellX, cellY, col, row);
color.a = 1;
Ogre::uint32 rsColor;
Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor);
memcpy(&colors[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32));
++vertX;
}
++vertY;
}
vertX_ = vertX;
}
vertY_ = vertY;
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);
}
Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY,
int x, int y)
{
// For the first/last row/column, we need to get the texture from the neighbour cell
// to get consistent blending at the borders
--x;
if (x < 0)
{
--cellX;
x += ESM::Land::LAND_TEXTURE_SIZE;
}
if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not?
{
++cellY;
y -= ESM::Land::LAND_TEXTURE_SIZE;
}
assert(x<ESM::Land::LAND_TEXTURE_SIZE);
assert(y<ESM::Land::LAND_TEXTURE_SIZE);
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
return std::make_pair(tex, land->mPlugin);
}
else
return std::make_pair(0,0);
}
std::string Storage::getTextureName(UniqueTextureId id)
{
if (id.first == 0)
return "_land_default.dds"; // Not sure if the default texture floatly is hardcoded?
// NB: All vtex ids are +1 compared to the ltex ids
const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second);
std::string texture = ltex->mTexture;
//TODO this is needed due to MWs messed up texture handling
texture = texture.substr(0, texture.rfind(".")) + ".dds";
return texture;
}
void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
bool pack, std::vector<Ogre::TexturePtr> &blendmaps, std::vector<LayerInfo> &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
// and interpolate the rest of the cell by hand? :/
Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f);
int cellX = origin.x;
int cellY = origin.y;
// Save the used texture indices so we know the total number of textures
// and number of required blend maps
std::set<UniqueTextureId> textureIndices;
// Due to the way the blending works, the base layer will always shine through in between
// blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible).
// To get a consistent look, we need to make sure to use the same base layer in all cells.
// So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell.
textureIndices.insert(std::make_pair(0,0));
for (int y=0; y<ESM::Land::LAND_TEXTURE_SIZE+1; ++y)
for (int x=0; x<ESM::Land::LAND_TEXTURE_SIZE+1; ++x)
{
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
textureIndices.insert(id);
}
// Makes sure the indices are sorted, or rather,
// retrieved as sorted. This is important to keep the splatting order
// consistent across cells.
std::map<UniqueTextureId, int> textureIndicesMap;
for (std::set<UniqueTextureId>::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it)
{
int size = textureIndicesMap.size();
textureIndicesMap[*it] = size;
layerList.push_back(getLayerInfo(getTextureName(*it)));
}
int numTextures = textureIndices.size();
// numTextures-1 since the base layer doesn't need blending
int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1);
int channels = pack ? 4 : 1;
// Second iteration - create and fill in the blend maps
const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1;
std::vector<Ogre::uchar> data;
data.resize(blendmapSize * blendmapSize * channels, 0);
for (int i=0; i<numBlendmaps; ++i)
{
Ogre::PixelFormat format = pack ? Ogre::PF_A8B8G8R8 : Ogre::PF_A8;
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, blendmapSize, blendmapSize, 0, format);
for (int y=0; y<blendmapSize; ++y)
{
for (int x=0; x<blendmapSize; ++x)
{
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x, y);
int layerIndex = textureIndicesMap.find(id)->second;
int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1);
int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0;
if (blendIndex == i)
data[y*blendmapSize*channels + x*channels + channel] = 255;
else
data[y*blendmapSize*channels + x*channels + channel] = 0;
}
}
// All done, upload to GPU
Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size()));
map->loadRawData(stream, blendmapSize, blendmapSize, format);
blendmaps.push_back(map);
}
}
float Storage::getHeightAt(const Ogre::Vector3 &worldPos)
{
int cellX = std::floor(worldPos.x / 8192.f);
int cellY = std::floor(worldPos.y / 8192.f);
ESM::Land* land = getLand(cellX, cellY);
if (!land)
return -2048;
// Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition
// Normalized position in the cell
float nX = (worldPos.x - (cellX * 8192))/8192.f;
float nY = (worldPos.y - (cellY * 8192))/8192.f;
// get left / bottom points (rounded down)
float factor = ESM::Land::LAND_SIZE - 1.0f;
float invFactor = 1.0f / factor;
int startX = static_cast<int>(nX * factor);
int startY = static_cast<int>(nY * factor);
int endX = startX + 1;
int endY = startY + 1;
assert(endX < ESM::Land::LAND_SIZE);
assert(endY < ESM::Land::LAND_SIZE);
// now get points in terrain space (effectively rounding them to boundaries)
float startXTS = startX * invFactor;
float startYTS = startY * invFactor;
float endXTS = endX * invFactor;
float endYTS = endY * invFactor;
// get parametric from start coord to next point
float xParam = (nX - startXTS) * factor;
float yParam = (nY - startYTS) * factor;
/* For even / odd tri strip rows, triangles are this shape:
even odd
3---2 3---2
| / | | \ |
0---1 0---1
*/
// Build all 4 positions in normalized cell space, using point-sampled height
Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f);
Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f);
Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f);
Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f);
// define this plane in terrain space
Ogre::Plane plane;
// (At the moment, all rows have the same triangle alignment)
if (true)
{
// odd row
bool secondTri = ((1.0 - yParam) > xParam);
if (secondTri)
plane.redefine(v0, v1, v3);
else
plane.redefine(v1, v2, v3);
}
else
{
// even row
bool secondTri = (yParam > xParam);
if (secondTri)
plane.redefine(v0, v2, v3);
else
plane.redefine(v0, v1, v2);
}
// Solve plane equation for z
return (-plane.normal.x * nX
-plane.normal.y * nY
- plane.d) / plane.normal.z * 8192;
}
float Storage::getVertexHeight(const ESM::Land *land, int x, int y)
{
assert(x < ESM::Land::LAND_SIZE);
assert(y < ESM::Land::LAND_SIZE);
return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x];
}
LayerInfo Storage::getLayerInfo(const std::string& texture)
{
// Already have this cached?
if (mLayerInfoMap.find(texture) != mLayerInfoMap.end())
return mLayerInfoMap[texture];
LayerInfo info;
info.mParallax = false;
info.mSpecular = false;
info.mDiffuseMap = "textures\\" + texture;
std::string texture_ = texture;
boost::replace_last(texture_, ".", "_nh.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
{
info.mNormalMap = "textures\\" + texture_;
info.mParallax = true;
}
else
{
texture_ = texture;
boost::replace_last(texture_, ".", "_n.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
info.mNormalMap = "textures\\" + texture_;
}
texture_ = texture;
boost::replace_last(texture_, ".", "_diffusespec.");
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_))
{
info.mDiffuseMap = "textures\\" + texture_;
info.mSpecular = true;
}
mLayerInfoMap[texture] = info;
return info;
}
}

@ -24,9 +24,6 @@ namespace Terrain
{ {
public: public:
virtual ~Storage() {} virtual ~Storage() {}
private:
virtual ESM::Land* getLand (int cellX, int cellY) = 0;
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
public: public:
/// Get bounds of the whole terrain in cell units /// Get bounds of the whole terrain in cell units
@ -40,7 +37,7 @@ namespace Terrain
/// @param min min height will be stored here /// @param min min height will be stored here
/// @param max max height will be stored here /// @param max max height will be stored here
/// @return true if there was data available for this terrain chunk /// @return true if there was data available for this terrain chunk
bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); virtual bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max) = 0;
/// Fill vertex buffers for a terrain chunk. /// Fill vertex buffers for a terrain chunk.
/// @param lodLevel LOD level, 0 = most detailed /// @param lodLevel LOD level, 0 = most detailed
@ -49,10 +46,10 @@ namespace Terrain
/// @param vertexBuffer buffer to write vertices /// @param vertexBuffer buffer to write vertices
/// @param normalBuffer buffer to write vertex normals /// @param normalBuffer buffer to write vertex normals
/// @param colourBuffer buffer to write vertex colours /// @param colourBuffer buffer to write vertex colours
void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, virtual void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center,
Ogre::HardwareVertexBufferSharedPtr vertexBuffer, Ogre::HardwareVertexBufferSharedPtr vertexBuffer,
Ogre::HardwareVertexBufferSharedPtr normalBuffer, Ogre::HardwareVertexBufferSharedPtr normalBuffer,
Ogre::HardwareVertexBufferSharedPtr colourBuffer); Ogre::HardwareVertexBufferSharedPtr colourBuffer) = 0;
/// Create textures holding layer blend values for a terrain chunk. /// 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 /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might
@ -64,31 +61,19 @@ namespace Terrain
/// can utilize packing, FFP can't. /// can utilize packing, FFP can't.
/// @param blendmaps created blendmaps will be written here /// @param blendmaps created blendmaps will be written here
/// @param layerList names of the layer textures used will be written here /// @param layerList names of the layer textures used will be written here
void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, virtual void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack,
std::vector<Ogre::TexturePtr>& blendmaps, std::vector<Ogre::TexturePtr>& blendmaps,
std::vector<LayerInfo>& layerList); std::vector<LayerInfo>& layerList) = 0;
float getHeightAt (const Ogre::Vector3& worldPos); virtual float getHeightAt (const Ogre::Vector3& worldPos) = 0;
private: virtual LayerInfo getDefaultLayer() = 0;
void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row);
void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row);
float getVertexHeight (const ESM::Land* land, int x, int y); /// Get the transformation factor for mapping cell units to world units.
virtual float getCellWorldSize() = 0;
// Since plugins can define new texture palettes, we need to know the plugin index too /// Get the number of vertices on one side for each cell. Should be (power of two)+1
// in order to retrieve the correct texture name. virtual int getCellVertices() = 0;
// pair <texture id, plugin id>
typedef std::pair<short, short> UniqueTextureId;
UniqueTextureId getVtexIndexAt(int cellX, int cellY,
int x, int y);
std::string getTextureName (UniqueTextureId id);
std::map<std::string, LayerInfo> mLayerInfoMap;
LayerInfo getLayerInfo(const std::string& texture);
}; };
} }

@ -6,7 +6,6 @@
#include <OgreHardwarePixelBuffer.h> #include <OgreHardwarePixelBuffer.h>
#include <OgreRoot.h> #include <OgreRoot.h>
#include <components/esm/loadland.hpp>
#include <components/loadinglistener/loadinglistener.hpp> #include <components/loadinglistener/loadinglistener.hpp>
#include "storage.hpp" #include "storage.hpp"
@ -114,9 +113,10 @@ namespace Terrain
// We arrived at a leaf // We arrived at a leaf
float minZ,maxZ; float minZ,maxZ;
Ogre::Vector2 center = node->getCenter(); Ogre::Vector2 center = node->getCenter();
float cellWorldSize = getStorage()->getCellWorldSize();
if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ)) if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ))
node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*8192, -halfSize*8192, minZ), node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*cellWorldSize, -halfSize*cellWorldSize, minZ),
Ogre::Vector3(halfSize*8192, halfSize*8192, maxZ))); Ogre::Vector3(halfSize*cellWorldSize, halfSize*cellWorldSize, maxZ)));
else else
node->markAsDummy(); // no data available for this node, skip it node->markAsDummy(); // no data available for this node, skip it
return; return;
@ -169,8 +169,9 @@ namespace Terrain
return Ogre::AxisAlignedBox::BOX_NULL; return Ogre::AxisAlignedBox::BOX_NULL;
QuadTreeNode* node = findNode(center, mRootNode); QuadTreeNode* node = findNode(center, mRootNode);
Ogre::AxisAlignedBox box = node->getBoundingBox(); Ogre::AxisAlignedBox box = node->getBoundingBox();
box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * 8192, float cellWorldSize = getStorage()->getCellWorldSize();
box.getMaximum() + Ogre::Vector3(center.x, center.y, 0) * 8192); 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 box;
} }
@ -208,6 +209,8 @@ namespace Terrain
Ogre::HardwareIndexBufferSharedPtr World::getIndexBuffer(int flags, size_t& numIndices) Ogre::HardwareIndexBufferSharedPtr World::getIndexBuffer(int flags, size_t& numIndices)
{ {
unsigned int verts = mStorage->getCellVertices();
if (mIndexBufferMap.find(flags) != mIndexBufferMap.end()) if (mIndexBufferMap.find(flags) != mIndexBufferMap.end())
{ {
numIndices = mIndexBufferMap[flags]->getNumIndexes(); numIndices = mIndexBufferMap[flags]->getNumIndexes();
@ -224,11 +227,11 @@ namespace Terrain
bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]); bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]);
size_t increment = 1 << lodLevel; size_t increment = 1 << lodLevel;
assert((int)increment < ESM::Land::LAND_SIZE); assert(increment < verts);
std::vector<short> indices; std::vector<short> indices;
indices.reserve((ESM::Land::LAND_SIZE-1)*(ESM::Land::LAND_SIZE-1)*2*3 / increment); indices.reserve((verts-1)*(verts-1)*2*3 / increment);
size_t rowStart = 0, colStart = 0, rowEnd = ESM::Land::LAND_SIZE-1, colEnd = ESM::Land::LAND_SIZE-1; 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, // 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 // mainly because stitching one edge would have an effect on corners and on the adjacent edges
if (anyDeltas) if (anyDeltas)
@ -242,13 +245,13 @@ namespace Terrain
{ {
for (size_t col = colStart; col < colEnd; col += increment) for (size_t col = colStart; col < colEnd; col += increment)
{ {
indices.push_back(ESM::Land::LAND_SIZE*col+row); indices.push_back(verts*col+row);
indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment); indices.push_back(verts*(col+increment)+row+increment);
indices.push_back(ESM::Land::LAND_SIZE*col+row+increment); indices.push_back(verts*col+row+increment);
indices.push_back(ESM::Land::LAND_SIZE*col+row); indices.push_back(verts*col+row);
indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row); indices.push_back(verts*(col+increment)+row);
indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment); indices.push_back(verts*(col+increment)+row+increment);
} }
} }
@ -261,96 +264,96 @@ namespace Terrain
// South // South
size_t row = 0; size_t row = 0;
size_t outerStep = 1 << (lodDeltas[South] + lodLevel); size_t outerStep = 1 << (lodDeltas[South] + lodLevel);
for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep) for (size_t col = 0; col < verts-1; col += outerStep)
{ {
indices.push_back(ESM::Land::LAND_SIZE*col+row); indices.push_back(verts*col+row);
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); indices.push_back(verts*(col+outerStep)+row);
// Make sure not to touch the right edge // Make sure not to touch the right edge
if (col+outerStep == ESM::Land::LAND_SIZE-1) if (col+outerStep == verts-1)
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep-innerStep)+row+innerStep); indices.push_back(verts*(col+outerStep-innerStep)+row+innerStep);
else else
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row+innerStep); indices.push_back(verts*(col+outerStep)+row+innerStep);
for (size_t i = 0; i < outerStep; i += innerStep) for (size_t i = 0; i < outerStep; i += innerStep)
{ {
// Make sure not to touch the left or right edges // Make sure not to touch the left or right edges
if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep) if (col+i == 0 || col+i == verts-1-innerStep)
continue; continue;
indices.push_back(ESM::Land::LAND_SIZE*(col)+row); indices.push_back(verts*(col)+row);
indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row+innerStep); indices.push_back(verts*(col+i+innerStep)+row+innerStep);
indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row+innerStep); indices.push_back(verts*(col+i)+row+innerStep);
} }
} }
// North // North
row = ESM::Land::LAND_SIZE-1; row = verts-1;
outerStep = 1 << (lodDeltas[North] + lodLevel); outerStep = 1 << (lodDeltas[North] + lodLevel);
for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep) for (size_t col = 0; col < verts-1; col += outerStep)
{ {
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); indices.push_back(verts*(col+outerStep)+row);
indices.push_back(ESM::Land::LAND_SIZE*col+row); indices.push_back(verts*col+row);
// Make sure not to touch the left edge // Make sure not to touch the left edge
if (col == 0) if (col == 0)
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row-innerStep); indices.push_back(verts*(col+innerStep)+row-innerStep);
else else
indices.push_back(ESM::Land::LAND_SIZE*col+row-innerStep); indices.push_back(verts*col+row-innerStep);
for (size_t i = 0; i < outerStep; i += innerStep) for (size_t i = 0; i < outerStep; i += innerStep)
{ {
// Make sure not to touch the left or right edges // Make sure not to touch the left or right edges
if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep) if (col+i == 0 || col+i == verts-1-innerStep)
continue; continue;
indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row-innerStep); indices.push_back(verts*(col+i)+row-innerStep);
indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row-innerStep); indices.push_back(verts*(col+i+innerStep)+row-innerStep);
indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); indices.push_back(verts*(col+outerStep)+row);
} }
} }
// West // West
size_t col = 0; size_t col = 0;
outerStep = 1 << (lodDeltas[West] + lodLevel); outerStep = 1 << (lodDeltas[West] + lodLevel);
for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep) for (size_t row = 0; row < verts-1; row += outerStep)
{ {
indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); indices.push_back(verts*col+row+outerStep);
indices.push_back(ESM::Land::LAND_SIZE*col+row); indices.push_back(verts*col+row);
// Make sure not to touch the top edge // Make sure not to touch the top edge
if (row+outerStep == ESM::Land::LAND_SIZE-1) if (row+outerStep == verts-1)
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep-innerStep); indices.push_back(verts*(col+innerStep)+row+outerStep-innerStep);
else else
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep); indices.push_back(verts*(col+innerStep)+row+outerStep);
for (size_t i = 0; i < outerStep; i += innerStep) for (size_t i = 0; i < outerStep; i += innerStep)
{ {
// Make sure not to touch the top or bottom edges // Make sure not to touch the top or bottom edges
if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep) if (row+i == 0 || row+i == verts-1-innerStep)
continue; continue;
indices.push_back(ESM::Land::LAND_SIZE*col+row); indices.push_back(verts*col+row);
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i); indices.push_back(verts*(col+innerStep)+row+i);
indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i+innerStep); indices.push_back(verts*(col+innerStep)+row+i+innerStep);
} }
} }
// East // East
col = ESM::Land::LAND_SIZE-1; col = verts-1;
outerStep = 1 << (lodDeltas[East] + lodLevel); outerStep = 1 << (lodDeltas[East] + lodLevel);
for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep) for (size_t row = 0; row < verts-1; row += outerStep)
{ {
indices.push_back(ESM::Land::LAND_SIZE*col+row); indices.push_back(verts*col+row);
indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); indices.push_back(verts*col+row+outerStep);
// Make sure not to touch the bottom edge // Make sure not to touch the bottom edge
if (row == 0) if (row == 0)
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+innerStep); indices.push_back(verts*(col-innerStep)+row+innerStep);
else else
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row); indices.push_back(verts*(col-innerStep)+row);
for (size_t i = 0; i < outerStep; i += innerStep) for (size_t i = 0; i < outerStep; i += innerStep)
{ {
// Make sure not to touch the top or bottom edges // Make sure not to touch the top or bottom edges
if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep) if (row+i == 0 || row+i == verts-1-innerStep)
continue; continue;
indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); indices.push_back(verts*col+row+outerStep);
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i+innerStep); indices.push_back(verts*(col-innerStep)+row+i+innerStep);
indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i); indices.push_back(verts*(col-innerStep)+row+i);
} }
} }
} }

@ -1,9 +1,13 @@
#ifndef CONFIG_H #ifndef VERSION_HPP
#define CONFIG_H #define VERSION_HPP
#define OPENMW_VERSION_MAJOR @OPENMW_VERSION_MAJOR@ #define OPENMW_VERSION_MAJOR @OPENMW_VERSION_MAJOR@
#define OPENMW_VERSION_MINOR @OPENMW_VERSION_MINOR@ #define OPENMW_VERSION_MINOR @OPENMW_VERSION_MINOR@
#define OPENMW_VERSION_RELEASE @OPENMW_VERSION_RELEASE@ #define OPENMW_VERSION_RELEASE @OPENMW_VERSION_RELEASE@
#define OPENMW_VERSION "@OPENMW_VERSION@" #define OPENMW_VERSION "@OPENMW_VERSION@"
#endif #define OPENMW_VERSION_COMMITHASH "@OPENMW_VERSION_COMMITHASH@"
#define OPENMW_VERSION_TAGHASH "@OPENMW_VERSION_TAGHASH@"
#endif // VERSION_HPP

@ -12,11 +12,10 @@
<!-- Avatar --> <!-- Avatar -->
<Widget type="Widget" skin="MW_Box" position="8 38 212 185" name="Avatar" align="Left Top Stretch"> <Widget type="Widget" skin="MW_Box" position="8 38 212 185" name="Avatar" align="Left Top Stretch">
<Widget type="ImageBox" skin="ImageBox" position="0 0 212 161" align="Stretch" name="AvatarImage">
<UserString key="ToolTipType" value="AvatarItemSelection"/> <UserString key="ToolTipType" value="AvatarItemSelection"/>
<Widget type="ImageBox" skin="ImageBox" position="0 0 212 185" align="Stretch" name="AvatarImage">
<Property key="NeedMouse" value="false"/>
</Widget> </Widget>
<Widget type="TextBox" skin="ProgressText" position="0 150 212 24" align="HCenter Bottom" name="ArmorRating"> <Widget type="TextBox" skin="ProgressText" position="0 161 212 24" align="HStretch Bottom" name="ArmorRating">
<Property key="NeedMouse" value="false"/> <Property key="NeedMouse" value="false"/>
</Widget> </Widget>
</Widget> </Widget>

@ -100,7 +100,9 @@
</Widget> </Widget>
<Widget type="Widget" skin="MW_Box" position="8 148 212 152" align="Left Top Stretch"> <Widget type="Widget" skin="MW_Box" position="8 148 212 152" align="Left Top Stretch">
<Widget type="Button" skin="" position="4 4 204 18" name="Attrib1Box" align="Left Top HStretch"> <!-- TODO: this should be a scroll view -->
<Widget type="Widget" skin="" position="4 4 204 144" align="Left Top Stretch">
<Widget type="Button" skin="" position="0 0 204 18" name="Attrib1Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/> <UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/> <UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeStrength}"/> <UserString key="Caption_AttributeName" value="#{sAttributeStrength}"/>
@ -114,7 +116,7 @@
</Widget> </Widget>
</Widget> </Widget>
<Widget type="Button" skin="" position="4 22 204 18" name="Attrib2Box" align="Left Top HStretch"> <Widget type="Button" skin="" position="0 18 204 18" name="Attrib2Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/> <UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/> <UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeIntelligence}"/> <UserString key="Caption_AttributeName" value="#{sAttributeIntelligence}"/>
@ -128,7 +130,7 @@
</Widget> </Widget>
</Widget> </Widget>
<Widget type="Button" skin="" position="4 40 204 18" name="Attrib3Box" align="Left Top HStretch"> <Widget type="Button" skin="" position="0 36 204 18" name="Attrib3Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/> <UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/> <UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeWillpower}"/> <UserString key="Caption_AttributeName" value="#{sAttributeWillpower}"/>
@ -142,7 +144,7 @@
</Widget> </Widget>
</Widget> </Widget>
<Widget type="Button" skin="" position="4 58 204 18" name="Attrib4Box" align="Left Top HStretch"> <Widget type="Button" skin="" position="0 54 204 18" name="Attrib4Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/> <UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/> <UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeAgility}"/> <UserString key="Caption_AttributeName" value="#{sAttributeAgility}"/>
@ -156,7 +158,7 @@
</Widget> </Widget>
</Widget> </Widget>
<Widget type="Button" skin="" position="4 76 204 18" name="Attrib5Box" align="Left Top HStretch"> <Widget type="Button" skin="" position="0 72 204 18" name="Attrib5Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/> <UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/> <UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeSpeed}"/> <UserString key="Caption_AttributeName" value="#{sAttributeSpeed}"/>
@ -170,7 +172,7 @@
</Widget> </Widget>
</Widget> </Widget>
<Widget type="Button" skin="" position="4 94 204 18" name="Attrib6Box" align="Left Top HStretch"> <Widget type="Button" skin="" position="0 90 204 18" name="Attrib6Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/> <UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/> <UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeEndurance}"/> <UserString key="Caption_AttributeName" value="#{sAttributeEndurance}"/>
@ -184,7 +186,7 @@
</Widget> </Widget>
</Widget> </Widget>
<Widget type="Button" skin="" position="4 112 204 18" name="Attrib7Box" align="Left Top HStretch"> <Widget type="Button" skin="" position="0 108 204 18" name="Attrib7Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/> <UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/> <UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributePersonality}"/> <UserString key="Caption_AttributeName" value="#{sAttributePersonality}"/>
@ -198,7 +200,7 @@
</Widget> </Widget>
</Widget> </Widget>
<Widget type="Button" skin="" position="4 130 204 18" name="Attrib8Box" align="Left Top HStretch"> <Widget type="Button" skin="" position="0 126 204 18" name="Attrib8Box" align="Left Top HStretch">
<UserString key="ToolTipType" value="Layout"/> <UserString key="ToolTipType" value="Layout"/>
<UserString key="ToolTipLayout" value="AttributeToolTip"/> <UserString key="ToolTipLayout" value="AttributeToolTip"/>
<UserString key="Caption_AttributeName" value="#{sAttributeLuck}"/> <UserString key="Caption_AttributeName" value="#{sAttributeLuck}"/>
@ -212,6 +214,7 @@
</Widget> </Widget>
</Widget> </Widget>
</Widget> </Widget>
</Widget>
</Widget> </Widget>

@ -2,6 +2,14 @@
<ui version="4.0"> <ui version="4.0">
<class>MainWindow</class> <class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow"> <widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>575</width>
<height>535</height>
</rect>
</property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>575</width> <width>575</width>
@ -55,6 +63,28 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="versionLabel">
<property name="text">
<string>OpenMW version</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons"> <property name="standardButtons">
@ -63,6 +93,8 @@
</widget> </widget>
</item> </item>
</layout> </layout>
</item>
</layout>
</widget> </widget>
</widget> </widget>
<resources> <resources>

Loading…
Cancel
Save