diff --git a/CMakeLists.txt b/CMakeLists.txt index f3ef797dd2..e11ad0c0ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ include (OpenMWMacros) # Version set (OPENMW_VERSION_MAJOR 0) -set (OPENMW_VERSION_MINOR 14) +set (OPENMW_VERSION_MINOR 15) set (OPENMW_VERSION_RELEASE 0) set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") @@ -103,6 +103,7 @@ set(OENGINE_OGRE ${LIBDIR}/openengine/ogre/mouselook.cpp ${LIBDIR}/openengine/ogre/fader.cpp ${LIBDIR}/openengine/ogre/imagerotate.cpp + ${LIBDIR}/openengine/ogre/atlas.cpp ) set(OENGINE_GUI ${LIBDIR}/openengine/gui/events.cpp @@ -122,6 +123,10 @@ set(OENGINE_BULLET ${LIBDIR}/openengine/bullet/physic.hpp ${LIBDIR}/openengine/bullet/BulletShapeLoader.cpp ${LIBDIR}/openengine/bullet/BulletShapeLoader.h + ${LIBDIR}/openengine/bullet/pmove.cpp + ${LIBDIR}/openengine/bullet/pmove.h + ${LIBDIR}/openengine/bullet/trace.cpp + ${LIBDIR}/openengine/bullet/trace.h ) diff --git a/Daedric Font License.txt b/Daedric Font License.txt new file mode 100644 index 0000000000..a1553d0b04 --- /dev/null +++ b/Daedric Font License.txt @@ -0,0 +1,10 @@ +Dongle's Oblivion Daedric font set +http://www.uesp.net/wiki/Lore:Daedric_Alphabet#Daedric_Font + +--------------------------------------------------- + +This was done entirely as a personal project. Bethesda Softworks graciously granted me the permission for it. I am not connected with them in any way. +You may freely use these fonts to create anything you'd like. You may re-distribute the fonts freely, over the Internet, or by any other means. Always keep the .zip file intact, and this read me included. +Please do not modify and redistribute the fonts without my permission. +You may NOT sell any of these fonts under any circumstances. This includes putting them on compilation font CDs for sale, putting them in a "members only" pay-area of a website, or any other means of financial gain connected in ANY way with the redistribution of any of these fonts. +You have my permission to create and sell any artwork made with these fonts, however you may need to contact Bethesda Softworks before doing so. diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 95b38d53ea..e156e4fbc2 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -1,7 +1,11 @@ +#include "graphicspage.hpp" + #include -#include "graphicspage.hpp" +#include + #include +#include GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, QWidget *parent) : QWidget(parent) @@ -17,12 +21,9 @@ GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, QWidget *parent) renderSystemLayout->addWidget(rendererLabel, 0, 0, 1, 1); renderSystemLayout->addWidget(mRendererComboBox, 0, 1, 1, 1); - mRendererStackedWidget = new QStackedWidget(rendererGroup); - QVBoxLayout *rendererGroupLayout = new QVBoxLayout(rendererGroup); rendererGroupLayout->addLayout(renderSystemLayout); - rendererGroupLayout->addWidget(mRendererStackedWidget); // Display QGroupBox *displayGroup = new QGroupBox(tr("Display"), this); @@ -52,107 +53,33 @@ GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, QWidget *parent) void GraphicsPage::createPages() { - // OpenGL rendering settings - QWidget *mOGLRendererPage = new QWidget(); + QWidget *main = new QWidget(); + QGridLayout *grid = new QGridLayout(main); - QLabel *OGLRTTLabel = new QLabel(tr("Preferred RTT Mode:"), mOGLRendererPage); - mOGLRTTComboBox = new QComboBox(mOGLRendererPage); + mVSyncCheckBox = new QCheckBox(tr("Vertical Sync"), main); + grid->addWidget(mVSyncCheckBox, 0, 0, 1, 1); - QLabel *OGLAntiAliasingLabel = new QLabel(tr("Antialiasing:"), mOGLRendererPage); - mOGLAntiAliasingComboBox = new QComboBox(mOGLRendererPage); + mFullScreenCheckBox = new QCheckBox(tr("Full Screen"), main); + grid->addWidget(mFullScreenCheckBox, 1, 0, 1, 1); + + QLabel *antiAliasingLabel = new QLabel(tr("Antialiasing:"), main); + mAntiAliasingComboBox = new QComboBox(main); + grid->addWidget(antiAliasingLabel, 2, 0, 1, 1); + grid->addWidget(mAntiAliasingComboBox, 2, 1, 1, 1); + + QLabel *resolutionLabel = new QLabel(tr("Resolution:"), main); + mResolutionComboBox = new QComboBox(main); + grid->addWidget(resolutionLabel, 3, 0, 1, 1); + grid->addWidget(mResolutionComboBox, 3, 1, 1, 1); - QGridLayout *OGLRendererLayout = new QGridLayout(mOGLRendererPage); QSpacerItem *vSpacer1 = new QSpacerItem(20, 10, QSizePolicy::Minimum, QSizePolicy::Expanding); + grid->addItem(vSpacer1, 4, 0, 1, 1); - OGLRendererLayout->addWidget(OGLRTTLabel, 0, 0, 1, 1); - OGLRendererLayout->addWidget(mOGLRTTComboBox, 0, 1, 1, 1); - OGLRendererLayout->addWidget(OGLAntiAliasingLabel, 1, 0, 1, 1); - OGLRendererLayout->addWidget(mOGLAntiAliasingComboBox, 1, 1, 1, 1); - OGLRendererLayout->addItem(vSpacer1, 2, 1, 1, 1); - - // OpenGL display settings - QWidget *mOGLDisplayPage = new QWidget(); - - QLabel *OGLResolutionLabel = new QLabel(tr("Resolution:"), mOGLDisplayPage); - mOGLResolutionComboBox = new QComboBox(mOGLDisplayPage); - - QLabel *OGLFrequencyLabel = new QLabel(tr("Display Frequency:"), mOGLDisplayPage); - mOGLFrequencyComboBox = new QComboBox(mOGLDisplayPage); - - mOGLVSyncCheckBox = new QCheckBox(tr("Vertical Sync"), mOGLDisplayPage); - mOGLFullScreenCheckBox = new QCheckBox(tr("Full Screen"), mOGLDisplayPage); - - QGridLayout *OGLDisplayLayout = new QGridLayout(mOGLDisplayPage); - QSpacerItem *vSpacer2 = new QSpacerItem(20, 10, QSizePolicy::Minimum, QSizePolicy::Minimum); - - OGLDisplayLayout->addWidget(OGLResolutionLabel, 0, 0, 1, 1); - OGLDisplayLayout->addWidget(mOGLResolutionComboBox, 0, 1, 1, 1); - OGLDisplayLayout->addWidget(OGLFrequencyLabel, 1, 0, 1, 1); - OGLDisplayLayout->addWidget(mOGLFrequencyComboBox, 1, 1, 1, 1); - - OGLDisplayLayout->addItem(vSpacer2, 2, 1, 1, 1); - OGLDisplayLayout->addWidget(mOGLVSyncCheckBox, 3, 0, 1, 1); - OGLDisplayLayout->addWidget(mOGLFullScreenCheckBox, 6, 0, 1, 1); - - // Direct3D rendering settings - QWidget *mD3DRendererPage = new QWidget(); - - QLabel *D3DRenderDeviceLabel = new QLabel(tr("Rendering Device:"), mD3DRendererPage); - mD3DRenderDeviceComboBox = new QComboBox(mD3DRendererPage); - - QLabel *D3DAntiAliasingLabel = new QLabel(tr("Antialiasing:"), mD3DRendererPage); - mD3DAntiAliasingComboBox = new QComboBox(mD3DRendererPage); - - QLabel *D3DFloatingPointLabel = new QLabel(tr("Floating-point Mode:"), mD3DRendererPage); - mD3DFloatingPointComboBox = new QComboBox(mD3DRendererPage); - - mD3DNvPerfCheckBox = new QCheckBox(tr("Allow NVPerfHUD"), mD3DRendererPage); - - QGridLayout *D3DRendererLayout = new QGridLayout(mD3DRendererPage); - QSpacerItem *vSpacer3 = new QSpacerItem(20, 10, QSizePolicy::Minimum, QSizePolicy::Minimum); - QSpacerItem *vSpacer4 = new QSpacerItem(20, 10, QSizePolicy::Minimum, QSizePolicy::Expanding); - - D3DRendererLayout->addWidget(D3DRenderDeviceLabel, 0, 0, 1, 1); - D3DRendererLayout->addWidget(mD3DRenderDeviceComboBox, 0, 1, 1, 1); - D3DRendererLayout->addWidget(D3DAntiAliasingLabel, 1, 0, 1, 1); - D3DRendererLayout->addWidget(mD3DAntiAliasingComboBox, 1, 1, 1, 1); - D3DRendererLayout->addWidget(D3DFloatingPointLabel, 2, 0, 1, 1); - D3DRendererLayout->addWidget(mD3DFloatingPointComboBox, 2, 1, 1, 1); - D3DRendererLayout->addItem(vSpacer3, 3, 1, 1, 1); - D3DRendererLayout->addWidget(mD3DNvPerfCheckBox, 4, 0, 1, 1); - D3DRendererLayout->addItem(vSpacer4, 5, 1, 1, 1); - - // Direct3D display settings - QWidget *mD3DDisplayPage = new QWidget(); - - QLabel *D3DResolutionLabel = new QLabel(tr("Resolution:"), mD3DDisplayPage); - mD3DResolutionComboBox = new QComboBox(mD3DDisplayPage); - - mD3DVSyncCheckBox = new QCheckBox(tr("Vertical Sync"), mD3DDisplayPage); - mD3DFullScreenCheckBox = new QCheckBox(tr("Full Screen"), mD3DDisplayPage); - - QGridLayout *mD3DDisplayLayout = new QGridLayout(mD3DDisplayPage); - QSpacerItem *vSpacer5 = new QSpacerItem(20, 10, QSizePolicy::Minimum, QSizePolicy::Minimum); - - mD3DDisplayLayout->addWidget(D3DResolutionLabel, 0, 0, 1, 1); - mD3DDisplayLayout->addWidget(mD3DResolutionComboBox, 0, 1, 1, 1); - mD3DDisplayLayout->addItem(vSpacer5, 1, 1, 1, 1); - mD3DDisplayLayout->addWidget(mD3DVSyncCheckBox, 2, 0, 1, 1); - mD3DDisplayLayout->addWidget(mD3DFullScreenCheckBox, 5, 0, 1, 1); - - // Add the created pages - mRendererStackedWidget->addWidget(mOGLRendererPage); - mRendererStackedWidget->addWidget(mD3DRendererPage); - - mDisplayStackedWidget->addWidget(mOGLDisplayPage); - mDisplayStackedWidget->addWidget(mD3DDisplayPage); + mDisplayStackedWidget->addWidget(main); } void GraphicsPage::setupConfig() { - QString ogreCfg = mCfgMgr.getOgreConfigPath().string().c_str(); - QFile file(ogreCfg); - mOgreConfig = new QSettings(ogreCfg, QSettings::IniFormat); } void GraphicsPage::setupOgre() @@ -164,32 +91,12 @@ void GraphicsPage::setupOgre() Ogre::LogManager* logMgr = OGRE_NEW Ogre::LogManager; logMgr->createLog((mCfgMgr.getLogPath().string() + "/launcherOgre.log"), true, false, false); - QString ogreCfg = QString::fromStdString(mCfgMgr.getOgreConfigPath().string()); - file.setFileName(ogreCfg); - - //we need to check that the path to the configuration file exists before we - //try and create an instance of Ogre::Root otherwise Ogre raises an exception - QDir configDir = QFileInfo(file).dir(); - if ( !configDir.exists() && !configDir.mkpath(configDir.path()) ) - { - QMessageBox msgBox; - msgBox.setWindowTitle("Error creating config file"); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(QString(tr("
Failed to create the configuration file

\ - Make sure you have write access to
%1

")).arg(configDir.path())); - msgBox.exec(); - - qApp->exit(1); - return; - } - try { #if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) - mOgre = new Ogre::Root("", file.fileName().toStdString(), "./launcherOgre.log"); + mOgre = new Ogre::Root("", "", "./launcherOgre.log"); #else - mOgre = new Ogre::Root(pluginCfg.toStdString(), file.fileName().toStdString(), "./launcherOgre.log"); + mOgre = new Ogre::Root(pluginCfg.toStdString(), "", "./launcherOgre.log"); #endif } catch(Ogre::Exception &ex) @@ -228,11 +135,19 @@ void GraphicsPage::setupOgre() mRendererComboBox->addItem((*r)->getName().c_str()); } - int index = mRendererComboBox->findText(mOgreConfig->value("Render System").toString()); + int index = mRendererComboBox->findText(QString::fromStdString(Settings::Manager::getString("render system", "Video"))); if ( index != -1) { mRendererComboBox->setCurrentIndex(index); } + else + { +#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 + mRendererComboBox->setCurrentIndex(mRendererComboBox->findText("Direct3D9 Rendering Subsystem")); +#else + mRendererComboBox->setCurrentIndex(mRendererComboBox->findText("OpenGL Rendering Subsystem")); +#endif + } // Create separate rendersystems QString openGLName = mRendererComboBox->itemText(mRendererComboBox->findText(QString("OpenGL"), Qt::MatchStartsWith)); @@ -255,217 +170,44 @@ void GraphicsPage::setupOgre() } // Now fill the GUI elements - // OpenGL - if (mOpenGLRenderSystem) { - mOGLRTTComboBox->addItems(getAvailableOptions(QString("RTT Preferred Mode"), mOpenGLRenderSystem)); - mOGLAntiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mOpenGLRenderSystem)); - mOGLResolutionComboBox->addItems(getAvailableOptions(QString("Video Mode"), mOpenGLRenderSystem)); - mOGLFrequencyComboBox->addItems(getAvailableOptions(QString("Display Frequency"), mOpenGLRenderSystem)); - } - - // Direct3D - if (mDirect3DRenderSystem) { - mD3DRenderDeviceComboBox->addItems(getAvailableOptions(QString("Rendering Device"), mDirect3DRenderSystem)); - mD3DAntiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mDirect3DRenderSystem)); - mD3DFloatingPointComboBox->addItems(getAvailableOptions(QString("Floating-point mode"), mDirect3DRenderSystem)); - mD3DResolutionComboBox->addItems(getAvailableOptions(QString("Video Mode"), mDirect3DRenderSystem)); - } + mAntiAliasingComboBox->clear(); + mResolutionComboBox->clear(); + mAntiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem)); + mResolutionComboBox->addItems(getAvailableOptions(QString("Video Mode"), mSelectedRenderSystem)); } void GraphicsPage::readConfig() { - // Read the config file settings - if (mOpenGLRenderSystem) { + if (Settings::Manager::getBool("vsync", "Video")) + mVSyncCheckBox->setCheckState(Qt::Checked); - int index = mOGLRTTComboBox->findText(getConfigValue("RTT Preferred Mode", mOpenGLRenderSystem)); - if ( index != -1) { - mOGLRTTComboBox->setCurrentIndex(index); - } + if (Settings::Manager::getBool("fullscreen", "Video")) + mFullScreenCheckBox->setCheckState(Qt::Checked); - index = mOGLAntiAliasingComboBox->findText(getConfigValue("FSAA", mOpenGLRenderSystem)); - if ( index != -1){ - mOGLAntiAliasingComboBox->setCurrentIndex(index); - } + int aaIndex = mAntiAliasingComboBox->findText(QString::fromStdString(Settings::Manager::getString("antialiasing", "Video"))); + if (aaIndex != -1) + mAntiAliasingComboBox->setCurrentIndex(aaIndex); - index = mOGLResolutionComboBox->findText(getConfigValue("Video Mode", mOpenGLRenderSystem)); - if ( index != -1) { - mOGLResolutionComboBox->setCurrentIndex(index); - } - - index = mOGLFrequencyComboBox->findText(getConfigValue("Display Frequency", mOpenGLRenderSystem)); - if ( index != -1) { - mOGLFrequencyComboBox->setCurrentIndex(index); - } - - // Now we do the same for the checkboxes - if (getConfigValue("VSync", mOpenGLRenderSystem) == QLatin1String("Yes")) { - mOGLVSyncCheckBox->setCheckState(Qt::Checked); - } - - if (getConfigValue("Full Screen", mOpenGLRenderSystem) == QLatin1String("Yes")) { - mOGLFullScreenCheckBox->setCheckState(Qt::Checked); - } - } - - if (mDirect3DRenderSystem) { - - int index = mD3DRenderDeviceComboBox->findText(getConfigValue("Rendering Device", mDirect3DRenderSystem)); - if ( index != -1) { - mD3DRenderDeviceComboBox->setCurrentIndex(index); - } - - index = mD3DAntiAliasingComboBox->findText(getConfigValue("FSAA", mDirect3DRenderSystem)); - if ( index != -1) { - mD3DAntiAliasingComboBox->setCurrentIndex(index); - } - - index = mD3DFloatingPointComboBox->findText(getConfigValue("Floating-point mode", mDirect3DRenderSystem)); - if ( index != -1) { - mD3DFloatingPointComboBox->setCurrentIndex(index); - } - - index = mD3DResolutionComboBox->findText(getConfigValue("Video Mode", mDirect3DRenderSystem)); - if ( index != -1) { - mD3DResolutionComboBox->setCurrentIndex(index); - } - - if (getConfigValue("Allow NVPerfHUD", mDirect3DRenderSystem) == QLatin1String("Yes")) { - mD3DNvPerfCheckBox->setCheckState(Qt::Checked); - } - - if (getConfigValue("VSync", mDirect3DRenderSystem) == QLatin1String("Yes")) { - mD3DVSyncCheckBox->setCheckState(Qt::Checked); - } - - if (getConfigValue("Full Screen", mDirect3DRenderSystem) == QLatin1String("Yes")) { - mD3DFullScreenCheckBox->setCheckState(Qt::Checked); - } - } + std::string resolution = boost::lexical_cast(Settings::Manager::getInt("resolution x", "Video")) + + " x " + boost::lexical_cast(Settings::Manager::getInt("resolution y", "Video")); + int resIndex = mResolutionComboBox->findText(QString::fromStdString(resolution)); + if (resIndex != -1) + mResolutionComboBox->setCurrentIndex(resIndex); } void GraphicsPage::writeConfig() { - mOgre->setRenderSystem(mSelectedRenderSystem); + Settings::Manager::setBool("vsync", "Video", mVSyncCheckBox->checkState()); + Settings::Manager::setBool("fullscreen", "Video", mFullScreenCheckBox->checkState()); + Settings::Manager::setString("antialiasing", "Video", mAntiAliasingComboBox->currentText().toStdString()); - if (mDirect3DRenderSystem) { - // Nvidia Performance HUD - if (mD3DNvPerfCheckBox->checkState() == Qt::Checked) { - mDirect3DRenderSystem->setConfigOption("Allow NVPerfHUD", "Yes"); - } else { - mDirect3DRenderSystem->setConfigOption("Allow NVPerfHUD", "No"); - } - - // Antialiasing - mDirect3DRenderSystem->setConfigOption("FSAA", mD3DAntiAliasingComboBox->currentText().toStdString()); - - // Full screen - if (mD3DFullScreenCheckBox->checkState() == Qt::Checked) { - mDirect3DRenderSystem->setConfigOption("Full Screen", "Yes"); - } else { - mDirect3DRenderSystem->setConfigOption("Full Screen", "No"); - } - - // Rendering device - mDirect3DRenderSystem->setConfigOption("Rendering Device", mD3DRenderDeviceComboBox->currentText().toStdString()); - - // VSync - if (mD3DVSyncCheckBox->checkState() == Qt::Checked) { - mDirect3DRenderSystem->setConfigOption("VSync", "Yes"); - } else { - mDirect3DRenderSystem->setConfigOption("VSync", "No"); - } - - // Resolution - mDirect3DRenderSystem->setConfigOption("Video Mode", mD3DResolutionComboBox->currentText().toStdString()); - } - - if (mOpenGLRenderSystem) { - // Display Frequency - mOpenGLRenderSystem->setConfigOption("Display Frequency", mOGLFrequencyComboBox->currentText().toStdString()); - - // Antialiasing - mOpenGLRenderSystem->setConfigOption("FSAA", mOGLAntiAliasingComboBox->currentText().toStdString()); - - // Full screen - if (mOGLFullScreenCheckBox->checkState() == Qt::Checked) { - mOpenGLRenderSystem->setConfigOption("Full Screen", "Yes"); - } else { - mOpenGLRenderSystem->setConfigOption("Full Screen", "No"); - } - - // RTT mode - mOpenGLRenderSystem->setConfigOption("RTT Preferred Mode", mOGLRTTComboBox->currentText().toStdString()); - - // VSync - if (mOGLVSyncCheckBox->checkState() == Qt::Checked) { - mOpenGLRenderSystem->setConfigOption("VSync", "Yes"); - } else { - mOpenGLRenderSystem->setConfigOption("VSync", "No"); - } - - // Resolution - mOpenGLRenderSystem->setConfigOption("Video Mode", mOGLResolutionComboBox->currentText().toStdString()); - } - - // Now we validate the options - QString ogreError = QString::fromStdString(mSelectedRenderSystem->validateConfigOptions()); - - if (!ogreError.isEmpty()) { - QMessageBox msgBox; - msgBox.setWindowTitle("Error validating Ogre configuration"); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
A problem occured while validating the graphics options

\ - The graphics options could not be saved.

\ - Press \"Show Details...\" for more information.
")); - msgBox.setDetailedText(ogreError); - msgBox.exec(); - - Ogre::LogManager::getSingletonPtr()->logMessage( "Caught exception in validateConfigOptions"); - - qCritical("Error validating configuration"); - - qApp->exit(1); - return; - } - - // Write the settings to the config file - - - try - { - mOgre->saveConfig(); - } - catch(Ogre::Exception &ex) - { - QString ogreError = QString::fromStdString(ex.getFullDescription().c_str()); - QMessageBox msgBox; - msgBox.setWindowTitle("Error writing Ogre configuration file"); - msgBox.setIcon(QMessageBox::Critical); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
Could not write the graphics configuration

\ - Please make sure you have the right permissions and try again.

\ - Press \"Show Details...\" for more information.
")); - msgBox.setDetailedText(ogreError); - msgBox.exec(); - - qCritical("Error saving Ogre configuration, the error reported was:\n %s", qPrintable(ogreError)); - - qApp->exit(1); - return; - } - -} - -QString GraphicsPage::getConfigValue(const QString &key, Ogre::RenderSystem *renderer) -{ - QString result; - - mOgreConfig->beginGroup(renderer->getName().c_str()); - result = mOgreConfig->value(key).toString(); - mOgreConfig->endGroup(); - - return result; + std::string resolution = mResolutionComboBox->currentText().toStdString(); + // parse resolution x and y from a string like "800 x 600" + size_t xPos = resolution.find("x"); + int resX = boost::lexical_cast(resolution.substr(0, xPos-1)); + int resY = boost::lexical_cast(resolution.substr(xPos+2, resolution.size()-(xPos+2))); + Settings::Manager::setInt("resolution x", "Video", resX); + Settings::Manager::setInt("resolution y", "Video", resY); } QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer) @@ -480,12 +222,17 @@ QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSy Ogre::StringVector::iterator opt_it; uint idx = 0; for (opt_it = i->second.possibleValues.begin (); - opt_it != i->second.possibleValues.end (); opt_it++, idx++) - { + opt_it != i->second.possibleValues.end (); opt_it++, idx++) + { - if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0) - result << QString::fromStdString((*opt_it).c_str()).simplified(); - } + if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0) + { + if (key == "FSAA" && *opt_it == "0") + result << QString("none"); + else + result << ((key == "FSAA") ? QString("MSAA ") : QString("")) + QString::fromStdString((*opt_it).c_str()).simplified(); + } + } } @@ -494,15 +241,11 @@ QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSy void GraphicsPage::rendererChanged(const QString &renderer) { - if (renderer.contains("Direct3D")) { - mRendererStackedWidget->setCurrentIndex(1); - mDisplayStackedWidget->setCurrentIndex(1); - } - - if (renderer.contains("OpenGL")) { - mRendererStackedWidget->setCurrentIndex(0); - mDisplayStackedWidget->setCurrentIndex(0); - } - mSelectedRenderSystem = mOgre->getRenderSystemByName(renderer.toStdString()); + + mAntiAliasingComboBox->clear(); + mResolutionComboBox->clear(); + + mAntiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem)); + mResolutionComboBox->addItems(getAvailableOptions(QString("Video Mode"), mSelectedRenderSystem)); } diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index 6a91a0628e..1b0c6f388c 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -49,33 +49,15 @@ private: QComboBox *mRendererComboBox; - QStackedWidget *mRendererStackedWidget; QStackedWidget *mDisplayStackedWidget; - // OpenGL - QComboBox *mOGLRTTComboBox; - QComboBox *mOGLAntiAliasingComboBox; - QComboBox *mOGLResolutionComboBox; - QComboBox *mOGLFrequencyComboBox; - - QCheckBox *mOGLVSyncCheckBox; - QCheckBox *mOGLFullScreenCheckBox; - - // Direct3D - QComboBox *mD3DRenderDeviceComboBox; - QComboBox *mD3DAntiAliasingComboBox; - QComboBox *mD3DFloatingPointComboBox; - QComboBox *mD3DResolutionComboBox; - - QCheckBox *mD3DNvPerfCheckBox; - QCheckBox *mD3DVSyncCheckBox; - QCheckBox *mD3DFullScreenCheckBox; - - QSettings *mOgreConfig; + QComboBox *mAntiAliasingComboBox; + QComboBox *mResolutionComboBox; + QCheckBox *mVSyncCheckBox; + QCheckBox *mFullScreenCheckBox; Files::ConfigurationManager &mCfgMgr; - QString getConfigValue(const QString &key, Ogre::RenderSystem *renderer); QStringList getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer); void createPages(); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 8bb618dd66..d404fed8e7 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -7,6 +7,28 @@ MainDialog::MainDialog() { + // Create the settings manager and load default settings file + const std::string localdefault = mCfgMgr.getLocalPath().string() + "/settings-default.cfg"; + const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/settings-default.cfg"; + + // prefer local + if (boost::filesystem::exists(localdefault)) + mSettings.loadDefault(localdefault); + else if (boost::filesystem::exists(globaldefault)) + mSettings.loadDefault(globaldefault); + else + throw std::runtime_error ("No default settings file found! Make sure the file \"settings-default.cfg\" was properly installed."); + + // load user settings if they exist, otherwise just load the default settings as user settings + const std::string settingspath = mCfgMgr.getUserPath().string() + "/settings.cfg"; + if (boost::filesystem::exists(settingspath)) + mSettings.loadUser(settingspath); + else if (boost::filesystem::exists(localdefault)) + mSettings.loadUser(localdefault); + else if (boost::filesystem::exists(globaldefault)) + mSettings.loadUser(globaldefault); + + mIconWidget = new QListWidget; mIconWidget->setObjectName("IconWidget"); mIconWidget->setViewMode(QListView::IconMode); @@ -178,6 +200,11 @@ void MainDialog::closeEvent(QCloseEvent *event) // Now write all config files mDataFilesPage->writeConfig(); mGraphicsPage->writeConfig(); + + // Save user settings + const std::string settingspath = mCfgMgr.getUserPath().string() + "/settings.cfg"; + mSettings.saveUser(settingspath); + event->accept(); } diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index d6d0e9974e..0065aa8c47 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -4,6 +4,7 @@ #include #include +#include class QListWidget; class QListWidgetItem; @@ -41,6 +42,7 @@ private: DataFilesPage *mDataFilesPage; Files::ConfigurationManager mCfgMgr; + Settings::Manager mSettings; }; #endif diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 5503a7c1a8..d396df648f 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -8,7 +8,9 @@ #include #include -MwIniImporter::MwIniImporter() { +MwIniImporter::MwIniImporter() + : mVerbose(false) +{ const char *map[][2] = { { "fps", "General:Show FPS" }, @@ -124,9 +126,9 @@ MwIniImporter::multistrmap MwIniImporter::loadCfgFile(std::string filename) { void MwIniImporter::merge(multistrmap &cfg, multistrmap &ini) { multistrmap::iterator cfgIt; multistrmap::iterator iniIt; - for(strmap::iterator it=mMergeMap.begin(); it!=mMergeMap.end(); it++) { + for(strmap::iterator it=mMergeMap.begin(); it!=mMergeMap.end(); ++it) { if((iniIt = ini.find(it->second)) != ini.end()) { - for(std::vector::iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); vc++) { + for(std::vector::iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); ++vc) { cfg.erase(it->first); insertMultistrmap(cfg, it->first, *vc); } @@ -139,9 +141,9 @@ void MwIniImporter::mergeFallback(multistrmap &cfg, multistrmap &ini) { multistrmap::iterator cfgIt; multistrmap::iterator iniIt; - for(std::vector::iterator it=mMergeFallback.begin(); it!=mMergeFallback.end(); it++) { + for(std::vector::iterator it=mMergeFallback.begin(); it!=mMergeFallback.end(); ++it) { if((iniIt = ini.find(*it)) != ini.end()) { - for(std::vector::iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); vc++) { + for(std::vector::iterator vc = iniIt->second.begin(); vc != iniIt->second.end(); ++vc) { std::string value(*it); std::replace( value.begin(), value.end(), ' ', '_' ); std::replace( value.begin(), value.end(), ':', '_' ); @@ -176,7 +178,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { break; } - for(std::vector::iterator entry = it->second.begin(); entry!=it->second.end(); entry++) { + for(std::vector::iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { std::string filetype(entry->substr(entry->length()-4, 3)); std::transform(filetype.begin(), filetype.end(), filetype.begin(), ::tolower); @@ -194,22 +196,22 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { cfg.erase("master"); cfg.insert( std::make_pair > ("master", std::vector() ) ); - for(std::vector::iterator it=esmFiles.begin(); it!=esmFiles.end(); it++) { + for(std::vector::iterator it=esmFiles.begin(); it!=esmFiles.end(); ++it) { cfg["master"].push_back(*it); } cfg.erase("plugin"); cfg.insert( std::make_pair > ("plugin", std::vector() ) ); - for(std::vector::iterator it=espFiles.begin(); it!=espFiles.end(); it++) { + for(std::vector::iterator it=espFiles.begin(); it!=espFiles.end(); ++it) { cfg["plugin"].push_back(*it); } } void MwIniImporter::writeToFile(boost::iostreams::stream &out, multistrmap &cfg) { - for(multistrmap::iterator it=cfg.begin(); it != cfg.end(); it++) { - for(std::vector::iterator entry=it->second.begin(); entry != it->second.end(); entry++) { + for(multistrmap::iterator it=cfg.begin(); it != cfg.end(); ++it) { + for(std::vector::iterator entry=it->second.begin(); entry != it->second.end(); ++entry) { out << (it->first) << "=" << (*entry) << std::endl; } } diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index fb0e1db694..045f504a74 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -16,6 +16,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky player animation npcanimation creatureanimation actors objects renderinginterface localmap occlusionquery terrain terrainmaterial water shadows shaderhelper + compositors ) add_openmw_dir (mwinput @@ -23,9 +24,11 @@ add_openmw_dir (mwinput ) add_openmw_dir (mwgui - layouts text_input widgets race class birth review window_manager console dialogue + text_input widgets race class birth review window_manager console dialogue dialogue_history window_base stats_window messagebox journalwindow charactercreation - map_window window_pinnable_base cursorreplace + map_window window_pinnable_base cursorreplace tooltips scrollwindow bookwindow list + formatting inventorywindow container hud countdialog tradewindow settingswindow + confirmationdialog alchemywindow referenceinterface spellwindow ) add_openmw_dir (mwdialogue @@ -46,7 +49,8 @@ add_openmw_dir (mwsound add_openmw_dir (mwworld refdata world physicssystem scene globals class action nullaction actionteleport containerstore actiontalk actiontake manualref player cellfunctors - cells localscripts customdata weather inventorystore ptr + cells localscripts customdata weather inventorystore ptr actionopen actionread + actionequip timestamp actionalchemy ) add_openmw_dir (mwclass @@ -56,6 +60,7 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanager stat creaturestats magiceffects movement actors drawstate spells + activespells ) add_openmw_dir (mwbase diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 4ae6ae747c..e192b1f888 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -74,47 +74,6 @@ void OMW::Engine::executeLocalScripts() localScripts.setIgnore (MWWorld::Ptr()); } -void OMW::Engine::updateFocusReport (float duration) -{ - - if ((mFocusTDiff += duration)>0.25) - { - mFocusTDiff = 0; - - std::string name; - - std::string handle = MWBase::Environment::get().getWorld()->getFacedHandle(); - - if (!handle.empty()) - { - // the faced handle is not updated immediately, so on a cell change it might - // point to an object that doesn't exist anymore - // therefore, we are catching the "Unknown Ogre handle" exception that occurs in this case - try - { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPtrViaHandle (handle); - - if (!ptr.isEmpty()){ - name = MWWorld::Class::get (ptr).getName (ptr); - - } - } - catch (std::runtime_error& e) - {} - } - - if (name!=mFocusName) - { - mFocusName = name; - - if (mFocusName.empty()) - std::cout << "Unfocus" << std::endl; - else - std::cout << "Focus: " << name << std::endl; - } - } -} - void OMW::Engine::setAnimationVerbose(bool animverbose){ if(animverbose){ NifOgre::NIFLoader::getSingletonPtr()->setOutputAnimFiles(true); @@ -135,14 +94,6 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) if (mUseSound) MWBase::Environment::get().getSoundManager()->update (evt.timeSinceLastFrame); - // update GUI - Ogre::RenderWindow* window = mOgre->getWindow(); - MWBase::Environment::get().getWindowManager()->wmUpdateFps(window->getLastFPS(), - window->getTriangleCount(), - window->getBatchCount()); - - MWBase::Environment::get().getWindowManager()->onFrame(mEnvironment.getFrameDuration()); - // global scripts MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); @@ -154,7 +105,7 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) // frame. // passing of time - if (MWBase::Environment::get().getWindowManager()->getMode()==MWGui::GM_Game) + if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) MWBase::Environment::get().getWorld()->advanceTime ( mEnvironment.getFrameDuration()*MWBase::Environment::get().getWorld()->getTimeScaleFactor()/3600); @@ -165,17 +116,21 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) // update actors std::vector > movement; MWBase::Environment::get().getMechanicsManager()->update (movement, mEnvironment.getFrameDuration(), - MWBase::Environment::get().getWindowManager()->getMode()!=MWGui::GM_Game); + MWBase::Environment::get().getWindowManager()->isGuiMode()); - if (MWBase::Environment::get().getWindowManager()->getMode()==MWGui::GM_Game) + if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) MWBase::Environment::get().getWorld()->doPhysics (movement, mEnvironment.getFrameDuration()); // update world MWBase::Environment::get().getWorld()->update (evt.timeSinceLastFrame); - // report focus object (for debugging) - if (mReportFocus) - updateFocusReport (mEnvironment.getFrameDuration()); + // update GUI + Ogre::RenderWindow* window = mOgre->getWindow(); + MWBase::Environment::get().getWindowManager()->wmUpdateFps(window->getLastFPS(), + window->getTriangleCount(), + window->getBatchCount()); + + MWBase::Environment::get().getWindowManager()->onFrame(evt.timeSinceLastFrame); } catch (const std::exception& e) { @@ -193,8 +148,6 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mNewGame (false) , mUseSound (true) , mCompileAll (false) - , mReportFocus (false) - , mFocusTDiff (0) , mScriptContext (0) , mFSStrict (false) , mCfgMgr(configurationManager) @@ -205,13 +158,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) OMW::Engine::~Engine() { - delete MWBase::Environment::get().getInputManager(); - delete MWBase::Environment::get().getSoundManager(); - delete MWBase::Environment::get().getMechanicsManager(); - delete MWBase::Environment::get().getDialogueManager(); - delete MWBase::Environment::get().getJournal(); - delete MWBase::Environment::get().getScriptManager(); - delete MWBase::Environment::get().getWorld(); + mEnvironment.cleanup(); delete mScriptContext; delete mOgre; } @@ -247,6 +194,12 @@ void OMW::Engine::addResourcesDirectory (const boost::filesystem::path& path) Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, true); } +void OMW::Engine::addZipResource (const boost::filesystem::path& path) +{ + mOgre->getRoot()->addResourceLocation (path.string(), "Zip", + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, false); +} + void OMW::Engine::enableFSStrict(bool fsStrict) { mFSStrict = fsStrict; @@ -305,31 +258,16 @@ void OMW::Engine::setNewGame(bool newGame) mNewGame = newGame; } -void OMW::Engine::setReportFocus (bool report) -{ - mReportFocus = report; -} - // Initialise and enter main loop. void OMW::Engine::go() { - mFocusTDiff = 0; assert (!mCellName.empty()); assert (!mMaster.empty()); assert (!mOgre); mOgre = new OEngine::Render::OgreRenderer; - //we need to ensure the path to the configuration exists before creating an - //instance of ogre root so that Ogre doesn't raise an exception when trying to - //access it - const boost::filesystem::path configPath = mCfgMgr.getOgreConfigPath().parent_path(); - if ( !boost::filesystem::exists(configPath) ) - { - boost::filesystem::create_directories(configPath); - } - // Create the settings manager and load default settings file Settings::Manager settings; const std::string localdefault = mCfgMgr.getLocalPath().string() + "/settings-default.cfg"; @@ -340,6 +278,8 @@ void OMW::Engine::go() settings.loadDefault(localdefault); else if (boost::filesystem::exists(globaldefault)) settings.loadDefault(globaldefault); + else + throw std::runtime_error ("No default settings file found! Make sure the file \"settings-default.cfg\" was properly installed."); // load user settings if they exist, otherwise just load the default settings as user settings const std::string settingspath = mCfgMgr.getUserPath().string() + "/settings.cfg"; @@ -359,10 +299,20 @@ void OMW::Engine::go() else if (boost::filesystem::exists(mCfgMgr.getGlobalPath().string() + "/transparency-overrides.cfg")) nifOverrides.loadTransparencyOverrides(mCfgMgr.getGlobalPath().string() + "/transparency-overrides.cfg"); - mOgre->configure(!boost::filesystem::is_regular_file(mCfgMgr.getOgreConfigPath()), - mCfgMgr.getOgreConfigPath().string(), + std::string renderSystem = settings.getString("render system", "Video"); + if (renderSystem == "") + { +#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 + renderSystem = "Direct3D9 Rendering Subsystem"; +#else + renderSystem = "OpenGL Rendering Subsystem"; +#endif + } + mOgre->configure( mCfgMgr.getLogPath().string(), - mCfgMgr.getPluginsConfigPath().string(), false); + mCfgMgr.getPluginsConfigPath().string(), + renderSystem, + false); // This has to be added BEFORE MyGUI is initialized, as it needs // to find core.xml here. @@ -373,9 +323,17 @@ void OMW::Engine::go() addResourcesDirectory(mResDir / "water"); addResourcesDirectory(mResDir / "gbuffer"); addResourcesDirectory(mResDir / "shadows"); + addZipResource(mResDir / "mygui" / "Obliviontt.zip"); // Create the window - mOgre->createWindow("OpenMW"); + OEngine::Render::WindowSettings windowSettings; + windowSettings.fullscreen = settings.getBool("fullscreen", "Video"); + windowSettings.window_x = settings.getInt("resolution x", "Video"); + windowSettings.window_y = settings.getInt("resolution y", "Video"); + windowSettings.vsync = settings.getBool("vsync", "Video"); + std::string aa = settings.getString("antialiasing", "Video"); + windowSettings.fsaa = (aa.substr(0, 4) == "MSAA") ? aa.substr(5, aa.size()-5) : "0"; + mOgre->createWindow("OpenMW", windowSettings); loadBSA(); @@ -463,7 +421,7 @@ void OMW::Engine::go() void OMW::Engine::activate() { - if (MWBase::Environment::get().getWindowManager()->getMode()!=MWGui::GM_Game) + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; std::string handle = MWBase::Environment::get().getWorld()->getFacedHandle(); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index ccd7314cbc..cf1ef3b9c9 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -62,6 +62,7 @@ namespace OMW /// \brief Main engine class, that brings together all the components of OpenMW class Engine : private Ogre::FrameListener { + MWBase::Environment mEnvironment; std::string mEncoding; Files::PathContainer mDataDirs; boost::filesystem::path mResDir; @@ -74,12 +75,9 @@ namespace OMW bool mNewGame; bool mUseSound; bool mCompileAll; - bool mReportFocus; - float mFocusTDiff; std::string mFocusName; std::map mFallbackMap; - MWBase::Environment mEnvironment; Compiler::Extensions mExtensions; Compiler::Context *mScriptContext; @@ -93,16 +91,16 @@ namespace OMW /// add resources directory /// \note This function works recursively. - void addResourcesDirectory (const boost::filesystem::path& path); + /// add a .zip resource + void addZipResource (const boost::filesystem::path& path); + /// Load all BSA files in data directory. void loadBSA(); void executeLocalScripts(); - void updateFocusReport (float duration); - virtual bool frameRenderingQueued (const Ogre::FrameEvent& evt); public: @@ -145,9 +143,6 @@ namespace OMW /// Start as a new game. void setNewGame(bool newGame); - /// Write name of focussed object to cout - void setReportFocus (bool report); - /// Initialise and enter main loop. void go(); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index df52faab14..68aa12fb36 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -155,9 +155,6 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" "\n\twin1252 - Western European (Latin) alphabet, used by default") - ("report-focus", bpo::value()->implicit_value(true) - ->default_value(false), "write name of focussed object to cout") - ("fallback", bpo::value()->default_value(FallbackMap(), "") ->multitoken()->composing(), "fallback values") @@ -265,7 +262,6 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setSoundUsage(!variables["nosound"].as()); engine.setScriptsVerbosity(variables["script-verbose"].as()); engine.setCompileAll(variables["script-all"].as()); - engine.setReportFocus(variables["report-focus"].as()); engine.setAnimationVerbose(variables["anim-verbose"].as()); engine.setFallbackValues(variables["fallback"].as().mMap); diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 8e9a9cfce3..792e240f4b 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -3,6 +3,19 @@ #include +#include "../mwinput/inputmanager.hpp" + +#include "../mwscript/scriptmanager.hpp" + +#include "../mwsound/soundmanager.hpp" + +#include "../mwworld/world.hpp" + +#include "../mwdialogue/dialoguemanager.hpp" +#include "../mwdialogue/journal.hpp" + +#include "../mwmechanics/mechanicsmanager.hpp" + MWBase::Environment *MWBase::Environment::sThis = 0; MWBase::Environment::Environment() @@ -15,6 +28,7 @@ MWBase::Environment::Environment() MWBase::Environment::~Environment() { + cleanup(); sThis = 0; } @@ -116,6 +130,30 @@ float MWBase::Environment::getFrameDuration() const return mFrameDuration; } +void MWBase::Environment::cleanup() +{ + delete mInputManager; + mInputManager = 0; + + delete mSoundManager; + mSoundManager = 0; + + delete mMechanicsManager; + mMechanicsManager = 0; + + delete mDialogueManager; + mDialogueManager = 0; + + delete mJournal; + mJournal = 0; + + delete mScriptManager; + mScriptManager = 0; + + delete mWorld; + mWorld = 0; +} + const MWBase::Environment& MWBase::Environment::get() { assert (sThis); diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index a010e7faa0..521beee0aa 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -43,7 +43,7 @@ namespace MWBase /// /// This class allows each mw-subsystem to access any others subsystem's top-level manager class. /// - /// \attention Environment does not take ownership of the manager class instances it is handed over in + /// \attention Environment takes ownership of the manager class instances it is handed over in /// the set* functions. class Environment { @@ -108,6 +108,9 @@ namespace MWBase float getFrameDuration() const; + void cleanup(); + ///< Delete all mw*-subsystems. + static const Environment& get(); ///< Return instance of this class. }; diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 5b5028f1ca..505f61f4ca 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -1,13 +1,14 @@ #include "activator.hpp" -#include "../mwrender/objects.hpp" #include #include #include "../mwworld/ptr.hpp" - +#include "../mwrender/objects.hpp" +#include "../mwbase/environment.hpp" +#include "../mwgui/window_manager.hpp" namespace MWClass { @@ -63,4 +64,28 @@ namespace MWClass registerClass (typeid (ESM::Activator).name(), instance); } + + bool Activator::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Activator::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + + std::string text; + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + info.text = text; + + return info; + } } diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index d0b1f25e06..223dd0a36b 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -18,6 +18,12 @@ namespace MWClass ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index e7c208d4a2..6945f3c271 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -9,9 +9,14 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/actionalchemy.hpp" +#include "../mwworld/world.hpp" #include "../mwrender/objects.hpp" +#include "../mwgui/window_manager.hpp" +#include "../mwgui/tooltips.hpp" + #include "../mwsound/soundmanager.hpp" namespace MWClass @@ -95,4 +100,51 @@ namespace MWClass { return std::string("Item Apparatus Down"); } + + std::string Apparatus::getInventoryIcon (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->icon; + } + + bool Apparatus::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Apparatus::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.icon = ref->base->icon; + + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + std::string text; + text += "\n" + store.gameSettings.search("sQuality")->str + ": " + MWGui::ToolTips::toString(ref->base->data.quality); + text += "\n" + store.gameSettings.search("sWeight")->str + ": " + MWGui::ToolTips::toString(ref->base->data.weight); + text += MWGui::ToolTips::getValueString(ref->base->data.value, store.gameSettings.search("sValue")->str); + + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + text += MWGui::ToolTips::getMiscString(ref->ref.owner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + } + info.text = text; + + return info; + } + + + boost::shared_ptr Apparatus::use (const MWWorld::Ptr& ptr) const + { + return boost::shared_ptr(new MWWorld::ActionAlchemy()); + } } diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 9015aedb85..f33f92e2c7 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -28,6 +28,12 @@ namespace MWClass virtual int getValue (const MWWorld::Ptr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + static void registerSelf(); virtual std::string getUpSoundId (const MWWorld::Ptr& ptr) const; @@ -35,6 +41,13 @@ namespace MWClass virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; ///< Return the put down sound Id + + virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + ///< Return name of inventory icon. + + virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) + const; + ///< Generate action for using via inventory menu }; } diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 3a34032611..83c0120c78 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -9,6 +9,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/world.hpp" @@ -16,6 +17,8 @@ #include "../mwrender/objects.hpp" +#include "../mwgui/window_manager.hpp" + #include "../mwsound/soundmanager.hpp" namespace MWClass @@ -138,8 +141,7 @@ namespace MWClass case ESM::Armor::Boots: typeGmst = "iBootsWeight"; break; case ESM::Armor::LGauntlet: case ESM::Armor::RGauntlet: typeGmst = "iGauntletWeight"; break; -/// \todo how to determine if shield light, medium or heavy? -// case ESM::Armor::Shield: + case ESM::Armor::Shield: typeGmst = "iShieldWeight"; break; case ESM::Armor::LBracer: case ESM::Armor::RBracer: typeGmst = "iGauntletWeight"; break; } @@ -196,4 +198,78 @@ namespace MWClass else return std::string("Item Armor Heavy Down"); } + + std::string Armor::getInventoryIcon (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->icon; + } + + bool Armor::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Armor::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.icon = ref->base->icon; + + std::string text; + + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + // get armor type string (light/medium/heavy) + int armorType = getEquipmentSkill(ptr); + std::string typeText; + if (armorType == ESM::Skill::LightArmor) + typeText = store.gameSettings.search("sLight")->str; + else if (armorType == ESM::Skill::MediumArmor) + typeText = store.gameSettings.search("sMedium")->str; + else + typeText = store.gameSettings.search("sHeavy")->str; + + text += "\n" + store.gameSettings.search("sArmorRating")->str + ": " + MWGui::ToolTips::toString(ref->base->data.armor); + + /// \todo store the current armor health somewhere + text += "\n" + store.gameSettings.search("sCondition")->str + ": " + MWGui::ToolTips::toString(ref->base->data.health); + + text += "\n" + store.gameSettings.search("sWeight")->str + ": " + MWGui::ToolTips::toString(ref->base->data.weight) + " (" + typeText + ")"; + text += MWGui::ToolTips::getValueString(ref->base->data.value, store.gameSettings.search("sValue")->str); + + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + text += MWGui::ToolTips::getMiscString(ref->ref.owner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + } + + info.enchant = ref->base->enchant; + + info.text = text; + + return info; + } + + std::string Armor::getEnchantment (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->enchant; + } + + boost::shared_ptr Armor::use (const MWWorld::Ptr& ptr) const + { + MWBase::Environment::get().getSoundManager()->playSound (getUpSoundId(ptr), 1.0, 1.0); + + return boost::shared_ptr(new MWWorld::ActionEquip(ptr)); + } } diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index 6cba077264..a638061625 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -39,6 +39,12 @@ namespace MWClass /// Return the index of the skill this item corresponds to when equiopped or -1, if there is /// no such skill. + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual int getValue (const MWWorld::Ptr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. @@ -49,6 +55,17 @@ namespace MWClass virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; ///< Return the put down sound Id + + virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + ///< Return name of inventory icon. + + virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + ///< @return the enchantment ID if the object is enchanted, otherwise an empty string + + virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) + const; + ///< Generate action for using via inventory menu + }; } diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index b3b71104b6..a107d9b210 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -8,10 +8,13 @@ #include "../mwbase/environment.hpp" #include "../mwworld/ptr.hpp" -#include "../mwworld/actiontake.hpp" +#include "../mwworld/actionread.hpp" +#include "../mwworld/world.hpp" #include "../mwrender/objects.hpp" +#include "../mwgui/window_manager.hpp" + #include "../mwsound/soundmanager.hpp" namespace MWClass @@ -57,12 +60,8 @@ namespace MWClass boost::shared_ptr Book::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const { - // TODO implement reading - - MWBase::Environment::get().getSoundManager()->playSound3D (ptr, getUpSoundId(ptr), 1.0, 1.0, MWSound::Play_NoTrack); - return boost::shared_ptr ( - new MWWorld::ActionTake (ptr)); + new MWWorld::ActionRead (ptr)); } std::string Book::getScript (const MWWorld::Ptr& ptr) const @@ -97,4 +96,62 @@ namespace MWClass { return std::string("Item Book Down"); } + + std::string Book::getInventoryIcon (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->icon; + } + + bool Book::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Book::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.icon = ref->base->icon; + + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + std::string text; + + text += "\n" + store.gameSettings.search("sWeight")->str + ": " + MWGui::ToolTips::toString(ref->base->data.weight); + text += MWGui::ToolTips::getValueString(ref->base->data.value, store.gameSettings.search("sValue")->str); + + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + text += MWGui::ToolTips::getMiscString(ref->ref.owner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + } + + info.enchant = ref->base->enchant; + + info.text = text; + + return info; + } + + std::string Book::getEnchantment (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->enchant; + } + + boost::shared_ptr Book::use (const MWWorld::Ptr& ptr) const + { + return boost::shared_ptr(new MWWorld::ActionRead(ptr)); + } + } diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index 975cdf12b3..ee3aac8d81 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -25,6 +25,12 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual int getValue (const MWWorld::Ptr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. @@ -35,6 +41,15 @@ namespace MWClass virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; ///< Return the put down sound Id + + virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + ///< Return name of inventory icon. + + virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + ///< @return the enchantment ID if the object is enchanted, otherwise an empty string + + virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) const; + ///< Generate action for using via inventory menu }; } diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 15f00bb053..11b515faf7 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -9,7 +9,12 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/world.hpp" + +#include "../mwgui/tooltips.hpp" +#include "../mwgui/window_manager.hpp" #include "../mwrender/objects.hpp" @@ -161,4 +166,63 @@ namespace MWClass } return std::string("Item Clothes Down"); } + + std::string Clothing::getInventoryIcon (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->icon; + } + + bool Clothing::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Clothing::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.icon = ref->base->icon; + + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + std::string text; + + text += "\n" + store.gameSettings.search("sWeight")->str + ": " + MWGui::ToolTips::toString(ref->base->data.weight); + text += MWGui::ToolTips::getValueString(ref->base->data.value, store.gameSettings.search("sValue")->str); + + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + text += MWGui::ToolTips::getMiscString(ref->ref.owner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + } + + info.enchant = ref->base->enchant; + + info.text = text; + + return info; + } + + std::string Clothing::getEnchantment (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->enchant; + } + + boost::shared_ptr Clothing::use (const MWWorld::Ptr& ptr) const + { + MWBase::Environment::get().getSoundManager()->playSound (getUpSoundId(ptr), 1.0, 1.0); + + return boost::shared_ptr(new MWWorld::ActionEquip(ptr)); + } } diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index 7fc05bef35..aba317be0e 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -33,6 +33,12 @@ namespace MWClass /// Return the index of the skill this item corresponds to when equiopped or -1, if there is /// no such skill. + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual int getValue (const MWWorld::Ptr& ptr) const; ///< Return trade value of the object. Throws an exception, if the object can't be traded. @@ -43,6 +49,16 @@ namespace MWClass virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; ///< Return the put down sound Id + + virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + ///< Return name of inventory icon. + + virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + ///< @return the enchantment ID if the object is enchanted, otherwise an empty string + + virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) + const; + ///< Generate action for using via inventory menu }; } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index a229cbe9b0..e9b8ce31fc 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -11,8 +11,13 @@ #include "../mwworld/nullaction.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/customdata.hpp" +#include "../mwworld/world.hpp" + +#include "../mwgui/window_manager.hpp" +#include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" +#include "../mwworld/actionopen.hpp" #include "../mwsound/soundmanager.hpp" @@ -82,6 +87,7 @@ namespace MWClass const std::string lockedSound = "LockedChest"; const std::string trapActivationSound = "Disarm Trap Fail"; + if (ptr.getCellRef().lockLevel>0) { // TODO check for key @@ -95,7 +101,8 @@ namespace MWClass if(ptr.getCellRef().trap.empty()) { // Not trapped, Inventory GUI goes here - return boost::shared_ptr (new MWWorld::NullAction); + //return boost::shared_ptr (new MWWorld::NullAction); + return boost::shared_ptr (new MWWorld::ActionOpen(ptr)); } else { @@ -138,4 +145,51 @@ namespace MWClass registerClass (typeid (ESM::Container).name(), instance); } + + bool Container::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Container::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name; + + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + std::string text; + if (ref->ref.lockLevel > 0) + text += "\n" + store.gameSettings.search("sLockLevel")->str + ": " + MWGui::ToolTips::toString(ref->ref.lockLevel); + if (ref->ref.trap != "") + text += "\n" + store.gameSettings.search("sTrapped")->str; + + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + text += MWGui::ToolTips::getMiscString(ref->ref.owner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + } + + info.text = text; + + return info; + } + + float Container::getCapacity (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->weight; + } + + float Container::getEncumbrance (const MWWorld::Ptr& ptr) const + { + return getContainerStore (ptr).getWeight(); + } } diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index c371e8898d..44f0fe927b 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -24,12 +24,26 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const; ///< Return container store virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual float getCapacity (const MWWorld::Ptr& ptr) const; + ///< Return total weight that fits into the object. Throws an exception, if the object can't + /// hold other objects. + + virtual float getEncumbrance (const MWWorld::Ptr& ptr) const; + ///< Returns total weight of objects inside this object (including modifications from magic + /// effects). Throws an exception, if the object can't hold other objects. + static void registerSelf(); }; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 80023f1a88..a3ede19b6f 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -5,6 +5,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/mechanicsmanager.hpp" +#include "../mwmechanics/magiceffects.hpp" #include "../mwbase/environment.hpp" @@ -13,6 +14,8 @@ #include "../mwworld/customdata.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwgui/window_manager.hpp" + namespace { struct CustomData : public MWWorld::CustomData @@ -54,8 +57,6 @@ namespace MWClass data->mCreatureStats.mLevel = ref->base->data.level; - // \todo add initial container content - // store ptr.getRefData().setCustomData (data.release()); } @@ -80,24 +81,15 @@ namespace MWClass ESMS::LiveCellRef *ref = ptr.get(); - const std::string &model = ref->base->model; assert (ref->base != NULL); if(!model.empty()){ physics.insertActorPhysics(ptr, "meshes\\" + model); } - } - void Creature::enable (const MWWorld::Ptr& ptr) const - { MWBase::Environment::get().getMechanicsManager()->addActor (ptr); } - void Creature::disable (const MWWorld::Ptr& ptr) const - { - MWBase::Environment::get().getMechanicsManager()->removeActor (ptr); - } - std::string Creature::getName (const MWWorld::Ptr& ptr) const { ESMS::LiveCellRef *ref = @@ -141,4 +133,49 @@ namespace MWClass registerClass (typeid (ESM::Creature).name(), instance); } + + bool Creature::hasToolTip (const MWWorld::Ptr& ptr) const + { + /// \todo We don't want tooltips for Creatures in combat mode. + + return true; + } + + MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name; + + std::string text; + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + info.text = text; + + return info; + } + + float Creature::getCapacity (const MWWorld::Ptr& ptr) const + { + const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); + return stats.mAttributes[0].getModified()*5; + } + + float Creature::getEncumbrance (const MWWorld::Ptr& ptr) const + { + float weight = getContainerStore (ptr).getWeight(); + + const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); + + weight -= stats.mMagicEffects.get (MWMechanics::EffectKey (8)).mMagnitude; // feather + + weight += stats.mMagicEffects.get (MWMechanics::EffectKey (7)).mMagnitude; // burden + + if (weight<0) + weight = 0; + + return weight; + } } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index fa07f9625a..237f54e82f 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -22,16 +22,16 @@ namespace MWClass virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; - virtual void enable (const MWWorld::Ptr& ptr) const; - ///< Enable reference; only does the non-rendering part - - virtual void disable (const MWWorld::Ptr& ptr) const; - ///< Enable reference; only does the non-rendering part - virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual MWMechanics::CreatureStats& getCreatureStats (const MWWorld::Ptr& ptr) const; ///< Return creature stats @@ -46,6 +46,14 @@ namespace MWClass virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr + virtual float getCapacity (const MWWorld::Ptr& ptr) const; + ///< Return total weight that fits into the object. Throws an exception, if the object can't + /// hold other objects. + + virtual float getEncumbrance (const MWWorld::Ptr& ptr) const; + ///< Returns total weight of objects inside this object (including modifications from magic + /// effects). Throws an exception, if the object can't hold other objects. + static void registerSelf(); }; } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 003beaa304..7041de1c68 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -13,6 +13,9 @@ #include "../mwworld/actionteleport.hpp" #include "../mwworld/world.hpp" +#include "../mwgui/window_manager.hpp" +#include "../mwgui/tooltips.hpp" + #include "../mwrender/objects.hpp" #include "../mwsound/soundmanager.hpp" @@ -143,4 +146,63 @@ namespace MWClass registerClass (typeid (ESM::Door).name(), instance); } + + bool Door::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Door::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name; + + std::string text; + + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + if (ref->ref.teleport) + { + std::string dest; + if (ref->ref.destCell != "") + { + // door leads to an interior, use interior name as tooltip + dest = ref->ref.destCell; + } + else + { + // door leads to exterior, use cell name (if any), otherwise translated region name + int x,y; + MWBase::Environment::get().getWorld()->positionToIndex (ref->ref.doorDest.pos[0], ref->ref.doorDest.pos[1], x, y); + const ESM::Cell* cell = store.cells.findExt(x,y); + if (cell->name != "") + dest = cell->name; + else + { + const ESM::Region* region = store.regions.search(cell->region); + dest = region->name; + } + } + text += "\n" + store.gameSettings.search("sTo")->str; + text += "\n"+dest; + } + + if (ref->ref.lockLevel > 0) + text += "\n" + store.gameSettings.search("sLockLevel")->str + ": " + MWGui::ToolTips::toString(ref->ref.lockLevel); + if (ref->ref.trap != "") + text += "\n" + store.gameSettings.search("sTrapped")->str; + + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + + info.text = text; + + return info; + } } diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 4779ed174a..63d1c1ab8b 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -22,6 +22,12 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual void lock (const MWWorld::Ptr& ptr, int lockLevel) const; ///< Lock object diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 79b1bb92f3..0c7fa7e698 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -9,6 +9,10 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/world.hpp" + +#include "../mwgui/window_manager.hpp" +#include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" @@ -93,4 +97,59 @@ namespace MWClass { return std::string("Item Ingredient Down"); } + + std::string Ingredient::getInventoryIcon (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->icon; + } + + bool Ingredient::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Ingredient::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.icon = ref->base->icon; + + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + std::string text; + + text += "\n" + store.gameSettings.search("sWeight")->str + ": " + MWGui::ToolTips::toString(ref->base->data.weight); + text += MWGui::ToolTips::getValueString(ref->base->data.value, store.gameSettings.search("sValue")->str); + + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + text += MWGui::ToolTips::getMiscString(ref->ref.owner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + } + + MWGui::Widgets::SpellEffectList list; + for (int i=0; i<4; ++i) + { + if (ref->base->data.effectID[i] < 0) + continue; + MWGui::Widgets::SpellEffectParams params; + params.mEffectID = ref->base->data.effectID[i]; + params.mAttribute = ref->base->data.attributes[i]; + params.mSkill = ref->base->data.skills[i]; + list.push_back(params); + } + info.effects = list; + + info.text = text; + + return info; + } } diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index d8190c8fc2..4c45bd69c3 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -22,6 +22,12 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr @@ -35,6 +41,9 @@ namespace MWClass virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; ///< Return the put down sound Id + + virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + ///< Return name of inventory icon. }; } diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index deb8c25671..1540334338 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -9,8 +9,13 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/actionequip.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/world.hpp" + +#include "../mwgui/window_manager.hpp" +#include "../mwgui/tooltips.hpp" #include "../mwsound/soundmanager.hpp" @@ -51,12 +56,6 @@ namespace MWClass if(!model.empty()){ physics.insertObjectPhysics(ptr, "meshes\\" + model); } - } - - void Light::enable (const MWWorld::Ptr& ptr) const - { - ESMS::LiveCellRef *ref = - ptr.get(); if (!ref->base->sound.empty()) { @@ -135,4 +134,54 @@ namespace MWClass { return std::string("Item Misc Down"); } + + + std::string Light::getInventoryIcon (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->icon; + } + + bool Light::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Light::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.icon = ref->base->icon; + + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + std::string text; + + text += "\n" + store.gameSettings.search("sWeight")->str + ": " + MWGui::ToolTips::toString(ref->base->data.weight); + text += MWGui::ToolTips::getValueString(ref->base->data.value, store.gameSettings.search("sValue")->str); + + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + text += MWGui::ToolTips::getMiscString(ref->ref.owner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + } + + info.text = text; + + return info; + } + + boost::shared_ptr Light::use (const MWWorld::Ptr& ptr) const + { + MWBase::Environment::get().getSoundManager()->playSound (getUpSoundId(ptr), 1.0, 1.0); + + return boost::shared_ptr(new MWWorld::ActionEquip(ptr)); + } } diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 5d139cb8fd..91193dfdca 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -14,15 +14,16 @@ namespace MWClass virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; - virtual void enable (const MWWorld::Ptr& ptr) const; - ///< Enable reference; only does the non-rendering part - /// \attention This is not the same as the script instruction with the same name. References - /// should only be enabled while in an active cell. - virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; ///< Generate action for activation @@ -44,6 +45,13 @@ namespace MWClass virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; ///< Return the put down sound Id + + virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + ///< Return name of inventory icon. + + virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) + const; + ///< Generate action for using via inventory menu }; } diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index d682538eac..27e292bc0e 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -9,7 +9,11 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/world.hpp" +#include "../mwgui/window_manager.hpp" +#include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" @@ -106,4 +110,57 @@ namespace MWClass { return std::string("Item Lockpick Down"); } + + std::string Lockpick::getInventoryIcon (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->icon; + } + + bool Lockpick::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Lockpick::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.icon = ref->base->icon; + + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + std::string text; + + /// \todo store remaining uses somewhere + + text += "\n" + store.gameSettings.search("sUses")->str + ": " + MWGui::ToolTips::toString(ref->base->data.uses); + text += "\n" + store.gameSettings.search("sQuality")->str + ": " + MWGui::ToolTips::toString(ref->base->data.quality); + text += "\n" + store.gameSettings.search("sWeight")->str + ": " + MWGui::ToolTips::toString(ref->base->data.weight); + text += MWGui::ToolTips::getValueString(ref->base->data.value, store.gameSettings.search("sValue")->str); + + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + text += MWGui::ToolTips::getMiscString(ref->ref.owner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + } + + info.text = text; + + return info; + } + + boost::shared_ptr Lockpick::use (const MWWorld::Ptr& ptr) const + { + MWBase::Environment::get().getSoundManager()->playSound (getUpSoundId(ptr), 1.0, 1.0); + + return boost::shared_ptr(new MWWorld::ActionEquip(ptr)); + } } diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index 042c726e44..26aab584c3 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -22,6 +22,12 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr @@ -39,6 +45,13 @@ namespace MWClass virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; ///< Return the put down sound Id + + virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + ///< Return name of inventory icon. + + virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) + const; + ///< Generate action for using via inventory menu }; } diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index bcbf268708..d8e0553b9e 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -1,6 +1,8 @@ #include "misc.hpp" +#include + #include #include @@ -9,11 +11,17 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/world.hpp" + +#include "../mwgui/window_manager.hpp" +#include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" #include "../mwsound/soundmanager.hpp" +#include + namespace MWClass { void Miscellaneous::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const @@ -91,7 +99,7 @@ namespace MWClass ESMS::LiveCellRef *ref = ptr.get(); - if (ref->base->name =="Gold") + if (ref->base->name == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) { return std::string("Item Gold Up"); } @@ -103,10 +111,74 @@ namespace MWClass ESMS::LiveCellRef *ref = ptr.get(); - if (ref->base->name =="Gold") + if (ref->base->name == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) { return std::string("Item Gold Down"); } return std::string("Item Misc Down"); } + + std::string Miscellaneous::getInventoryIcon (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->icon; + } + + bool Miscellaneous::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Miscellaneous::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + int count = ptr.getRefData().getCount(); + + bool isGold = (ref->base->name == store.gameSettings.search("sGold")->str); + if (isGold && count == 1) + count = ref->base->data.value; + + std::string countString; + if (!isGold) + countString = MWGui::ToolTips::getCountString(count); + else // gold displays its count also if it's 1. + countString = " (" + boost::lexical_cast(count) + ")"; + + info.caption = ref->base->name + countString; + info.icon = ref->base->icon; + + if (ref->ref.soul != "") + { + const ESM::Creature *creature = store.creatures.search(ref->ref.soul); + info.caption += " (" + creature->name + ")"; + } + + std::string text; + + if (!isGold) + { + text += "\n" + store.gameSettings.search("sWeight")->str + ": " + MWGui::ToolTips::toString(ref->base->data.weight); + text += MWGui::ToolTips::getValueString(ref->base->data.value, store.gameSettings.search("sValue")->str); + } + + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + text += MWGui::ToolTips::getMiscString(ref->ref.owner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + } + + info.text = text; + + return info; + } } diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index bc05169c0e..46b5b9662d 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -22,6 +22,12 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr @@ -35,6 +41,9 @@ namespace MWClass virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; ///< Return the put down sound Id + + virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + ///< Return name of inventory icon. }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index d26f5e3cdf..fd81dc8ec8 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -3,6 +3,8 @@ #include +#include + #include #include @@ -18,6 +20,8 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/customdata.hpp" +#include "../mwgui/window_manager.hpp" + #include "../mwbase/environment.hpp" namespace @@ -54,13 +58,15 @@ namespace MWClass // NPC stats if (!ref->base->faction.empty()) { + std::string faction = ref->base->faction; + boost::algorithm::to_lower(faction); if(ref->base->npdt52.gold != -10) { - data->mNpcStats.mFactionRank[ref->base->faction] = (int)ref->base->npdt52.rank; + data->mNpcStats.mFactionRank[faction] = (int)ref->base->npdt52.rank; } else { - data->mNpcStats.mFactionRank[ref->base->faction] = (int)ref->base->npdt12.rank; + data->mNpcStats.mFactionRank[faction] = (int)ref->base->npdt12.rank; } } @@ -86,11 +92,9 @@ namespace MWClass } else { - //TODO: do something with npdt12 maybe:p + /// \todo do something with npdt12 maybe:p } - // \todo add initial container content - // store ptr.getRefData().setCustomData (data.release()); } @@ -106,44 +110,27 @@ namespace MWClass void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - - renderingInterface.getActors().insertNPC(ptr, getInventoryStore(ptr)); - } void Npc::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const { - - ESMS::LiveCellRef *ref = ptr.get(); - assert (ref->base != NULL); - std::string headID = ref->base->head; - std::string bodyRaceID = headID.substr(0, headID.find_last_of("head_") - 4); - bool beast = bodyRaceID == "b_n_khajiit_m_" || bodyRaceID == "b_n_khajiit_f_" || bodyRaceID == "b_n_argonian_m_" || bodyRaceID == "b_n_argonian_f_"; - + std::string headID = ref->base->head; + std::string bodyRaceID = headID.substr(0, headID.find_last_of("head_") - 4); + bool beast = bodyRaceID == "b_n_khajiit_m_" || bodyRaceID == "b_n_khajiit_f_" || bodyRaceID == "b_n_argonian_m_" || bodyRaceID == "b_n_argonian_f_"; std::string smodel = "meshes\\base_anim.nif"; - if(beast) - smodel = "meshes\\base_animkna.nif"; - physics.insertActorPhysics(ptr, smodel); + if(beast) + smodel = "meshes\\base_animkna.nif"; + physics.insertActorPhysics(ptr, smodel); - - } - - void Npc::enable (const MWWorld::Ptr& ptr) const - { MWBase::Environment::get().getMechanicsManager()->addActor (ptr); } - void Npc::disable (const MWWorld::Ptr& ptr) const - { - MWBase::Environment::get().getMechanicsManager()->removeActor (ptr); - } - std::string Npc::getName (const MWWorld::Ptr& ptr) const { ESMS::LiveCellRef *ref = @@ -285,11 +272,12 @@ namespace MWClass { Ogre::Vector3 vector (0, 0, 0); - vector.x = - getMovementSettings (ptr).mLeftRight * 200; - vector.y = getMovementSettings (ptr).mForwardBackward * 200; + vector.x = - getMovementSettings (ptr).mLeftRight * 127; + vector.y = getMovementSettings (ptr).mForwardBackward * 127; + vector.z = getMovementSettings(ptr).mUpDown * 127; - if (getStance (ptr, Run, false)) - vector *= 2; + //if (getStance (ptr, Run, false)) + // vector *= 2; return vector; } @@ -299,4 +287,49 @@ namespace MWClass boost::shared_ptr instance (new Npc); registerClass (typeid (ESM::NPC).name(), instance); } + + bool Npc::hasToolTip (const MWWorld::Ptr& ptr) const + { + /// \todo We don't want tooltips for NPCs in combat mode. + + return true; + } + + MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name; + + std::string text; + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + info.text = text; + + return info; + } + + float Npc::getCapacity (const MWWorld::Ptr& ptr) const + { + const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); + return stats.mAttributes[0].getModified()*5; + } + + float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const + { + float weight = getContainerStore (ptr).getWeight(); + + const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); + + weight -= stats.mMagicEffects.get (MWMechanics::EffectKey (8)).mMagnitude; // feather + + weight += stats.mMagicEffects.get (MWMechanics::EffectKey (7)).mMagnitude; // burden + + if (weight<0) + weight = 0; + + return weight; + } } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 1ef77bd1b2..4cb733977e 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -19,12 +19,6 @@ namespace MWClass virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; - virtual void enable (const MWWorld::Ptr& ptr) const; - ///< Enable reference; only does the non-rendering part - - virtual void disable (const MWWorld::Ptr& ptr) const; - ///< Enable reference; only does the non-rendering part - virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -38,6 +32,12 @@ namespace MWClass virtual MWWorld::ContainerStore& getContainerStore (const MWWorld::Ptr& ptr) const; ///< Return container store + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual MWWorld::InventoryStore& getInventoryStore (const MWWorld::Ptr& ptr) const; ///< Return inventory store @@ -68,6 +68,14 @@ namespace MWClass ///< Return desired movement vector (determined based on movement settings, /// stance and stats). + virtual float getCapacity (const MWWorld::Ptr& ptr) const; + ///< Return total weight that fits into the object. Throws an exception, if the object can't + /// hold other objects. + + virtual float getEncumbrance (const MWWorld::Ptr& ptr) const; + ///< Returns total weight of objects inside this object (including modifications from magic + /// effects). Throws an exception, if the object can't hold other objects. + static void registerSelf(); }; } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 790b4d13ef..157af01f55 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -9,6 +9,10 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/world.hpp" + +#include "../mwgui/window_manager.hpp" +#include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" @@ -95,4 +99,48 @@ namespace MWClass { return std::string("Item Potion Down"); } + + std::string Potion::getInventoryIcon (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->icon; + } + + bool Potion::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Potion::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.icon = ref->base->icon; + + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + std::string text; + + text += "\n" + store.gameSettings.search("sWeight")->str + ": " + MWGui::ToolTips::toString(ref->base->data.weight); + text += MWGui::ToolTips::getValueString(ref->base->data.value, store.gameSettings.search("sValue")->str); + + info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->base->effects); + + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + text += MWGui::ToolTips::getMiscString(ref->ref.owner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + } + + info.text = text; + + return info; + } } diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index f9137766b1..74779864a1 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -22,6 +22,12 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr @@ -35,6 +41,9 @@ namespace MWClass virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; ///< Return the put down sound Id + + virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + ///< Return name of inventory icon. }; } diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index bfb1d29510..0b256d7293 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -9,7 +9,11 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/world.hpp" +#include "../mwgui/window_manager.hpp" +#include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" @@ -105,4 +109,57 @@ namespace MWClass { return std::string("Item Probe Down"); } + + std::string Probe::getInventoryIcon (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->icon; + } + + bool Probe::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Probe::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.icon = ref->base->icon; + + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + std::string text; + + /// \todo store remaining uses somewhere + + text += "\n" + store.gameSettings.search("sUses")->str + ": " + MWGui::ToolTips::toString(ref->base->data.uses); + text += "\n" + store.gameSettings.search("sQuality")->str + ": " + MWGui::ToolTips::toString(ref->base->data.quality); + text += "\n" + store.gameSettings.search("sWeight")->str + ": " + MWGui::ToolTips::toString(ref->base->data.weight); + text += MWGui::ToolTips::getValueString(ref->base->data.value, store.gameSettings.search("sValue")->str); + + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + text += MWGui::ToolTips::getMiscString(ref->ref.owner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + } + + info.text = text; + + return info; + } + + boost::shared_ptr Probe::use (const MWWorld::Ptr& ptr) const + { + MWBase::Environment::get().getSoundManager()->playSound (getUpSoundId(ptr), 1.0, 1.0); + + return boost::shared_ptr(new MWWorld::ActionEquip(ptr)); + } } diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index 543fa3f391..51b046fda2 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -22,6 +22,12 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr @@ -39,6 +45,13 @@ namespace MWClass virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; ///< Return the put down sound Id + + virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + ///< Return name of inventory icon. + + virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) + const; + ///< Generate action for using via inventory menu }; } diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 8bae71e194..5666a95a50 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -9,6 +9,9 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/world.hpp" +#include "../mwgui/window_manager.hpp" +#include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" @@ -95,4 +98,49 @@ namespace MWClass { return std::string("Item Repair Down"); } + + std::string Repair::getInventoryIcon (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->icon; + } + + bool Repair::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Repair::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.icon = ref->base->icon; + + std::string text; + + /// \todo store remaining uses somewhere + + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + text += "\n" + store.gameSettings.search("sUses")->str + ": " + MWGui::ToolTips::toString(ref->base->data.uses); + text += "\n" + store.gameSettings.search("sQuality")->str + ": " + MWGui::ToolTips::toString(ref->base->data.quality); + text += "\n" + store.gameSettings.search("sWeight")->str + ": " + MWGui::ToolTips::toString(ref->base->data.weight); + text += MWGui::ToolTips::getValueString(ref->base->data.value, store.gameSettings.search("sValue")->str); + + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + text += MWGui::ToolTips::getMiscString(ref->ref.owner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + } + + info.text = text; + + return info; + } } diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index 446ab325da..1e935e1543 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -22,6 +22,12 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual std::string getScript (const MWWorld::Ptr& ptr) const; ///< Return name of the script attached to ptr @@ -35,6 +41,9 @@ namespace MWClass virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; ///< Return the put down sound Id + + virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + ///< Return name of inventory icon. }; } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 22745f82d5..482d618b01 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -9,7 +9,12 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" +#include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/world.hpp" + +#include "../mwgui/window_manager.hpp" +#include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" @@ -245,4 +250,117 @@ namespace MWClass return std::string("Item Misc Down"); } + + std::string Weapon::getInventoryIcon (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->icon; + } + + bool Weapon::hasToolTip (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return (ref->base->name != ""); + } + + MWGui::ToolTipInfo Weapon::getToolTipInfo (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + MWGui::ToolTipInfo info; + info.caption = ref->base->name + MWGui::ToolTips::getCountString(ptr.getRefData().getCount()); + info.icon = ref->base->icon; + + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + std::string text; + + // weapon type & damage. arrows / bolts don't have his info. + if (ref->base->data.type < 12) + { + text += "\n" + store.gameSettings.search("sType")->str + " "; + + std::map > mapping; + mapping[ESM::Weapon::ShortBladeOneHand] = std::make_pair("sSkillShortblade", "sOneHanded"); + mapping[ESM::Weapon::LongBladeOneHand] = std::make_pair("sSkillLongblade", "sOneHanded"); + mapping[ESM::Weapon::LongBladeTwoHand] = std::make_pair("sSkillLongblade", "sTwoHanded"); + mapping[ESM::Weapon::BluntOneHand] = std::make_pair("sSkillBluntweapon", "sOneHanded"); + mapping[ESM::Weapon::BluntTwoClose] = std::make_pair("sSkillBluntweapon", "sTwoHanded"); + mapping[ESM::Weapon::BluntTwoWide] = std::make_pair("sSkillBluntweapon", "sTwoHanded"); + mapping[ESM::Weapon::SpearTwoWide] = std::make_pair("sSkillSpear", "sTwoHanded"); + mapping[ESM::Weapon::AxeOneHand] = std::make_pair("sSkillAxe", "sOneHanded"); + mapping[ESM::Weapon::AxeTwoHand] = std::make_pair("sSkillAxe", "sTwoHanded"); + mapping[ESM::Weapon::MarksmanBow] = std::make_pair("sSkillMarksman", ""); + mapping[ESM::Weapon::MarksmanCrossbow] = std::make_pair("sSkillMarksman", ""); + mapping[ESM::Weapon::MarksmanThrown] = std::make_pair("sSkillMarksman", ""); + + std::string type = mapping[ref->base->data.type].first; + std::string oneOrTwoHanded = mapping[ref->base->data.type].second; + + text += store.gameSettings.search(type)->str + + ((oneOrTwoHanded != "") ? ", " + store.gameSettings.search(oneOrTwoHanded)->str : ""); + + // weapon damage + if (ref->base->data.type >= 9) + { + // marksman + text += "\n" + store.gameSettings.search("sAttack")->str + ": " + + MWGui::ToolTips::toString(static_cast(ref->base->data.chop[0])) + + " - " + MWGui::ToolTips::toString(static_cast(ref->base->data.chop[1])); + } + else + { + // Chop + text += "\n" + store.gameSettings.search("sChop")->str + ": " + + MWGui::ToolTips::toString(static_cast(ref->base->data.chop[0])) + + " - " + MWGui::ToolTips::toString(static_cast(ref->base->data.chop[1])); + // Slash + text += "\n" + store.gameSettings.search("sSlash")->str + ": " + + MWGui::ToolTips::toString(static_cast(ref->base->data.slash[0])) + + " - " + MWGui::ToolTips::toString(static_cast(ref->base->data.slash[1])); + // Thrust + text += "\n" + store.gameSettings.search("sThrust")->str + ": " + + MWGui::ToolTips::toString(static_cast(ref->base->data.thrust[0])) + + " - " + MWGui::ToolTips::toString(static_cast(ref->base->data.thrust[1])); + } + } + + /// \todo store the current weapon health somewhere + if (ref->base->data.type < 11) // thrown weapons and arrows/bolts don't have health, only quantity + text += "\n" + store.gameSettings.search("sCondition")->str + ": " + MWGui::ToolTips::toString(ref->base->data.health); + + text += "\n" + store.gameSettings.search("sWeight")->str + ": " + MWGui::ToolTips::toString(ref->base->data.weight); + text += MWGui::ToolTips::getValueString(ref->base->data.value, store.gameSettings.search("sValue")->str); + + info.enchant = ref->base->enchant; + + if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { + text += MWGui::ToolTips::getMiscString(ref->ref.owner, "Owner"); + text += MWGui::ToolTips::getMiscString(ref->base->script, "Script"); + } + + info.text = text; + + return info; + } + + std::string Weapon::getEnchantment (const MWWorld::Ptr& ptr) const + { + ESMS::LiveCellRef *ref = + ptr.get(); + + return ref->base->enchant; + } + + boost::shared_ptr Weapon::use (const MWWorld::Ptr& ptr) const + { + MWBase::Environment::get().getSoundManager()->playSound (getUpSoundId(ptr), 1.0, 1.0); + + return boost::shared_ptr(new MWWorld::ActionEquip(ptr)); + } } diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index 204d327ade..92d703b4ae 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -22,6 +22,12 @@ namespace MWClass const MWWorld::Ptr& actor) const; ///< Generate action for activation + virtual bool hasToolTip (const MWWorld::Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const MWWorld::Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual bool hasItemHealth (const MWWorld::Ptr& ptr) const; ///< \return Item health data available? @@ -49,6 +55,17 @@ namespace MWClass virtual std::string getDownSoundId (const MWWorld::Ptr& ptr) const; ///< Return the put down sound Id + + virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + ///< Return name of inventory icon. + + virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + ///< @return the enchantment ID if the object is enchanted, otherwise an empty string + + virtual boost::shared_ptr use (const MWWorld::Ptr& ptr) + const; + ///< Generate action for using via inventory menu + }; } diff --git a/apps/openmw/mwdialogue/dialoguemanager.cpp b/apps/openmw/mwdialogue/dialoguemanager.cpp index 24ee4ee7f6..a19356d07f 100644 --- a/apps/openmw/mwdialogue/dialoguemanager.cpp +++ b/apps/openmw/mwdialogue/dialoguemanager.cpp @@ -54,6 +54,20 @@ namespace return lowerCase; } + bool stringCompareNoCase (std::string first, std::string second) + { + unsigned int i=0; + while ( (itolower(second[i])) return false; + ++i; + } + if (first.length() bool selectCompare (char comp, T1 value1, T2 value2) @@ -147,6 +161,8 @@ namespace MWDialogue bool DialogueManager::functionFilter(const MWWorld::Ptr& actor, const ESM::DialInfo& info,bool choice) { + bool isCreature = (actor.getTypeName() != typeid(ESM::NPC).name()); + for (std::vector::const_iterator iter (info.selects.begin()); iter != info.selects.end(); ++iter) { @@ -181,13 +197,16 @@ namespace MWDialogue case 46://Same faction { + if (isCreature) + return false; + MWMechanics::NpcStats PCstats = MWWorld::Class::get(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()).getNpcStats(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); MWMechanics::NpcStats NPCstats = MWWorld::Class::get(actor).getNpcStats(actor); int sameFaction = 0; if(!NPCstats.mFactionRank.empty()) { std::string NPCFaction = NPCstats.mFactionRank.begin()->first; - if(PCstats.mFactionRank.find(NPCFaction) != PCstats.mFactionRank.end()) sameFaction = 1; + if(PCstats.mFactionRank.find(toLower(NPCFaction)) != PCstats.mFactionRank.end()) sameFaction = 1; } if(!selectCompare(comp,sameFaction,select.i)) return false; } @@ -269,6 +288,8 @@ namespace MWDialogue bool DialogueManager::isMatching (const MWWorld::Ptr& actor, const ESM::DialInfo::SelectStruct& select) const { + bool isCreature = (actor.getTypeName() != typeid(ESM::NPC).name()); + char type = select.selectRule[1]; if (type!='0') @@ -342,7 +363,7 @@ namespace MWDialogue int sum = 0; for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) - if (iter->getCellRef().refID==name) + if (toLower(iter->getCellRef().refID) == toLower(name)) sum += iter->getRefData().getCount(); if(!selectCompare(comp,sum,select.i)) return false; } @@ -366,6 +387,9 @@ namespace MWDialogue return true; case '8':// not faction + if (isCreature) + return false; + if(select.type==ESM::VT_Int) { ESMS::LiveCellRef* npc = actor.get(); @@ -380,6 +404,9 @@ namespace MWDialogue return true; case '9':// not class + if (isCreature) + return false; + if(select.type==ESM::VT_Int) { ESMS::LiveCellRef* npc = actor.get(); @@ -394,6 +421,9 @@ namespace MWDialogue return true; case 'A'://not Race + if (isCreature) + return false; + if(select.type==ESM::VT_Int) { ESMS::LiveCellRef* npc = actor.get(); @@ -450,6 +480,8 @@ namespace MWDialogue bool DialogueManager::isMatching (const MWWorld::Ptr& actor, const ESM::DialInfo& info) const { + bool isCreature = (actor.getTypeName() != typeid(ESM::NPC).name()); + // actor id if (!info.actor.empty()) if (toLower (info.actor)!=MWWorld::Class::get (actor).getId (actor)) @@ -458,6 +490,9 @@ namespace MWDialogue //NPC race if (!info.race.empty()) { + if (isCreature) + return false; + ESMS::LiveCellRef *cellRef = actor.get(); if (!cellRef) @@ -470,6 +505,9 @@ namespace MWDialogue //NPC class if (!info.clas.empty()) { + if (isCreature) + return false; + ESMS::LiveCellRef *cellRef = actor.get(); if (!cellRef) @@ -482,9 +520,12 @@ namespace MWDialogue //NPC faction if (!info.npcFaction.empty()) { + if (isCreature) + return false; + //MWWorld::Class npcClass = MWWorld::Class::get(actor); MWMechanics::NpcStats stats = MWWorld::Class::get(actor).getNpcStats(actor); - std::map::iterator it = stats.mFactionRank.find(info.npcFaction); + std::map::iterator it = stats.mFactionRank.find(toLower(info.npcFaction)); if(it!=stats.mFactionRank.end()) { //check rank @@ -501,7 +542,7 @@ namespace MWDialogue if(!info.pcFaction.empty()) { MWMechanics::NpcStats stats = MWWorld::Class::get(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()).getNpcStats(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - std::map::iterator it = stats.mFactionRank.find(info.pcFaction); + std::map::iterator it = stats.mFactionRank.find(toLower(info.pcFaction)); if(it!=stats.mFactionRank.end()) { //check rank @@ -515,16 +556,18 @@ namespace MWDialogue } //check gender - ESMS::LiveCellRef* npc = actor.get(); - if(npc->base->flags&npc->base->Female) + if (!isCreature) { - if(static_cast (info.data.gender)==0) return false; + ESMS::LiveCellRef* npc = actor.get(); + if(npc->base->flags&npc->base->Female) + { + if(static_cast (info.data.gender)==0) return false; + } + else + { + if(static_cast (info.data.gender)==1) return false; + } } - else - { - if(static_cast (info.data.gender)==1) return false; - } - // check cell if (!info.cell.empty()) @@ -564,7 +607,7 @@ namespace MWDialogue void DialogueManager::parseText(std::string text) { std::list::iterator it; - for(it = actorKnownTopics.begin();it != actorKnownTopics.end();it++) + for(it = actorKnownTopics.begin();it != actorKnownTopics.end();++it) { size_t pos = find_str_ci(text,*it,0); if(pos !=std::string::npos) @@ -592,9 +635,9 @@ namespace MWDialogue actorKnownTopics.clear(); //initialise the GUI - MWBase::Environment::get().getInputManager()->setGuiMode(MWGui::GM_Dialogue); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Dialogue); MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - win->startDialogue(MWWorld::Class::get (actor).getName (actor)); + win->startDialogue(actor, MWWorld::Class::get (actor).getName (actor)); //setup the list of topics known by the actor. Topics who are also on the knownTopics list will be added to the GUI updateTopics(); @@ -723,7 +766,41 @@ namespace MWDialogue } } } + + // check the available services of this actor + int services = 0; + if (mActor.getTypeName() == typeid(ESM::NPC).name()) + { + ESMS::LiveCellRef* ref = mActor.get(); + if (ref->base->hasAI) + services = ref->base->AI.services; + } + else if (mActor.getTypeName() == typeid(ESM::Creature).name()) + { + ESMS::LiveCellRef* ref = mActor.get(); + if (ref->base->hasAI) + services = ref->base->AI.services; + } + + if (services & ESM::NPC::Weapon + || services & ESM::NPC::Armor + || services & ESM::NPC::Clothing + || services & ESM::NPC::Books + || services & ESM::NPC::Ingredients + || services & ESM::NPC::Picks + || services & ESM::NPC::Probes + || services & ESM::NPC::Lights + || services & ESM::NPC::Apparatus + || services & ESM::NPC::RepairItem + || services & ESM::NPC::Misc) + win->setShowTrade(true); + else + win->setShowTrade(false); + + // sort again, because the previous sort was case-sensitive + keywordList.sort(stringCompareNoCase); win->setKeywords(keywordList); + mChoice = choice; } @@ -766,7 +843,7 @@ namespace MWDialogue void DialogueManager::goodbyeSelected() { - MWBase::Environment::get().getInputManager()->setGuiMode(MWGui::GM_Game); + MWBase::Environment::get().getWindowManager()->popGuiMode(); } void DialogueManager::questionAnswered(std::string answere) @@ -815,12 +892,15 @@ namespace MWDialogue { MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); win->askQuestion(question); - mChoiceMap[question] = choice; + mChoiceMap[toLower(question)] = choice; mIsInChoice = true; } std::string DialogueManager::getFaction() { + if (mActor.getTypeName() != typeid(ESM::NPC).name()) + return ""; + std::string factionID(""); MWMechanics::NpcStats stats = MWWorld::Class::get(mActor).getNpcStats(mActor); if(stats.mFactionRank.empty()) @@ -833,4 +913,11 @@ namespace MWDialogue } return factionID; } + + void DialogueManager::goodbye() + { + MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); + + win->goodbye(); + } } diff --git a/apps/openmw/mwdialogue/dialoguemanager.hpp b/apps/openmw/mwdialogue/dialoguemanager.hpp index a3e37987db..992175c0cf 100644 --- a/apps/openmw/mwdialogue/dialoguemanager.hpp +++ b/apps/openmw/mwdialogue/dialoguemanager.hpp @@ -56,6 +56,8 @@ namespace MWDialogue void askQuestion(std::string question,int choice); + void goodbye(); + ///get the faction of the actor you are talking with std::string getFaction(); diff --git a/apps/openmw/mwdialogue/journal.cpp b/apps/openmw/mwdialogue/journal.cpp index e0245406ef..eb828a76cb 100644 --- a/apps/openmw/mwdialogue/journal.cpp +++ b/apps/openmw/mwdialogue/journal.cpp @@ -6,6 +6,8 @@ #include "../mwgui/window_manager.hpp" #include "../mwgui/messagebox.hpp" +#include "../mwworld/world.hpp" + namespace MWDialogue { Quest& Journal::getQuest (const std::string& id) @@ -38,7 +40,7 @@ namespace MWDialogue quest.addEntry (entry, *MWBase::Environment::get().getWorld()); // we are doing slicing on purpose here std::vector empty; - std::string notification = "Your Journal has been updated."; + std::string notification = MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sJournalEntry")->str; MWBase::Environment::get().getWindowManager()->messageBox (notification, empty); } diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp new file mode 100644 index 0000000000..9c26bfabe4 --- /dev/null +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -0,0 +1,512 @@ +#include "alchemywindow.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/world.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwsound/soundmanager.hpp" + +#include "window_manager.hpp" + +namespace +{ + std::string getIconPath(MWWorld::Ptr ptr) + { + std::string path = std::string("icons\\"); + path += MWWorld::Class::get(ptr).getInventoryIcon(ptr); + int pos = path.rfind("."); + path.erase(pos); + path.append(".dds"); + return path; + } +} + +namespace MWGui +{ + AlchemyWindow::AlchemyWindow(WindowManager& parWindowManager) + : WindowBase("openmw_alchemy_window_layout.xml", parWindowManager) + , ContainerBase(0) + { + getWidget(mCreateButton, "CreateButton"); + getWidget(mCancelButton, "CancelButton"); + getWidget(mIngredient1, "Ingredient1"); + getWidget(mIngredient2, "Ingredient2"); + getWidget(mIngredient3, "Ingredient3"); + getWidget(mIngredient4, "Ingredient4"); + getWidget(mApparatus1, "Apparatus1"); + getWidget(mApparatus2, "Apparatus2"); + getWidget(mApparatus3, "Apparatus3"); + getWidget(mApparatus4, "Apparatus4"); + getWidget(mEffectsBox, "CreatedEffects"); + getWidget(mNameEdit, "NameEdit"); + + mIngredient1->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); + mIngredient2->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); + mIngredient3->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); + mIngredient4->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); + + MyGUI::Widget* buttonBox = mCancelButton->getParent(); + int cancelButtonWidth = mCancelButton->getTextSize().width + 24; + mCancelButton->setCoord(buttonBox->getWidth() - cancelButtonWidth, + mCancelButton->getTop(), cancelButtonWidth, mCancelButton->getHeight()); + int createButtonWidth = mCreateButton->getTextSize().width + 24; + mCreateButton->setCoord(buttonBox->getWidth() - createButtonWidth - cancelButtonWidth - 4, + mCreateButton->getTop(), createButtonWidth, mCreateButton->getHeight()); + + mCreateButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCreateButtonClicked); + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked); + + MyGUI::ScrollView* itemView; + MyGUI::Widget* containerWidget; + getWidget(containerWidget, "Items"); + getWidget(itemView, "ItemView"); + setWidgets(containerWidget, itemView); + + center(); + } + + void AlchemyWindow::onCancelButtonClicked(MyGUI::Widget* _sender) + { + mWindowManager.popGuiMode(); + mWindowManager.popGuiMode(); + } + + void AlchemyWindow::onCreateButtonClicked(MyGUI::Widget* _sender) + { + // check if mortar & pestle is available (always needed) + /// \todo check albemic, calcinator, retort (sometimes needed) + if (!mApparatus1->isUserString("ToolTipType")) + { + mWindowManager.messageBox("#{sNotifyMessage45}", std::vector()); + return; + } + + // make sure 2 or more ingredients were selected + int numIngreds = 0; + if (mIngredient1->isUserString("ToolTipType")) + ++numIngreds; + if (mIngredient2->isUserString("ToolTipType")) + ++numIngreds; + if (mIngredient3->isUserString("ToolTipType")) + ++numIngreds; + if (mIngredient4->isUserString("ToolTipType")) + ++numIngreds; + if (numIngreds < 2) + { + mWindowManager.messageBox("#{sNotifyMessage6a}", std::vector()); + return; + } + + // make sure a name was entered + std::string name = mNameEdit->getCaption(); + boost::algorithm::trim(name); + if (name == "") + { + mWindowManager.messageBox("#{sNotifyMessage37}", std::vector()); + return; + } + + // if there are no created effects, the potion will always fail (but the ingredients won't be destroyed) + if (mEffects.empty()) + { + mWindowManager.messageBox("#{sNotifyMessage8}", std::vector()); + MWBase::Environment::get().getSoundManager()->playSound("potion fail", 1.f, 1.f); + return; + } + + if (rand() % 2 == 0) /// \todo + { + ESM::Potion newPotion; + newPotion.name = mNameEdit->getCaption(); + ESM::EffectList effects; + for (unsigned int i=0; i<4; ++i) + { + if (mEffects.size() >= i+1) + { + ESM::ENAMstruct effect; + effect.effectID = mEffects[i].mEffectID; + effect.area = 0; + effect.range = ESM::RT_Self; + effect.skill = mEffects[i].mSkill; + effect.attribute = mEffects[i].mAttribute; + effect.magnMin = 1; /// \todo + effect.magnMax = 10; /// \todo + effect.duration = 60; /// \todo + effects.list.push_back(effect); + } + } + + // UESP Wiki / Morrowind:Alchemy + // "The weight of a potion is an average of the weight of the ingredients, rounded down." + // note by scrawl: not rounding down here, I can't imagine a created potion to + // have 0 weight when using ingredients with 0.1 weight respectively + float weight = 0; + if (mIngredient1->isUserString("ToolTipType")) + weight += mIngredient1->getUserData()->get()->base->data.weight; + if (mIngredient2->isUserString("ToolTipType")) + weight += mIngredient2->getUserData()->get()->base->data.weight; + if (mIngredient3->isUserString("ToolTipType")) + weight += mIngredient3->getUserData()->get()->base->data.weight; + if (mIngredient4->isUserString("ToolTipType")) + weight += mIngredient4->getUserData()->get()->base->data.weight; + newPotion.data.weight = weight / float(numIngreds); + + newPotion.data.value = 100; /// \todo + newPotion.effects = effects; + // pick a random mesh and icon + std::vector names; + /// \todo is the mesh/icon dependent on alchemy skill? + names.push_back("standard"); + names.push_back("bargain"); + names.push_back("cheap"); + names.push_back("fresh"); + names.push_back("exclusive"); + names.push_back("quality"); + int random = rand() % names.size(); + newPotion.model = "m\\misc_potion_" + names[random ] + "_01.nif"; + newPotion.icon = "m\\tx_potion_" + names[random ] + "_01.dds"; + + // check if a similiar potion record exists already + bool found = false; + std::string objectId; + typedef std::map PotionMap; + PotionMap potions = MWBase::Environment::get().getWorld()->getStore().potions.list; + for (PotionMap::const_iterator it = potions.begin(); it != potions.end(); ++it) + { + if (found) break; + + if (it->second.data.value == newPotion.data.value + && it->second.data.weight == newPotion.data.weight + && it->second.name == newPotion.name + && it->second.effects.list.size() == newPotion.effects.list.size()) + { + // check effects + for (unsigned int i=0; i < it->second.effects.list.size(); ++i) + { + const ESM::ENAMstruct& a = it->second.effects.list[i]; + const ESM::ENAMstruct& b = newPotion.effects.list[i]; + if (a.effectID == b.effectID + && a.area == b.area + && a.range == b.range + && a.skill == b.skill + && a.attribute == b.attribute + && a.magnMin == b.magnMin + && a.magnMax == b.magnMax + && a.duration == b.duration) + { + found = true; + objectId = it->first; + break; + } + } + } + } + + if (!found) + { + std::pair result = MWBase::Environment::get().getWorld()->createRecord(newPotion); + objectId = result.first; + } + + // create a reference and add it to player inventory + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), objectId); + MWWorld::ContainerStore& store = MWWorld::Class::get(mPtr).getContainerStore(mPtr); + ref.getPtr().getRefData().setCount(1); + store.add(ref.getPtr()); + + mWindowManager.messageBox("#{sPotionSuccess}", std::vector()); + MWBase::Environment::get().getSoundManager()->playSound("potion success", 1.f, 1.f); + } + else + { + // potion failed + mWindowManager.messageBox("#{sNotifyMessage8}", std::vector()); + MWBase::Environment::get().getSoundManager()->playSound("potion fail", 1.f, 1.f); + } + + // reduce count of the ingredients + if (mIngredient1->isUserString("ToolTipType")) + { + MWWorld::Ptr ingred = *mIngredient1->getUserData(); + ingred.getRefData().setCount(ingred.getRefData().getCount()-1); + if (ingred.getRefData().getCount() == 0) + removeIngredient(mIngredient1); + } + if (mIngredient2->isUserString("ToolTipType")) + { + MWWorld::Ptr ingred = *mIngredient2->getUserData(); + ingred.getRefData().setCount(ingred.getRefData().getCount()-1); + if (ingred.getRefData().getCount() == 0) + removeIngredient(mIngredient2); + } + if (mIngredient3->isUserString("ToolTipType")) + { + MWWorld::Ptr ingred = *mIngredient3->getUserData(); + ingred.getRefData().setCount(ingred.getRefData().getCount()-1); + if (ingred.getRefData().getCount() == 0) + removeIngredient(mIngredient3); + } + if (mIngredient4->isUserString("ToolTipType")) + { + MWWorld::Ptr ingred = *mIngredient4->getUserData(); + ingred.getRefData().setCount(ingred.getRefData().getCount()-1); + if (ingred.getRefData().getCount() == 0) + removeIngredient(mIngredient4); + } + update(); + } + + void AlchemyWindow::open() + { + openContainer(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + setFilter(ContainerBase::Filter_Ingredients); + + // pick the best available apparatus + MWWorld::ContainerStore& store = MWWorld::Class::get(mPtr).getContainerStore(mPtr); + + MWWorld::Ptr bestAlbemic; + MWWorld::Ptr bestMortarPestle; + MWWorld::Ptr bestCalcinator; + MWWorld::Ptr bestRetort; + + for (MWWorld::ContainerStoreIterator it(store.begin(MWWorld::ContainerStore::Type_Apparatus)); + it != store.end(); ++it) + { + ESMS::LiveCellRef* ref = it->get(); + if (ref->base->data.type == ESM::Apparatus::Albemic + && (bestAlbemic.isEmpty() || ref->base->data.quality > bestAlbemic.get()->base->data.quality)) + bestAlbemic = *it; + else if (ref->base->data.type == ESM::Apparatus::MortarPestle + && (bestMortarPestle.isEmpty() || ref->base->data.quality > bestMortarPestle.get()->base->data.quality)) + bestMortarPestle = *it; + else if (ref->base->data.type == ESM::Apparatus::Calcinator + && (bestCalcinator.isEmpty() || ref->base->data.quality > bestCalcinator.get()->base->data.quality)) + bestCalcinator = *it; + else if (ref->base->data.type == ESM::Apparatus::Retort + && (bestRetort.isEmpty() || ref->base->data.quality > bestRetort.get()->base->data.quality)) + bestRetort = *it; + } + + if (!bestMortarPestle.isEmpty()) + { + mApparatus1->setUserString("ToolTipType", "ItemPtr"); + mApparatus1->setUserData(bestMortarPestle); + mApparatus1->setImageTexture(getIconPath(bestMortarPestle)); + } + if (!bestAlbemic.isEmpty()) + { + mApparatus2->setUserString("ToolTipType", "ItemPtr"); + mApparatus2->setUserData(bestAlbemic); + mApparatus2->setImageTexture(getIconPath(bestAlbemic)); + } + if (!bestCalcinator.isEmpty()) + { + mApparatus3->setUserString("ToolTipType", "ItemPtr"); + mApparatus3->setUserData(bestCalcinator); + mApparatus3->setImageTexture(getIconPath(bestCalcinator)); + } + if (!bestRetort.isEmpty()) + { + mApparatus4->setUserString("ToolTipType", "ItemPtr"); + mApparatus4->setUserData(bestRetort); + mApparatus4->setImageTexture(getIconPath(bestRetort)); + } + } + + void AlchemyWindow::onIngredientSelected(MyGUI::Widget* _sender) + { + removeIngredient(_sender); + drawItems(); + update(); + } + + void AlchemyWindow::onSelectedItemImpl(MWWorld::Ptr item) + { + MyGUI::ImageBox* add = NULL; + + // don't allow to add an ingredient that is already added + // (which could happen if two similiar ingredients don't stack because of script / owner) + bool alreadyAdded = false; + std::string name = MWWorld::Class::get(item).getName(item); + if (mIngredient1->isUserString("ToolTipType")) + { + MWWorld::Ptr item2 = *mIngredient1->getUserData(); + std::string name2 = MWWorld::Class::get(item2).getName(item2); + if (name == name2) + alreadyAdded = true; + } + if (mIngredient2->isUserString("ToolTipType")) + { + MWWorld::Ptr item2 = *mIngredient2->getUserData(); + std::string name2 = MWWorld::Class::get(item2).getName(item2); + if (name == name2) + alreadyAdded = true; + } + if (mIngredient3->isUserString("ToolTipType")) + { + MWWorld::Ptr item2 = *mIngredient3->getUserData(); + std::string name2 = MWWorld::Class::get(item2).getName(item2); + if (name == name2) + alreadyAdded = true; + } + if (mIngredient4->isUserString("ToolTipType")) + { + MWWorld::Ptr item2 = *mIngredient4->getUserData(); + std::string name2 = MWWorld::Class::get(item2).getName(item2); + if (name == name2) + alreadyAdded = true; + } + if (alreadyAdded) + return; + + if (!mIngredient1->isUserString("ToolTipType")) + add = mIngredient1; + if (add == NULL && !mIngredient2->isUserString("ToolTipType")) + add = mIngredient2; + if (add == NULL && !mIngredient3->isUserString("ToolTipType")) + add = mIngredient3; + if (add == NULL && !mIngredient4->isUserString("ToolTipType")) + add = mIngredient4; + + if (add != NULL) + { + add->setUserString("ToolTipType", "ItemPtr"); + add->setUserData(item); + add->setImageTexture(getIconPath(item)); + drawItems(); + update(); + + std::string sound = MWWorld::Class::get(item).getUpSoundId(item); + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); + } + } + + std::vector AlchemyWindow::itemsToIgnore() + { + std::vector ignore; + // don't show ingredients that are currently selected in the "available ingredients" box. + if (mIngredient1->isUserString("ToolTipType")) + ignore.push_back(*mIngredient1->getUserData()); + if (mIngredient2->isUserString("ToolTipType")) + ignore.push_back(*mIngredient2->getUserData()); + if (mIngredient3->isUserString("ToolTipType")) + ignore.push_back(*mIngredient3->getUserData()); + if (mIngredient4->isUserString("ToolTipType")) + ignore.push_back(*mIngredient4->getUserData()); + + return ignore; + } + + void AlchemyWindow::update() + { + Widgets::SpellEffectList effects; + + for (int i=0; i<4; ++i) + { + MyGUI::ImageBox* ingredient; + if (i==0) + ingredient = mIngredient1; + else if (i==1) + ingredient = mIngredient2; + else if (i==2) + ingredient = mIngredient3; + else if (i==3) + ingredient = mIngredient4; + + if (!ingredient->isUserString("ToolTipType")) + continue; + + // add the effects of this ingredient to list of effects + ESMS::LiveCellRef* ref = ingredient->getUserData()->get(); + for (int i=0; i<4; ++i) + { + if (ref->base->data.effectID[i] < 0) + continue; + MWGui::Widgets::SpellEffectParams params; + params.mEffectID = ref->base->data.effectID[i]; + params.mAttribute = ref->base->data.attributes[i]; + params.mSkill = ref->base->data.skills[i]; + effects.push_back(params); + } + + // update ingredient count labels + if (ingredient->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(ingredient->getChildAt(0)); + + MyGUI::TextBox* text = ingredient->createWidget("SandBrightText", MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label")); + text->setTextAlign(MyGUI::Align::Right); + text->setNeedMouseFocus(false); + text->setTextShadow(true); + text->setTextShadowColour(MyGUI::Colour(0,0,0)); + text->setCaption(getCountString(ingredient->getUserData()->getRefData().getCount())); + } + + // now remove effects that are only present once + Widgets::SpellEffectList::iterator it = effects.begin(); + while (it != effects.end()) + { + Widgets::SpellEffectList::iterator next = it; + ++next; + bool found = false; + for (; next != effects.end(); ++next) + { + if (*next == *it) + found = true; + } + + if (!found) + it = effects.erase(it); + else + ++it; + } + + // now remove duplicates, and don't allow more than 4 effects + Widgets::SpellEffectList old = effects; + effects.clear(); + int i=0; + for (Widgets::SpellEffectList::iterator it = old.begin(); + it != old.end(); ++it) + { + bool found = false; + for (Widgets::SpellEffectList::iterator it2 = effects.begin(); + it2 != effects.end(); ++it2) + { + // MW considers all "foritfy attribute" effects as the same effect. See the + // "Can't create multi-state boost potions" discussion on http://www.uesp.net/wiki/Morrowind_talk:Alchemy + // thus, we are only checking effectID here and not attribute or skill + if (it2->mEffectID == it->mEffectID) + found = true; + } + if (!found && i<4) + { + ++i; + effects.push_back(*it); + } + } + mEffects = effects; + + while (mEffectsBox->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mEffectsBox->getChildAt(0)); + + MyGUI::IntCoord coord(0, 0, mEffectsBox->getWidth(), 24); + Widgets::MWEffectListPtr effectsWidget = mEffectsBox->createWidget + ("MW_StatName", coord, Align::Left | Align::Top); + effectsWidget->setWindowManager(&mWindowManager); + effectsWidget->setEffectList(effects); + + std::vector effectItems; + effectsWidget->createEffectWidgets(effectItems, mEffectsBox, coord, false, 0); + effectsWidget->setCoord(coord); + } + + void AlchemyWindow::removeIngredient(MyGUI::Widget* ingredient) + { + ingredient->clearUserStrings(); + static_cast(ingredient)->setImageTexture(""); + if (ingredient->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(ingredient->getChildAt(0)); + } +} diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp new file mode 100644 index 0000000000..c01a18e413 --- /dev/null +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -0,0 +1,51 @@ +#ifndef MWGUI_ALCHEMY_H +#define MWGUI_ALCHEMY_H + +#include "window_base.hpp" +#include "container.hpp" + +namespace MWGui +{ + class AlchemyWindow : public WindowBase, public ContainerBase + { + public: + AlchemyWindow(WindowManager& parWindowManager); + + virtual void open(); + + protected: + MyGUI::Button* mCreateButton; + MyGUI::Button* mCancelButton; + + MyGUI::ImageBox* mIngredient1; + MyGUI::ImageBox* mIngredient2; + MyGUI::ImageBox* mIngredient3; + MyGUI::ImageBox* mIngredient4; + + MyGUI::ImageBox* mApparatus1; + MyGUI::ImageBox* mApparatus2; + MyGUI::ImageBox* mApparatus3; + MyGUI::ImageBox* mApparatus4; + + MyGUI::Widget* mEffectsBox; + + MyGUI::EditBox* mNameEdit; + + Widgets::SpellEffectList mEffects; // effects of created potion + + void onCancelButtonClicked(MyGUI::Widget* _sender); + void onCreateButtonClicked(MyGUI::Widget* _sender); + void onIngredientSelected(MyGUI::Widget* _sender); + + virtual void onSelectedItemImpl(MWWorld::Ptr item); + virtual std::vector itemsToIgnore(); + + void removeIngredient(MyGUI::Widget* ingredient); + + virtual void onReferenceUnavailable() { ; } + + void update(); + }; +} + +#endif diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index e9c15fab4a..49f90d1a07 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -25,13 +25,13 @@ BirthDialog::BirthDialog(WindowManager& parWindowManager) birthList->eventListMouseItemActivate += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); birthList->eventListChangePosition += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); - // TODO: These buttons should be managed by a Dialog class MyGUI::ButtonPtr backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BirthDialog::onBackClicked); MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); + okButton->setCaption(mWindowManager.getGameSettingString("sOK", "")); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BirthDialog::onOkClicked); updateBirths(); @@ -46,21 +46,16 @@ void BirthDialog::setNextButtonShow(bool shown) MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); - // TODO: All hardcoded coords for buttons are temporary, will be replaced with a dynamic system. if (shown) - { - okButton->setCaption("Next"); - - // Adjust back button when next is shown - backButton->setCoord(MyGUI::IntCoord(375 - 18, 340, 53, 23)); - okButton->setCoord(MyGUI::IntCoord(431 - 18, 340, 42 + 18, 23)); - } + okButton->setCaption(mWindowManager.getGameSettingString("sNext", "")); else - { - okButton->setCaption("OK"); - backButton->setCoord(MyGUI::IntCoord(375, 340, 53, 23)); - okButton->setCoord(MyGUI::IntCoord(431, 340, 42, 23)); - } + okButton->setCaption(mWindowManager.getGameSettingString("sOK", "")); + + int okButtonWidth = okButton->getTextSize().width + 24; + int backButtonWidth = backButton->getTextSize().width + 24; + + okButton->setCoord(473 - okButtonWidth, 340, okButtonWidth, 23); + backButton->setCoord(473 - okButtonWidth - backButtonWidth - 6, 340, backButtonWidth, 23); } void BirthDialog::open() @@ -206,7 +201,7 @@ void BirthDialog::updateSpells() MyGUI::IntCoord spellCoord = coord; spellCoord.height = 24; // TODO: This should be fetched from the skin somehow, or perhaps a widget in the layout as a template? - spellWidget->createEffectWidgets(spellItems, spellArea, spellCoord); + spellWidget->createEffectWidgets(spellItems, spellArea, spellCoord, (category == 0) ? MWEffectList::EF_Constant : 0); coord.top = spellCoord.top; ++i; diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp new file mode 100644 index 0000000000..f1873b5508 --- /dev/null +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -0,0 +1,149 @@ +#include "bookwindow.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwinput/inputmanager.hpp" +#include "../mwsound/soundmanager.hpp" +#include "../mwworld/actiontake.hpp" + +#include "formatting.hpp" +#include "window_manager.hpp" + +using namespace MWGui; + +BookWindow::BookWindow (WindowManager& parWindowManager) : + WindowBase("openmw_book_layout.xml", parWindowManager) +{ + getWidget(mCloseButton, "CloseButton"); + mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BookWindow::onCloseButtonClicked); + + getWidget(mTakeButton, "TakeButton"); + mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BookWindow::onTakeButtonClicked); + + getWidget(mNextPageButton, "NextPageBTN"); + mNextPageButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BookWindow::onNextPageButtonClicked); + + getWidget(mPrevPageButton, "PrevPageBTN"); + mPrevPageButton->eventMouseButtonClick += MyGUI::newDelegate(this, &BookWindow::onPrevPageButtonClicked); + + getWidget(mLeftPageNumber, "LeftPageNumber"); + getWidget(mRightPageNumber, "RightPageNumber"); + + getWidget(mLeftPage, "LeftPage"); + getWidget(mRightPage, "RightPage"); + + center(); +} + +void BookWindow::clearPages() +{ + for (std::vector::iterator it=mPages.begin(); + it!=mPages.end(); ++it) + { + MyGUI::Gui::getInstance().destroyWidget(*it); + } + mPages.clear(); +} + +void BookWindow::open (MWWorld::Ptr book) +{ + mBook = book; + + clearPages(); + mCurrentPage = 0; + + MWBase::Environment::get().getSoundManager()->playSound ("book open", 1.0, 1.0); + + ESMS::LiveCellRef *ref = + mBook.get(); + + BookTextParser parser; + std::vector results = parser.split(ref->base->text, mLeftPage->getSize().width, mLeftPage->getSize().height); + + int i=0; + for (std::vector::iterator it=results.begin(); + it!=results.end(); ++it) + { + MyGUI::Widget* parent; + if (i%2 == 0) + parent = mLeftPage; + else + parent = mRightPage; + + MyGUI::Widget* pageWidget = parent->createWidgetReal("", MyGUI::FloatCoord(0.0,0.0,1.0,1.0), MyGUI::Align::Default, "BookPage" + boost::lexical_cast(i)); + parser.parse(*it, pageWidget, mLeftPage->getSize().width); + mPages.push_back(pageWidget); + ++i; + } + + updatePages(); + + setTakeButtonShow(true); +} + +void BookWindow::setTakeButtonShow(bool show) +{ + mTakeButton->setVisible(show); +} + +void BookWindow::onCloseButtonClicked (MyGUI::Widget* _sender) +{ + // no 3d sounds because the object could be in a container. + MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0); + + mWindowManager.popGuiMode(); +} + +void BookWindow::onTakeButtonClicked (MyGUI::Widget* _sender) +{ + MWBase::Environment::get().getSoundManager()->playSound ("Item Book Up", 1.0, 1.0, MWSound::Play_NoTrack); + + MWWorld::ActionTake take(mBook); + take.execute(); + + mWindowManager.popGuiMode(); +} + +void BookWindow::onNextPageButtonClicked (MyGUI::Widget* _sender) +{ + if ((mCurrentPage+1)*2 < mPages.size()) + { + MWBase::Environment::get().getSoundManager()->playSound ("book page2", 1.0, 1.0); + + ++mCurrentPage; + + updatePages(); + } +} + +void BookWindow::onPrevPageButtonClicked (MyGUI::Widget* _sender) +{ + if (mCurrentPage > 0) + { + MWBase::Environment::get().getSoundManager()->playSound ("book page", 1.0, 1.0); + + --mCurrentPage; + + updatePages(); + } +} + +void BookWindow::updatePages() +{ + mLeftPageNumber->setCaption( boost::lexical_cast(mCurrentPage*2 + 1) ); + mRightPageNumber->setCaption( boost::lexical_cast(mCurrentPage*2 + 2) ); + + unsigned int i=0; + for (std::vector::iterator it = mPages.begin(); + it != mPages.end(); ++it) + { + if (mCurrentPage*2 == i || mCurrentPage*2+1 == i) + (*it)->setVisible(true); + else + { + (*it)->setVisible(false); + } + ++i; + } +} diff --git a/apps/openmw/mwgui/bookwindow.hpp b/apps/openmw/mwgui/bookwindow.hpp new file mode 100644 index 0000000000..9ea0114338 --- /dev/null +++ b/apps/openmw/mwgui/bookwindow.hpp @@ -0,0 +1,46 @@ +#ifndef MWGUI_BOOKWINDOW_H +#define MWGUI_BOOKWINDOW_H + +#include "window_base.hpp" + +#include "../mwworld/ptr.hpp" + +namespace MWGui +{ + class BookWindow : public WindowBase + { + public: + BookWindow(WindowManager& parWindowManager); + + void open(MWWorld::Ptr book); + void setTakeButtonShow(bool show); + + protected: + void onNextPageButtonClicked (MyGUI::Widget* _sender); + void onPrevPageButtonClicked (MyGUI::Widget* _sender); + void onCloseButtonClicked (MyGUI::Widget* _sender); + void onTakeButtonClicked (MyGUI::Widget* _sender); + + void updatePages(); + void clearPages(); + + private: + MyGUI::Button* mCloseButton; + MyGUI::Button* mTakeButton; + MyGUI::Button* mNextPageButton; + MyGUI::Button* mPrevPageButton; + MyGUI::TextBox* mLeftPageNumber; + MyGUI::TextBox* mRightPageNumber; + MyGUI::Widget* mLeftPage; + MyGUI::Widget* mRightPage; + + unsigned int mCurrentPage; // 0 is first page + std::vector mPages; + + MWWorld::Ptr mBook; + }; + +} + +#endif + diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 9a23758558..3f9a94e2db 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -9,6 +9,7 @@ #include "mode.hpp" #include "../mwbase/environment.hpp" +#include "../mwsound/soundmanager.hpp" namespace { @@ -16,6 +17,7 @@ namespace { const char* mText; const char* mButtons[3]; + const char* mSound; ESM::Class::Specialization mSpecializations[3]; // The specialization for each answer }; @@ -25,6 +27,7 @@ namespace {"Draw your dagger, mercifully endings its life with a single thrust.", "Use herbs from your pack to put it to sleep.", "Do not interfere in the natural evolution of events, but rather take the opportunity to learn more about a strange animal that you have never seen before."}, + "vo\\misc\\chargen qa1.wav", {ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth} }, // Question 2 @@ -32,6 +35,7 @@ namespace {"Work in the forge with him casting iron for a new plow.", "Gather herbs for your mother who is preparing dinner.", "Go catch fish at the stream using a net and line."}, + "vo\\misc\\chargen qa2.wav", {ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth} }, // Question 3 @@ -39,6 +43,7 @@ namespace {"Beat up your cousin, then tell him that if he ever calls you that nickname again, you will bloody him worse than this time.", "Make up a story that makes your nickname a badge of honor instead of something humiliating.", "Make up an even more embarrassing nickname for him and use it constantly until he learns his lesson."}, + "vo\\misc\\chargen qa3.wav", {ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth} }, // Question 4 @@ -46,6 +51,7 @@ namespace {"This is a terrible practice. A person's thoughts are his own and no one, not even a king, has the right to make such an invasion into another human's mind.", "Loyal followers to the king have nothing to fear from a Telepath. It is important to have a method of finding assassins and spies before it is too late.", "In these times, it is a necessary evil. Although you do not necessarily like the idea, a Telepath could have certain advantages during a time of war or in finding someone innocent of a crime."}, + "vo\\misc\\chargen qa4.wav", {ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth} }, // Question 5 @@ -53,6 +59,7 @@ namespace {"Return to the store and give the shopkeeper his hard-earned money, explaining to him the mistake?", "Decide to put the extra money to good use and purchase items that would help your family?", "Pocket the extra money, knowing that shopkeepers in general tend to overcharge customers anyway?"}, + "vo\\misc\\chargen qa5.wav", {ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth} }, // Question 6 @@ -60,6 +67,7 @@ namespace {"Pick up the bag and signal to the guard, knowing that the only honorable thing to do is return the money to its rightful owner.", "Leave the bag there, knowing that it is better not to get involved.", "Pick up the bag and pocket it, knowing that the extra windfall will help your family in times of trouble."}, + "vo\\misc\\chargen qa6.wav", {ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth} }, // Question 7 @@ -67,6 +75,7 @@ namespace {"Decline his offer, knowing that your father expects you to do the work, and it is better not to be in debt.", "Ask him to help you, knowing that two people can do the job faster than one, and agree to help him with one task of his choosing in the future.", "Accept his offer, reasoning that as long as the stables are cleaned, it matters not who does the cleaning."}, + "vo\\misc\\chargen qa7.wav", {ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth} }, // Question 8 @@ -74,6 +83,7 @@ namespace {"Position yourself between the pipe and your mother.", "Grab the hot pipe and try to push it away.", "Push your mother out of the way."}, + "vo\\misc\\chargen qa8.wav", {ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth} }, // Question 9 @@ -81,6 +91,7 @@ namespace {"Drop the sweetroll and step on it, then get ready for the fight.", "Give him the sweetroll now without argument, knowing that later this afternoon you will have all your friends with you and can come and take whatever he owes you.", "Act like you're going to give him the sweetroll, but at the last minute throw it in the air, hoping that they'll pay attention to it long enough for you to get a shot in on the leader."}, + "vo\\misc\\chargen qa9.wav", {ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth} }, // Question 10 @@ -88,6 +99,7 @@ namespace {"Rush to the town's aid immediately, despite your lack of knowledge of the circumstances.", "Stand aside and allow the man and the mob to pass, realizing it is probably best not to get involved.", "Rush to the man's aid immediately, despite your lack of knowledge of the circumstances."}, + "vo\\misc\\chargen qa10.wav", {ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth} } } }; @@ -98,7 +110,6 @@ using namespace MWGui; CharacterCreation::CharacterCreation(WindowManager* _wm) : mNameDialog(0) , mRaceDialog(0) - , mDialogueWindow(0) , mClassChoiceDialog(0) , mGenerateClassQuestionDialog(0) , mGenerateClassResultDialog(0) @@ -106,11 +117,62 @@ CharacterCreation::CharacterCreation(WindowManager* _wm) , mCreateClassDialog(0) , mBirthSignDialog(0) , mReviewDialog(0) + , mGenerateClassStep(0) , mWM(_wm) { mCreationStage = CSE_NotStarted; } +void CharacterCreation::setValue (const std::string& id, const MWMechanics::Stat& value) +{ + if (mReviewDialog) + { + static const char *ids[] = + { + "AttribVal1", "AttribVal2", "AttribVal3", "AttribVal4", "AttribVal5", + "AttribVal6", "AttribVal7", "AttribVal8", + 0 + }; + + for (int i=0; ids[i]; ++i) + { + if (ids[i]==id) + mReviewDialog->setAttribute(ESM::Attribute::AttributeID(i), value); + } + } +} + +void CharacterCreation::setValue (const std::string& id, const MWMechanics::DynamicStat& value) +{ + if (mReviewDialog) + { + if (id == "HBar") + { + mReviewDialog->setHealth (value); + } + else if (id == "MBar") + { + mReviewDialog->setMagicka (value); + } + else if (id == "FBar") + { + mReviewDialog->setFatigue (value); + } + } +} + +void CharacterCreation::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat& value) +{ + if (mReviewDialog) + mReviewDialog->setSkillValue(parSkill, value); +} + +void CharacterCreation::configureSkills (const SkillList& major, const SkillList& minor) +{ + if (mReviewDialog) + mReviewDialog->configureSkills(major, minor); +} + void CharacterCreation::spawnDialog(const char id) { switch (id) @@ -161,7 +223,6 @@ void CharacterCreation::spawnDialog(const char id) mWM->removeDialog(mBirthSignDialog); mBirthSignDialog = new BirthDialog(*mWM); mBirthSignDialog->setNextButtonShow(mCreationStage >= CSE_BirthSignChosen); - mBirthSignDialog->setBirthId(mPlayerBirthSignId); mBirthSignDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogDone); mBirthSignDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onBirthSignDialogBack); mBirthSignDialog->open(); @@ -171,6 +232,7 @@ void CharacterCreation::spawnDialog(const char id) if (mCreateClassDialog) mWM->removeDialog(mCreateClassDialog); mCreateClassDialog = new CreateClassDialog(*mWM); + mCreateClassDialog->setNextButtonShow(mCreationStage >= CSE_ClassChosen); mCreateClassDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogDone); mCreateClassDialog->eventBack += MyGUI::newDelegate(this, &CharacterCreation::onCreateClassDialogBack); mCreateClassDialog->open(); @@ -197,20 +259,22 @@ void CharacterCreation::spawnDialog(const char id) mReviewDialog->setFatigue(mPlayerFatigue); { - std::map >::iterator end = mPlayerAttributes.end(); - for (std::map >::iterator it = mPlayerAttributes.begin(); it != end; ++it) + std::map > attributes = mWM->getPlayerAttributeValues(); + for (std::map >::iterator it = attributes.begin(); + it != attributes.end(); ++it) { mReviewDialog->setAttribute(it->first, it->second); } } { - std::map >::iterator end = mPlayerSkillValues.end(); - for (std::map >::iterator it = mPlayerSkillValues.begin(); it != end; ++it) + std::map > skills = mWM->getPlayerSkillValues(); + for (std::map >::iterator it = skills.begin(); + it != skills.end(); ++it) { mReviewDialog->setSkillValue(it->first, it->second); } - mReviewDialog->configureSkills(mPlayerMajorSkills, mPlayerMinorSkills); + mReviewDialog->configureSkills(mWM->getPlayerMajorSkills(), mWM->getPlayerMinorSkills()); } mReviewDialog->eventDone += MyGUI::newDelegate(this, &CharacterCreation::onReviewDialogDone); @@ -241,7 +305,7 @@ void CharacterCreation::onReviewDialogDone(WindowBase* parWindow) if (mReviewDialog) mWM->removeDialog(mReviewDialog); - mWM->setGuiMode(GM_Game); + mWM->popGuiMode(); } void CharacterCreation::onReviewDialogBack() @@ -249,7 +313,7 @@ void CharacterCreation::onReviewDialogBack() if (mReviewDialog) mWM->removeDialog(mReviewDialog); - mWM->setGuiMode(GM_Birth); + mWM->pushGuiMode(GM_Birth); } void CharacterCreation::onReviewActivateDialog(int parDialog) @@ -258,19 +322,21 @@ void CharacterCreation::onReviewActivateDialog(int parDialog) mWM->removeDialog(mReviewDialog); mCreationStage = CSE_ReviewNext; + mWM->popGuiMode(); + switch(parDialog) { case ReviewDialog::NAME_DIALOG: - mWM->setGuiMode(GM_Name); + mWM->pushGuiMode(GM_Name); break; case ReviewDialog::RACE_DIALOG: - mWM->setGuiMode(GM_Race); + mWM->pushGuiMode(GM_Race); break; case ReviewDialog::CLASS_DIALOG: - mWM->setGuiMode(GM_Class); + mWM->pushGuiMode(GM_Class); break; case ReviewDialog::BIRTHSIGN_DIALOG: - mWM->setGuiMode(GM_Birth); + mWM->pushGuiMode(GM_Birth); }; } @@ -292,13 +358,19 @@ void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow) //TODO This bit gets repeated a few times; wrap it in a function if (mCreationStage == CSE_ReviewNext) - mWM->setGuiMode(GM_Review); + { + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Review); + } else if (mCreationStage >= CSE_ClassChosen) - mWM->setGuiMode(GM_Birth); + { + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Birth); + } else { mCreationStage = CSE_ClassChosen; - mWM->setGuiMode(GM_Game); + mWM->popGuiMode(); } } @@ -312,7 +384,8 @@ void CharacterCreation::onPickClassDialogBack() mWM->removeDialog(mPickClassDialog); } - mWM->setGuiMode(GM_Class); + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Class); } void CharacterCreation::onClassChoice(int _index) @@ -322,19 +395,21 @@ void CharacterCreation::onClassChoice(int _index) mWM->removeDialog(mClassChoiceDialog); } + mWM->popGuiMode(); + switch(_index) { case ClassChoiceDialog::Class_Generate: - mWM->setGuiMode(GM_ClassGenerate); + mWM->pushGuiMode(GM_ClassGenerate); break; case ClassChoiceDialog::Class_Pick: - mWM->setGuiMode(GM_ClassPick); + mWM->pushGuiMode(GM_ClassPick); break; case ClassChoiceDialog::Class_Create: - mWM->setGuiMode(GM_ClassCreate); + mWM->pushGuiMode(GM_ClassCreate); break; case ClassChoiceDialog::Class_Back: - mWM->setGuiMode(GM_Race); + mWM->pushGuiMode(GM_Race); break; }; @@ -351,13 +426,19 @@ void CharacterCreation::onNameDialogDone(WindowBase* parWindow) } if (mCreationStage == CSE_ReviewNext) - mWM->setGuiMode(GM_Review); + { + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Review); + } else if (mCreationStage >= CSE_NameChosen) - mWM->setGuiMode(GM_Race); + { + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Race); + } else { mCreationStage = CSE_NameChosen; - mWM->setGuiMode(GM_Game); + mWM->popGuiMode(); } } @@ -371,7 +452,8 @@ void CharacterCreation::onRaceDialogBack() mWM->removeDialog(mRaceDialog); } - mWM->setGuiMode(GM_Name); + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Name); } void CharacterCreation::onRaceDialogDone(WindowBase* parWindow) @@ -386,13 +468,19 @@ void CharacterCreation::onRaceDialogDone(WindowBase* parWindow) } if (mCreationStage == CSE_ReviewNext) - mWM->setGuiMode(GM_Review); - else if(mCreationStage >= CSE_RaceChosen) - mWM->setGuiMode(GM_Class); + { + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Review); + } + else if (mCreationStage >= CSE_RaceChosen) + { + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Class); + } else { mCreationStage = CSE_RaceChosen; - mWM->setGuiMode(GM_Game); + mWM->popGuiMode(); } } @@ -401,18 +489,20 @@ void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow) if (mBirthSignDialog) { mPlayerBirthSignId = mBirthSignDialog->getBirthId(); - mWM->setBirthSign(mPlayerBirthSignId); if (!mPlayerBirthSignId.empty()) MWBase::Environment::get().getMechanicsManager()->setPlayerBirthsign(mPlayerBirthSignId); mWM->removeDialog(mBirthSignDialog); } if (mCreationStage >= CSE_BirthSignChosen) - mWM->setGuiMode(GM_Review); + { + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Review); + } else { mCreationStage = CSE_BirthSignChosen; - mWM->setGuiMode(GM_Game); + mWM->popGuiMode(); } } @@ -424,7 +514,8 @@ void CharacterCreation::onBirthSignDialogBack() mWM->removeDialog(mBirthSignDialog); } - mWM->setGuiMode(GM_Class); + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Class); } void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow) @@ -459,13 +550,19 @@ void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow) } if (mCreationStage == CSE_ReviewNext) - mWM->setGuiMode(GM_Review); + { + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Review); + } else if (mCreationStage >= CSE_ClassChosen) - mWM->setGuiMode(GM_Birth); + { + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Birth); + } else { mCreationStage = CSE_ClassChosen; - mWM->setGuiMode(GM_Game); + mWM->popGuiMode(); } } @@ -474,16 +571,20 @@ void CharacterCreation::onCreateClassDialogBack() if (mCreateClassDialog) mWM->removeDialog(mCreateClassDialog); - mWM->setGuiMode(GM_Class); + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Class); } void CharacterCreation::onClassQuestionChosen(int _index) { + MWBase::Environment::get().getSoundManager()->stopSay(); + if (mGenerateClassQuestionDialog) mWM->removeDialog(mGenerateClassQuestionDialog); if (_index < 0 || _index >= 3) { - mWM->setGuiMode(GM_Class); + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Class); return; } @@ -568,7 +669,8 @@ void CharacterCreation::showClassQuestionDialog() if (mGenerateClassStep > sGenerateClassSteps.size()) { - mWM->setGuiMode(GM_Class); + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Class); return; } @@ -584,6 +686,8 @@ void CharacterCreation::showClassQuestionDialog() mGenerateClassQuestionDialog->setButtons(buttons); mGenerateClassQuestionDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassQuestionChosen); mGenerateClassQuestionDialog->open(); + + MWBase::Environment::get().getSoundManager()->say(sGenerateClassSteps[mGenerateClassStep].mSound); } void CharacterCreation::onGenerateClassBack() @@ -595,7 +699,8 @@ void CharacterCreation::onGenerateClassBack() mWM->removeDialog(mGenerateClassResultDialog); MWBase::Environment::get().getMechanicsManager()->setPlayerClass(mGenerateClass); - mWM->setGuiMode(GM_Class); + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Class); } void CharacterCreation::onGenerateClassDone(WindowBase* parWindow) @@ -605,13 +710,19 @@ void CharacterCreation::onGenerateClassDone(WindowBase* parWindow) MWBase::Environment::get().getMechanicsManager()->setPlayerClass(mGenerateClass); if (mCreationStage == CSE_ReviewNext) - mWM->setGuiMode(GM_Review); + { + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Review); + } else if (mCreationStage >= CSE_ClassChosen) - mWM->setGuiMode(GM_Birth); + { + mWM->popGuiMode(); + mWM->pushGuiMode(GM_Birth); + } else { mCreationStage = CSE_ClassChosen; - mWM->setGuiMode(GM_Game); + mWM->popGuiMode(); } } @@ -619,7 +730,6 @@ CharacterCreation::~CharacterCreation() { delete mNameDialog; delete mRaceDialog; - delete mDialogueWindow; delete mClassChoiceDialog; delete mGenerateClassQuestionDialog; delete mGenerateClassResultDialog; diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp index 222754cdc3..02b48ffe23 100644 --- a/apps/openmw/mwgui/charactercreation.hpp +++ b/apps/openmw/mwgui/charactercreation.hpp @@ -42,11 +42,15 @@ namespace MWGui void setPlayerFatigue (const MWMechanics::DynamicStat& value); + void setValue (const std::string& id, const MWMechanics::Stat& value); + void setValue (const std::string& id, const MWMechanics::DynamicStat& value); + void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat& value); + void configureSkills (const SkillList& major, const SkillList& minor); + private: //Dialogs TextInputDialog* mNameDialog; RaceDialog* mRaceDialog; - DialogueWindow* mDialogueWindow; ClassChoiceDialog* mClassChoiceDialog; InfoBoxDialog* mGenerateClassQuestionDialog; GenerateClassResultDialog* mGenerateClassResultDialog; @@ -62,9 +66,6 @@ namespace MWGui std::string mPlayerRaceId; std::string mPlayerBirthSignId; ESM::Class mPlayerClass; - std::map > mPlayerAttributes; - SkillList mPlayerMajorSkills, mPlayerMinorSkills; - std::map > mPlayerSkillValues; MWMechanics::DynamicStat mPlayerHealth; MWMechanics::DynamicStat mPlayerMagicka; MWMechanics::DynamicStat mPlayerFatigue; diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index 75e534b42a..0961f6327e 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -1,6 +1,4 @@ #include "class.hpp" -#include "window_manager.hpp" -#include "components/esm_store/store.hpp" #include #include @@ -8,6 +6,11 @@ #include #include +#include + +#include "window_manager.hpp" +#include "tooltips.hpp" + #undef min #undef max @@ -26,14 +29,19 @@ GenerateClassResultDialog::GenerateClassResultDialog(WindowManager& parWindowMan getWidget(classImage, "ClassImage"); getWidget(className, "ClassName"); - // TODO: These buttons should be managed by a Dialog class MyGUI::ButtonPtr backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onBackClicked); MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); + okButton->setCaption(mWindowManager.getGameSettingString("sOK", "")); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &GenerateClassResultDialog::onOkClicked); + + int okButtonWidth = okButton->getTextSize().width + 24; + int backButtonWidth = backButton->getTextSize().width + 24; + okButton->setCoord(315 - okButtonWidth, 219, okButtonWidth, 23); + backButton->setCoord(315 - okButtonWidth - backButtonWidth - 6, 219, backButtonWidth, 23); } void GenerateClassResultDialog::open() @@ -74,17 +82,13 @@ PickClassDialog::PickClassDialog(WindowManager& parWindowManager) // Centre dialog center(); - setText("SpecializationT", mWindowManager.getGameSettingString("sChooseClassMenu1", "Specialization")); getWidget(specializationName, "SpecializationName"); - setText("FavoriteAttributesT", mWindowManager.getGameSettingString("sChooseClassMenu2", "Favorite Attributes:")); getWidget(favoriteAttribute[0], "FavoriteAttribute0"); getWidget(favoriteAttribute[1], "FavoriteAttribute1"); favoriteAttribute[0]->setWindowManager(&mWindowManager); favoriteAttribute[1]->setWindowManager(&mWindowManager); - setText("MajorSkillT", mWindowManager.getGameSettingString("sChooseClassMenu3", "Major Skills:")); - setText("MinorSkillT", mWindowManager.getGameSettingString("sChooseClassMenu4", "Minor Skills:")); for(int i = 0; i < 5; i++) { char theIndex = '0'+i; @@ -102,7 +106,6 @@ PickClassDialog::PickClassDialog(WindowManager& parWindowManager) getWidget(classImage, "ClassImage"); - // TODO: These buttons should be managed by a Dialog class MyGUI::ButtonPtr backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PickClassDialog::onBackClicked); @@ -123,21 +126,16 @@ void PickClassDialog::setNextButtonShow(bool shown) MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); - // TODO: All hardcoded coords for buttons are temporary, will be replaced with a dynamic system. if (shown) - { - okButton->setCaption("Next"); - - // Adjust back button when next is shown - backButton->setCoord(MyGUI::IntCoord(382 - 18, 265, 53, 23)); - okButton->setCoord(MyGUI::IntCoord(434 - 18, 265, 42 + 18, 23)); - } + okButton->setCaption(mWindowManager.getGameSettingString("sNext", "")); else - { - okButton->setCaption("OK"); - backButton->setCoord(MyGUI::IntCoord(382, 265, 53, 23)); - okButton->setCoord(MyGUI::IntCoord(434, 265, 42, 23)); - } + okButton->setCaption(mWindowManager.getGameSettingString("sOK", "")); + + int okButtonWidth = okButton->getTextSize().width + 24; + int backButtonWidth = backButton->getTextSize().width + 24; + + okButton->setCoord(476 - okButtonWidth, 265, okButtonWidth, 23); + backButton->setCoord(476 - okButtonWidth - backButtonWidth - 6, 265, backButtonWidth, 23); } void PickClassDialog::open() @@ -232,15 +230,21 @@ void PickClassDialog::updateStats() "sSpecializationMagic", "sSpecializationStealth" }; - specializationName->setCaption(mWindowManager.getGameSettingString(specIds[specialization], specIds[specialization])); + std::string specName = mWindowManager.getGameSettingString(specIds[specialization], specIds[specialization]); + specializationName->setCaption(specName); + ToolTips::createSpecializationToolTip(specializationName, specName, specialization); favoriteAttribute[0]->setAttributeId(klass->data.attribute[0]); favoriteAttribute[1]->setAttributeId(klass->data.attribute[1]); + ToolTips::createAttributeToolTip(favoriteAttribute[0], favoriteAttribute[0]->getAttributeId()); + ToolTips::createAttributeToolTip(favoriteAttribute[1], favoriteAttribute[1]->getAttributeId()); for (int i = 0; i < 5; ++i) { - majorSkill[i]->setSkillNumber(klass->data.skills[i][0]); - minorSkill[i]->setSkillNumber(klass->data.skills[i][1]); + minorSkill[i]->setSkillNumber(klass->data.skills[i][0]); + majorSkill[i]->setSkillNumber(klass->data.skills[i][1]); + ToolTips::createSkillToolTip(minorSkill[i], klass->data.skills[i][0]); + ToolTips::createSkillToolTip(majorSkill[i], klass->data.skills[i][1]); } classImage->setImageTexture(std::string("textures\\levelup\\") + currentClassId + ".dds"); @@ -388,7 +392,6 @@ CreateClassDialog::CreateClassDialog(WindowManager& parWindowManager) setText("SpecializationT", mWindowManager.getGameSettingString("sChooseClassMenu1", "Specialization")); getWidget(specializationName, "SpecializationName"); - specializationName->setCaption(mWindowManager.getGameSettingString(ESM::Class::gmstSpecializationIds[ESM::Class::Combat], "")); specializationName->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onSpecializationClicked); setText("FavoriteAttributesT", mWindowManager.getGameSettingString("sChooseClassMenu2", "Favorite Attributes:")); @@ -423,9 +426,9 @@ CreateClassDialog::CreateClassDialog(WindowManager& parWindowManager) // Make sure the edit box has focus MyGUI::InputManager::getInstance().setKeyFocusWidget(editName); - // TODO: These buttons should be managed by a Dialog class MyGUI::ButtonPtr descriptionButton; getWidget(descriptionButton, "DescriptionButton"); + descriptionButton->setCaption(mWindowManager.getGameSettingString("sCreateClassMenu1", "")); descriptionButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CreateClassDialog::onDescriptionClicked); MyGUI::ButtonPtr backButton; @@ -452,6 +455,9 @@ CreateClassDialog::CreateClassDialog(WindowManager& parWindowManager) minorSkill[2]->setSkillId(ESM::Skill::Spear); minorSkill[3]->setSkillId(ESM::Skill::Athletics); minorSkill[4]->setSkillId(ESM::Skill::Enchant); + + setSpecialization(0); + update(); } CreateClassDialog::~CreateClassDialog() @@ -462,6 +468,18 @@ CreateClassDialog::~CreateClassDialog() delete descDialog; } +void CreateClassDialog::update() +{ + for (int i = 0; i < 5; ++i) + { + ToolTips::createSkillToolTip(majorSkill[i], majorSkill[i]->getSkillId()); + ToolTips::createSkillToolTip(minorSkill[i], minorSkill[i]->getSkillId()); + } + + ToolTips::createAttributeToolTip(favoriteAttribute0, favoriteAttribute0->getAttributeId()); + ToolTips::createAttributeToolTip(favoriteAttribute1, favoriteAttribute1->getAttributeId()); +} + std::string CreateClassDialog::getName() const { return editName->getOnlyText(); @@ -500,39 +518,34 @@ std::vector CreateClassDialog::getMinorSkills() const std::vector v; for(int i=0; i < 5; i++) { - v.push_back(majorSkill[i]->getSkillId()); + v.push_back(minorSkill[i]->getSkillId()); } return v; } void CreateClassDialog::setNextButtonShow(bool shown) { - MyGUI::ButtonPtr descriptionButton; - getWidget(descriptionButton, "DescriptionButton"); - MyGUI::ButtonPtr backButton; getWidget(backButton, "BackButton"); MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); - // TODO: All hardcoded coords for buttons are temporary, will be replaced with a dynamic system. - if (shown) - { - okButton->setCaption("Next"); + MyGUI::ButtonPtr descriptionButton; + getWidget(descriptionButton, "DescriptionButton"); - // Adjust back button when next is shown - descriptionButton->setCoord(MyGUI::IntCoord(207 - 18, 158, 143, 23)); - backButton->setCoord(MyGUI::IntCoord(356 - 18, 158, 53, 23)); - okButton->setCoord(MyGUI::IntCoord(417 - 18, 158, 42 + 18, 23)); - } + if (shown) + okButton->setCaption(mWindowManager.getGameSettingString("sNext", "")); else - { - okButton->setCaption("OK"); - descriptionButton->setCoord(MyGUI::IntCoord(207, 158, 143, 23)); - backButton->setCoord(MyGUI::IntCoord(356, 158, 53, 23)); - okButton->setCoord(MyGUI::IntCoord(417, 158, 42, 23)); - } + okButton->setCaption(mWindowManager.getGameSettingString("sOK", "")); + + int okButtonWidth = okButton->getTextSize().width + 24; + int backButtonWidth = backButton->getTextSize().width + 24; + int descriptionButtonWidth = descriptionButton->getTextSize().width + 24; + + okButton->setCoord(459 - okButtonWidth, 158, okButtonWidth, 23); + backButton->setCoord(459 - okButtonWidth - backButtonWidth - 6, 158, backButtonWidth, 23); + descriptionButton->setCoord(459 - okButtonWidth - backButtonWidth - descriptionButtonWidth - 12, 158, descriptionButtonWidth, 23); } void CreateClassDialog::open() @@ -545,20 +558,30 @@ void CreateClassDialog::open() void CreateClassDialog::onDialogCancel() { if (specDialog) - specDialog->setVisible(false); + { + mWindowManager.removeDialog(specDialog); + specDialog = 0; + } if (attribDialog) - attribDialog->setVisible(false); + { + mWindowManager.removeDialog(attribDialog); + attribDialog = 0; + } if (skillDialog) - skillDialog->setVisible(false); + { + mWindowManager.removeDialog(skillDialog); + skillDialog = 0; + } if (descDialog) - descDialog->setVisible(false); - // TODO: Delete dialogs here + { + mWindowManager.removeDialog(descDialog); + descDialog = 0; + } } void CreateClassDialog::onSpecializationClicked(MyGUI::WidgetPtr _sender) { - if (specDialog) - delete specDialog; + delete specDialog; specDialog = new SelectSpecializationDialog(mWindowManager); specDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); specDialog->eventItemSelected += MyGUI::newDelegate(this, &CreateClassDialog::onSpecializationSelected); @@ -568,14 +591,28 @@ void CreateClassDialog::onSpecializationClicked(MyGUI::WidgetPtr _sender) void CreateClassDialog::onSpecializationSelected() { specializationId = specDialog->getSpecializationId(); - specializationName->setCaption(mWindowManager.getGameSettingString(ESM::Class::gmstSpecializationIds[specializationId], "")); - specDialog->setVisible(false); + setSpecialization(specializationId); + + mWindowManager.removeDialog(specDialog); + specDialog = 0; +} + +void CreateClassDialog::setSpecialization(int id) +{ + specializationId = (ESM::Class::Specialization) id; + static const char *specIds[3] = { + "sSpecializationCombat", + "sSpecializationMagic", + "sSpecializationStealth" + }; + std::string specName = mWindowManager.getGameSettingString(specIds[specializationId], specIds[specializationId]); + specializationName->setCaption(specName); + ToolTips::createSpecializationToolTip(specializationName, specName, specializationId); } void CreateClassDialog::onAttributeClicked(Widgets::MWAttributePtr _sender) { - if (attribDialog) - delete attribDialog; + delete attribDialog; attribDialog = new SelectAttributeDialog(mWindowManager); attribDialog->setAffectedWidget(_sender); attribDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); @@ -598,13 +635,15 @@ void CreateClassDialog::onAttributeSelected() favoriteAttribute0->setAttributeId(favoriteAttribute1->getAttributeId()); } attribute->setAttributeId(id); - attribDialog->setVisible(false); + mWindowManager.removeDialog(attribDialog); + attribDialog = 0; + + update(); } void CreateClassDialog::onSkillClicked(Widgets::MWSkillPtr _sender) { - if (skillDialog) - delete skillDialog; + delete skillDialog; skillDialog = new SelectSkillDialog(mWindowManager); skillDialog->setAffectedWidget(_sender); skillDialog->eventCancel += MyGUI::newDelegate(this, &CreateClassDialog::onDialogCancel); @@ -631,7 +670,9 @@ void CreateClassDialog::onSkillSelected() } skill->setSkillId(skillDialog->getSkillId()); - skillDialog->setVisible(false); + mWindowManager.removeDialog(skillDialog); + skillDialog = 0; + update(); } void CreateClassDialog::onDescriptionClicked(MyGUI::Widget* _sender) @@ -646,6 +687,7 @@ void CreateClassDialog::onDescriptionEntered(WindowBase* parWindow) { description = descDialog->getTextInput(); mWindowManager.removeDialog(descDialog); + descDialog = 0; } void CreateClassDialog::onOkClicked(MyGUI::Widget* _sender) @@ -671,19 +713,35 @@ SelectSpecializationDialog::SelectSpecializationDialog(WindowManager& parWindowM getWidget(specialization0, "Specialization0"); getWidget(specialization1, "Specialization1"); getWidget(specialization2, "Specialization2"); - specialization0->setCaption(mWindowManager.getGameSettingString(ESM::Class::gmstSpecializationIds[ESM::Class::Combat], "")); + std::string combat = mWindowManager.getGameSettingString(ESM::Class::gmstSpecializationIds[ESM::Class::Combat], ""); + std::string magic = mWindowManager.getGameSettingString(ESM::Class::gmstSpecializationIds[ESM::Class::Magic], ""); + std::string stealth = mWindowManager.getGameSettingString(ESM::Class::gmstSpecializationIds[ESM::Class::Stealth], ""); + + specialization0->setCaption(combat); specialization0->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); - specialization1->setCaption(mWindowManager.getGameSettingString(ESM::Class::gmstSpecializationIds[ESM::Class::Magic], "")); + specialization1->setCaption(magic); specialization1->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); - specialization2->setCaption(mWindowManager.getGameSettingString(ESM::Class::gmstSpecializationIds[ESM::Class::Stealth], "")); + specialization2->setCaption(stealth); specialization2->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onSpecializationClicked); specializationId = ESM::Class::Combat; - // TODO: These buttons should be managed by a Dialog class + ToolTips::createSpecializationToolTip(specialization0, combat, ESM::Class::Combat); + ToolTips::createSpecializationToolTip(specialization1, magic, ESM::Class::Magic); + ToolTips::createSpecializationToolTip(specialization2, stealth, ESM::Class::Stealth); + MyGUI::ButtonPtr cancelButton; getWidget(cancelButton, "CancelButton"); cancelButton->setCaption(mWindowManager.getGameSettingString("sCancel", "")); cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSpecializationDialog::onCancelClicked); + int buttonWidth = cancelButton->getTextSize().width + 24; + cancelButton->setCoord(216 - buttonWidth, 90, buttonWidth, 21); + + MyGUI::InputManager::getInstance().addWidgetModal(mMainWidget); +} + +SelectSpecializationDialog::~SelectSpecializationDialog() +{ + MyGUI::InputManager::getInstance().removeWidgetModal(mMainWidget); } // widget controls @@ -726,13 +784,22 @@ SelectAttributeDialog::SelectAttributeDialog(WindowManager& parWindowManager) attribute->setWindowManager(&parWindowManager); attribute->setAttributeId(ESM::Attribute::attributeIds[i]); attribute->eventClicked += MyGUI::newDelegate(this, &SelectAttributeDialog::onAttributeClicked); + ToolTips::createAttributeToolTip(attribute, attribute->getAttributeId()); } - // TODO: These buttons should be managed by a Dialog class MyGUI::ButtonPtr cancelButton; getWidget(cancelButton, "CancelButton"); cancelButton->setCaption(mWindowManager.getGameSettingString("sCancel", "")); cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectAttributeDialog::onCancelClicked); + int buttonWidth = cancelButton->getTextSize().width + 24; + cancelButton->setCoord(186 - buttonWidth, 180, buttonWidth, 21); + + MyGUI::InputManager::getInstance().addWidgetModal(mMainWidget); +} + +SelectAttributeDialog::~SelectAttributeDialog() +{ + MyGUI::InputManager::getInstance().removeWidgetModal(mMainWidget); } // widget controls @@ -814,14 +881,23 @@ SelectSkillDialog::SelectSkillDialog(WindowManager& parWindowManager) skills[spec][i].widget->setWindowManager(&mWindowManager); skills[spec][i].widget->setSkillId(skills[spec][i].skillId); skills[spec][i].widget->eventClicked += MyGUI::newDelegate(this, &SelectSkillDialog::onSkillClicked); + ToolTips::createSkillToolTip(skills[spec][i].widget, skills[spec][i].widget->getSkillId()); } } - // TODO: These buttons should be managed by a Dialog class MyGUI::ButtonPtr cancelButton; getWidget(cancelButton, "CancelButton"); cancelButton->setCaption(mWindowManager.getGameSettingString("sCancel", "")); cancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SelectSkillDialog::onCancelClicked); + int buttonWidth = cancelButton->getTextSize().width + 24; + cancelButton->setCoord(447 - buttonWidth, 218, buttonWidth, 21); + + MyGUI::InputManager::getInstance().addWidgetModal(mMainWidget); +} + +SelectSkillDialog::~SelectSkillDialog() +{ + MyGUI::InputManager::getInstance().removeWidgetModal(mMainWidget); } // widget controls @@ -847,14 +923,22 @@ DescriptionDialog::DescriptionDialog(WindowManager& parWindowManager) getWidget(textEdit, "TextEdit"); - // TODO: These buttons should be managed by a Dialog class MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &DescriptionDialog::onOkClicked); okButton->setCaption(mWindowManager.getGameSettingString("sInputMenu1", "")); + int buttonWidth = okButton->getTextSize().width + 24; + okButton->setCoord(234 - buttonWidth, 214, buttonWidth, 24); // Make sure the edit box has focus MyGUI::InputManager::getInstance().setKeyFocusWidget(textEdit); + + MyGUI::InputManager::getInstance().addWidgetModal(mMainWidget); +} + +DescriptionDialog::~DescriptionDialog() +{ + MyGUI::InputManager::getInstance().removeWidgetModal(mMainWidget); } // widget controls diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp index 0e3348086d..868052800d 100644 --- a/apps/openmw/mwgui/class.hpp +++ b/apps/openmw/mwgui/class.hpp @@ -34,7 +34,7 @@ namespace MWGui typedef delegates::CMultiDelegate1 EventHandle_Int; /** Event : Button was clicked.\n - signature : void method(MyGUI::WidgetPtr widget, int index)\n + signature : void method(int index)\n */ EventHandle_Int eventButtonSelected; @@ -139,6 +139,7 @@ namespace MWGui { public: SelectSpecializationDialog(WindowManager& parWindowManager); + ~SelectSpecializationDialog(); ESM::Class::Specialization getSpecializationId() const { return specializationId; } @@ -169,6 +170,7 @@ namespace MWGui { public: SelectAttributeDialog(WindowManager& parWindowManager); + ~SelectAttributeDialog(); ESM::Attribute::AttributeID getAttributeId() const { return attributeId; } Widgets::MWAttributePtr getAffectedWidget() const { return affectedWidget; } @@ -201,6 +203,7 @@ namespace MWGui { public: SelectSkillDialog(WindowManager& parWindowManager); + ~SelectSkillDialog(); ESM::Skill::SkillEnum getSkillId() const { return skillId; } Widgets::MWSkillPtr getAffectedWidget() const { return affectedWidget; } @@ -236,6 +239,7 @@ namespace MWGui { public: DescriptionDialog(WindowManager& parWindowManager); + ~DescriptionDialog(); std::string getTextInput() const { return textEdit ? textEdit->getOnlyText() : ""; } void setTextInput(const std::string &text) { if (textEdit) textEdit->setOnlyText(text); } @@ -285,6 +289,10 @@ namespace MWGui void onDescriptionEntered(WindowBase* parWindow); void onDialogCancel(); + void setSpecialization(int id); + + void update(); + private: MyGUI::EditPtr editName; MyGUI::TextBox* specializationName; diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp new file mode 100644 index 0000000000..5e12c32964 --- /dev/null +++ b/apps/openmw/mwgui/confirmationdialog.cpp @@ -0,0 +1,70 @@ +#include "confirmationdialog.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/world.hpp" + +namespace MWGui +{ + ConfirmationDialog::ConfirmationDialog(WindowManager& parWindowManager) : + WindowBase("openmw_confirmation_dialog_layout.xml", parWindowManager) + { + getWidget(mMessage, "Message"); + getWidget(mOkButton, "OkButton"); + getWidget(mCancelButton, "CancelButton"); + + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ConfirmationDialog::onCancelButtonClicked); + mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ConfirmationDialog::onOkButtonClicked); + } + + void ConfirmationDialog::open(const std::string& message) + { + setVisible(true); + + mMessage->setCaptionWithReplacing(message); + + int height = mMessage->getTextSize().height + 72; + + mMainWidget->setSize(mMainWidget->getWidth(), height); + + mMessage->setSize(mMessage->getWidth(), mMessage->getTextSize().height+24); + + center(); + + // make other gui elements inaccessible while this dialog is open + MyGUI::InputManager::getInstance().addWidgetModal(mMainWidget); + + int okButtonWidth = mOkButton->getTextSize().width + 24; + mOkButton->setCoord(mMainWidget->getWidth() - 30 - okButtonWidth, + mOkButton->getTop(), + okButtonWidth, + mOkButton->getHeight()); + + int cancelButtonWidth = mCancelButton->getTextSize().width + 24; + mCancelButton->setCoord(mMainWidget->getWidth() - 30 - okButtonWidth - cancelButtonWidth - 8, + mCancelButton->getTop(), + cancelButtonWidth, + mCancelButton->getHeight()); + } + + void ConfirmationDialog::onCancelButtonClicked(MyGUI::Widget* _sender) + { + eventCancelClicked(); + + close(); + } + + void ConfirmationDialog::onOkButtonClicked(MyGUI::Widget* _sender) + { + eventOkClicked(); + + close(); + } + + void ConfirmationDialog::close() + { + setVisible(false); + MyGUI::InputManager::getInstance().removeWidgetModal(mMainWidget); + } +} diff --git a/apps/openmw/mwgui/confirmationdialog.hpp b/apps/openmw/mwgui/confirmationdialog.hpp new file mode 100644 index 0000000000..d278274a03 --- /dev/null +++ b/apps/openmw/mwgui/confirmationdialog.hpp @@ -0,0 +1,35 @@ +#ifndef MWGUI_CONFIRMATIONDIALOG_H +#define MWGUI_CONFIRMATIONDIALOG_H + +#include "window_base.hpp" + +namespace MWGui +{ + class ConfirmationDialog : public WindowBase + { + public: + ConfirmationDialog(WindowManager& parWindowManager); + void open(const std::string& message); + + typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; + + /** Event : Ok button was clicked.\n + signature : void method()\n + */ + EventHandle_Void eventOkClicked; + EventHandle_Void eventCancelClicked; + + private: + MyGUI::EditBox* mMessage; + MyGUI::Button* mOkButton; + MyGUI::Button* mCancelButton; + + void onCancelButtonClicked(MyGUI::Widget* _sender); + void onOkButtonClicked(MyGUI::Widget* _sender); + + void close(); + }; + +} + +#endif diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 8e15abddd6..8115835f40 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -138,6 +138,7 @@ namespace MWGui void Console::disable() { setVisible(false); + setSelectedObject(MWWorld::Ptr()); // Remove keyboard focus from the console input whenever the // console is turned off MyGUI::InputManager::getInstance().setKeyFocusWidget(NULL); @@ -240,7 +241,7 @@ namespace MWGui { try { - ConsoleInterpreterContext interpreterContext (*this, MWWorld::Ptr()); + ConsoleInterpreterContext interpreterContext (*this, mPtr); Interpreter::Interpreter interpreter; MWScript::installOpcodes (interpreter); std::vector code; @@ -267,7 +268,7 @@ namespace MWGui /* Are there quotation marks? */ if( tmp.find('"') != string::npos ) { int numquotes=0; - for(string::iterator it=tmp.begin(); it < tmp.end(); it++) { + for(string::iterator it=tmp.begin(); it < tmp.end(); ++it) { if( *it == '"' ) numquotes++; } @@ -310,7 +311,7 @@ namespace MWGui } /* Iterate through the vector. */ - for(vector::iterator it=mNames.begin(); it < mNames.end();it++) { + for(vector::iterator it=mNames.begin(); it < mNames.end();++it) { bool string_different=false; /* Is the string shorter than the input string? If yes skip it. */ @@ -358,7 +359,7 @@ namespace MWGui int i = tmp.length(); for(string::iterator iter=matches.front().begin()+tmp.length(); iter < matches.front().end(); iter++, i++) { - for(vector::iterator it=matches.begin(); it < matches.end();it++) { + for(vector::iterator it=matches.begin(); it < matches.end();++it) { if( tolower((*it)[i]) != tolower(*iter) ) { /* Append the longest match to the end of the output string*/ output.append(matches.front().substr( 0, i)); @@ -370,4 +371,24 @@ namespace MWGui /* All keywords match with the shortest. Append it to the output string and return it. */ return output.append(matches.front()); } + + void Console::onResChange(int width, int height) + { + setCoord(10,10, width-10, height/2); + } + + void Console::setSelectedObject(const MWWorld::Ptr& object) + { + mPtr = object; + if (!mPtr.isEmpty()) + setTitle("#{sConsoleTitle} (" + mPtr.getCellRef().refID + ")"); + else + setTitle("#{sConsoleTitle}"); + MyGUI::InputManager::getInstance().setKeyFocusWidget(command); + } + + void Console::onReferenceUnavailable() + { + setSelectedObject(MWWorld::Ptr()); + } } diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index 6974d83330..eadf4aa4ea 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -16,9 +16,11 @@ #include "../mwscript/compilercontext.hpp" #include "../mwscript/interpretercontext.hpp" +#include "referenceinterface.hpp" + namespace MWGui { - class Console : private OEngine::GUI::Layout, private Compiler::ErrorHandler + class Console : private OEngine::GUI::Layout, private Compiler::ErrorHandler, public ReferenceInterface { private: @@ -39,7 +41,17 @@ namespace MWGui /// \note The list may contain duplicates (if a name is a keyword and an identifier at the same /// time). - public: + public: + + void setSelectedObject(const MWWorld::Ptr& object); + ///< Set the implicit object for script execution + + protected: + + virtual void onReferenceUnavailable(); + + + public: MyGUI::EditPtr command; MyGUI::EditPtr history; @@ -58,6 +70,8 @@ namespace MWGui void setFont(const std::string &fntName); + void onResChange(int width, int height); + void clearHistory(); // Print a message to the console. Messages may contain color diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp new file mode 100644 index 0000000000..6775fa6439 --- /dev/null +++ b/apps/openmw/mwgui/container.cpp @@ -0,0 +1,681 @@ +#include "container.hpp" + +#include +#include +#include +#include +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwworld/world.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" +#include "../mwclass/container.hpp" +#include "../mwinput/inputmanager.hpp" +#include "../mwsound/soundmanager.hpp" + +#include "window_manager.hpp" +#include "widgets.hpp" +#include "countdialog.hpp" +#include "tradewindow.hpp" +#include "inventorywindow.hpp" + +using namespace MWGui; +using namespace Widgets; + + +namespace +{ + bool compareType(std::string type1, std::string type2) + { + // this defines the sorting order of types. types that are first in the vector, appear before other types. + std::vector mapping; + mapping.push_back( typeid(ESM::Weapon).name() ); + mapping.push_back( typeid(ESM::Armor).name() ); + mapping.push_back( typeid(ESM::Clothing).name() ); + mapping.push_back( typeid(ESM::Potion).name() ); + mapping.push_back( typeid(ESM::Ingredient).name() ); + mapping.push_back( typeid(ESM::Apparatus).name() ); + mapping.push_back( typeid(ESM::Book).name() ); + mapping.push_back( typeid(ESM::Light).name() ); + mapping.push_back( typeid(ESM::Miscellaneous).name() ); + mapping.push_back( typeid(ESM::Tool).name() ); + mapping.push_back( typeid(ESM::Repair).name() ); + mapping.push_back( typeid(ESM::Probe).name() ); + + assert( std::find(mapping.begin(), mapping.end(), type1) != mapping.end() ); + assert( std::find(mapping.begin(), mapping.end(), type2) != mapping.end() ); + + return std::find(mapping.begin(), mapping.end(), type1) < std::find(mapping.begin(), mapping.end(), type2); + } + + bool sortItems(MWWorld::Ptr left, MWWorld::Ptr right) + { + if (left.getTypeName() == right.getTypeName()) + { + int cmp = MWWorld::Class::get(left).getName(left).compare( + MWWorld::Class::get(right).getName(right)); + return cmp < 0; + } + else + { + return compareType(left.getTypeName(), right.getTypeName()); + } + } +} + + +ContainerBase::ContainerBase(DragAndDrop* dragAndDrop) : + mDragAndDrop(dragAndDrop), + mFilter(ContainerBase::Filter_All) +{ +} + +void ContainerBase::setWidgets(Widget* containerWidget, ScrollView* itemView) +{ + mContainerWidget = containerWidget; + mItemView = itemView; + + mContainerWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerBase::onContainerClicked); + mContainerWidget->eventMouseWheel += MyGUI::newDelegate(this, &ContainerWindow::onMouseWheel); +} + +ContainerBase::~ContainerBase() +{ +} + +void ContainerBase::onSelectedItem(MyGUI::Widget* _sender) +{ + mSelectedItem = _sender; + + if (mDragAndDrop && !isTrading()) + { + if(!mDragAndDrop->mIsOnDragAndDrop) + { + MWWorld::Ptr object = (*_sender->getUserData()); + int count = object.getRefData().getCount(); + + if (MyGUI::InputManager::getInstance().isShiftPressed() || count == 1) + { + startDragItem(_sender, count); + } + else if (MyGUI::InputManager::getInstance().isControlPressed()) + { + startDragItem(_sender, 1); + } + else + { + std::string message = MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sTake")->str; + CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); + dialog->open(MWWorld::Class::get(object).getName(object), message, count); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerBase::startDragItem); + } + } + else + onContainerClicked(mContainerWidget); + } + else if (isTrading()) + { + MWWorld::Ptr object = (*_sender->getUserData()); + int count = object.getRefData().getCount(); + + if (isInventory()) + { + // the player is trying to sell an item, check if the merchant accepts it + // also, don't allow selling gold (let's be better than Morrowind at this, can we?) + if (!MWBase::Environment::get().getWindowManager()->getTradeWindow()->npcAcceptsItem(object) + || MWWorld::Class::get(object).getName(object) == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) + { + // user notification "i don't buy this item" + MWBase::Environment::get().getWindowManager()-> + messageBox(MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sBarterDialog4")->str, std::vector()); + return; + } + } + + bool buying = isTradeWindow(); // buying or selling? + std::string message = buying ? MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sQuanityMenuMessage02")->str + : MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sQuanityMenuMessage01")->str; + + if (std::find(mBoughtItems.begin(), mBoughtItems.end(), object) != mBoughtItems.end()) + { + if (MyGUI::InputManager::getInstance().isShiftPressed() || count == 1) + { + sellAlreadyBoughtItem(NULL, count); + } + else if (MyGUI::InputManager::getInstance().isControlPressed()) + { + sellAlreadyBoughtItem(NULL, 1); + } + else + { + CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); + dialog->open(MWWorld::Class::get(object).getName(object), message, count); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerBase::sellAlreadyBoughtItem); + } + } + else + { + if (MyGUI::InputManager::getInstance().isShiftPressed() || count == 1) + { + sellItem(NULL, count); + } + else if (MyGUI::InputManager::getInstance().isControlPressed()) + { + sellItem(NULL, 1); + } + else + { + CountDialog* dialog = MWBase::Environment::get().getWindowManager()->getCountDialog(); + dialog->open(MWWorld::Class::get(object).getName(object), message, count); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &ContainerBase::sellItem); + } + } + } + else + { + onSelectedItemImpl(*_sender->getUserData()); + } +} + +void ContainerBase::sellAlreadyBoughtItem(MyGUI::Widget* _sender, int count) +{ + MWWorld::Ptr object = *mSelectedItem->getUserData(); + + if (isInventory()) + { + MWBase::Environment::get().getWindowManager()->getTradeWindow()->addItem(object, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->buyFromNpc(object, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->drawItems(); + } + else + { + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->addItem(object, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->sellToNpc(object, count); + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->drawItems(); + } + + std::string sound = MWWorld::Class::get(object).getUpSoundId(object); + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); + + drawItems(); +} + +void ContainerBase::sellItem(MyGUI::Widget* _sender, int count) +{ + MWWorld::Ptr object = *mSelectedItem->getUserData(); + + if (isInventory()) + { + MWBase::Environment::get().getWindowManager()->getTradeWindow()->addBarteredItem(object, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->buyFromNpc(object, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->drawItems(); + } + else + { + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->addBarteredItem(object, count); + MWBase::Environment::get().getWindowManager()->getTradeWindow()->sellToNpc(object, count); + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->drawItems(); + } + + std::string sound = MWWorld::Class::get(object).getUpSoundId(object); + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); + + drawItems(); +} + +void ContainerBase::startDragItem(MyGUI::Widget* _sender, int count) +{ + mDragAndDrop->mIsOnDragAndDrop = true; + mSelectedItem->detachFromWidget(); + mSelectedItem->attachToWidget(mDragAndDrop->mDragAndDropWidget); + + MWWorld::Ptr object = *mSelectedItem->getUserData(); + _unequipItem(object); + + mDragAndDrop->mDraggedCount = count; + + mDragAndDrop->mDraggedFrom = this; + + std::string sound = MWWorld::Class::get(object).getUpSoundId(object); + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); + + mDragAndDrop->mDraggedWidget = mSelectedItem; + static_cast(mSelectedItem)->setImageTexture(""); // remove the background texture (not visible during drag) + static_cast(mSelectedItem->getChildAt(0)->getChildAt(0))->setCaption( + getCountString(mDragAndDrop->mDraggedCount)); + + drawItems(); + + MWBase::Environment::get().getWindowManager()->setDragDrop(true); +} + +void ContainerBase::onContainerClicked(MyGUI::Widget* _sender) +{ + if (mDragAndDrop == NULL) return; + + if(mDragAndDrop->mIsOnDragAndDrop) //drop item here + { + MWWorld::Ptr object = *mDragAndDrop->mDraggedWidget->getUserData(); + MWWorld::ContainerStore& containerStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); + + if (mDragAndDrop->mDraggedFrom != this) + { + assert(object.getContainerStore() && "Item is not in a container!"); + + // check the container's Organic flag (if this is a container). container with Organic flag doesn't allow putting items inside + if (mPtr.getTypeName() == typeid(ESM::Container).name()) + { + ESMS::LiveCellRef* ref = mPtr.get(); + if (ref->base->flags & ESM::Container::Organic) + { + // user notification + MWBase::Environment::get().getWindowManager()-> + messageBox(MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sContentsMessage2")->str, std::vector()); + return; + } + } + + int origCount = object.getRefData().getCount(); + + // check that we don't exceed the allowed weight (only for containers, not for inventory) + if (!isInventory()) + { + float capacity = MWWorld::Class::get(mPtr).getCapacity(mPtr); + + // try adding the item, and if weight is exceeded, just remove it again. + object.getRefData().setCount(mDragAndDrop->mDraggedCount); + MWWorld::ContainerStoreIterator it = containerStore.add(object); + + float curWeight = MWWorld::Class::get(mPtr).getEncumbrance(mPtr); + if (curWeight > capacity) + { + it->getRefData().setCount(0); + object.getRefData().setCount(origCount); + // user notification + MWBase::Environment::get().getWindowManager()-> + messageBox(MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sContentsMessage3")->str, std::vector()); + return; + } + else + { + object.getRefData().setCount(origCount - mDragAndDrop->mDraggedCount); + } + std::cout << "container weight " << curWeight << "/" << capacity << std::endl; + } + else + { + object.getRefData().setCount (mDragAndDrop->mDraggedCount); + containerStore.add(object); + object.getRefData().setCount (origCount - mDragAndDrop->mDraggedCount); + } + } + + mDragAndDrop->mIsOnDragAndDrop = false; + MyGUI::Gui::getInstance().destroyWidget(mDragAndDrop->mDraggedWidget); + drawItems(); + mDragAndDrop->mDraggedFrom->drawItems(); + + MWBase::Environment::get().getWindowManager()->setDragDrop(false); + + std::string sound = MWWorld::Class::get(object).getDownSoundId(object); + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); + } +} + +void ContainerBase::onMouseWheel(MyGUI::Widget* _sender, int _rel) +{ + if (mItemView->getViewOffset().left + _rel*0.3 > 0) + mItemView->setViewOffset(MyGUI::IntPoint(0, 0)); + else + mItemView->setViewOffset(MyGUI::IntPoint(mItemView->getViewOffset().left + _rel*0.3, 0)); +} + +void ContainerBase::setFilter(ContainerBase::Filter filter) +{ + mFilter = filter; + drawItems(); +} + +void ContainerBase::openContainer(MWWorld::Ptr container) +{ + mPtr = container; +} + +void ContainerBase::drawItems() +{ + while (mContainerWidget->getChildCount()) + { + MyGUI::Gui::getInstance().destroyWidget(mContainerWidget->getChildAt(0)); + } + MWWorld::ContainerStore& containerStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); + + int x = 0; + int y = 0; + int maxHeight = mItemView->getSize().height - 58; + + bool onlyMagic = false; + int categories; + if (mFilter == Filter_All) + categories = MWWorld::ContainerStore::Type_All; + else if (mFilter == Filter_Weapon) + categories = MWWorld::ContainerStore::Type_Weapon; + else if (mFilter == Filter_Apparel) + categories = MWWorld::ContainerStore::Type_Clothing + MWWorld::ContainerStore::Type_Armor; + else if (mFilter == Filter_Magic) + { + categories = MWWorld::ContainerStore::Type_Clothing + MWWorld::ContainerStore::Type_Armor + + MWWorld::ContainerStore::Type_Weapon + MWWorld::ContainerStore::Type_Book + + MWWorld::ContainerStore::Type_Potion; + onlyMagic = true; + } + else if (mFilter == Filter_Misc) + { + categories = MWWorld::ContainerStore::Type_Miscellaneous + MWWorld::ContainerStore::Type_Book + + MWWorld::ContainerStore::Type_Ingredient + MWWorld::ContainerStore::Type_Repair + + MWWorld::ContainerStore::Type_Lockpick + MWWorld::ContainerStore::Type_Light + + MWWorld::ContainerStore::Type_Apparatus + MWWorld::ContainerStore::Type_Probe; + } + else if (mFilter == Filter_Ingredients) + categories = MWWorld::ContainerStore::Type_Ingredient; + + /// \todo performance improvement: don't create/destroy all the widgets everytime the container window changes size, only reposition them + + std::vector< std::pair > items; + + std::vector equippedItems = getEquippedItems(); + + // add bought items (always at the beginning) + std::vector boughtItems; + for (MWWorld::ContainerStoreIterator it (mBoughtItems.begin()); it!=mBoughtItems.end(); ++it) + { + boughtItems.push_back(*it); + } + std::sort(boughtItems.begin(), boughtItems.end(), sortItems); + + for (std::vector::iterator it=boughtItems.begin(); + it != boughtItems.end(); ++it) + { + items.push_back( std::make_pair(*it, ItemState_Barter) ); + } + + // filter out the equipped items of categories we don't want + std::vector unwantedItems = equippedItems; + for (MWWorld::ContainerStoreIterator iter (containerStore.begin(categories)); iter!=containerStore.end(); ++iter) + { + std::vector::iterator found = std::find(unwantedItems.begin(), unwantedItems.end(), *iter); + if (found != unwantedItems.end()) + { + unwantedItems.erase(found); + } + } + // now erase everything that's still in unwantedItems. + for (std::vector::iterator it=unwantedItems.begin(); + it != unwantedItems.end(); ++it) + { + std::vector::iterator found = std::find(equippedItems.begin(), equippedItems.end(), *it); + assert(found != equippedItems.end()); + equippedItems.erase(found); + } + // and add the items that are left (= have the correct category) + if (!ignoreEquippedItems()) + { + for (std::vector::const_iterator it=equippedItems.begin(); + it != equippedItems.end(); ++it) + { + items.push_back( std::make_pair(*it, ItemState_Equipped) ); + } + } + + std::vector ignoreItems = itemsToIgnore(); + + // now add the regular items + std::vector regularItems; + for (MWWorld::ContainerStoreIterator iter (containerStore.begin(categories)); iter!=containerStore.end(); ++iter) + { + if (std::find(equippedItems.begin(), equippedItems.end(), *iter) == equippedItems.end() + && std::find(ignoreItems.begin(), ignoreItems.end(), *iter) == ignoreItems.end() + && std::find(mBoughtItems.begin(), mBoughtItems.end(), *iter) == mBoughtItems.end()) + regularItems.push_back(*iter); + } + + // sort them and add + std::sort(regularItems.begin(), regularItems.end(), sortItems); + for (std::vector::const_iterator it=regularItems.begin(); it!=regularItems.end(); ++it) + { + items.push_back( std::make_pair(*it, ItemState_Normal) ); + } + + for (std::vector< std::pair >::const_iterator it=items.begin(); + it != items.end(); ++it) + { + const MWWorld::Ptr* iter = &((*it).first); + + int displayCount = iter->getRefData().getCount(); + if (mDragAndDrop != NULL && mDragAndDrop->mIsOnDragAndDrop && *iter == *mDragAndDrop->mDraggedWidget->getUserData()) + { + displayCount -= mDragAndDrop->mDraggedCount; + } + if(displayCount > 0 && !(onlyMagic && it->second != ItemState_Barter && MWWorld::Class::get(*iter).getEnchantment(*iter) == "" && iter->getTypeName() != typeid(ESM::Potion).name())) + { + std::string path = std::string("icons\\"); + path += MWWorld::Class::get(*iter).getInventoryIcon(*iter); + + // background widget (for the "equipped" frame and magic item background image) + bool isMagic = (MWWorld::Class::get(*iter).getEnchantment(*iter) != ""); + MyGUI::ImageBox* backgroundWidget = mContainerWidget->createWidget("ImageBox", MyGUI::IntCoord(x, y, 42, 42), MyGUI::Align::Default); + backgroundWidget->setUserString("ToolTipType", "ItemPtr"); + backgroundWidget->setUserData(*iter); + + std::string backgroundTex = "textures\\menu_icon"; + if (isMagic) + backgroundTex += "_magic"; + if (it->second == ItemState_Normal) + { + if (!isMagic) + backgroundTex = ""; + } + else if (it->second == ItemState_Equipped) + { + backgroundTex += "_equip"; + } + else if (it->second == ItemState_Barter) + { + backgroundTex += "_barter"; + } + if (backgroundTex != "") + backgroundTex += ".dds"; + + backgroundWidget->setImageTexture(backgroundTex); + if (it->second == ItemState_Barter && !isMagic) + backgroundWidget->setProperty("ImageCoord", "2 2 42 42"); + else + backgroundWidget->setProperty("ImageCoord", "0 0 42 42"); + backgroundWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerBase::onSelectedItem); + backgroundWidget->eventMouseWheel += MyGUI::newDelegate(this, &ContainerBase::onMouseWheel); + + // image + ImageBox* image = backgroundWidget->createWidget("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); + int pos = path.rfind("."); + path.erase(pos); + path.append(".dds"); + image->setImageTexture(path); + image->setNeedMouseFocus(false); + + // text widget that shows item count + MyGUI::TextBox* text = image->createWidget("SandBrightText", MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label")); + text->setTextAlign(MyGUI::Align::Right); + text->setNeedMouseFocus(false); + text->setTextShadow(true); + text->setTextShadowColour(MyGUI::Colour(0,0,0)); + text->setCaption(getCountString(displayCount)); + + y += 42; + if (y > maxHeight) + { + x += 42; + y = 0; + } + + } + } + + MyGUI::IntSize size = MyGUI::IntSize(std::max(mItemView->getSize().width, x+42), mItemView->getSize().height); + mItemView->setCanvasSize(size); + mContainerWidget->setSize(size); + + notifyContentChanged(); +} + +std::string ContainerBase::getCountString(const int count) +{ + if (count == 1) + return ""; + if (count > 9999) + return boost::lexical_cast(int(count/1000.f)) + "k"; + else + return boost::lexical_cast(count); +} + +void ContainerBase::addBarteredItem(MWWorld::Ptr item, int count) +{ + int origCount = item.getRefData().getCount(); + item.getRefData().setCount(count); + MWWorld::ContainerStoreIterator it = mBoughtItems.add(item); + item.getRefData().setCount(origCount - count); +} + +void ContainerBase::addItem(MWWorld::Ptr item, int count) +{ + MWWorld::ContainerStore& containerStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); + + int origCount = item.getRefData().getCount(); + + item.getRefData().setCount(count); + MWWorld::ContainerStoreIterator it = containerStore.add(item); + + item.getRefData().setCount(origCount - count); +} + +void ContainerBase::transferBoughtItems() +{ + MWWorld::ContainerStore& containerStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); + + for (MWWorld::ContainerStoreIterator it(mBoughtItems.begin()); it != mBoughtItems.end(); ++it) + { + containerStore.add(*it); + } +} + +void ContainerBase::returnBoughtItems(MWWorld::ContainerStore& store) +{ + for (MWWorld::ContainerStoreIterator it(mBoughtItems.begin()); it != mBoughtItems.end(); ++it) + { + store.add(*it); + } +} + +MWWorld::ContainerStore& ContainerBase::getContainerStore() +{ + MWWorld::ContainerStore& store = MWWorld::Class::get(mPtr).getContainerStore(mPtr); + return store; +} + +// ------------------------------------------------------------------------------------------------ + +ContainerWindow::ContainerWindow(WindowManager& parWindowManager,DragAndDrop* dragAndDrop) + : ContainerBase(dragAndDrop) + , WindowBase("openmw_container_window_layout.xml", parWindowManager) +{ + getWidget(mTakeButton, "TakeButton"); + getWidget(mCloseButton, "CloseButton"); + + MyGUI::ScrollView* itemView; + MyGUI::Widget* containerWidget; + getWidget(containerWidget, "Items"); + getWidget(itemView, "ItemView"); + setWidgets(containerWidget, itemView); + + mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onCloseButtonClicked); + mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ContainerWindow::onTakeAllButtonClicked); + + // adjust buttons size to fit text + int closeButtonWidth = mCloseButton->getTextSize().width+24; + int takeButtonWidth = mTakeButton->getTextSize().width+24; + mCloseButton->setCoord(600-20-closeButtonWidth, mCloseButton->getCoord().top, closeButtonWidth, mCloseButton->getCoord().height); + mTakeButton->setCoord(600-20-closeButtonWidth-takeButtonWidth-8, mTakeButton->getCoord().top, takeButtonWidth, mTakeButton->getCoord().height); + + int w = MyGUI::RenderManager::getInstance().getViewSize().width; + //int h = MyGUI::RenderManager::getInstance().getViewSize().height; + + static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &ContainerWindow::onWindowResize); + + setCoord(w-600,0,600,300); +} + +ContainerWindow::~ContainerWindow() +{ +} + +void ContainerWindow::onWindowResize(MyGUI::Window* window) +{ + drawItems(); +} + +void ContainerWindow::open(MWWorld::Ptr container) +{ + openContainer(container); + setTitle(MWWorld::Class::get(container).getName(container)); + drawItems(); +} + +void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) +{ + if(mDragAndDrop == NULL || !mDragAndDrop->mIsOnDragAndDrop) + { + MWBase::Environment::get().getWindowManager()->popGuiMode(); + } +} + +void ContainerWindow::onTakeAllButtonClicked(MyGUI::Widget* _sender) +{ + if(mDragAndDrop == NULL || !mDragAndDrop->mIsOnDragAndDrop) + { + // transfer everything into the player's inventory + MWWorld::ContainerStore& containerStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::ContainerStore& playerStore = MWWorld::Class::get(player).getContainerStore(player); + + int i=0; + for (MWWorld::ContainerStoreIterator iter (containerStore.begin()); iter!=containerStore.end(); ++iter) + { + playerStore.add(*iter); + + if (i==0) + { + // play the sound of the first object + std::string sound = MWWorld::Class::get(*iter).getUpSoundId(*iter); + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); + } + + ++i; + } + + containerStore.clear(); + + MWBase::Environment::get().getWindowManager()->popGuiMode(); + } +} + +void ContainerWindow::onReferenceUnavailable() +{ + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); +} diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp new file mode 100644 index 0000000000..88e445a7bb --- /dev/null +++ b/apps/openmw/mwgui/container.hpp @@ -0,0 +1,147 @@ +#ifndef MGUI_CONTAINER_H +#define MGUI_CONTAINER_H + +#include + +#include "window_base.hpp" +#include "referenceinterface.hpp" + +#include "../mwclass/container.hpp" +#include "../mwworld/ptr.hpp" +#include "../mwworld/containerstore.hpp" + + +namespace MWWorld +{ + class Environment; +} + +namespace MyGUI +{ + class Gui; + class Widget; +} + +namespace MWGui +{ + class WindowManager; + class ContainerWindow; + class ContainerBase; +} + + +namespace MWGui +{ + class DragAndDrop + { + public: + bool mIsOnDragAndDrop; + MyGUI::Widget* mDraggedWidget; + MyGUI::Widget* mDragAndDropWidget; + ContainerBase* mDraggedFrom; + int mDraggedCount; + }; + + class ContainerBase : public ReferenceInterface + { + public: + ContainerBase(DragAndDrop* dragAndDrop); + virtual ~ContainerBase(); + + enum Filter + { + Filter_All = 0x01, + Filter_Weapon = 0x02, + Filter_Apparel = 0x03, + Filter_Magic = 0x04, + Filter_Misc = 0x05, + + Filter_Ingredients = 0x06 + }; + + enum ItemState + { + ItemState_Normal = 0x01, + ItemState_Equipped = 0x02, + ItemState_Barter = 0x03 + }; + + void setWidgets(MyGUI::Widget* containerWidget, MyGUI::ScrollView* itemView); ///< only call once + + void addBarteredItem(MWWorld::Ptr item, int count); + void addItem(MWWorld::Ptr item, int count); + + void transferBoughtItems(); ///< transfer bought items into the inventory + void returnBoughtItems(MWWorld::ContainerStore& store); ///< return bought items into the specified ContainerStore + + MWWorld::ContainerStore& getContainerStore(); + MWWorld::ContainerStore& getBoughtItems() { return mBoughtItems; } + + void openContainer(MWWorld::Ptr container); + void setFilter(Filter filter); ///< set category filter + void drawItems(); + + protected: + MyGUI::ScrollView* mItemView; + MyGUI::Widget* mContainerWidget; + + MyGUI::Widget* mSelectedItem; + + DragAndDrop* mDragAndDrop; + + Filter mFilter; + + // bought items are put in a separate ContainerStore so that they don't stack with other (not bought) items. + MWWorld::ContainerStore mBoughtItems; + + void onSelectedItem(MyGUI::Widget* _sender); + void onContainerClicked(MyGUI::Widget* _sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + + /// start dragging an item (drag & drop) + void startDragItem(MyGUI::Widget* _sender, int count); + + /// sell an item from this container + void sellItem(MyGUI::Widget* _sender, int count); + + /// sell an item from this container, that was previously just bought + void sellAlreadyBoughtItem(MyGUI::Widget* _sender, int count); + + std::string getCountString(const int count); + + virtual bool isTradeWindow() { return false; } + virtual bool isInventory() { return false; } + virtual std::vector getEquippedItems() { return std::vector(); } + virtual void _unequipItem(MWWorld::Ptr item) { ; } + + virtual bool isTrading() { return false; } + + virtual void onSelectedItemImpl(MWWorld::Ptr item) { ; } + + virtual bool ignoreEquippedItems() { return false; } + virtual std::vector itemsToIgnore() { return std::vector(); } + + virtual void notifyContentChanged() { ; } + }; + + class ContainerWindow : public ContainerBase, public WindowBase + { + public: + ContainerWindow(WindowManager& parWindowManager,DragAndDrop* dragAndDrop); + + virtual ~ContainerWindow(); + + void open(MWWorld::Ptr container); + + protected: + MyGUI::Button* mTakeButton; + MyGUI::Button* mCloseButton; + + void onWindowResize(MyGUI::Window* window); + void onCloseButtonClicked(MyGUI::Widget* _sender); + void onTakeAllButtonClicked(MyGUI::Widget* _sender); + + virtual void onReferenceUnavailable(); + }; +} +#endif // CONTAINER_H diff --git a/apps/openmw/mwgui/countdialog.cpp b/apps/openmw/mwgui/countdialog.cpp new file mode 100644 index 0000000000..e0a9bb9082 --- /dev/null +++ b/apps/openmw/mwgui/countdialog.cpp @@ -0,0 +1,108 @@ +#include "countdialog.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/world.hpp" + +namespace MWGui +{ + CountDialog::CountDialog(WindowManager& parWindowManager) : + WindowBase("openmw_count_window_layout.xml", parWindowManager) + { + getWidget(mSlider, "CountSlider"); + getWidget(mItemEdit, "ItemEdit"); + getWidget(mItemText, "ItemText"); + getWidget(mLabelText, "LabelText"); + getWidget(mOkButton, "OkButton"); + getWidget(mCancelButton, "CancelButton"); + + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CountDialog::onCancelButtonClicked); + mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &CountDialog::onOkButtonClicked); + mItemEdit->eventEditTextChange += MyGUI::newDelegate(this, &CountDialog::onEditTextChange); + mSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &CountDialog::onSliderMoved); + } + + void CountDialog::open(const std::string& item, const std::string& message, const int maxCount) + { + setVisible(true); + + mLabelText->setCaption(message); + + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + + mSlider->setScrollRange(maxCount); + mItemText->setCaption(item); + + int width = std::max(mItemText->getTextSize().width + 128, 320); + setCoord(viewSize.width/2 - width/2, + viewSize.height/2 - mMainWidget->getHeight()/2, + width, + mMainWidget->getHeight()); + + // make other gui elements inaccessible while this dialog is open + MyGUI::InputManager::getInstance().addWidgetModal(mMainWidget); + + MyGUI::InputManager::getInstance().setKeyFocusWidget(mItemEdit); + + mSlider->setScrollPosition(maxCount-1); + mItemEdit->setCaption(boost::lexical_cast(maxCount)); + + int okButtonWidth = mOkButton->getTextSize().width + 24; + mOkButton->setCoord(width - 30 - okButtonWidth, + mOkButton->getTop(), + okButtonWidth, + mOkButton->getHeight()); + + int cancelButtonWidth = mCancelButton->getTextSize().width + 24; + mCancelButton->setCoord(width - 30 - okButtonWidth - cancelButtonWidth - 8, + mCancelButton->getTop(), + cancelButtonWidth, + mCancelButton->getHeight()); + } + + void CountDialog::onCancelButtonClicked(MyGUI::Widget* _sender) + { + close(); + } + + void CountDialog::onOkButtonClicked(MyGUI::Widget* _sender) + { + eventOkClicked(NULL, mSlider->getScrollPosition()+1); + + close(); + } + + void CountDialog::onEditTextChange(MyGUI::EditBox* _sender) + { + if (_sender->getCaption() == "") + return; + + unsigned int count; + try + { + count = boost::lexical_cast(_sender->getCaption()); + } + catch (std::bad_cast&) + { + count = 1; + } + if (count > mSlider->getScrollRange()) + { + count = mSlider->getScrollRange(); + } + mSlider->setScrollPosition(count-1); + onSliderMoved(mSlider, count-1); + } + + void CountDialog::onSliderMoved(MyGUI::ScrollBar* _sender, size_t _position) + { + mItemEdit->setCaption(boost::lexical_cast(_position+1)); + } + + void CountDialog::close() + { + setVisible(false); + MyGUI::InputManager::getInstance().removeWidgetModal(mMainWidget); + } +} diff --git a/apps/openmw/mwgui/countdialog.hpp b/apps/openmw/mwgui/countdialog.hpp new file mode 100644 index 0000000000..aac17b846d --- /dev/null +++ b/apps/openmw/mwgui/countdialog.hpp @@ -0,0 +1,39 @@ +#ifndef MWGUI_COUNTDIALOG_H +#define MWGUI_COUNTDIALOG_H + +#include "window_base.hpp" + +namespace MWGui +{ + class CountDialog : public WindowBase + { + public: + CountDialog(WindowManager& parWindowManager); + void open(const std::string& item, const std::string& message, const int maxCount); + + typedef MyGUI::delegates::CMultiDelegate2 EventHandle_WidgetInt; + + /** Event : Ok button was clicked.\n + signature : void method(MyGUI::Widget* _sender, int _count)\n + */ + EventHandle_WidgetInt eventOkClicked; + + private: + MyGUI::ScrollBar* mSlider; + MyGUI::EditBox* mItemEdit; + MyGUI::TextBox* mItemText; + MyGUI::TextBox* mLabelText; + MyGUI::Button* mOkButton; + MyGUI::Button* mCancelButton; + + void onCancelButtonClicked(MyGUI::Widget* _sender); + void onOkButtonClicked(MyGUI::Widget* _sender); + void onEditTextChange(MyGUI::EditBox* _sender); + void onSliderMoved(MyGUI::ScrollBar* _sender, size_t _position); + + void close(); + }; + +} + +#endif diff --git a/apps/openmw/mwgui/cursorreplace.cpp b/apps/openmw/mwgui/cursorreplace.cpp index 2079538fc2..e66f543266 100644 --- a/apps/openmw/mwgui/cursorreplace.cpp +++ b/apps/openmw/mwgui/cursorreplace.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -13,4 +14,6 @@ CursorReplace::CursorReplace() OEngine::Render::ImageRotate::rotate("textures\\tx_cursormove.dds", "mwpointer_vresize.png", 90); OEngine::Render::ImageRotate::rotate("textures\\tx_cursormove.dds", "mwpointer_dresize1.png", -45); OEngine::Render::ImageRotate::rotate("textures\\tx_cursormove.dds", "mwpointer_dresize2.png", 45); + + OEngine::Render::Atlas::createFromFile("atlas1.cfg", "mwgui1", "textures\\"); } diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 2386cf9a32..79b2d68bab 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -1,10 +1,4 @@ #include "dialogue.hpp" -#include "dialogue_history.hpp" -#include "window_manager.hpp" -#include "widgets.hpp" -#include "components/esm_store/store.hpp" -#include "../mwbase/environment.hpp" -#include "../mwdialogue/dialoguemanager.hpp" #include #include @@ -13,6 +7,18 @@ #include #include +#include + +#include "../mwbase/environment.hpp" +#include "../mwdialogue/dialoguemanager.hpp" + +#include "dialogue_history.hpp" +#include "window_manager.hpp" +#include "widgets.hpp" +#include "list.hpp" +#include "tradewindow.hpp" +#include "inventorywindow.hpp" + using namespace MWGui; using namespace Widgets; @@ -38,6 +44,8 @@ std::string::size_type find_str_ci(const std::string& str, const std::string& su DialogueWindow::DialogueWindow(WindowManager& parWindowManager) : WindowBase("openmw_dialogue_window_layout.xml", parWindowManager) + , mEnabled(true) + , mShowTrade(false) { // Centre dialog center(); @@ -50,16 +58,13 @@ DialogueWindow::DialogueWindow(WindowManager& parWindowManager) //An EditBox cannot receive mouse click events, so we use an //invisible widget on top of the editbox to receive them - /// \todo scrolling the dialogue history with the mouse wheel doesn't work using this solution getWidget(eventbox, "EventBox"); eventbox->eventMouseButtonClick += MyGUI::newDelegate(this, &DialogueWindow::onHistoryClicked); + eventbox->eventMouseWheel += MyGUI::newDelegate(this, &DialogueWindow::onMouseWheel); //Topics list getWidget(topicsList, "TopicsList"); - topicsList->setScrollVisible(true); - //topicsList->eventListSelectAccept += MyGUI::newDelegate(this, &DialogueWindow::onSelectTopic); - topicsList->eventListMouseItemActivate += MyGUI::newDelegate(this, &DialogueWindow::onSelectTopic); - //topicsList->eventListChangePosition += MyGUI::newDelegate(this, &DialogueWindow::onSelectTopic); + topicsList->eventItemSelected += MyGUI::newDelegate(this, &DialogueWindow::onSelectTopic); MyGUI::ButtonPtr byeButton; getWidget(byeButton, "ByeButton"); @@ -67,6 +72,8 @@ DialogueWindow::DialogueWindow(WindowManager& parWindowManager) getWidget(pDispositionBar, "Disposition"); getWidget(pDispositionText,"DispositionText"); + + static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &DialogueWindow::onWindowResize); } void DialogueWindow::onHistoryClicked(MyGUI::Widget* _sender) @@ -79,22 +86,30 @@ void DialogueWindow::onHistoryClicked(MyGUI::Widget* _sender) size_t cursorPosition = t->getCursorPosition(lastPressed); MyGUI::UString color = history->getColorAtPos(cursorPosition); + + if (!mEnabled && color == "#572D21") + MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); + if(color != "#B29154") { UString key = history->getColorTextAt(cursorPosition); if(color == "#686EBA") MWBase::Environment::get().getDialogueManager()->keywordSelected(lower_string(key)); - if(color == "#572D21") MWBase::Environment::get().getDialogueManager()->questionAnswered(key); + if(color == "#572D21") MWBase::Environment::get().getDialogueManager()->questionAnswered(lower_string(key)); } } -void DialogueWindow::open() +void DialogueWindow::onWindowResize(MyGUI::Window* _sender) { - topicsList->removeAllItems(); - pTopicsText.clear(); - history->eraseText(0,history->getTextLength()); - updateOptions(); - setVisible(true); + topicsList->adjustSize(); +} + +void DialogueWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) +{ + if (history->getVScrollPosition() - _rel*0.3 < 0) + history->setVScrollPosition(0); + else + history->setVScrollPosition(history->getVScrollPosition() - _rel*0.3); } void DialogueWindow::onByeClicked(MyGUI::Widget* _sender) @@ -102,36 +117,59 @@ void DialogueWindow::onByeClicked(MyGUI::Widget* _sender) MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); } -void DialogueWindow::onSelectTopic(MyGUI::ListBox* _sender, size_t _index) +void DialogueWindow::onSelectTopic(std::string topic) { - if (_index == MyGUI::ITEM_NONE) - return; - std::string topic = _sender->getItemNameAt(_index); - MWBase::Environment::get().getDialogueManager()->keywordSelected(lower_string(topic)); + if (!mEnabled) return; + + if (topic == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sBarter")->str) + { + /// \todo check if the player is allowed to trade with this actor (e.g. faction rank high enough)? + mWindowManager.pushGuiMode(GM_Barter); + mWindowManager.getTradeWindow()->startTrade(mPtr); + } + + else + MWBase::Environment::get().getDialogueManager()->keywordSelected(lower_string(topic)); } -void DialogueWindow::startDialogue(std::string npcName) +void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName) { - static_cast(mMainWidget)->setCaption(npcName); - adjustWindowCaption(); + mEnabled = true; + mPtr = actor; + topicsList->setEnabled(true); + setTitle(npcName); + + topicsList->clear(); + history->eraseText(0,history->getTextLength()); + updateOptions(); } void DialogueWindow::setKeywords(std::list keyWords) { - topicsList->removeAllItems(); - for(std::list::iterator it = keyWords.begin(); it != keyWords.end(); it++) + topicsList->clear(); + + bool anyService = mShowTrade; + + if (mShowTrade) + topicsList->addItem(MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sBarter")->str); + + if (anyService) + topicsList->addSeparator(); + + for(std::list::iterator it = keyWords.begin(); it != keyWords.end(); ++it) { topicsList->addItem(*it); } + topicsList->adjustSize(); } void DialogueWindow::removeKeyword(std::string keyWord) { - if(topicsList->findItemIndexWith(keyWord) != MyGUI::ITEM_NONE) + if(topicsList->hasItem(keyWord)) { - topicsList->removeItemAt(topicsList->findItemIndexWith(keyWord)); - pTopicsText.erase(keyWord); + topicsList->removeItem(keyWord); } + topicsList->adjustSize(); } void addColorInString(std::string& str, const std::string& keyword,std::string color1, std::string color2) @@ -170,7 +208,8 @@ std::string DialogueWindow::parseText(std::string text) for(unsigned int i = 0;igetItemCount();i++) { std::string keyWord = topicsList->getItemNameAt(i); - addColorInString(text,keyWord,"#686EBA","#B29154"); + if (keyWord != "") + addColorInString(text,keyWord,"#686EBA","#B29154"); } return text; } @@ -203,8 +242,7 @@ void DialogueWindow::askQuestion(std::string question) void DialogueWindow::updateOptions() { //Clear the list of topics - topicsList->removeAllItems(); - pTopicsText.clear(); + topicsList->clear(); history->eraseText(0,history->getTextLength()); pDispositionBar->setProgressRange(100); @@ -212,3 +250,15 @@ void DialogueWindow::updateOptions() pDispositionText->eraseText(0,pDispositionText->getTextLength()); pDispositionText->addText("#B29154"+std::string("40/100")+"#B29154"); } + +void DialogueWindow::goodbye() +{ + history->addDialogText("\n#572D21" + MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGoodbye")->str); + topicsList->setEnabled(false); + mEnabled = false; +} + +void DialogueWindow::onReferenceUnavailable() +{ + mWindowManager.removeGuiMode(GM_Dialogue); +} diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 61e8c124c5..aa66cd5798 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -2,11 +2,19 @@ #define MWGUI_DIALOGE_H #include "window_base.hpp" +#include "referenceinterface.hpp" #include +#include "../mwworld/ptr.hpp" + namespace MWGui { class WindowManager; + + namespace Widgets + { + class MWList; + } } /* @@ -18,13 +26,11 @@ namespace MWGui { class DialogueHistory; - class DialogueWindow: public WindowBase + class DialogueWindow: public WindowBase, public ReferenceInterface { public: DialogueWindow(WindowManager& parWindowManager); - void open(); - // Events typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; @@ -33,18 +39,27 @@ namespace MWGui */ EventHandle_Void eventBye; - void startDialogue(std::string npcName); + void startDialogue(MWWorld::Ptr actor, std::string npcName); void stopDialogue(); void setKeywords(std::list keyWord); void removeKeyword(std::string keyWord); void addText(std::string text); void addTitle(std::string text); void askQuestion(std::string question); + void goodbye(); + + // various service button visibilities, depending if the npc/creature talked to has these services + // make sure to call these before setKeywords() + void setShowTrade(bool show) { mShowTrade = show; } protected: - void onSelectTopic(MyGUI::ListBox* _sender, size_t _index); + void onSelectTopic(std::string topic); void onByeClicked(MyGUI::Widget* _sender); void onHistoryClicked(MyGUI::Widget* _sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + void onWindowResize(MyGUI::Window* _sender); + + virtual void onReferenceUnavailable(); private: void updateOptions(); @@ -53,11 +68,15 @@ namespace MWGui */ std::string parseText(std::string text); + // various service button visibilities, depending if the npc/creature talked to has these services + bool mShowTrade; + + bool mEnabled; + DialogueHistory* history; - MyGUI::ListBox* topicsList; + Widgets::MWList* topicsList; MyGUI::ProgressPtr pDispositionBar; MyGUI::EditPtr pDispositionText; - std::map pTopicsText;// this map links keyword and "real" text. }; } #endif diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp new file mode 100644 index 0000000000..53c23c25d5 --- /dev/null +++ b/apps/openmw/mwgui/formatting.cpp @@ -0,0 +1,331 @@ +#include "formatting.hpp" + +#include +#include + +using namespace MWGui; + +namespace +{ + int convertFromHex(std::string hex) + { + int value = 0; + + int a = 0; + int b = hex.length() - 1; + for (; b >= 0; a++, b--) + { + if (hex[b] >= '0' && hex[b] <= '9') + { + value += (hex[b] - '0') * (1 << (a * 4)); + } + else + { + switch (hex[b]) + { + case 'A': + case 'a': + value += 10 * (1 << (a * 4)); + break; + + case 'B': + case 'b': + value += 11 * (1 << (a * 4)); + break; + + case 'C': + case 'c': + value += 12 * (1 << (a * 4)); + break; + + case 'D': + case 'd': + value += 13 * (1 << (a * 4)); + break; + + case 'E': + case 'e': + value += 14 * (1 << (a * 4)); + break; + + case 'F': + case 'f': + value += 15 * (1 << (a * 4)); + break; + + default: + throw std::runtime_error("invalid character in hex number"); + break; + } + } + } + + return value; + } +} + +std::vector BookTextParser::split(std::string text, const int width, const int height) +{ + std::vector result; + + boost::algorithm::replace_all(text, "
", "\n"); + boost::algorithm::replace_all(text, "

", "\n\n"); + + const int spacing = 48; + + while (text.size() > 0) + { + // read in characters until we have exceeded the size, or run out of text + int currentWidth = 0; + int currentHeight = 0; + std::string currentText; + std::string currentWord; + + unsigned int i=0; + while (currentHeight <= height-spacing && i', i) == std::string::npos) + throw std::runtime_error("BookTextParser Error: Tag is not terminated"); + + if (text.size() > i+4 && text.substr(i, 4) == "', i)-i), false); + currentHeight += (mHeight-h); + currentWidth = 0; + } + else if (text.size() > i+5 && text.substr(i, 5) == "', i)-i)); + currentHeight += 18; // keep this in sync with the font size + currentWidth = 0; + } + else if (text.size() > i+4 && text.substr(i, 4) == "', i)-i)); + currentHeight += 18; // keep this in sync with the font size + currentWidth = 0; + } + + currentText += text.substr(i, text.find('>', i)-i+1); + i = text.find('>', i); + } + else if (text[i] == '\n') + { + currentHeight += 18; // keep this in sync with the font size + currentWidth = 0; + currentWord = ""; + currentText += text[i]; + } + else if (text[i] == ' ') + { + currentWidth += 3; // keep this in sync with the font's SpaceWidth property + currentWord = ""; + currentText += text[i]; + } + else + { + currentWidth += + MyGUI::FontManager::getInstance().getByName (mTextStyle.mFont == "Default" ? "EB Garamond" : mTextStyle.mFont) + ->getGlyphInfo(static_cast(text[i]))->width; + currentWord += text[i]; + currentText += text[i]; + } + + if (currentWidth > width) + { + currentHeight += 18; // keep this in sync with the font size + currentWidth = 0; + + // add size of the current word + unsigned int j=0; + while (jgetGlyphInfo(static_cast(currentWord[j]))->width; + ++j; + } + } + + ++i; + } + if (currentHeight > height-spacing) + { + // remove the last word + currentText.erase(currentText.size()-currentWord.size(), currentText.size()); + } + + result.push_back(currentText); + text.erase(0, currentText.size()); + } + + return result; +} + +MyGUI::IntSize BookTextParser::parse(std::string text, MyGUI::Widget* parent, const int width) +{ + mParent = parent; + mWidth = width; + mHeight = 0; + + assert(mParent); + while (mParent->getChildCount()) + { + MyGUI::Gui::getInstance().destroyWidget(mParent->getChildAt(0)); + } + + boost::algorithm::replace_all(text, "
", "\n"); + boost::algorithm::replace_all(text, "

", "\n\n"); + + // remove leading newlines + //while (text[0] == '\n') + // text.erase(0); + + // remove trailing " + if (text[text.size()-1] == '\"') + text.erase(text.size()-1); + + parseSubText(text); + return MyGUI::IntSize(mWidth, mHeight); +} + +void BookTextParser::parseImage(std::string tag, bool createWidget) +{ + int src_start = tag.find("SRC=")+5; + std::string image = tag.substr(src_start, tag.find('"', src_start)-src_start); + + // fix texture extension to .dds + if (image.size() > 4) + { + image[image.size()-3] = 'd'; + image[image.size()-2] = 'd'; + image[image.size()-1] = 's'; + } + + int width_start = tag.find("WIDTH=")+7; + int width = boost::lexical_cast(tag.substr(width_start, tag.find('"', width_start)-width_start)); + + int height_start = tag.find("HEIGHT=")+8; + int height = boost::lexical_cast(tag.substr(height_start, tag.find('"', height_start)-height_start)); + + if (createWidget) + { + MyGUI::ImageBox* box = mParent->createWidget ("ImageBox", + MyGUI::IntCoord(0, mHeight, width, height), MyGUI::Align::Left | MyGUI::Align::Top, + mParent->getName() + boost::lexical_cast(mParent->getChildCount())); + box->setImageTexture("bookart\\" + image); + box->setProperty("NeedMouse", "false"); + } + + mWidth = std::max(mWidth, width); + mHeight += height; +} + +void BookTextParser::parseDiv(std::string tag) +{ + if (tag.find("ALIGN=") == std::string::npos) + return; + + int align_start = tag.find("ALIGN=")+7; + std::string align = tag.substr(align_start, tag.find('"', align_start)-align_start); + if (align == "CENTER") + mTextStyle.mTextAlign = MyGUI::Align::HCenter; + else if (align == "LEFT") + mTextStyle.mTextAlign = MyGUI::Align::Left; +} + +void BookTextParser::parseFont(std::string tag) +{ + if (tag.find("COLOR=") != std::string::npos) + { + int color_start = tag.find("COLOR=")+7; + std::string color = tag.substr(color_start, tag.find('"', color_start)-color_start); + + mTextStyle.mColour = MyGUI::Colour( + convertFromHex(color.substr(0, 2))/255.0, + convertFromHex(color.substr(2, 2))/255.0, + convertFromHex(color.substr(4, 2))/255.0); + } + if (tag.find("FACE=") != std::string::npos) + { + int face_start = tag.find("FACE=")+6; + std::string face = tag.substr(face_start, tag.find('"', face_start)-face_start); + + if (face != "Magic Cards") + mTextStyle.mFont = face; + } + if (tag.find("SIZE=") != std::string::npos) + { + /// \todo + } +} + +void BookTextParser::parseSubText(std::string text) +{ + if (text[0] == '<') + { + if (text.find('>') == std::string::npos) + throw std::runtime_error("BookTextParser Error: Tag is not terminated"); + + if (text.size() > 4 && text.substr(0, 4) == "'))); + else if (text.size() > 5 && text.substr(0, 5) == "'))); + else if (text.size() > 4 && text.substr(0, 4) == "'))); + + text.erase(0, text.find('>')+1); + } + + bool tagFound = false; + std::string realText; // real text, without tags + unsigned int i=0; + for (; i= text.size()) + throw std::runtime_error("BookTextParser Error: Tag is not terminated"); + ++i; + c = text[i]; + } + continue; + } + else + { + tagFound = true; + break; + } + } + else + realText += c; + } + + MyGUI::EditBox* box = mParent->createWidget("NormalText", + MyGUI::IntCoord(0, mHeight, mWidth, 24), MyGUI::Align::Left | MyGUI::Align::Top, + mParent->getName() + boost::lexical_cast(mParent->getChildCount())); + box->setProperty("Static", "true"); + box->setProperty("MultiLine", "true"); + box->setProperty("WordWrap", "true"); + box->setProperty("NeedMouse", "false"); + box->setMaxTextLength(realText.size()); + box->setTextAlign(mTextStyle.mTextAlign); + box->setTextColour(mTextStyle.mColour); + box->setFontName(mTextStyle.mFont); + box->setCaption(realText); + box->setSize(box->getSize().width, box->getTextSize().height); + mHeight += box->getTextSize().height; + + if (tagFound) + { + parseSubText(text.substr(i, text.size())); + } +} diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp new file mode 100644 index 0000000000..a1e115491d --- /dev/null +++ b/apps/openmw/mwgui/formatting.hpp @@ -0,0 +1,56 @@ +#ifndef MWGUI_FORMATTING_H +#define MWGUI_FORMATTING_H + +#include + +namespace MWGui +{ + struct TextStyle + { + TextStyle() : + mColour(0,0,0) + , mFont("Default") + , mTextSize(16) + , mTextAlign(MyGUI::Align::Left | MyGUI::Align::Top) + { + } + + MyGUI::Colour mColour; + std::string mFont; + int mTextSize; + MyGUI::Align mTextAlign; + }; + + /// \brief utilities for parsing book/scroll text as mygui widgets + class BookTextParser + { + public: + /** + * Parse markup as MyGUI widgets + * @param markup to parse + * @param parent for the created widgets + * @param maximum width + * @return size of the created widgets + */ + MyGUI::IntSize parse(std::string text, MyGUI::Widget* parent, const int width); + + /** + * Split the specified text into pieces that fit in the area specified by width and height parameters + */ + std::vector split(std::string text, const int width, const int height); + + protected: + void parseSubText(std::string text); + + void parseImage(std::string tag, bool createWidget=true); + void parseDiv(std::string tag); + void parseFont(std::string tag); + private: + MyGUI::Widget* mParent; + int mWidth; // maximum width + int mHeight; // current height + TextStyle mTextStyle; + }; +} + +#endif diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp new file mode 100644 index 0000000000..95d66eb81d --- /dev/null +++ b/apps/openmw/mwgui/hud.cpp @@ -0,0 +1,516 @@ +#include "hud.hpp" + +#include + +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwsound/soundmanager.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/world.hpp" +#include "../mwworld/player.hpp" + +#include "inventorywindow.hpp" +#include "window_manager.hpp" +#include "container.hpp" +#include "console.hpp" + +using namespace MWGui; + +HUD::HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop) + : Layout("openmw_hud_layout.xml") + , health(NULL) + , magicka(NULL) + , stamina(NULL) + , weapImage(NULL) + , spellImage(NULL) + , weapStatus(NULL) + , spellStatus(NULL) + , effectBox(NULL) + , effect1(NULL) + , minimap(NULL) + , compass(NULL) + , crosshair(NULL) + , fpsbox(NULL) + , fpscounter(NULL) + , trianglecounter(NULL) + , batchcounter(NULL) + , hmsBaseLeft(0) + , weapBoxBaseLeft(0) + , spellBoxBaseLeft(0) + , effectBoxBaseRight(0) + , minimapBoxBaseRight(0) + , mDragAndDrop(dragAndDrop) + , mCellNameTimer(0.0f) + , mCellNameBox(NULL) + , mMapVisible(true) + , mWeaponVisible(true) + , mSpellVisible(true) + , mWorldMouseOver(false) +{ + setCoord(0,0, width, height); + + // Energy bars + getWidget(mHealthFrame, "HealthFrame"); + getWidget(health, "Health"); + getWidget(magicka, "Magicka"); + getWidget(stamina, "Stamina"); + + hmsBaseLeft = mHealthFrame->getLeft(); + + MyGUI::Widget *healthFrame, *magickaFrame, *fatigueFrame; + getWidget(healthFrame, "HealthFrame"); + getWidget(magickaFrame, "MagickaFrame"); + getWidget(fatigueFrame, "FatigueFrame"); + healthFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked); + magickaFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked); + fatigueFrame->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onHMSClicked); + + const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + + // Item and spell images and status bars + getWidget(weapBox, "WeapBox"); + getWidget(weapImage, "WeapImage"); + getWidget(weapStatus, "WeapStatus"); + weapBoxBaseLeft = weapBox->getLeft(); + weapBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWeaponClicked); + + getWidget(spellBox, "SpellBox"); + getWidget(spellImage, "SpellImage"); + getWidget(spellStatus, "SpellStatus"); + spellBoxBaseLeft = spellBox->getLeft(); + spellBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMagicClicked); + + getWidget(effectBox, "EffectBox"); + getWidget(effect1, "Effect1"); + effectBoxBaseRight = viewSize.width - effectBox->getRight(); + effectBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMagicClicked); + + getWidget(minimapBox, "MiniMapBox"); + minimapBoxBaseRight = viewSize.width - minimapBox->getRight(); + minimapBox->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMapClicked); + getWidget(minimap, "MiniMap"); + getWidget(compass, "Compass"); + + getWidget(mCellNameBox, "CellName"); + getWidget(mWeaponSpellBox, "WeaponSpellName"); + + getWidget(crosshair, "Crosshair"); + + setFpsLevel(fpsLevel); + + getWidget(trianglecounter, "TriangleCounter"); + getWidget(batchcounter, "BatchCounter"); + + setEffect("icons\\s\\tx_s_chameleon.dds"); + + LocalMapBase::init(minimap, compass, this); + + mMainWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWorldClicked); + mMainWidget->eventMouseMove += MyGUI::newDelegate(this, &HUD::onWorldMouseOver); + mMainWidget->eventMouseLostFocus += MyGUI::newDelegate(this, &HUD::onWorldMouseLostFocus); +} + +void HUD::setFpsLevel(int level) +{ + fpscounter = 0; + + MyGUI::Widget* fps; + getWidget(fps, "FPSBoxAdv"); + fps->setVisible(false); + getWidget(fps, "FPSBox"); + fps->setVisible(false); + + if (level == 2) + { + getWidget(fpsbox, "FPSBoxAdv"); + fpsbox->setVisible(true); + getWidget(fpscounter, "FPSCounterAdv"); + } + else if (level == 1) + { + getWidget(fpsbox, "FPSBox"); + fpsbox->setVisible(true); + getWidget(fpscounter, "FPSCounter"); + } +} + +void HUD::setFPS(float fps) +{ + if (fpscounter) + fpscounter->setCaption(boost::lexical_cast((int)fps)); +} + +void HUD::setTriangleCount(size_t count) +{ + trianglecounter->setCaption(boost::lexical_cast(count)); +} + +void HUD::setBatchCount(size_t count) +{ + batchcounter->setCaption(boost::lexical_cast(count)); +} + +void HUD::setEffect(const char *img) +{ + effect1->setImageTexture(img); +} + +void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& value) +{ + static const char *ids[] = + { + "HBar", "MBar", "FBar", 0 + }; + + for (int i=0; ids[i]; ++i) + if (ids[i]==id) + { + MyGUI::Widget* w; + std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); + switch (i) + { + case 0: + health->setProgressRange (value.getModified()); + health->setProgressPosition (value.getCurrent()); + getWidget(w, "HealthFrame"); + w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); + break; + case 1: + magicka->setProgressRange (value.getModified()); + magicka->setProgressPosition (value.getCurrent()); + getWidget(w, "MagickaFrame"); + w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + break; + case 2: + stamina->setProgressRange (value.getModified()); + stamina->setProgressPosition (value.getCurrent()); + getWidget(w, "FatigueFrame"); + w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); + break; + } + } +} + +void HUD::setBottomLeftVisibility(bool hmsVisible, bool weapVisible, bool spellVisible) +{ + int weapDx = 0, spellDx = 0; + if (!hmsVisible) + spellDx = weapDx = weapBoxBaseLeft - hmsBaseLeft; + + if (!weapVisible) + spellDx += spellBoxBaseLeft - weapBoxBaseLeft; + + mWeaponVisible = weapVisible; + mSpellVisible = spellVisible; + if (!mWeaponVisible && !mSpellVisible) + mWeaponSpellBox->setVisible(false); + + health->setVisible(hmsVisible); + stamina->setVisible(hmsVisible); + magicka->setVisible(hmsVisible); + weapBox->setPosition(weapBoxBaseLeft - weapDx, weapBox->getTop()); + weapBox->setVisible(weapVisible); + spellBox->setPosition(spellBoxBaseLeft - spellDx, spellBox->getTop()); + spellBox->setVisible(spellVisible); +} + +void HUD::setBottomRightVisibility(bool effectBoxVisible, bool minimapBoxVisible) +{ + const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + + // effect box can have variable width -> variable left coordinate + int effectsDx = 0; + if (!minimapBoxVisible) + effectsDx = (viewSize.width - minimapBoxBaseRight) - (viewSize.width - effectBoxBaseRight); + + mMapVisible = minimapBoxVisible; + minimapBox->setVisible(minimapBoxVisible); + effectBox->setPosition((viewSize.width - effectBoxBaseRight) - effectBox->getWidth() + effectsDx, effectBox->getTop()); + effectBox->setVisible(effectBoxVisible); +} + +void HUD::onWorldClicked(MyGUI::Widget* _sender) +{ + if (mDragAndDrop->mIsOnDragAndDrop) + { + // drop item into the gameworld + MWWorld::Ptr object = *mDragAndDrop->mDraggedWidget->getUserData(); + + MWWorld::World* world = MWBase::Environment::get().getWorld(); + + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::IntPoint cursorPosition = MyGUI::InputManager::getInstance().getMousePosition(); + float mouseX = cursorPosition.left / float(viewSize.width); + float mouseY = cursorPosition.top / float(viewSize.height); + + int origCount = object.getRefData().getCount(); + object.getRefData().setCount(mDragAndDrop->mDraggedCount); + + if (world->canPlaceObject(mouseX, mouseY)) + world->placeObject(object, mouseX, mouseY); + else + world->dropObjectOnGround(object); + + MyGUI::PointerManager::getInstance().setPointer("arrow"); + + std::string sound = MWWorld::Class::get(object).getDownSoundId(object); + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); + + // remove object from the container it was coming from + object.getRefData().setCount(origCount - mDragAndDrop->mDraggedCount); + + mDragAndDrop->mIsOnDragAndDrop = false; + MyGUI::Gui::getInstance().destroyWidget(mDragAndDrop->mDraggedWidget); + mDragAndDrop->mDraggedWidget = 0; + + MWBase::Environment::get().getWindowManager()->setDragDrop(false); + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->drawItems(); + } + else + { + GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode(); + + if ( (mode != GM_Console) && (mode != GM_Container) && (mode != GM_Inventory) ) + return; + + std::string handle = MWBase::Environment::get().getWorld()->getFacedHandle(); + MWWorld::Ptr object; + try + { + object = MWBase::Environment::get().getWorld()->getPtrViaHandle(handle); + } + catch (std::exception& e) + { + return; + } + + if (mode == GM_Console) + MWBase::Environment::get().getWindowManager()->getConsole()->setSelectedObject(object); + else if ((mode == GM_Container) || (mode == GM_Inventory)) + { + // pick up object + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->pickUpObject(object); + } + } +} + +void HUD::onWorldMouseOver(MyGUI::Widget* _sender, int x, int y) +{ + if (mDragAndDrop->mIsOnDragAndDrop) + { + mWorldMouseOver = false; + + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::IntPoint cursorPosition = MyGUI::InputManager::getInstance().getMousePosition(); + float mouseX = cursorPosition.left / float(viewSize.width); + float mouseY = cursorPosition.top / float(viewSize.height); + + MWWorld::World* world = MWBase::Environment::get().getWorld(); + + // if we can't drop the object at the wanted position, show the "drop on ground" cursor. + bool canDrop = world->canPlaceObject(mouseX, mouseY); + + if (!canDrop) + MyGUI::PointerManager::getInstance().setPointer("drop_ground"); + else + MyGUI::PointerManager::getInstance().setPointer("arrow"); + + } + else + { + MyGUI::PointerManager::getInstance().setPointer("arrow"); + mWorldMouseOver = true; + } +} + +void HUD::onWorldMouseLostFocus(MyGUI::Widget* _sender, MyGUI::Widget* _new) +{ + MyGUI::PointerManager::getInstance().setPointer("arrow"); + mWorldMouseOver = false; +} + +void HUD::onHMSClicked(MyGUI::Widget* _sender) +{ + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Stats); +} + +void HUD::onMapClicked(MyGUI::Widget* _sender) +{ + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Map); +} + +void HUD::onWeaponClicked(MyGUI::Widget* _sender) +{ + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Inventory); +} + +void HUD::onMagicClicked(MyGUI::Widget* _sender) +{ + MWBase::Environment::get().getWindowManager()->toggleVisible(GW_Magic); +} + +void HUD::setCellName(const std::string& cellName) +{ + if (mCellName != cellName) + { + mCellNameTimer = 5.0f; + mCellName = cellName; + + mCellNameBox->setCaption(mCellName); + mCellNameBox->setVisible(mMapVisible); + } +} + +void HUD::onFrame(float dt) +{ + mCellNameTimer -= dt; + mWeaponSpellTimer -= dt; + if (mCellNameTimer < 0) + mCellNameBox->setVisible(false); + if (mWeaponSpellTimer < 0) + mWeaponSpellBox->setVisible(false); +} + +void HUD::onResChange(int width, int height) +{ + setCoord(0, 0, width, height); +} + +void HUD::setSelectedSpell(const std::string& spellId, int successChancePercent) +{ + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(spellId); + std::string spellName = spell->name; + if (spellName != mSpellName && mSpellVisible) + { + mWeaponSpellTimer = 5.0f; + mSpellName = spellName; + mWeaponSpellBox->setCaption(mSpellName); + mWeaponSpellBox->setVisible(true); + } + + spellStatus->setProgressRange(100); + spellStatus->setProgressPosition(successChancePercent); + + if (spellImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(spellImage->getChildAt(0)); + + spellBox->setUserString("ToolTipType", "Spell"); + spellBox->setUserString("Spell", spellId); + + // use the icon of the first effect + const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().magicEffects.find(spell->effects.list.front().effectID); + std::string icon = effect->icon; + int slashPos = icon.find("\\"); + icon.insert(slashPos+1, "b_"); + icon = std::string("icons\\") + icon; + Widgets::fixTexturePath(icon); + spellImage->setImageTexture(icon); +} + +void HUD::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent) +{ + std::string itemName = MWWorld::Class::get(item).getName(item); + if (itemName != mSpellName && mSpellVisible) + { + mWeaponSpellTimer = 5.0f; + mSpellName = itemName; + mWeaponSpellBox->setCaption(mSpellName); + mWeaponSpellBox->setVisible(true); + } + + spellStatus->setProgressRange(100); + spellStatus->setProgressPosition(chargePercent); + + if (spellImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(spellImage->getChildAt(0)); + + spellBox->setUserString("ToolTipType", "ItemPtr"); + spellBox->setUserData(item); + + spellImage->setImageTexture("textures\\menu_icon_magic_mini.dds"); + MyGUI::ImageBox* itemBox = spellImage->createWidgetReal("ImageBox", MyGUI::FloatCoord(0,0,1,1) + , MyGUI::Align::Stretch); + + std::string path = std::string("icons\\"); + path+=MWWorld::Class::get(item).getInventoryIcon(item); + Widgets::fixTexturePath(path); + itemBox->setImageTexture(path); + itemBox->setNeedMouseFocus(false); +} + +void HUD::setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent) +{ + std::string itemName = MWWorld::Class::get(item).getName(item); + if (itemName != mWeaponName && mWeaponVisible) + { + mWeaponSpellTimer = 5.0f; + mWeaponName = itemName; + mWeaponSpellBox->setCaption(mWeaponName); + mWeaponSpellBox->setVisible(true); + } + + weapBox->setUserString("ToolTipType", "ItemPtr"); + weapBox->setUserData(item); + + weapStatus->setProgressRange(100); + weapStatus->setProgressPosition(durabilityPercent); + + if (weapImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(weapImage->getChildAt(0)); + + std::string path = std::string("icons\\"); + path+=MWWorld::Class::get(item).getInventoryIcon(item); + Widgets::fixTexturePath(path); + + if (MWWorld::Class::get(item).getEnchantment(item) != "") + { + weapImage->setImageTexture("textures\\menu_icon_magic_mini.dds"); + MyGUI::ImageBox* itemBox = weapImage->createWidgetReal("ImageBox", MyGUI::FloatCoord(0,0,1,1) + , MyGUI::Align::Stretch); + itemBox->setImageTexture(path); + itemBox->setNeedMouseFocus(false); + } + else + weapImage->setImageTexture(path); +} + +void HUD::unsetSelectedSpell() +{ + std::string spellName = "#{sNone}"; + if (spellName != mSpellName && mSpellVisible) + { + mWeaponSpellTimer = 5.0f; + mSpellName = spellName; + mWeaponSpellBox->setCaptionWithReplacing(mSpellName); + mWeaponSpellBox->setVisible(true); + } + + if (spellImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(spellImage->getChildAt(0)); + spellStatus->setProgressRange(100); + spellStatus->setProgressPosition(0); + spellImage->setImageTexture(""); + spellBox->clearUserStrings(); +} + +void HUD::unsetSelectedWeapon() +{ + std::string itemName = "#{sSkillHandtohand}"; + if (itemName != mWeaponName && mWeaponVisible) + { + mWeaponSpellTimer = 5.0f; + mWeaponName = itemName; + mWeaponSpellBox->setCaptionWithReplacing(mWeaponName); + mWeaponSpellBox->setVisible(true); + } + + if (weapImage->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(weapImage->getChildAt(0)); + weapStatus->setProgressRange(100); + weapStatus->setProgressPosition(0); + weapImage->setImageTexture("icons\\k\\stealth_handtohand.dds"); + weapBox->clearUserStrings(); +} diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp new file mode 100644 index 0000000000..0bfe5c20f2 --- /dev/null +++ b/apps/openmw/mwgui/hud.hpp @@ -0,0 +1,85 @@ +#include "map_window.hpp" + +#include + +#include "../mwmechanics/stat.hpp" +#include "../mwworld/ptr.hpp" + +namespace MWGui +{ + class DragAndDrop; + + class HUD : public OEngine::GUI::Layout, public LocalMapBase + { + public: + HUD(int width, int height, int fpsLevel, DragAndDrop* dragAndDrop); + void setEffect(const char *img); + void setValue (const std::string& id, const MWMechanics::DynamicStat& value); + void setFPS(float fps); + void setTriangleCount(size_t count); + void setBatchCount(size_t count); + void setBottomLeftVisibility(bool hmsVisible, bool weapVisible, bool spellVisible); + void setBottomRightVisibility(bool effectBoxVisible, bool minimapVisible); + void setFpsLevel(const int level); + + void setSelectedSpell(const std::string& spellId, int successChancePercent); + void setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent); + void setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent); + void unsetSelectedSpell(); + void unsetSelectedWeapon(); + + void onFrame(float dt); + void onResChange(int width, int height); + + void setCellName(const std::string& cellName); + + bool getWorldMouseOver() { return mWorldMouseOver; } + + MyGUI::ProgressPtr health, magicka, stamina; + MyGUI::Widget* mHealthFrame; + MyGUI::Widget *weapBox, *spellBox; + MyGUI::ImageBox *weapImage, *spellImage; + MyGUI::ProgressPtr weapStatus, spellStatus; + MyGUI::Widget *effectBox, *minimapBox; + MyGUI::ImageBox* effect1; + MyGUI::ScrollView* minimap; + MyGUI::ImageBox* compass; + MyGUI::ImageBox* crosshair; + MyGUI::TextBox* mCellNameBox; + MyGUI::TextBox* mWeaponSpellBox; + + MyGUI::WidgetPtr fpsbox; + MyGUI::TextBox* fpscounter; + MyGUI::TextBox* trianglecounter; + MyGUI::TextBox* batchcounter; + + private: + // bottom left elements + int hmsBaseLeft, weapBoxBaseLeft, spellBoxBaseLeft; + // bottom right elements + int minimapBoxBaseRight, effectBoxBaseRight; + + DragAndDrop* mDragAndDrop; + + std::string mCellName; + float mCellNameTimer; + + std::string mWeaponName; + std::string mSpellName; + float mWeaponSpellTimer; + + bool mMapVisible; + bool mWeaponVisible; + bool mSpellVisible; + + bool mWorldMouseOver; + + void onWorldClicked(MyGUI::Widget* _sender); + void onWorldMouseOver(MyGUI::Widget* _sender, int x, int y); + void onWorldMouseLostFocus(MyGUI::Widget* _sender, MyGUI::Widget* _new); + void onHMSClicked(MyGUI::Widget* _sender); + void onWeaponClicked(MyGUI::Widget* _sender); + void onMagicClicked(MyGUI::Widget* _sender); + void onMapClicked(MyGUI::Widget* _sender); + }; +} diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp new file mode 100644 index 0000000000..a26a958bd0 --- /dev/null +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -0,0 +1,349 @@ +#include "inventorywindow.hpp" + +#include +#include +#include +#include +#include + +#include + +#include "../mwclass/container.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/world.hpp" +#include "../mwworld/player.hpp" +#include "../mwbase/environment.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwworld/actiontake.hpp" +#include "../mwsound/soundmanager.hpp" + +#include "window_manager.hpp" +#include "widgets.hpp" +#include "bookwindow.hpp" +#include "scrollwindow.hpp" +#include "spellwindow.hpp" + +namespace +{ + std::string toLower (const std::string& name) + { + std::string lowerCase; + + std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), + (int(*)(int)) std::tolower); + + return lowerCase; + } +} + +namespace MWGui +{ + + InventoryWindow::InventoryWindow(WindowManager& parWindowManager,DragAndDrop* dragAndDrop) + : ContainerBase(dragAndDrop) + , WindowPinnableBase("openmw_inventory_window_layout.xml", parWindowManager) + , mTrading(false) + { + static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize); + + getWidget(mAvatar, "Avatar"); + getWidget(mEncumbranceBar, "EncumbranceBar"); + getWidget(mEncumbranceText, "EncumbranceBarT"); + getWidget(mFilterAll, "AllButton"); + getWidget(mFilterWeapon, "WeaponButton"); + getWidget(mFilterApparel, "ApparelButton"); + getWidget(mFilterMagic, "MagicButton"); + getWidget(mFilterMisc, "MiscButton"); + getWidget(mLeftPane, "LeftPane"); + getWidget(mRightPane, "RightPane"); + + mAvatar->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked); + + MyGUI::ScrollView* itemView; + MyGUI::Widget* containerWidget; + getWidget(containerWidget, "Items"); + getWidget(itemView, "ItemView"); + setWidgets(containerWidget, itemView); + + // adjust size of buttons to fit text + int curX = 0; + mFilterAll->setSize( mFilterAll->getTextSize().width + 24, mFilterAll->getSize().height ); + curX += mFilterAll->getTextSize().width + 24 + 4; + + mFilterWeapon->setPosition(curX, mFilterWeapon->getPosition().top); + mFilterWeapon->setSize( mFilterWeapon->getTextSize().width + 24, mFilterWeapon->getSize().height ); + curX += mFilterWeapon->getTextSize().width + 24 + 4; + + mFilterApparel->setPosition(curX, mFilterApparel->getPosition().top); + mFilterApparel->setSize( mFilterApparel->getTextSize().width + 24, mFilterApparel->getSize().height ); + curX += mFilterApparel->getTextSize().width + 24 + 4; + + mFilterMagic->setPosition(curX, mFilterMagic->getPosition().top); + mFilterMagic->setSize( mFilterMagic->getTextSize().width + 24, mFilterMagic->getSize().height ); + curX += mFilterMagic->getTextSize().width + 24 + 4; + + mFilterMisc->setPosition(curX, mFilterMisc->getPosition().top); + mFilterMisc->setSize( mFilterMisc->getTextSize().width + 24, mFilterMisc->getSize().height ); + + mFilterAll->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); + mFilterWeapon->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); + mFilterApparel->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); + mFilterMagic->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); + mFilterMisc->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); + + mFilterAll->setStateSelected(true); + + setCoord(0, 342, 498, 258); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + openContainer(player); + } + + void InventoryWindow::open() + { + updateEncumbranceBar(); + + mTrading = false; + + mBoughtItems.clear(); + + onWindowResize(static_cast(mMainWidget)); + } + + void InventoryWindow::onWindowResize(MyGUI::Window* _sender) + { + const float aspect = 0.5; // fixed aspect ratio for the left pane + mLeftPane->setSize( (_sender->getSize().height-44) * aspect, _sender->getSize().height-44 ); + mRightPane->setCoord( mLeftPane->getPosition().left + (_sender->getSize().height-44) * aspect + 4, + mRightPane->getPosition().top, + _sender->getSize().width - 12 - (_sender->getSize().height-44) * aspect - 15, + _sender->getSize().height-44 ); + drawItems(); + } + + void InventoryWindow::onFilterChanged(MyGUI::Widget* _sender) + { + if (_sender == mFilterAll) + setFilter(ContainerBase::Filter_All); + else if (_sender == mFilterWeapon) + setFilter(ContainerBase::Filter_Weapon); + else if (_sender == mFilterApparel) + setFilter(ContainerBase::Filter_Apparel); + else if (_sender == mFilterMagic) + setFilter(ContainerBase::Filter_Magic); + else if (_sender == mFilterMisc) + setFilter(ContainerBase::Filter_Misc); + + mFilterAll->setStateSelected(false); + mFilterWeapon->setStateSelected(false); + mFilterApparel->setStateSelected(false); + mFilterMagic->setStateSelected(false); + mFilterMisc->setStateSelected(false); + + static_cast(_sender)->setStateSelected(true); + } + + void InventoryWindow::onPinToggled() + { + mWindowManager.setWeaponVisibility(!mPinned); + } + + void InventoryWindow::onAvatarClicked(MyGUI::Widget* _sender) + { + if (mDragAndDrop->mIsOnDragAndDrop) + { + MWWorld::Ptr ptr = *mDragAndDrop->mDraggedWidget->getUserData(); + + if (mDragAndDrop->mDraggedFrom != this) + { + // add item to the player's inventory + MWWorld::ContainerStore& invStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); + MWWorld::ContainerStoreIterator it = invStore.begin(); + + int origCount = ptr.getRefData().getCount(); + ptr.getRefData().setCount(origCount - mDragAndDrop->mDraggedCount); + it = invStore.add(ptr); + (*it).getRefData().setCount(mDragAndDrop->mDraggedCount); + ptr = *it; + } + + /// \todo scripts + + boost::shared_ptr action = MWWorld::Class::get(ptr).use(ptr); + + action->execute(); + + // this is necessary for books/scrolls: if they are already in the player's inventory, + // the "Take" button should not be visible. + // NOTE: the take button is "reset" when the window opens, so we can safely do the following + // without screwing up future book windows + if (mDragAndDrop->mDraggedFrom == this) + { + mWindowManager.getBookWindow()->setTakeButtonShow(false); + mWindowManager.getScrollWindow()->setTakeButtonShow(false); + } + + mDragAndDrop->mIsOnDragAndDrop = false; + MyGUI::Gui::getInstance().destroyWidget(mDragAndDrop->mDraggedWidget); + + mWindowManager.setDragDrop(false); + + drawItems(); + + // update selected weapon icon + MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator weaponSlot = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weaponSlot == invStore.end()) + mWindowManager.unsetSelectedWeapon(); + else + mWindowManager.setSelectedWeapon(*weaponSlot, 100); /// \todo track weapon durability + + } + } + + std::vector InventoryWindow::getEquippedItems() + { + MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + + std::vector items; + + for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + MWWorld::ContainerStoreIterator it = invStore.getSlot(slot); + if (it != invStore.end()) + { + items.push_back(*it); + } + } + + return items; + } + + void InventoryWindow::_unequipItem(MWWorld::Ptr item) + { + MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + + for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + MWWorld::ContainerStoreIterator it = invStore.getSlot(slot); + if (it != invStore.end() && *it == item) + { + invStore.equip(slot, invStore.end()); + return; + } + } + } + + void InventoryWindow::updateEncumbranceBar() + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + + float capacity = MWWorld::Class::get(player).getCapacity(player); + float encumbrance = MWWorld::Class::get(player).getEncumbrance(player); + mEncumbranceBar->setProgressRange(capacity); + mEncumbranceBar->setProgressPosition(encumbrance); + mEncumbranceText->setCaption( boost::lexical_cast(int(encumbrance)) + "/" + boost::lexical_cast(int(capacity)) ); + } + + void InventoryWindow::onFrame() + { + if (!mMainWidget->getVisible()) + return; + + updateEncumbranceBar(); + } + + int InventoryWindow::getPlayerGold() + { + MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + + for (MWWorld::ContainerStoreIterator it = invStore.begin(); + it != invStore.end(); ++it) + { + if (toLower(it->getCellRef().refID) == "gold_001") + return it->getRefData().getCount(); + } + return 0; + } + + void InventoryWindow::startTrade() + { + mTrading = true; + } + + void InventoryWindow::notifyContentChanged() + { + // update the spell window just in case new enchanted items were added to inventory + if (mWindowManager.getSpellWindow()) + mWindowManager.getSpellWindow()->updateSpells(); + + // update selected weapon icon + MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator weaponSlot = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weaponSlot == invStore.end()) + mWindowManager.unsetSelectedWeapon(); + else + mWindowManager.setSelectedWeapon(*weaponSlot, 100); /// \todo track weapon durability + } + + void InventoryWindow::pickUpObject (MWWorld::Ptr object) + { + /// \todo scripts + + // make sure the object is of a type that can be picked up + std::string type = object.getTypeName(); + if ( (type != typeid(ESM::Apparatus).name()) + && (type != typeid(ESM::Armor).name()) + && (type != typeid(ESM::Book).name()) + && (type != typeid(ESM::Clothing).name()) + && (type != typeid(ESM::Ingredient).name()) + && (type != typeid(ESM::Light).name()) + && (type != typeid(ESM::Miscellaneous).name()) + && (type != typeid(ESM::Tool).name()) + && (type != typeid(ESM::Probe).name()) + && (type != typeid(ESM::Repair).name()) + && (type != typeid(ESM::Weapon).name()) + && (type != typeid(ESM::Potion).name())) + return; + + // sound + std::string sound = MWWorld::Class::get(object).getUpSoundId(object); + MWBase::Environment::get().getSoundManager()->playSound(sound, 1, 1); + + int count = object.getRefData().getCount(); + + // add to player inventory + // can't use ActionTake here because we need an MWWorld::Ptr to the newly inserted object + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::Ptr newObject = *MWWorld::Class::get (player).getContainerStore (player).add (object); + // remove from world + MWBase::Environment::get().getWorld()->deleteObject (object); + + mDragAndDrop->mIsOnDragAndDrop = true; + mDragAndDrop->mDraggedCount = count; + + std::string path = std::string("icons\\"); + path += MWWorld::Class::get(newObject).getInventoryIcon(newObject); + MyGUI::ImageBox* baseWidget = mContainerWidget->createWidget("ImageBox", MyGUI::IntCoord(0, 0, 42, 42), MyGUI::Align::Default); + baseWidget->detachFromWidget(); + baseWidget->attachToWidget(mDragAndDrop->mDragAndDropWidget); + baseWidget->setUserData(newObject); + mDragAndDrop->mDraggedWidget = baseWidget; + ImageBox* image = baseWidget->createWidget("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); + int pos = path.rfind("."); + path.erase(pos); + path.append(".dds"); + image->setImageTexture(path); + image->setNeedMouseFocus(false); + + // text widget that shows item count + MyGUI::TextBox* text = image->createWidget("SandBrightText", MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label")); + text->setTextAlign(MyGUI::Align::Right); + text->setNeedMouseFocus(false); + text->setTextShadow(true); + text->setTextShadowColour(MyGUI::Colour(0,0,0)); + text->setCaption(getCountString(count)); + mDragAndDrop->mDraggedFrom = this; + } +} diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp new file mode 100644 index 0000000000..82da3efea7 --- /dev/null +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -0,0 +1,60 @@ +#ifndef MGUI_Inventory_H +#define MGUI_Inventory_H + +#include "container.hpp" +#include "window_pinnable_base.hpp" + +namespace MWGui +{ + class InventoryWindow : public ContainerBase, public WindowPinnableBase + { + public: + InventoryWindow(WindowManager& parWindowManager,DragAndDrop* dragAndDrop); + + virtual void open(); + + /// start trading, disables item drag&drop + void startTrade(); + + void onFrame(); + + void pickUpObject (MWWorld::Ptr object); + + int getPlayerGold(); + + protected: + MyGUI::Widget* mAvatar; + MyGUI::TextBox* mArmorRating; + MyGUI::ProgressBar* mEncumbranceBar; + MyGUI::TextBox* mEncumbranceText; + + MyGUI::Widget* mLeftPane; + MyGUI::Widget* mRightPane; + + MyGUI::Button* mFilterAll; + MyGUI::Button* mFilterWeapon; + MyGUI::Button* mFilterApparel; + MyGUI::Button* mFilterMagic; + MyGUI::Button* mFilterMisc; + + bool mTrading; + + void onWindowResize(MyGUI::Window* _sender); + void onFilterChanged(MyGUI::Widget* _sender); + void onAvatarClicked(MyGUI::Widget* _sender); + void onPinToggled(); + + void updateEncumbranceBar(); + + virtual bool isTrading() { return mTrading; } + virtual bool isInventory() { return true; } + virtual std::vector getEquippedItems(); + virtual void _unequipItem(MWWorld::Ptr item); + + virtual void onReferenceUnavailable() { ; } + + virtual void notifyContentChanged(); + }; +} + +#endif // Inventory_H diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index e67eda7771..7f9000bc75 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -82,6 +82,7 @@ book formatText(std::string text,book mBook,int maxLine, int lineSize) MWGui::JournalWindow::JournalWindow (WindowManager& parWindowManager) : WindowBase("openmw_journal_layout.xml", parWindowManager) , lastPos(0) + , mVisible(false) { //setCoord(0,0,498, 342); center(); @@ -109,9 +110,15 @@ MWGui::JournalWindow::JournalWindow (WindowManager& parWindowManager) //std::list list = formatText("OpenMW rgh dsfg sqef srg ZT uzql n ZLIEHRF LQSJH GLOIjf qjfmj hslkdgn jlkdjhg qlr isgli shli uhs fiuh qksf cg ksjnf lkqsnbf ksbf sbfkl zbf kuyzflkj sbgdfkj zlfh ozhjfmo hzmfh lizuf rty qzt ezy tkyEZT RYYJ DG fgh is an open-source implementation of the game engine found in the game Morrowind. This is a dumb test text msodjbg smojg smoig fiiinnn"); //std::list list = formatText(); //displayLeftText(list.front()); +} - MyGUI::WindowPtr t = static_cast(mMainWidget); - t->eventWindowChangeCoord += MyGUI::newDelegate(this, &JournalWindow::onWindowResize); +void MWGui::JournalWindow::setVisible(bool visible) +{ + if (mVisible && !visible) + MWBase::Environment::get().getSoundManager()->playSound ("book close", 1.0, 1.0); + mVisible = visible; + + mMainWidget->setVisible(visible); } void MWGui::JournalWindow::open() @@ -124,7 +131,7 @@ void MWGui::JournalWindow::open() book journal; journal.endLine = 0; - for(std::deque::const_iterator it = MWBase::Environment::get().getJournal()->begin();it!=MWBase::Environment::get().getJournal()->end();it++) + for(std::deque::const_iterator it = MWBase::Environment::get().getJournal()->begin();it!=MWBase::Environment::get().getJournal()->end();++it) { std::string a = it->getText(MWBase::Environment::get().getWorld()->getStore()); journal = formatText(a,journal,10,17); @@ -134,7 +141,7 @@ void MWGui::JournalWindow::open() //std::string a = MWBase::Environment::get().getJournal()->begin()->getText(MWBase::Environment::get().getWorld()->getStore()); //std::list journal = formatText(a,10,20,1); bool left = true; - for(std::list::iterator it = journal.pages.begin(); it != journal.pages.end();it++) + for(std::list::iterator it = journal.pages.begin(); it != journal.pages.end();++it) { if(left) { @@ -159,10 +166,6 @@ void MWGui::JournalWindow::open() } } -void MWGui::JournalWindow::onWindowResize(MyGUI::Window* window) -{ -} - void MWGui::JournalWindow::displayLeftText(std::string text) { mLeftTextWidget->eraseText(0,mLeftTextWidget->getTextLength()); diff --git a/apps/openmw/mwgui/journalwindow.hpp b/apps/openmw/mwgui/journalwindow.hpp index 4656c7a482..cacdb9414c 100644 --- a/apps/openmw/mwgui/journalwindow.hpp +++ b/apps/openmw/mwgui/journalwindow.hpp @@ -18,16 +18,9 @@ namespace MWGui JournalWindow(WindowManager& parWindowManager); void open(); + virtual void setVisible(bool visible); // only used to play close sound + private: - enum ColorStyle - { - CS_Sub, - CS_Normal, - CS_Super - }; - - void onWindowResize(MyGUI::Window* window); - void displayLeftText(std::string text); void displayRightText(std::string text); @@ -50,6 +43,7 @@ namespace MWGui std::vector leftPages; std::vector rightPages; int mPageNumber; //store the number of the current left page + bool mVisible; }; } diff --git a/apps/openmw/mwgui/layouts.cpp b/apps/openmw/mwgui/layouts.cpp deleted file mode 100644 index 21302d7c1d..0000000000 --- a/apps/openmw/mwgui/layouts.cpp +++ /dev/null @@ -1,332 +0,0 @@ -#include "layouts.hpp" - -#include "../mwmechanics/mechanicsmanager.hpp" -#include "window_manager.hpp" - -#include -#include -#include - -#undef min -#undef max - -using namespace MWGui; - - -HUD::HUD(int width, int height, int fpsLevel) - : Layout("openmw_hud_layout.xml") - , health(NULL) - , magicka(NULL) - , stamina(NULL) - , weapImage(NULL) - , spellImage(NULL) - , weapStatus(NULL) - , spellStatus(NULL) - , effectBox(NULL) - , effect1(NULL) - , minimap(NULL) - , compass(NULL) - , crosshair(NULL) - , fpsbox(NULL) - , fpscounter(NULL) - , trianglecounter(NULL) - , batchcounter(NULL) - , hmsBaseLeft(0) - , weapBoxBaseLeft(0) - , spellBoxBaseLeft(0) - , effectBoxBaseRight(0) - , minimapBoxBaseRight(0) -{ - setCoord(0,0, width, height); - - // Energy bars - getWidget(health, "Health"); - getWidget(magicka, "Magicka"); - getWidget(stamina, "Stamina"); - hmsBaseLeft = health->getLeft(); - - // Item and spell images and status bars - getWidget(weapBox, "WeapBox"); - getWidget(weapImage, "WeapImage"); - getWidget(weapStatus, "WeapStatus"); - weapBoxBaseLeft = weapBox->getLeft(); - - getWidget(spellBox, "SpellBox"); - getWidget(spellImage, "SpellImage"); - getWidget(spellStatus, "SpellStatus"); - spellBoxBaseLeft = spellBox->getLeft(); - - getWidget(effectBox, "EffectBox"); - getWidget(effect1, "Effect1"); - effectBoxBaseRight = effectBox->getRight(); - - getWidget(minimapBox, "MiniMapBox"); - minimapBoxBaseRight = minimapBox->getRight(); - getWidget(minimap, "MiniMap"); - getWidget(compass, "Compass"); - - getWidget(crosshair, "Crosshair"); - - setFpsLevel(fpsLevel); - - getWidget(trianglecounter, "TriangleCounter"); - getWidget(batchcounter, "BatchCounter"); - - compass->setImageTexture("textures\\compass.dds"); - crosshair->setImageTexture("textures\\target.dds"); - - // These are just demo values, you should replace these with - // real calls from outside the class later. - setWeapIcon("icons\\w\\tx_knife_iron.dds"); - setWeapStatus(90, 100); - setSpellIcon("icons\\s\\b_tx_s_rstor_health.dds"); - setSpellStatus(65, 100); - setEffect("icons\\s\\tx_s_chameleon.dds"); - - LocalMapBase::init(minimap, this); -} - -void HUD::setFpsLevel(int level) -{ - MyGUI::Widget* fps; - getWidget(fps, "FPSBoxAdv"); - fps->setVisible(false); - getWidget(fps, "FPSBox"); - fps->setVisible(false); - - if (level == 2) - { - getWidget(fpsbox, "FPSBoxAdv"); - fpsbox->setVisible(true); - getWidget(fpscounter, "FPSCounterAdv"); - } - else if (level == 1) - { - getWidget(fpsbox, "FPSBox"); - fpsbox->setVisible(true); - getWidget(fpscounter, "FPSCounter"); - } -} - -void HUD::setFPS(float fps) -{ - fpscounter->setCaption(boost::lexical_cast((int)fps)); -} - -void HUD::setTriangleCount(size_t count) -{ - trianglecounter->setCaption(boost::lexical_cast(count)); -} - -void HUD::setBatchCount(size_t count) -{ - batchcounter->setCaption(boost::lexical_cast(count)); -} - -void HUD::setStats(int h, int hmax, int m, int mmax, int s, int smax) -{ - health->setProgressRange(hmax); - health->setProgressPosition(h); - magicka->setProgressRange(mmax); - magicka->setProgressPosition(m); - stamina->setProgressRange(smax); - stamina->setProgressPosition(s); -} - -void HUD::setWeapIcon(const char *str) -{ - weapImage->setImageTexture(str); -} - -void HUD::setSpellIcon(const char *str) -{ - spellImage->setImageTexture(str); -} - -void HUD::setWeapStatus(int s, int smax) -{ - weapStatus->setProgressRange(smax); - weapStatus->setProgressPosition(s); -} - -void HUD::setSpellStatus(int s, int smax) -{ - spellStatus->setProgressRange(smax); - spellStatus->setProgressPosition(s); -} - -void HUD::setEffect(const char *img) -{ - effect1->setImageTexture(img); -} - -void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& value) -{ - static const char *ids[] = - { - "HBar", "MBar", "FBar", 0 - }; - - for (int i=0; ids[i]; ++i) - if (ids[i]==id) - { - switch (i) - { - case 0: - health->setProgressRange (value.getModified()); - health->setProgressPosition (value.getCurrent()); - break; - case 1: - magicka->setProgressRange (value.getModified()); - magicka->setProgressPosition (value.getCurrent()); - break; - case 2: - stamina->setProgressRange (value.getModified()); - stamina->setProgressPosition (value.getCurrent()); - break; - } - } -} - -void HUD::setPlayerDir(const float x, const float y) -{ - if (!minimapBox->getVisible() || (x == mLastPositionX && y == mLastPositionY)) return; - - MyGUI::ISubWidget* main = compass->getSubWidgetMain(); - MyGUI::RotatingSkin* rotatingSubskin = main->castType(); - rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); - float angle = std::atan2(x,y); - rotatingSubskin->setAngle(angle); - mLastPositionX = x; - mLastPositionY = y; -} - -void HUD::setPlayerPos(const float x, const float y) -{ - if (!minimapBox->getVisible() || (x == mLastDirectionX && y == mLastDirectionY)) return; - - MyGUI::IntSize size = minimap->getCanvasSize(); - MyGUI::IntPoint middle = MyGUI::IntPoint((1/3.f + x/3.f)*size.width,(1/3.f + y/3.f)*size.height); - MyGUI::IntCoord viewsize = minimap->getCoord(); - MyGUI::IntPoint pos(0.5*viewsize.width - middle.left, 0.5*viewsize.height - middle.top); - - minimap->setViewOffset(pos); - compass->setPosition(MyGUI::IntPoint(x*512-16, y*512-16)); - - mLastDirectionX = x; - mLastDirectionY = y; -} - -void HUD::setBottomLeftVisibility(bool hmsVisible, bool weapVisible, bool spellVisible) -{ - int weapDx = 0, spellDx = 0; - if (!hmsVisible) - spellDx = weapDx = weapBoxBaseLeft - hmsBaseLeft; - - if (!weapVisible) - spellDx -= spellBoxBaseLeft - weapBoxBaseLeft; - - health->setVisible(hmsVisible); - stamina->setVisible(hmsVisible); - magicka->setVisible(hmsVisible); - weapBox->setPosition(weapBoxBaseLeft - weapDx, weapBox->getTop()); - weapBox->setVisible(weapVisible); - spellBox->setPosition(spellBoxBaseLeft - spellDx, spellBox->getTop()); - spellBox->setVisible(spellVisible); -} - -void HUD::setBottomRightVisibility(bool effectBoxVisible, bool minimapBoxVisible) -{ - // effect box can have variable width -> variable left coordinate - int effectsDx = 0; - if (!minimapBoxVisible) - effectsDx = minimapBoxBaseRight - effectBoxBaseRight; - - minimapBox->setVisible(minimapBoxVisible); - effectBox->setPosition(effectBoxBaseRight - effectBox->getWidth() + effectsDx, effectBox->getTop()); - effectBox->setVisible(effectBoxVisible); -} - -LocalMapBase::LocalMapBase() - : mCurX(0) - , mCurY(0) - , mInterior(false) - , mFogOfWar(true) - , mLocalMap(NULL) - , mPrefix() - , mChanged(true) - , mLayout(NULL) - , mLastPositionX(0.0f) - , mLastPositionY(0.0f) - , mLastDirectionX(0.0f) - , mLastDirectionY(0.0f) -{ -} - -void LocalMapBase::init(MyGUI::ScrollView* widget, OEngine::GUI::Layout* layout) -{ - mLocalMap = widget; - mLayout = layout; -} - -void LocalMapBase::setCellPrefix(const std::string& prefix) -{ - mPrefix = prefix; - mChanged = true; -} - -void LocalMapBase::toggleFogOfWar() -{ - mFogOfWar = !mFogOfWar; - applyFogOfWar(); -} - -void LocalMapBase::applyFogOfWar() -{ - for (int mx=0; mx<3; ++mx) - { - for (int my=0; my<3; ++my) - { - std::string name = "Map_" + boost::lexical_cast(mx) + "_" - + boost::lexical_cast(my); - std::string image = mPrefix+"_"+ boost::lexical_cast(mCurX + (mx-1)) + "_" - + boost::lexical_cast(mCurY + (mInterior ? (my-1) : -1*(my-1))); - MyGUI::ImageBox* fog; - mLayout->getWidget(fog, name+"_fog"); - fog->setImageTexture(mFogOfWar ? - ((MyGUI::RenderManager::getInstance().getTexture(image+"_fog") != 0) ? image+"_fog" - : "black.png" ) - : ""); - } - } -} - -void LocalMapBase::setActiveCell(const int x, const int y, bool interior) -{ - if (x==mCurX && y==mCurY && mInterior==interior && !mChanged) return; // don't do anything if we're still in the same cell - for (int mx=0; mx<3; ++mx) - { - for (int my=0; my<3; ++my) - { - std::string name = "Map_" + boost::lexical_cast(mx) + "_" - + boost::lexical_cast(my); - - std::string image = mPrefix+"_"+ boost::lexical_cast(x + (mx-1)) + "_" - + boost::lexical_cast(y + (interior ? (my-1) : -1*(my-1))); - - MyGUI::ImageBox* box; - mLayout->getWidget(box, name); - - if (MyGUI::RenderManager::getInstance().getTexture(image) != 0) - box->setImageTexture(image); - else - box->setImageTexture("black.png"); - } - } - mInterior = interior; - mCurX = x; - mCurY = y; - mChanged = false; - applyFogOfWar(); -} - diff --git a/apps/openmw/mwgui/layouts.hpp b/apps/openmw/mwgui/layouts.hpp deleted file mode 100644 index 19d96d2efa..0000000000 --- a/apps/openmw/mwgui/layouts.hpp +++ /dev/null @@ -1,236 +0,0 @@ -#ifndef MWGUI_LAYOUTS_H -#define MWGUI_LAYOUTS_H - -#include - -#include -#include - -#include -#include -#include -#include - -#include "../mwmechanics/stat.hpp" -#include "window_base.hpp" - -#include - -/* - This file contains classes corresponding to window layouts - defined in resources/mygui/ *.xml. - - Each class inherites GUI::Layout and loads the XML file, and - provides some helper functions to manipulate the elements of the - window. - - The windows are never created or destroyed (except at startup and - shutdown), they are only hid. You can control visibility with - setVisible(). - */ - -namespace MWGui -{ - class LocalMapBase - { - public: - LocalMapBase(); - void init(MyGUI::ScrollView* widget, OEngine::GUI::Layout* layout); - - void setCellPrefix(const std::string& prefix); - void setActiveCell(const int x, const int y, bool interior=false); - - void toggleFogOfWar(); - - protected: - int mCurX, mCurY; - bool mInterior; - MyGUI::ScrollView* mLocalMap; - std::string mPrefix; - bool mChanged; - bool mFogOfWar; - - void applyFogOfWar(); - - OEngine::GUI::Layout* mLayout; - - float mLastPositionX; - float mLastPositionY; - float mLastDirectionX; - float mLastDirectionY; - }; - - class HUD : public OEngine::GUI::Layout, public LocalMapBase - { - public: - HUD(int width, int height, int fpsLevel); - void setStats(int h, int hmax, int m, int mmax, int s, int smax); - void setWeapIcon(const char *str); - void setSpellIcon(const char *str); - void setWeapStatus(int s, int smax); - void setSpellStatus(int s, int smax); - void setEffect(const char *img); - void setValue (const std::string& id, const MWMechanics::DynamicStat& value); - void setFPS(float fps); - void setTriangleCount(size_t count); - void setBatchCount(size_t count); - void setPlayerDir(const float x, const float y); - void setPlayerPos(const float x, const float y); - void setBottomLeftVisibility(bool hmsVisible, bool weapVisible, bool spellVisible); - void setBottomRightVisibility(bool effectBoxVisible, bool minimapVisible); - void setFpsLevel(const int level); - - MyGUI::ProgressPtr health, magicka, stamina; - MyGUI::Widget *weapBox, *spellBox; - MyGUI::ImageBox *weapImage, *spellImage; - MyGUI::ProgressPtr weapStatus, spellStatus; - MyGUI::Widget *effectBox, *minimapBox; - MyGUI::ImageBox* effect1; - MyGUI::ScrollView* minimap; - MyGUI::ImageBox* compass; - MyGUI::ImageBox* crosshair; - - MyGUI::WidgetPtr fpsbox; - MyGUI::TextBox* fpscounter; - MyGUI::TextBox* trianglecounter; - MyGUI::TextBox* batchcounter; - - private: - // bottom left elements - int hmsBaseLeft, weapBoxBaseLeft, spellBoxBaseLeft; - // bottom right elements - int minimapBoxBaseRight, effectBoxBaseRight; - }; - - class MainMenu : public OEngine::GUI::Layout - { - public: - MainMenu(int w, int h) - : Layout("openmw_mainmenu_layout.xml") - { - setCoord(0,0,w,h); - } - }; - -#if 0 - class InventoryWindow : public OEngine::GUI::Layout - { - public: - enum CategoryMode - { - CM_All = 0, // All items - CM_Weapon = 1, // Only weapons - CM_Apparel = 2, // Apparel - CM_Magic = 3, // Magic - CM_Misc = 4 // Misc - }; - - InventoryWindow () - : Layout("openmw_inventory_window_layout.xml") - , categoryMode(CM_All) - - // color should be fetched from skin - , activeColor(0, 0, 1) - , inactiveColor(0.7, 0.7, 0.7) - { - setCoord(0, 200, 600, 400); - - // These are just demo values, you should replace these with - // real calls from outside the class later. - - mMainWidget->setCaption("Glass Frostsword"); - setText("EncumbranceBarT", "176/210"); - - MyGUI::ProgressPtr pt; - getWidget(pt, "EncumbranceBar"); - pt->setProgressRange(210); - pt->setProgressPosition(176); - - MyGUI::WidgetPtr avatar; - getWidget(avatar, "Avatar"); - - // Adjust armor rating text to bottom of avatar widget - MyGUI::TextBox* armor_rating; - getWidget(armor_rating, "ArmorRating"); - armor_rating->setCaption("Armor: 11"); - MyGUI::IntCoord coord = armor_rating->getCoord(); - coord.top = avatar->getCoord().height - 4 - coord.height; - armor_rating->setCoord(coord); - - names[0] = "All"; - names[1] = "Weapon"; - names[2] = "Apparel"; - names[3] = "Magic"; - names[4] = "Misc"; - - boost::array categories = { { - CM_All, CM_Weapon, CM_Apparel, CM_Magic, CM_Misc - } }; - - // Initialize buttons with text and adjust sizes, also mark All as active button - int margin = 2; - int last_x = 0; - for (int i = 0; i < categories.size(); ++i) - { - CategoryMode mode = categories[i]; - std::string name = names[mode]; - name += "Button"; - setText(name, names[mode]); - getWidget(buttons[mode], name); - - MyGUI::ButtonPtr &button_pt = buttons[mode]; - if (mode == CM_All) - button_pt->setTextColour(activeColor); - else - button_pt->setTextColour(inactiveColor); - MyGUI::IntCoord coord = button_pt->getCoord(); - coord.left = last_x; - last_x += coord.width + margin; - button_pt->setCoord(coord); - - button_pt->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onCategorySelected); - } - } - - void setCategory(CategoryMode mode) - { - MyGUI::ButtonPtr pt = getCategoryButton(categoryMode); - pt->setTextColour(inactiveColor); - - pt = getCategoryButton(mode); - pt->setTextColour(activeColor); - categoryMode = mode; - } - - MyGUI::ButtonPtr getCategoryButton(CategoryMode mode) - { - return buttons[mode]; - } - - void onCategorySelected(MyGUI::Widget *widget) - { - boost::array categories = { { - CM_All, CM_Weapon, CM_Apparel, CM_Magic, CM_Misc - } }; - - for (int i = 0; i < categories.size(); ++i) - { - CategoryMode mode = categories[i]; - if (widget == buttons[mode]) - { - setCategory(mode); - return; - } - } - } - - CategoryMode categoryMode; // Current category filter - MyGUI::ButtonPtr buttons[5]; // Button pointers - std::string names[5]; // Names of category buttons - - MyGUI::Colour activeColor; - MyGUI::Colour inactiveColor; - }; -#endif -} -#endif diff --git a/apps/openmw/mwgui/list.cpp b/apps/openmw/mwgui/list.cpp new file mode 100644 index 0000000000..661fb2e683 --- /dev/null +++ b/apps/openmw/mwgui/list.cpp @@ -0,0 +1,131 @@ +#include "list.hpp" + +#include + +using namespace MWGui; +using namespace MWGui::Widgets; + +MWList::MWList() : + mClient(0) + , mScrollView(0) + , mItemHeight(0) +{ +} + +void MWList::initialiseOverride() +{ + Base::initialiseOverride(); + + assignWidget(mClient, "Client"); + if (mClient == 0) + mClient = this; + + mScrollView = mClient->createWidgetReal( + "MW_ScrollView", MyGUI::FloatCoord(0.0, 0.0, 1.0, 1.0), + MyGUI::Align::Top | MyGUI::Align::Left | MyGUI::Align::Stretch, getName() + "_ScrollView"); +} + +void MWList::addItem(const std::string& name) +{ + mItems.push_back(name); +} + +void MWList::addSeparator() +{ + mItems.push_back(""); +} + +void MWList::adjustSize() +{ + redraw(); +} + +void MWList::redraw(bool scrollbarShown) +{ + const int _scrollBarWidth = 24; // fetch this from skin? + const int scrollBarWidth = scrollbarShown ? _scrollBarWidth : 0; + const int spacing = 3; + + while (mScrollView->getChildCount()) + { + MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0)); + } + + mItemHeight = 0; + for (std::vector::const_iterator it=mItems.begin(); + it!=mItems.end(); ++it) + { + if (*it != "") + { + MyGUI::Button* button = mScrollView->createWidget( + "MW_ListLine", MyGUI::IntCoord(0, mItemHeight, mScrollView->getSize().width - scrollBarWidth - 2, 24), + MyGUI::Align::Left | MyGUI::Align::Top, getName() + "_item_" + (*it)); + button->setCaption((*it)); + button->getSubWidgetText()->setWordWrap(true); + button->getSubWidgetText()->setTextAlign(MyGUI::Align::Left); + button->eventMouseWheel += MyGUI::newDelegate(this, &MWList::onMouseWheel); + button->eventMouseButtonClick += MyGUI::newDelegate(this, &MWList::onItemSelected); + + int height = button->getTextSize().height; + button->setSize(MyGUI::IntSize(button->getSize().width, height)); + + mItemHeight += height + spacing; + } + else + { + MyGUI::ImageBox* separator = mScrollView->createWidget("MW_HLine", + MyGUI::IntCoord(2, mItemHeight, mScrollView->getWidth()-4, 18), + MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); + separator->setNeedMouseFocus(false); + + mItemHeight += 18 + spacing; + } + } + mScrollView->setCanvasSize(mClient->getSize().width + (_scrollBarWidth-scrollBarWidth), std::max(mItemHeight, mClient->getSize().height)); + + if (!scrollbarShown && mItemHeight > mClient->getSize().height) + redraw(true); +} + +bool MWList::hasItem(const std::string& name) +{ + return (std::find(mItems.begin(), mItems.end(), name) != mItems.end()); +} + +unsigned int MWList::getItemCount() +{ + return mItems.size(); +} + +std::string MWList::getItemNameAt(unsigned int at) +{ + assert(at < mItems.size() && "List item out of bounds"); + return mItems[at]; +} + +void MWList::removeItem(const std::string& name) +{ + assert( std::find(mItems.begin(), mItems.end(), name) != mItems.end() ); + mItems.erase( std::find(mItems.begin(), mItems.end(), name) ); +} + +void MWList::clear() +{ + mItems.clear(); +} + +void MWList::onMouseWheel(MyGUI::Widget* _sender, int _rel) +{ + //NB view offset is negative + if (mScrollView->getViewOffset().top + _rel*0.3 > 0) + mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); + else + mScrollView->setViewOffset(MyGUI::IntPoint(0, mScrollView->getViewOffset().top + _rel*0.3)); +} + +void MWList::onItemSelected(MyGUI::Widget* _sender) +{ + std::string name = static_cast(_sender)->getCaption(); + + eventItemSelected(name); +} diff --git a/apps/openmw/mwgui/list.hpp b/apps/openmw/mwgui/list.hpp new file mode 100644 index 0000000000..2b765c2e11 --- /dev/null +++ b/apps/openmw/mwgui/list.hpp @@ -0,0 +1,60 @@ +#ifndef MWGUI_LIST_HPP +#define MWGUI_LIST_HPP + +#include + +namespace MWGui +{ + namespace Widgets + { + /** + * \brief a very simple list widget that supports word-wrapping entries + * \note if the width or height of the list changes, you must call adjustSize() method + */ + class MWList : public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(MWList) + public: + MWList(); + + typedef MyGUI::delegates::CMultiDelegate1 EventHandle_String; + + /** + * Event: Item selected with the mouse. + * signature: void method(std::string itemName) + */ + EventHandle_String eventItemSelected; + + /** + * Call after the size of the list changed, or items were inserted/removed + */ + void adjustSize(); + + void addItem(const std::string& name); + void addSeparator(); ///< add a seperator between the current and the next item. + void removeItem(const std::string& name); + bool hasItem(const std::string& name); + unsigned int getItemCount(); + std::string getItemNameAt(unsigned int at); ///< \attention if there are separators, this method will return "" at the place where the separator is + void clear(); + + protected: + void initialiseOverride(); + + void redraw(bool scrollbarShown = false); + + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + void onItemSelected(MyGUI::Widget* _sender); + + private: + MyGUI::ScrollView* mScrollView; + MyGUI::Widget* mClient; + + std::vector mItems; + + int mItemHeight; // height of all items + }; + } +} + +#endif diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp new file mode 100644 index 0000000000..b32f2d900b --- /dev/null +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -0,0 +1,16 @@ +#include + +namespace MWGui +{ + + class MainMenu : public OEngine::GUI::Layout + { + public: + MainMenu(int w, int h) + : Layout("openmw_mainmenu_layout.xml") + { + setCoord(0,0,w,h); + } + }; + +} diff --git a/apps/openmw/mwgui/map_window.cpp b/apps/openmw/mwgui/map_window.cpp index 0e9d57c3c2..a51b66e2bf 100644 --- a/apps/openmw/mwgui/map_window.cpp +++ b/apps/openmw/mwgui/map_window.cpp @@ -1,67 +1,148 @@ #include "map_window.hpp" #include "window_manager.hpp" -/* -#include "../mwmechanics/mechanicsmanager.hpp" -#include -#include -#include +#include -#undef min -#undef max -*/ using namespace MWGui; -MapWindow::MapWindow(WindowManager& parWindowManager) : - MWGui::WindowPinnableBase("openmw_map_window_layout.xml", parWindowManager), - mGlobal(false) +LocalMapBase::LocalMapBase() + : mCurX(0) + , mCurY(0) + , mInterior(false) + , mFogOfWar(true) + , mLocalMap(NULL) + , mMapDragAndDrop(false) + , mPrefix() + , mChanged(true) + , mLayout(NULL) + , mLastPositionX(0.0f) + , mLastPositionY(0.0f) + , mLastDirectionX(0.0f) + , mLastDirectionY(0.0f) + , mCompass(NULL) { - setCoord(500,0,320,300); - setText("WorldButton", "World"); - setImage("Compass", "textures\\compass.dds"); - - // Obviously you should override this later on - setCellName("No Cell Loaded"); - - getWidget(mLocalMap, "LocalMap"); - getWidget(mGlobalMap, "GlobalMap"); - getWidget(mPlayerArrow, "Compass"); - - getWidget(mButton, "WorldButton"); - mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked); - - MyGUI::Button* eventbox; - getWidget(eventbox, "EventBox"); - eventbox->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); - eventbox->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); - - LocalMapBase::init(mLocalMap, this); } -void MapWindow::setCellName(const std::string& cellName) +void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, OEngine::GUI::Layout* layout, bool mapDragAndDrop) { - static_cast(mMainWidget)->setCaption(cellName); - adjustWindowCaption(); + mLocalMap = widget; + mLayout = layout; + mMapDragAndDrop = mapDragAndDrop; + mCompass = compass; + + // create 3x3 map widgets, 512x512 each, holding a 1024x1024 texture each + const int widgetSize = 512; + for (int mx=0; mx<3; ++mx) + { + for (int my=0; my<3; ++my) + { + MyGUI::ImageBox* map = mLocalMap->createWidget("ImageBox", + MyGUI::IntCoord(mx*widgetSize, my*widgetSize, widgetSize, widgetSize), + MyGUI::Align::Top | MyGUI::Align::Left, "Map_" + boost::lexical_cast(mx) + "_" + boost::lexical_cast(my)); + + MyGUI::ImageBox* fog = map->createWidget("ImageBox", + MyGUI::IntCoord(0, 0, widgetSize, widgetSize), + MyGUI::Align::Top | MyGUI::Align::Left, "Map_" + boost::lexical_cast(mx) + "_" + boost::lexical_cast(my) + "_fog"); + + if (!mMapDragAndDrop) + { + map->setNeedMouseFocus(false); + fog->setNeedMouseFocus(false); + } + + mMapWidgets.push_back(map); + mFogWidgets.push_back(fog); + } + } } -void MapWindow::setPlayerPos(const float x, const float y) +void LocalMapBase::setCellPrefix(const std::string& prefix) { - if (mGlobal || !mVisible || (x == mLastPositionX && y == mLastPositionY)) return; + mPrefix = prefix; + mChanged = true; +} + +void LocalMapBase::toggleFogOfWar() +{ + mFogOfWar = !mFogOfWar; + applyFogOfWar(); +} + +void LocalMapBase::applyFogOfWar() +{ + for (int mx=0; mx<3; ++mx) + { + for (int my=0; my<3; ++my) + { + std::string name = "Map_" + boost::lexical_cast(mx) + "_" + + boost::lexical_cast(my); + + std::string image = mPrefix+"_"+ boost::lexical_cast(mCurX + (mx-1)) + "_" + + boost::lexical_cast(mCurY + (mInterior ? (my-1) : -1*(my-1))); + MyGUI::ImageBox* fog = mFogWidgets[my + 3*mx]; + fog->setImageTexture(mFogOfWar ? + ((MyGUI::RenderManager::getInstance().getTexture(image+"_fog") != 0) ? image+"_fog" + : "black.png" ) + : ""); + } + } +} + +void LocalMapBase::setActiveCell(const int x, const int y, bool interior) +{ + if (x==mCurX && y==mCurY && mInterior==interior && !mChanged) return; // don't do anything if we're still in the same cell + for (int mx=0; mx<3; ++mx) + { + for (int my=0; my<3; ++my) + { + std::string image = mPrefix+"_"+ boost::lexical_cast(x + (mx-1)) + "_" + + boost::lexical_cast(y + (interior ? (my-1) : -1*(my-1))); + + std::string name = "Map_" + boost::lexical_cast(mx) + "_" + + boost::lexical_cast(my); + + MyGUI::ImageBox* box = mMapWidgets[my + 3*mx]; + + if (MyGUI::RenderManager::getInstance().getTexture(image) != 0) + box->setImageTexture(image); + else + box->setImageTexture("black.png"); + } + } + mInterior = interior; + mCurX = x; + mCurY = y; + mChanged = false; + applyFogOfWar(); + + // set the compass texture again, because MyGUI determines sorting of ImageBox widgets + // based on the last setImageTexture call + std::string tex = "textures\\compass.dds"; + mCompass->setImageTexture(""); + mCompass->setImageTexture(tex); +} + + +void LocalMapBase::setPlayerPos(const float x, const float y) +{ + if (x == mLastPositionX && y == mLastPositionY) + return; MyGUI::IntSize size = mLocalMap->getCanvasSize(); MyGUI::IntPoint middle = MyGUI::IntPoint((1/3.f + x/3.f)*size.width,(1/3.f + y/3.f)*size.height); MyGUI::IntCoord viewsize = mLocalMap->getCoord(); MyGUI::IntPoint pos(0.5*viewsize.width - middle.left, 0.5*viewsize.height - middle.top); mLocalMap->setViewOffset(pos); - mPlayerArrow->setPosition(MyGUI::IntPoint(x*512-16, y*512-16)); + mCompass->setPosition(MyGUI::IntPoint(512+x*512-16, 512+y*512-16)); mLastPositionX = x; mLastPositionY = y; } -void MapWindow::setPlayerDir(const float x, const float y) +void LocalMapBase::setPlayerDir(const float x, const float y) { - if (!mVisible || (x == mLastDirectionX && y == mLastDirectionY)) return; - MyGUI::ISubWidget* main = mPlayerArrow->getSubWidgetMain(); + if (x == mLastDirectionX && y == mLastDirectionY) + return; + MyGUI::ISubWidget* main = mCompass->getSubWidgetMain(); MyGUI::RotatingSkin* rotatingSubskin = main->castType(); rotatingSubskin->setCenter(MyGUI::IntPoint(16,16)); float angle = std::atan2(x,y); @@ -71,6 +152,37 @@ void MapWindow::setPlayerDir(const float x, const float y) mLastDirectionY = y; } +// ------------------------------------------------------------------------------------------ + +MapWindow::MapWindow(WindowManager& parWindowManager) : + MWGui::WindowPinnableBase("openmw_map_window_layout.xml", parWindowManager), + mGlobal(false) +{ + setCoord(500,0,320,300); + + getWidget(mLocalMap, "LocalMap"); + getWidget(mGlobalMap, "GlobalMap"); + getWidget(mPlayerArrow, "Compass"); + + getWidget(mButton, "WorldButton"); + mButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MapWindow::onWorldButtonClicked); + mButton->setCaptionWithReplacing("#{sWorld}"); + int width = mButton->getTextSize().width + 24; + mButton->setCoord(mMainWidget->getSize().width - width - 22, mMainWidget->getSize().height - 64, width, 22); + + MyGUI::Button* eventbox; + getWidget(eventbox, "EventBox"); + eventbox->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); + eventbox->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); + + LocalMapBase::init(mLocalMap, mPlayerArrow, this); +} + +void MapWindow::setCellName(const std::string& cellName) +{ + setTitle(cellName); +} + void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) { if (_id!=MyGUI::MouseButton::Left) return; @@ -97,7 +209,10 @@ void MapWindow::onWorldButtonClicked(MyGUI::Widget* _sender) mGlobalMap->setVisible(mGlobal); mLocalMap->setVisible(!mGlobal); - mButton->setCaption( mGlobal ? "Local" : "World" ); + mButton->setCaptionWithReplacing( mGlobal ? "#{sLocal}" : + "#{sWorld}"); + int width = mButton->getTextSize().width + 24; + mButton->setCoord(mMainWidget->getSize().width - width - 22, mMainWidget->getSize().height - 64, width, 22); } void MapWindow::onPinToggled() diff --git a/apps/openmw/mwgui/map_window.hpp b/apps/openmw/mwgui/map_window.hpp index d14221a408..8d3392b827 100644 --- a/apps/openmw/mwgui/map_window.hpp +++ b/apps/openmw/mwgui/map_window.hpp @@ -1,19 +1,53 @@ #ifndef MWGUI_MAPWINDOW_H #define MWGUI_MAPWINDOW_H -#include "layouts.hpp" #include "window_pinnable_base.hpp" namespace MWGui { + class LocalMapBase + { + public: + LocalMapBase(); + void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, OEngine::GUI::Layout* layout, bool mapDragAndDrop=false); + + void setCellPrefix(const std::string& prefix); + void setActiveCell(const int x, const int y, bool interior=false); + void setPlayerDir(const float x, const float y); + void setPlayerPos(const float x, const float y); + + void toggleFogOfWar(); + + protected: + int mCurX, mCurY; + bool mInterior; + MyGUI::ScrollView* mLocalMap; + MyGUI::ImageBox* mCompass; + std::string mPrefix; + bool mChanged; + bool mFogOfWar; + + std::vector mMapWidgets; + std::vector mFogWidgets; + + void applyFogOfWar(); + + OEngine::GUI::Layout* mLayout; + + bool mMapDragAndDrop; + + float mLastPositionX; + float mLastPositionY; + float mLastDirectionX; + float mLastDirectionY; + }; + class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase { public: MapWindow(WindowManager& parWindowManager); virtual ~MapWindow(){} - void setPlayerPos(const float x, const float y); - void setPlayerDir(const float x, const float y); void setCellName(const std::string& cellName); private: diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index c103bcb8b8..0bb7d3c1cf 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -15,6 +15,13 @@ void MessageBoxManager::onFrame (float frameDuration) std::vector::iterator it; for(it = mTimers.begin(); it != mTimers.end();) { + // if this messagebox is already deleted, remove the timer and move on + if (std::find(mMessageBoxes.begin(), mMessageBoxes.end(), it->messageBox) == mMessageBoxes.end()) + { + it = mTimers.erase(it); + continue; + } + it->current += frameDuration; if(it->current >= it->max) { @@ -51,7 +58,7 @@ void MessageBoxManager::onFrame (float frameDuration) if(mInterMessageBoxe != NULL && mInterMessageBoxe->mMarkedToDelete) { delete mInterMessageBoxe; mInterMessageBoxe = NULL; - mWindowManager->setNextMode(GM_Game); + mWindowManager->popGuiMode(); } } @@ -154,7 +161,7 @@ MessageBox::MessageBox(MessageBoxManager& parMessageBoxManager, const std::strin getWidget(mMessageWidget, "message"); mMessageWidget->setOverflowToTheLeft(true); - mMessageWidget->addText(cMessage); + mMessageWidget->setCaptionWithReplacing(cMessage); MyGUI::IntSize size; size.width = mFixedWidth; diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp index 55f0952ce5..8b502ce7c9 100644 --- a/apps/openmw/mwgui/mode.hpp +++ b/apps/openmw/mwgui/mode.hpp @@ -5,19 +5,21 @@ namespace MWGui { enum GuiMode { - GM_Game, // Game mode, only HUD + GM_Settings, // Settings window GM_Inventory, // Inventory mode + GM_Container, GM_MainMenu, // Main menu mode GM_Console, // Console mode GM_Journal, // Journal mode - // None of the following are implemented yet + GM_Scroll, // Read scroll + GM_Book, // Read book + GM_Alchemy, // Make potions GM_Dialogue, // NPC interaction GM_Barter, GM_Rest, - // .. more here .. // Startup character creation dialogs GM_Name, diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 880c0bc520..dea365ac23 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -1,7 +1,4 @@ #include "race.hpp" -#include "window_manager.hpp" -#include "widgets.hpp" -#include "components/esm_store/store.hpp" #include #include @@ -10,6 +7,12 @@ #include #include +#include + +#include "window_manager.hpp" +#include "widgets.hpp" +#include "tooltips.hpp" + using namespace MWGui; using namespace Widgets; @@ -51,7 +54,7 @@ RaceDialog::RaceDialog(WindowManager& parWindowManager) prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousFace); nextButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectNextFace); - setText("HairChoiceT", mWindowManager.getGameSettingString("sRaceMenu3", "Change Hair")); + setText("HairChoiceT", mWindowManager.getGameSettingString("sRaceMenu4", "Change Hair")); getWidget(prevButton, "PrevHairButton"); getWidget(nextButton, "NextHairButton"); prevButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onSelectPreviousHair); @@ -69,13 +72,13 @@ RaceDialog::RaceDialog(WindowManager& parWindowManager) setText("SpellPowerT", mWindowManager.getGameSettingString("sRaceMenu7", "Specials")); getWidget(spellPowerList, "SpellPowerList"); - // TODO: These buttons should be managed by a Dialog class MyGUI::ButtonPtr backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onBackClicked); MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); + okButton->setCaption(mWindowManager.getGameSettingString("sOK", "")); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &RaceDialog::onOkClicked); updateRaces(); @@ -91,21 +94,16 @@ void RaceDialog::setNextButtonShow(bool shown) MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); - // TODO: All hardcoded coords for buttons are temporary, will be replaced with a dynamic system. if (shown) - { - okButton->setCaption("Next"); - - // Adjust back button when next is shown - backButton->setCoord(MyGUI::IntCoord(471 - 18, 397, 53, 23)); - okButton->setCoord(MyGUI::IntCoord(532 - 18, 397, 42 + 18, 23)); - } + okButton->setCaption(mWindowManager.getGameSettingString("sNext", "")); else - { - okButton->setCaption("OK"); - backButton->setCoord(MyGUI::IntCoord(471, 397, 53, 23)); - okButton->setCoord(MyGUI::IntCoord(532, 397, 42, 23)); - } + okButton->setCaption(mWindowManager.getGameSettingString("sOK", "")); + + int okButtonWidth = okButton->getTextSize().width + 24; + int backButtonWidth = backButton->getTextSize().width + 24; + + okButton->setCoord(574 - okButtonWidth, 397, okButtonWidth, 23); + backButton->setCoord(574 - okButtonWidth - backButtonWidth - 6, 397, backButtonWidth, 23); } void RaceDialog::open() @@ -260,6 +258,8 @@ void RaceDialog::updateSkills() skillWidget->setWindowManager(&mWindowManager); skillWidget->setSkillNumber(skillId); skillWidget->setSkillValue(MWSkill::SkillValue(race->data.bonus[i].bonus)); + ToolTips::createSkillToolTip(skillWidget, skillId); + skillItems.push_back(skillWidget); @@ -293,6 +293,8 @@ void RaceDialog::updateSpellPowers() spellPowerWidget = spellPowerList->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + boost::lexical_cast(i)); spellPowerWidget->setWindowManager(&mWindowManager); spellPowerWidget->setSpellId(spellpower); + spellPowerWidget->setUserString("ToolTipType", "Spell"); + spellPowerWidget->setUserString("Spell", spellpower); spellPowerItems.push_back(spellPowerWidget); diff --git a/apps/openmw/mwgui/referenceinterface.cpp b/apps/openmw/mwgui/referenceinterface.cpp new file mode 100644 index 0000000000..c6e710952f --- /dev/null +++ b/apps/openmw/mwgui/referenceinterface.cpp @@ -0,0 +1,28 @@ +#include "referenceinterface.hpp" + +#include "../mwworld/player.hpp" +#include "../mwworld/world.hpp" +#include "../mwbase/environment.hpp" + +namespace MWGui +{ + ReferenceInterface::ReferenceInterface() + : mCurrentPlayerCell(NULL) + { + } + + void ReferenceInterface::checkReferenceAvailable() + { + if (mPtr.isEmpty()) + return; + + MWWorld::Ptr::CellStore* playerCell = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell(); + + // check if player has changed cell, or count of the reference has become 0 + if ((playerCell != mCurrentPlayerCell && mCurrentPlayerCell != NULL) + || mPtr.getRefData().getCount() == 0) + onReferenceUnavailable(); + + mCurrentPlayerCell = playerCell; + } +} diff --git a/apps/openmw/mwgui/referenceinterface.hpp b/apps/openmw/mwgui/referenceinterface.hpp new file mode 100644 index 0000000000..40844b2388 --- /dev/null +++ b/apps/openmw/mwgui/referenceinterface.hpp @@ -0,0 +1,29 @@ +#ifndef MWGUI_REFERENCEINTERFACE_H +#define MWGUI_REFERENCEINTERFACE_H + +#include "../mwworld/ptr.hpp" + +namespace MWGui +{ + /// \brief this class is intended for GUI interfaces that access an MW-Reference + /// for example dialogue window accesses an NPC, or Container window accesses a Container + /// these classes have to be automatically closed if the reference becomes unavailable + /// make sure that checkReferenceAvailable() is called every frame and that onReferenceUnavailable() has been overridden + class ReferenceInterface + { + public: + ReferenceInterface(); + + void checkReferenceAvailable(); ///< closes the window, if the MW-reference has become unavailable + + protected: + virtual void onReferenceUnavailable() = 0; ///< called when reference has become unavailable + + MWWorld::Ptr mPtr; + + private: + MWWorld::Ptr::CellStore* mCurrentPlayerCell; + }; +} + +#endif diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index cb0d9969c6..97a3a6e154 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -1,12 +1,15 @@ #include "review.hpp" -#include "window_manager.hpp" -#include "widgets.hpp" -#include "components/esm_store/store.hpp" + +#include #include #include -#include +#include + +#include "window_manager.hpp" +#include "widgets.hpp" +#include "tooltips.hpp" #undef min #undef max @@ -27,22 +30,22 @@ ReviewDialog::ReviewDialog(WindowManager& parWindowManager) ButtonPtr button; getWidget(nameWidget, "NameText"); getWidget(button, "NameButton"); - button->setCaption(mWindowManager.getGameSettingString("sName", "")); + adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onNameClicked);; getWidget(raceWidget, "RaceText"); getWidget(button, "RaceButton"); - button->setCaption(mWindowManager.getGameSettingString("sRace", "")); + adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onRaceClicked);; getWidget(classWidget, "ClassText"); getWidget(button, "ClassButton"); - button->setCaption(mWindowManager.getGameSettingString("sClass", "")); + adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onClassClicked);; getWidget(birthSignWidget, "SignText"); getWidget(button, "SignButton"); - button->setCaption(mWindowManager.getGameSettingString("sBirthSign", "")); + adjustButtonSize(button); button->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBirthSignClicked);; // Setup dynamic stats @@ -74,7 +77,7 @@ ReviewDialog::ReviewDialog(WindowManager& parWindowManager) getWidget(skillAreaWidget, "Skills"); getWidget(skillClientWidget, "SkillClient"); getWidget(skillScrollerWidget, "SkillScroller"); - + skillClientWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); skillScrollerWidget->eventScrollChangePosition += MyGUI::newDelegate(this, &ReviewDialog::onScrollChangePosition); updateScroller(); @@ -86,14 +89,19 @@ ReviewDialog::ReviewDialog(WindowManager& parWindowManager) static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &ReviewDialog::onWindowResize); - // TODO: These buttons should be managed by a Dialog class MyGUI::ButtonPtr backButton; getWidget(backButton, "BackButton"); backButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onBackClicked); MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); + okButton->setCaption(mWindowManager.getGameSettingString("sOK", "")); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ReviewDialog::onOkClicked); + + int backButtonWidth = backButton->getTextSize().width + 24; + int okButtonWidth = okButton->getTextSize().width + 24; + okButton->setCoord(502 - okButtonWidth, 372, okButtonWidth, 23); + backButton->setCoord(502 - okButtonWidth - backButtonWidth - 6, 372, backButtonWidth, 23); } void ReviewDialog::open() @@ -132,13 +140,17 @@ void ReviewDialog::setRace(const std::string &raceId_) raceId = raceId_; const ESM::Race *race = mWindowManager.getStore().races.search(raceId); if (race) + { + ToolTips::createRaceToolTip(raceWidget, race); raceWidget->setCaption(race->name); + } } void ReviewDialog::setClass(const ESM::Class& class_) { klass = class_; classWidget->setCaption(klass.name); + ToolTips::createClassToolTip(classWidget, klass); } void ReviewDialog::setBirthSign(const std::string& signId) @@ -146,22 +158,31 @@ void ReviewDialog::setBirthSign(const std::string& signId) birthSignId = signId; const ESM::BirthSign *sign = mWindowManager.getStore().birthSigns.search(birthSignId); if (sign) + { birthSignWidget->setCaption(sign->name); + ToolTips::createBirthsignToolTip(birthSignWidget, birthSignId); + } } void ReviewDialog::setHealth(const MWMechanics::DynamicStat& value) { health->setValue(value.getCurrent(), value.getModified()); + std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); + health->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); } void ReviewDialog::setMagicka(const MWMechanics::DynamicStat& value) { magicka->setValue(value.getCurrent(), value.getModified()); + std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); + magicka->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); } void ReviewDialog::setFatigue(const MWMechanics::DynamicStat& value) { fatigue->setValue(value.getCurrent(), value.getModified()); + std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); + fatigue->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); } void ReviewDialog::setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::Stat& value) @@ -181,14 +202,16 @@ void ReviewDialog::setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanic { float modified = value.getModified(), base = value.getBase(); std::string text = boost::lexical_cast(std::floor(modified)); - ColorStyle style = CS_Normal; + std::string state = "normal"; if (modified > base) - style = CS_Super; + state = "increased"; else if (modified < base) - style = CS_Sub; + state = "decreased"; - setStyledText(widget, style, text); + widget->setCaption(text); + widget->_setWidgetState(state); } + } void ReviewDialog::configureSkills(const std::vector& major, const std::vector& minor) @@ -208,22 +231,15 @@ void ReviewDialog::configureSkills(const std::vector& major, const std::vec if (skillSet.find(skill) == skillSet.end()) miscSkills.push_back(skill); } -} -void ReviewDialog::setStyledText(MyGUI::TextBox* widget, ColorStyle style, const std::string &value) -{ - widget->setCaption(value); - if (style == CS_Super) - widget->setTextColour(MyGUI::Colour(0, 1, 0)); - else if (style == CS_Sub) - widget->setTextColour(MyGUI::Colour(1, 0, 0)); - else - widget->setTextColour(MyGUI::Colour(1, 1, 1)); + updateSkillArea(); } void ReviewDialog::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::ImageBox* separator = skillClientWidget->createWidget("MW_HLine", MyGUI::IntCoord(10, coord1.top, coord1.width + coord2.width - 4, 18), MyGUI::Align::Default); + separator->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); + skillWidgets.push_back(separator); coord1.top += separator->getHeight(); @@ -233,6 +249,7 @@ void ReviewDialog::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2 void ReviewDialog::addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::TextBox* groupWidget = skillClientWidget->createWidget("SandBrightText", MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), MyGUI::Align::Default); + groupWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); groupWidget->setCaption(label); skillWidgets.push_back(groupWidget); @@ -240,16 +257,19 @@ void ReviewDialog::addGroup(const std::string &label, MyGUI::IntCoord &coord1, M coord2.top += lineHeight; } -MyGUI::TextBox* ReviewDialog::addValueItem(const std::string text, const std::string &value, ColorStyle style, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) +MyGUI::TextBox* ReviewDialog::addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::TextBox* skillNameWidget; MyGUI::TextBox* skillValueWidget; skillNameWidget = skillClientWidget->createWidget("SandText", coord1, MyGUI::Align::Default); skillNameWidget->setCaption(text); + skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); - skillValueWidget = skillClientWidget->createWidget("SandTextRight", coord2, MyGUI::Align::Default); - setStyledText(skillValueWidget, style, value); + skillValueWidget = skillClientWidget->createWidget("SandTextRight", coord2, MyGUI::Align::Top | MyGUI::Align::Right); + skillValueWidget->setCaption(value); + skillValueWidget->_setWidgetState(state); + skillValueWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); skillWidgets.push_back(skillNameWidget); skillWidgets.push_back(skillValueWidget); @@ -260,12 +280,13 @@ MyGUI::TextBox* ReviewDialog::addValueItem(const std::string text, const std::st return skillValueWidget; } -void ReviewDialog::addItem(const std::string text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) +void ReviewDialog::addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::TextBox* skillNameWidget; skillNameWidget = skillClientWidget->createWidget("SandText", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default); skillNameWidget->setCaption(text); + skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); skillWidgets.push_back(skillNameWidget); @@ -295,12 +316,18 @@ void ReviewDialog::addSkills(const SkillList &skills, const std::string &titleId float base = stat.getBase(); float modified = stat.getModified(); - ColorStyle style = CS_Normal; + std::string state = "normal"; if (modified > base) - style = CS_Super; + state = "increased"; else if (modified < base) - style = CS_Sub; - MyGUI::TextBox* widget = addValueItem(mWindowManager.getGameSettingString(skillNameId, skillNameId), boost::lexical_cast(static_cast(modified)), style, coord1, coord2); + state = "decreased"; + MyGUI::TextBox* widget = addValueItem(mWindowManager.getGameSettingString(skillNameId, skillNameId), boost::lexical_cast(static_cast(modified)), state, coord1, coord2); + + for (int i=0; i<2; ++i) + { + ToolTips::createSkillToolTip(skillWidgets[skillWidgets.size()-1-i], skillId); + } + skillWidgetMap[skillId] = widget; } } @@ -334,6 +361,8 @@ void ReviewDialog::updateScroller() { skillScrollerWidget->setScrollRange(std::max(clientHeight - skillClientWidget->getHeight(), 0)); skillScrollerWidget->setScrollPage(std::max(skillClientWidget->getHeight() - lineHeight, 0)); + if (clientHeight != 0) + skillScrollerWidget->setTrackSize( (skillAreaWidget->getHeight() / float(clientHeight)) * skillScrollerWidget->getLineSize() ); } // widget controls @@ -367,3 +396,15 @@ void ReviewDialog::onBirthSignClicked(MyGUI::Widget* _sender) { eventActivateDialog(BIRTHSIGN_DIALOG); } + +void ReviewDialog::onMouseWheel(MyGUI::Widget* _sender, int _rel) +{ + if (skillScrollerWidget->getScrollPosition() - _rel*0.3 < 0) + skillScrollerWidget->setScrollPosition(0); + else if (skillScrollerWidget->getScrollPosition() - _rel*0.3 > skillScrollerWidget->getScrollRange()-1) + skillScrollerWidget->setScrollPosition(skillScrollerWidget->getScrollRange()-1); + else + skillScrollerWidget->setScrollPosition(skillScrollerWidget->getScrollPosition() - _rel*0.3); + + onScrollChangePosition(skillScrollerWidget, skillScrollerWidget->getScrollPosition()); +} diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index 588c1b6b5d..6118168d57 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -68,19 +68,14 @@ namespace MWGui void onClassClicked(MyGUI::Widget* _sender); void onBirthSignClicked(MyGUI::Widget* _sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + private: - enum ColorStyle - { - CS_Sub, - CS_Normal, - CS_Super - }; - void setStyledText(MyGUI::TextBox* widget, ColorStyle style, const std::string &value); void addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); - MyGUI::TextBox* addValueItem(const std::string text, const std::string &value, ColorStyle style, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); - void addItem(const std::string text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); + MyGUI::TextBox* addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); + void addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void updateScroller(); void updateSkillArea(); diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp new file mode 100644 index 0000000000..f4d45fc265 --- /dev/null +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -0,0 +1,70 @@ +#include "scrollwindow.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwinput/inputmanager.hpp" +#include "../mwworld/actiontake.hpp" +#include "../mwsound/soundmanager.hpp" + +#include "formatting.hpp" +#include "window_manager.hpp" + +using namespace MWGui; + +ScrollWindow::ScrollWindow (WindowManager& parWindowManager) : + WindowBase("openmw_scroll_layout.xml", parWindowManager) +{ + getWidget(mTextView, "TextView"); + + getWidget(mCloseButton, "CloseButton"); + mCloseButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ScrollWindow::onCloseButtonClicked); + + getWidget(mTakeButton, "TakeButton"); + mTakeButton->eventMouseButtonClick += MyGUI::newDelegate(this, &ScrollWindow::onTakeButtonClicked); + + center(); +} + +void ScrollWindow::open (MWWorld::Ptr scroll) +{ + // no 3d sounds because the object could be in a container. + MWBase::Environment::get().getSoundManager()->playSound ("scroll", 1.0, 1.0); + + mScroll = scroll; + + ESMS::LiveCellRef *ref = + mScroll.get(); + + BookTextParser parser; + MyGUI::IntSize size = parser.parse(ref->base->text, mTextView, 390); + + if (size.height > mTextView->getSize().height) + mTextView->setCanvasSize(MyGUI::IntSize(410, size.height)); + else + mTextView->setCanvasSize(410, mTextView->getSize().height); + + mTextView->setViewOffset(MyGUI::IntPoint(0,0)); + + setTakeButtonShow(true); +} + +void ScrollWindow::setTakeButtonShow(bool show) +{ + mTakeButton->setVisible(show); +} + +void ScrollWindow::onCloseButtonClicked (MyGUI::Widget* _sender) +{ + MWBase::Environment::get().getSoundManager()->playSound ("scroll", 1.0, 1.0); + + mWindowManager.popGuiMode(); +} + +void ScrollWindow::onTakeButtonClicked (MyGUI::Widget* _sender) +{ + MWBase::Environment::get().getSoundManager()->playSound ("Item Book Up", 1.0, 1.0, MWSound::Play_NoTrack); + + MWWorld::ActionTake take(mScroll); + take.execute(); + + mWindowManager.popGuiMode(); +} diff --git a/apps/openmw/mwgui/scrollwindow.hpp b/apps/openmw/mwgui/scrollwindow.hpp new file mode 100644 index 0000000000..d58596b4be --- /dev/null +++ b/apps/openmw/mwgui/scrollwindow.hpp @@ -0,0 +1,32 @@ +#ifndef MWGUI_SCROLLWINDOW_H +#define MWGUI_SCROLLWINDOW_H + +#include "window_base.hpp" + +#include "../mwworld/ptr.hpp" + +namespace MWGui +{ + class ScrollWindow : public WindowBase + { + public: + ScrollWindow (WindowManager& parWindowManager); + + void open (MWWorld::Ptr scroll); + void setTakeButtonShow(bool show); + + protected: + void onCloseButtonClicked (MyGUI::Widget* _sender); + void onTakeButtonClicked (MyGUI::Widget* _sender); + + private: + MyGUI::Button* mCloseButton; + MyGUI::Button* mTakeButton; + MyGUI::ScrollView* mTextView; + + MWWorld::Ptr mScroll; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp new file mode 100644 index 0000000000..67ccf9d531 --- /dev/null +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -0,0 +1,342 @@ +#include "settingswindow.hpp" + +#include +#include +#include + +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/world.hpp" +#include "../mwsound/soundmanager.hpp" +#include "../mwinput/inputmanager.hpp" + +#include "window_manager.hpp" +#include "confirmationdialog.hpp" + +namespace +{ + std::string fpsLevelToStr(int level) + { + if (level == 0) + return "#{sOff}"; + else if (level == 1) + return "Basic"; + else + return "Detailed"; + } + + std::string textureFilteringToStr(const std::string& val) + { + if (val == "none") + return "None"; + else if (val == "anisotropic") + return "Anisotropic"; + else if (val == "bilinear") + return "Bilinear"; + else + return "Trilinear"; + } +} + +namespace MWGui +{ + SettingsWindow::SettingsWindow(WindowManager& parWindowManager) : + WindowBase("openmw_settings_window_layout.xml", parWindowManager) + { + getWidget(mOkButton, "OkButton"); + getWidget(mResolutionList, "ResolutionList"); + getWidget(mMenuTransparencySlider, "MenuTransparencySlider"); + getWidget(mToolTipDelaySlider, "ToolTipDelaySlider"); + getWidget(mViewDistanceSlider, "ViewDistanceSlider"); + getWidget(mFullscreenButton, "FullscreenButton"); + getWidget(mVSyncButton, "VSyncButton"); + getWidget(mFPSButton, "FPSButton"); + getWidget(mFOVSlider, "FOVSlider"); + getWidget(mMasterVolumeSlider, "MasterVolume"); + getWidget(mVoiceVolumeSlider, "VoiceVolume"); + getWidget(mEffectsVolumeSlider, "EffectsVolume"); + getWidget(mFootstepsVolumeSlider, "FootstepsVolume"); + getWidget(mMusicVolumeSlider, "MusicVolume"); + getWidget(mAnisotropySlider, "AnisotropySlider"); + getWidget(mTextureFilteringButton, "TextureFilteringButton"); + getWidget(mAnisotropyLabel, "AnisotropyLabel"); + getWidget(mAnisotropyBox, "AnisotropyBox"); + getWidget(mWaterShaderButton, "WaterShaderButton"); + getWidget(mReflectObjectsButton, "ReflectObjectsButton"); + getWidget(mReflectActorsButton, "ReflectActorsButton"); + getWidget(mReflectTerrainButton, "ReflectTerrainButton"); + + mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); + mFullscreenButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mWaterShaderButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mReflectObjectsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mReflectTerrainButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mReflectActorsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mTextureFilteringButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringToggled); + mVSyncButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mFPSButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onFpsToggled); + mMenuTransparencySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + mFOVSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + mToolTipDelaySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + mViewDistanceSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + mResolutionList->eventListChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onResolutionSelected); + mAnisotropySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + + mMasterVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + mVoiceVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + mEffectsVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + mFootstepsVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + mMusicVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); + + center(); + + int okSize = mOkButton->getTextSize().width + 24; + mOkButton->setCoord(mMainWidget->getWidth()-16-okSize, mOkButton->getTop(), + okSize, mOkButton->getHeight()); + + // fill resolution list + Ogre::RenderSystem* rs = Ogre::Root::getSingleton().getRenderSystem(); + const Ogre::StringVector& videoModes = rs->getConfigOptions()["Video Mode"].possibleValues; + for (Ogre::StringVector::const_iterator it=videoModes.begin(); + it!=videoModes.end(); ++it) + { + mResolutionList->addItem(*it); + } + + // read settings + int menu_transparency = (mMenuTransparencySlider->getScrollRange()-1) * Settings::Manager::getFloat("menu transparency", "GUI"); + mMenuTransparencySlider->setScrollPosition(menu_transparency); + int tooltip_delay = (mToolTipDelaySlider->getScrollRange()-1) * Settings::Manager::getFloat("tooltip delay", "GUI"); + mToolTipDelaySlider->setScrollPosition(tooltip_delay); + + float fovVal = (Settings::Manager::getFloat("field of view", "General")-sFovMin)/(sFovMax-sFovMin); + mFOVSlider->setScrollPosition(fovVal * (mFOVSlider->getScrollRange()-1)); + MyGUI::TextBox* fovText; + getWidget(fovText, "FovText"); + fovText->setCaption("Field of View (" + boost::lexical_cast(int(Settings::Manager::getFloat("field of view", "General"))) + ")"); + + float anisotropyVal = Settings::Manager::getInt("anisotropy", "General") / 16.0; + mAnisotropySlider->setScrollPosition(anisotropyVal * (mAnisotropySlider->getScrollRange()-1)); + std::string tf = Settings::Manager::getString("texture filtering", "General"); + mTextureFilteringButton->setCaption(textureFilteringToStr(tf)); + mAnisotropyLabel->setCaption("Anisotropy (" + boost::lexical_cast(Settings::Manager::getInt("anisotropy", "General")) + ")"); + mAnisotropyBox->setVisible(tf == "anisotropic"); + + float val = (Settings::Manager::getFloat("max viewing distance", "Viewing distance")-sViewDistMin)/(sViewDistMax-sViewDistMin); + int viewdist = (mViewDistanceSlider->getScrollRange()-1) * val; + mViewDistanceSlider->setScrollPosition(viewdist); + + mMasterVolumeSlider->setScrollPosition(Settings::Manager::getFloat("master volume", "Sound") * (mMasterVolumeSlider->getScrollRange()-1)); + mMusicVolumeSlider->setScrollPosition(Settings::Manager::getFloat("music volume", "Sound") * (mMusicVolumeSlider->getScrollRange()-1)); + mEffectsVolumeSlider->setScrollPosition(Settings::Manager::getFloat("sfx volume", "Sound") * (mEffectsVolumeSlider->getScrollRange()-1)); + mFootstepsVolumeSlider->setScrollPosition(Settings::Manager::getFloat("footsteps volume", "Sound") * (mFootstepsVolumeSlider->getScrollRange()-1)); + mVoiceVolumeSlider->setScrollPosition(Settings::Manager::getFloat("voice volume", "Sound") * (mVoiceVolumeSlider->getScrollRange()-1)); + + mWaterShaderButton->setCaptionWithReplacing(Settings::Manager::getBool("shader", "Water") ? "#{sOn}" : "#{sOff}"); + mReflectObjectsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect objects", "Water") ? "#{sOn}" : "#{sOff}"); + mReflectActorsButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect actors", "Water") ? "#{sOn}" : "#{sOff}"); + mReflectTerrainButton->setCaptionWithReplacing(Settings::Manager::getBool("reflect terrain", "Water") ? "#{sOn}" : "#{sOff}"); + + if (!MWRender::RenderingManager::waterShaderSupported()) + { + mWaterShaderButton->setEnabled(false); + mReflectObjectsButton->setEnabled(false); + mReflectActorsButton->setEnabled(false); + mReflectTerrainButton->setEnabled(false); + } + + mFullscreenButton->setCaptionWithReplacing(Settings::Manager::getBool("fullscreen", "Video") ? "#{sOn}" : "#{sOff}"); + mVSyncButton->setCaptionWithReplacing(Settings::Manager::getBool("vsync", "Video") ? "#{sOn}": "#{sOff}"); + mFPSButton->setCaptionWithReplacing(fpsLevelToStr(Settings::Manager::getInt("fps", "HUD"))); + } + + void SettingsWindow::onOkButtonClicked(MyGUI::Widget* _sender) + { + mWindowManager.popGuiMode(); + } + + void SettingsWindow::onResolutionSelected(MyGUI::ListBox* _sender, size_t index) + { + if (index == MyGUI::ITEM_NONE) + return; + + ConfirmationDialog* dialog = mWindowManager.getConfirmationDialog(); + dialog->open("#{sNotifyMessage67}"); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &SettingsWindow::onResolutionAccept); + dialog->eventCancelClicked.clear(); + dialog->eventCancelClicked += MyGUI::newDelegate(this, &SettingsWindow::onResolutionAccept); + } + + void SettingsWindow::onResolutionAccept() + { + std::string resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected()); + size_t xPos = resStr.find("x"); + std::string resXStr = resStr.substr(0, xPos-1); + Ogre::StringUtil::trim(resXStr); + std::string resYStr = resStr.substr(xPos+2, resStr.size()-(xPos+2)); + Ogre::StringUtil::trim(resYStr); + int resX = boost::lexical_cast(resXStr); + int resY = boost::lexical_cast(resYStr); + + Settings::Manager::setInt("resolution x", "Video", resX); + Settings::Manager::setInt("resolution y", "Video", resY); + + apply(); + mResolutionList->setIndexSelected(MyGUI::ITEM_NONE); + } + + void SettingsWindow::onResolutionCancel() + { + mResolutionList->setIndexSelected(MyGUI::ITEM_NONE); + } + + void SettingsWindow::onButtonToggled(MyGUI::Widget* _sender) + { + std::string on = mWindowManager.getGameSettingString("sOn", "On"); + std::string off = mWindowManager.getGameSettingString("sOff", "On"); + bool newState; + if (_sender->castType()->getCaption() == on) + { + _sender->castType()->setCaption(off); + newState = false; + } + else + { + _sender->castType()->setCaption(on); + newState = true; + } + + if (_sender == mFullscreenButton) + { + // check if this resolution is supported in fullscreen + bool supported = false; + for (unsigned int i=0; igetItemCount(); ++i) + { + std::string resStr = mResolutionList->getItemNameAt(i); + size_t xPos = resStr.find("x"); + std::string resXStr = resStr.substr(0, xPos-1); + Ogre::StringUtil::trim(resXStr); + std::string resYStr = resStr.substr(xPos+2, resStr.size()-(xPos+2)); + Ogre::StringUtil::trim(resYStr); + int resX = boost::lexical_cast(resXStr); + int resY = boost::lexical_cast(resYStr); + + if (resX == Settings::Manager::getInt("resolution x", "Video") + && resY == Settings::Manager::getInt("resolution y", "Video")) + supported = true; + } + + if (!supported) + { + std::string msg = "This resolution is not supported in Fullscreen mode. Please select a resolution from the list."; + MWBase::Environment::get().getWindowManager()-> + messageBox(msg, std::vector()); + _sender->castType()->setCaption(off); + } + else + { + Settings::Manager::setBool("fullscreen", "Video", newState); + apply(); + } + } + else if (_sender == mVSyncButton) + { + Settings::Manager::setBool("vsync", "Video", newState); + MWBase::Environment::get().getWindowManager()-> + messageBox("VSync will be applied after a restart", std::vector()); + } + else + { + if (_sender == mWaterShaderButton) + Settings::Manager::setBool("shader", "Water", newState); + else if (_sender == mReflectObjectsButton) + { + Settings::Manager::setBool("reflect misc", "Water", newState); + Settings::Manager::setBool("reflect statics", "Water", newState); + Settings::Manager::setBool("reflect statics small", "Water", newState); + } + else if (_sender == mReflectActorsButton) + Settings::Manager::setBool("reflect actors", "Water", newState); + else if (_sender == mReflectTerrainButton) + Settings::Manager::setBool("reflect terrain", "Water", newState); + + apply(); + } + } + + void SettingsWindow::onFpsToggled(MyGUI::Widget* _sender) + { + int newLevel = (Settings::Manager::getInt("fps", "HUD") + 1) % 3; + Settings::Manager::setInt("fps", "HUD", newLevel); + mFPSButton->setCaptionWithReplacing(fpsLevelToStr(newLevel)); + apply(); + } + + void SettingsWindow::onTextureFilteringToggled(MyGUI::Widget* _sender) + { + std::string current = Settings::Manager::getString("texture filtering", "General"); + std::string next; + if (current == "none") + next = "bilinear"; + else if (current == "bilinear") + next = "trilinear"; + else if (current == "trilinear") + next = "anisotropic"; + else + next = "none"; + + mTextureFilteringButton->setCaption(textureFilteringToStr(next)); + mAnisotropyBox->setVisible(next == "anisotropic"); + + Settings::Manager::setString("texture filtering", "General", next); + apply(); + } + + void SettingsWindow::onSliderChangePosition(MyGUI::ScrollBar* scroller, size_t pos) + { + float val = pos / float(scroller->getScrollRange()-1); + if (scroller == mMenuTransparencySlider) + Settings::Manager::setFloat("menu transparency", "GUI", val); + else if (scroller == mToolTipDelaySlider) + Settings::Manager::setFloat("tooltip delay", "GUI", val); + else if (scroller == mViewDistanceSlider) + Settings::Manager::setFloat("max viewing distance", "Viewing distance", (1-val) * sViewDistMin + val * sViewDistMax); + else if (scroller == mFOVSlider) + { + MyGUI::TextBox* fovText; + getWidget(fovText, "FovText"); + fovText->setCaption("Field of View (" + boost::lexical_cast(int((1-val) * sFovMin + val * sFovMax)) + ")"); + Settings::Manager::setFloat("field of view", "General", (1-val) * sFovMin + val * sFovMax); + } + else if (scroller == mAnisotropySlider) + { + mAnisotropyLabel->setCaption("Anisotropy (" + boost::lexical_cast(int(val*16)) + ")"); + Settings::Manager::setInt("anisotropy", "General", val * 16); + } + else if (scroller == mMasterVolumeSlider) + Settings::Manager::setFloat("master volume", "Sound", val); + else if (scroller == mVoiceVolumeSlider) + Settings::Manager::setFloat("voice volume", "Sound", val); + else if (scroller == mEffectsVolumeSlider) + Settings::Manager::setFloat("sfx volume", "Sound", val); + else if (scroller == mFootstepsVolumeSlider) + Settings::Manager::setFloat("footsteps volume", "Sound", val); + else if (scroller == mMusicVolumeSlider) + Settings::Manager::setFloat("music volume", "Sound", val); + + apply(); + } + + void SettingsWindow::apply() + { + const Settings::CategorySettingVector changed = Settings::Manager::apply(); + MWBase::Environment::get().getWorld()->processChangedSettings(changed); + MWBase::Environment::get().getSoundManager()->processChangedSettings(changed); + MWBase::Environment::get().getWindowManager()->processChangedSettings(changed); + MWBase::Environment::get().getInputManager()->processChangedSettings(changed); + } +} diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp new file mode 100644 index 0000000000..ce95edbd2e --- /dev/null +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -0,0 +1,67 @@ +#ifndef MWGUI_SETTINGS_H +#define MWGUI_SETTINGS_H + +#include "window_base.hpp" + +namespace MWGui +{ + class WindowManager; +} + +namespace MWGui +{ + class SettingsWindow : public WindowBase + { + public: + SettingsWindow(WindowManager& parWindowManager); + + private: + static const float sFovMin = 30; + static const float sFovMax = 140; + static const float sViewDistMin = 2000; + static const float sViewDistMax = 5600; + + protected: + MyGUI::Button* mOkButton; + + MyGUI::ScrollBar* mMenuTransparencySlider; + MyGUI::ScrollBar* mToolTipDelaySlider; + + // graphics + MyGUI::ListBox* mResolutionList; + MyGUI::Button* mFullscreenButton; + MyGUI::Button* mVSyncButton; + MyGUI::Button* mFPSButton; + MyGUI::ScrollBar* mViewDistanceSlider; + MyGUI::ScrollBar* mFOVSlider; + MyGUI::ScrollBar* mAnisotropySlider; + MyGUI::Button* mTextureFilteringButton; + MyGUI::TextBox* mAnisotropyLabel; + MyGUI::Widget* mAnisotropyBox; + MyGUI::Button* mWaterShaderButton; + MyGUI::Button* mReflectObjectsButton; + MyGUI::Button* mReflectActorsButton; + MyGUI::Button* mReflectTerrainButton; + + // audio + MyGUI::ScrollBar* mMasterVolumeSlider; + MyGUI::ScrollBar* mVoiceVolumeSlider; + MyGUI::ScrollBar* mEffectsVolumeSlider; + MyGUI::ScrollBar* mFootstepsVolumeSlider; + MyGUI::ScrollBar* mMusicVolumeSlider; + + void onOkButtonClicked(MyGUI::Widget* _sender); + void onFpsToggled(MyGUI::Widget* _sender); + void onTextureFilteringToggled(MyGUI::Widget* _sender); + void onSliderChangePosition(MyGUI::ScrollBar* scroller, size_t pos); + void onButtonToggled(MyGUI::Widget* _sender); + void onResolutionSelected(MyGUI::ListBox* _sender, size_t index); + void onResolutionAccept(); + void onResolutionCancel(); + + void apply(); + }; +} + +#endif + diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp new file mode 100644 index 0000000000..d34ce68d93 --- /dev/null +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -0,0 +1,459 @@ +#include "spellwindow.hpp" + +#include +#include +#include + +#include "../mwworld/world.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwbase/environment.hpp" +#include "../mwmechanics/spells.hpp" +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/spellsuccess.hpp" +#include "../mwsound/soundmanager.hpp" + +#include "window_manager.hpp" +#include "inventorywindow.hpp" +#include "confirmationdialog.hpp" + +namespace +{ + bool sortSpells(const std::string& left, const std::string& right) + { + const ESM::Spell* a = MWBase::Environment::get().getWorld()->getStore().spells.find(left); + const ESM::Spell* b = MWBase::Environment::get().getWorld()->getStore().spells.find(right); + + int cmp = a->name.compare(b->name); + return cmp < 0; + } + + bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right) + { + int cmp = MWWorld::Class::get(left).getName(left).compare( + MWWorld::Class::get(right).getName(right)); + return cmp < 0; + } +} + +namespace MWGui +{ + SpellWindow::SpellWindow(WindowManager& parWindowManager) + : WindowPinnableBase("openmw_spell_window_layout.xml", parWindowManager) + , mHeight(0) + , mWidth(0) + { + getWidget(mSpellView, "SpellView"); + getWidget(mEffectBox, "EffectsBox"); + + setCoord(498, 300, 302, 300); + + updateSpells(); + + mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SpellWindow::onWindowResize); + } + + void SpellWindow::onPinToggled() + { + mWindowManager.setSpellVisibility(!mPinned); + } + + void SpellWindow::open() + { + updateSpells(); + } + + void SpellWindow::updateSpells() + { + const int spellHeight = 18; + + mHeight = 0; + while (mSpellView->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mSpellView->getChildAt(0)); + + // retrieve all player spells, divide them into Powers and Spells and sort them + std::vector spellList; + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); + MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWMechanics::Spells& spells = stats.mSpells; + + // the following code switches between selected enchanted item and selected spell (only one of these + // can be active at a time) + std::string selectedSpell = spells.getSelectedSpell(); + MWWorld::Ptr selectedItem; + if (store.getSelectedEnchantItem() != store.end()) + { + selectedSpell = ""; + selectedItem = *store.getSelectedEnchantItem(); + + bool allowSelectedItem = true; + + // make sure that the item is still in the player inventory, otherwise it can't be selected + bool found = false; + for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it) + { + if (*it == selectedItem) + found = true; + } + if (!found) + allowSelectedItem = false; + + // if the selected item can be equipped, make sure that it actually is equipped + std::pair, bool> slots; + slots = MWWorld::Class::get(selectedItem).getEquipmentSlots(selectedItem); + if (!slots.first.empty()) + { + bool equipped = false; + for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) + { + if (store.getSlot(i) != store.end() && *store.getSlot(i) == selectedItem) + { + equipped = true; + break; + } + } + + if (!equipped) + allowSelectedItem = false; + } + + if (!allowSelectedItem) + { + store.setSelectedEnchantItem(store.end()); + spells.setSelectedSpell(""); + mWindowManager.unsetSelectedSpell(); + selectedItem = MWWorld::Ptr(); + } + } + + + + for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + { + spellList.push_back(*it); + } + + std::vector powers; + std::vector::iterator it = spellList.begin(); + while (it != spellList.end()) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(*it); + if (spell->data.type == ESM::Spell::ST_Power) + { + powers.push_back(*it); + it = spellList.erase(it); + } + else if (spell->data.type == ESM::Spell::ST_Ability) + { + // abilities are always active and don't show in the spell window. + it = spellList.erase(it); + } + else + ++it; + } + std::sort(powers.begin(), powers.end(), sortSpells); + std::sort(spellList.begin(), spellList.end(), sortSpells); + + // retrieve player's enchanted items + std::vector items; + for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it) + { + std::string enchantId = MWWorld::Class::get(*it).getEnchantment(*it); + if (enchantId != "") + { + // only add items with "Cast once" or "Cast on use" + const ESM::Enchantment* enchant = MWBase::Environment::get().getWorld()->getStore().enchants.find(enchantId); + int type = enchant->data.type; + if (type != ESM::Enchantment::CastOnce + && type != ESM::Enchantment::WhenUsed) + continue; + + items.push_back(*it); + } + } + std::sort(items.begin(), items.end(), sortItems); + + + int height = estimateHeight(items.size() + powers.size() + spellList.size()); + bool scrollVisible = height > mSpellView->getHeight(); + mWidth = mSpellView->getWidth() - (scrollVisible ? 18 : 0); + + // powers + addGroup("#{sPowers}", ""); + + for (std::vector::const_iterator it = powers.begin(); it != powers.end(); ++it) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(*it); + MyGUI::Button* t = mSpellView->createWidget("SpellText", + MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + t->setCaption(spell->name); + t->setTextAlign(MyGUI::Align::Left); + t->setUserString("ToolTipType", "Spell"); + t->setUserString("Spell", *it); + t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); + t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); + + if (*it == selectedSpell) + t->setStateSelected(true); + + mHeight += spellHeight; + } + + // other spells + addGroup("#{sSpells}", "#{sCostChance}"); + for (std::vector::const_iterator it = spellList.begin(); it != spellList.end(); ++it) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(*it); + MyGUI::Button* t = mSpellView->createWidget("SpellText", + MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + t->setCaption(spell->name); + t->setTextAlign(MyGUI::Align::Left); + t->setUserString("ToolTipType", "Spell"); + t->setUserString("Spell", *it); + t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); + t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); + t->setStateSelected(*it == selectedSpell); + + // cost / success chance + MyGUI::Button* costChance = mSpellView->createWidget("SpellText", + MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + std::string cost = boost::lexical_cast(spell->data.cost); + std::string chance = boost::lexical_cast(int(MWMechanics::getSpellSuccessChance(*it, player))); + costChance->setCaption(cost + "/" + chance); + costChance->setTextAlign(MyGUI::Align::Right); + costChance->setNeedMouseFocus(false); + costChance->setStateSelected(*it == selectedSpell); + + + mHeight += spellHeight; + } + + + // enchanted items + addGroup("#{sMagicItem}", "#{sCostCharge}"); + + for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) + { + MWWorld::Ptr item = *it; + + const ESM::Enchantment* enchant = MWBase::Environment::get().getWorld()->getStore().enchants.find(MWWorld::Class::get(item).getEnchantment(item)); + + // check if the item is currently equipped (will display in a different color) + bool equipped = false; + for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) + { + if (store.getSlot(i) != store.end() && *store.getSlot(i) == item) + { + equipped = true; + break; + } + } + + MyGUI::Button* t = mSpellView->createWidget(equipped ? "SpellText" : "SpellTextUnequipped", + MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + t->setCaption(MWWorld::Class::get(item).getName(item)); + t->setTextAlign(MyGUI::Align::Left); + t->setUserData(item); + t->setUserString("ToolTipType", "ItemPtr"); + t->setUserString("Equipped", equipped ? "true" : "false"); + t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected); + t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); + t->setStateSelected(item == selectedItem); + + // cost / charge + MyGUI::Button* costCharge = mSpellView->createWidget(equipped ? "SpellText" : "SpellTextUnequipped", + MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + + std::string cost = boost::lexical_cast(enchant->data.cost); + std::string charge = boost::lexical_cast(enchant->data.charge); /// \todo track current charge + if (enchant->data.type == ESM::Enchantment::CastOnce) + { + // this is Morrowind behaviour + cost = "100"; + charge = "100"; + } + + costCharge->setCaption(cost + "/" + charge); + costCharge->setTextAlign(MyGUI::Align::Right); + costCharge->setNeedMouseFocus(false); + costCharge->setStateSelected(item == selectedItem); + + mHeight += spellHeight; + } + + mSpellView->setCanvasSize(mSpellView->getWidth(), std::max(mSpellView->getHeight(), mHeight)); + } + + void SpellWindow::addGroup(const std::string &label, const std::string& label2) + { + if (mSpellView->getChildCount() > 0) + { + MyGUI::ImageBox* separator = mSpellView->createWidget("MW_HLine", + MyGUI::IntCoord(4, mHeight, mWidth-8, 18), + MyGUI::Align::Left | MyGUI::Align::Top); + separator->setNeedMouseFocus(false); + mHeight += 18; + } + + MyGUI::TextBox* groupWidget = mSpellView->createWidget("SandBrightText", + MyGUI::IntCoord(0, mHeight, mWidth, 24), + MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); + groupWidget->setCaptionWithReplacing(label); + groupWidget->setTextAlign(MyGUI::Align::Left); + groupWidget->setNeedMouseFocus(false); + + if (label2 != "") + { + MyGUI::TextBox* groupWidget2 = mSpellView->createWidget("SandBrightText", + MyGUI::IntCoord(0, mHeight, mWidth-4, 24), + MyGUI::Align::Left | MyGUI::Align::Top); + groupWidget2->setCaptionWithReplacing(label2); + groupWidget2->setTextAlign(MyGUI::Align::Right); + groupWidget2->setNeedMouseFocus(false); + } + + mHeight += 24; + } + + void SpellWindow::onWindowResize(MyGUI::Window* _sender) + { + updateSpells(); + } + + void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); + MWMechanics::Spells& spells = stats.mSpells; + MWWorld::Ptr item = *_sender->getUserData(); + + // retrieve ContainerStoreIterator to the item + MWWorld::ContainerStoreIterator it = store.begin(); + for (; it != store.end(); ++it) + { + if (*it == item) + { + break; + } + } + assert(it != store.end()); + + // equip, if it can be equipped and is not already equipped + if (_sender->getUserString("Equipped") == "false" + && !MWWorld::Class::get(item).getEquipmentSlots(item).first.empty()) + { + // sound + MWBase::Environment::get().getSoundManager()->playSound(MWWorld::Class::get(item).getUpSoundId(item), 1.0, 1.0); + + // Note: can't use Class::use here because enchanted scrolls for example would then open the scroll window instead of equipping + + /// \todo the following code is pretty much copy&paste from ActionEquip, put it in a function? + // slots that this item can be equipped in + std::pair, bool> slots = MWWorld::Class::get(item).getEquipmentSlots(item); + + // equip the item in the first free slot + for (std::vector::const_iterator slot=slots.first.begin(); + slot!=slots.first.end(); ++slot) + { + // if all slots are occupied, replace the last slot + if (slot == --slots.first.end()) + { + store.equip(*slot, it); + break; + } + + if (store.getSlot(*slot) == store.end()) + { + // slot is not occupied + store.equip(*slot, it); + break; + } + } + /// \todo scripts? + + // since we changed equipping status, update the inventory window + mWindowManager.getInventoryWindow()->drawItems(); + } + + store.setSelectedEnchantItem(it); + spells.setSelectedSpell(""); + mWindowManager.setSelectedEnchantItem(item, 100); /// \todo track charge % + + updateSpells(); + } + + void SpellWindow::onSpellSelected(MyGUI::Widget* _sender) + { + std::string spellId = _sender->getUserString("Spell"); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); + MWMechanics::Spells& spells = stats.mSpells; + + if (MyGUI::InputManager::getInstance().isShiftPressed()) + { + // delete spell, if allowed + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(spellId); + if (spell->data.flags & ESM::Spell::F_Always + || spell->data.type == ESM::Spell::ST_Power) + { + mWindowManager.messageBox("#{sDeleteSpellError}", std::vector()); + } + else + { + // ask for confirmation + mSpellToDelete = spellId; + ConfirmationDialog* dialog = mWindowManager.getConfirmationDialog(); + std::string question = mWindowManager.getGameSettingString("sQuestionDeleteSpell", "Delete %s?"); + question = boost::str(boost::format(question) % spell->name); + dialog->open(question); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &SpellWindow::onDeleteSpellAccept); + dialog->eventCancelClicked.clear(); + } + } + else + { + spells.setSelectedSpell(spellId); + store.setSelectedEnchantItem(store.end()); + mWindowManager.setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); + } + + updateSpells(); + } + + int SpellWindow::estimateHeight(int numSpells) const + { + int height = 0; + height += 24 * 3 + 18 * 2; // group headings + height += numSpells * 18; + return height; + } + + void SpellWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) + { + if (mSpellView->getViewOffset().top + _rel*0.3 > 0) + mSpellView->setViewOffset(MyGUI::IntPoint(0, 0)); + else + mSpellView->setViewOffset(MyGUI::IntPoint(0, mSpellView->getViewOffset().top + _rel*0.3)); + } + + void SpellWindow::onDeleteSpellAccept() + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); + MWMechanics::Spells& spells = stats.mSpells; + + if (spells.getSelectedSpell() == mSpellToDelete) + { + spells.setSelectedSpell(""); + mWindowManager.unsetSelectedSpell(); + } + + spells.remove(mSpellToDelete); + + updateSpells(); + } +} diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp new file mode 100644 index 0000000000..87e4783ba2 --- /dev/null +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -0,0 +1,39 @@ +#ifndef MWGUI_SPELLWINDOW_H +#define MWGUI_SPELLWINDOW_H + +#include "window_pinnable_base.hpp" + +namespace MWGui +{ + class SpellWindow : public WindowPinnableBase + { + public: + SpellWindow(WindowManager& parWindowManager); + + void updateSpells(); + + protected: + MyGUI::ScrollView* mSpellView; + MyGUI::Widget* mEffectBox; + + int mHeight; + int mWidth; + + std::string mSpellToDelete; + + void addGroup(const std::string& label, const std::string& label2); + + int estimateHeight(int numSpells) const; + + void onWindowResize(MyGUI::Window* _sender); + void onEnchantedItemSelected(MyGUI::Widget* _sender); + void onSpellSelected(MyGUI::Widget* _sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + void onDeleteSpellAccept(); + + virtual void onPinToggled(); + virtual void open(); + }; +} + +#endif diff --git a/apps/openmw/mwgui/stats_window.cpp b/apps/openmw/mwgui/stats_window.cpp index 675e5141fa..3584af7c7d 100644 --- a/apps/openmw/mwgui/stats_window.cpp +++ b/apps/openmw/mwgui/stats_window.cpp @@ -1,13 +1,20 @@ #include "stats_window.hpp" -#include "../mwmechanics/mechanicsmanager.hpp" -#include "window_manager.hpp" - #include #include #include + #include +#include "../mwmechanics/mechanicsmanager.hpp" +#include "../mwworld/world.hpp" +#include "../mwworld/player.hpp" +#include "../mwbase/environment.hpp" + +#include "window_manager.hpp" +#include "tooltips.hpp" + + using namespace MWGui; const int StatsWindow::lineHeight = 18; @@ -24,11 +31,12 @@ StatsWindow::StatsWindow (WindowManager& parWindowManager) , skillValues() , skillWidgetMap() , factionWidgetMap() - , factions() + , mFactions() , birthSignId() , reputation(0) , bounty(0) , skillWidgets() + , mChanged(true) { setCoord(0,0,498, 342); @@ -42,12 +50,6 @@ StatsWindow::StatsWindow (WindowManager& parWindowManager) { "Attrib6", "sAttributeEndurance" }, { "Attrib7", "sAttributePersonality" }, { "Attrib8", "sAttributeLuck" }, - { "Health_str", "sHealth" }, - { "Magicka_str", "sMagic" }, - { "Fatigue_str", "sFatigue" }, - { "Level_str", "sLevel" }, - { "Race_str", "sRace" }, - { "Class_str", "sClass" }, { 0, 0 } }; @@ -60,6 +62,10 @@ StatsWindow::StatsWindow (WindowManager& parWindowManager) getWidget(skillAreaWidget, "Skills"); getWidget(skillClientWidget, "SkillClient"); getWidget(skillScrollerWidget, "SkillScroller"); + getWidget(mLeftPane, "LeftPane"); + getWidget(mRightPane, "RightPane"); + + skillClientWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); skillScrollerWidget->eventScrollChangePosition += MyGUI::newDelegate(this, &StatsWindow::onScrollChangePosition); updateScroller(); @@ -89,8 +95,22 @@ void StatsWindow::onScrollChangePosition(MyGUI::ScrollBar* scroller, size_t pos) } } +void StatsWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) +{ + if (skillScrollerWidget->getScrollPosition() - _rel*0.3 < 0) + skillScrollerWidget->setScrollPosition(0); + else if (skillScrollerWidget->getScrollPosition() - _rel*0.3 > skillScrollerWidget->getScrollRange()-1) + skillScrollerWidget->setScrollPosition(skillScrollerWidget->getScrollRange()-1); + else + skillScrollerWidget->setScrollPosition(skillScrollerWidget->getScrollPosition() - _rel*0.3); + + onScrollChangePosition(skillScrollerWidget, skillScrollerWidget->getScrollPosition()); +} + void StatsWindow::onWindowResize(MyGUI::Window* window) { + mLeftPane->setCoord( MyGUI::IntCoord(0, 0, 0.44*window->getSize().width, window->getSize().height) ); + mRightPane->setCoord( MyGUI::IntCoord(0.44*window->getSize().width, 0, 0.56*window->getSize().width, window->getSize().height) ); updateScroller(); } @@ -109,17 +129,7 @@ void StatsWindow::setBar(const std::string& name, const std::string& tname, int void StatsWindow::setPlayerName(const std::string& playerName) { static_cast(mMainWidget)->setCaption(playerName); -} - -void StatsWindow::setStyledText(MyGUI::TextBox* widget, ColorStyle style, const std::string &value) -{ - widget->setCaption(value); - if (style == CS_Super) - widget->setTextColour(MyGUI::Colour(0, 1, 0)); - else if (style == CS_Sub) - widget->setTextColour(MyGUI::Colour(1, 0, 0)); - else - widget->setTextColour(MyGUI::Colour(1, 1, 1)); + adjustWindowCaption(); } void StatsWindow::setValue (const std::string& id, const MWMechanics::Stat& value) @@ -138,12 +148,15 @@ void StatsWindow::setValue (const std::string& id, const MWMechanics::Stat& valueString << value.getModified(); setText (id, valueString.str()); + MyGUI::TextBox* box; + getWidget(box, id); + if (value.getModified()>value.getBase()) - setTextColor (id, 0, 1, 0); + box->_setWidgetState("increased"); else if (value.getModified()_setWidgetState("decreased"); else - setTextColor (id, 1, 1, 1); + box->_setWidgetState("normal"); break; } @@ -158,11 +171,32 @@ void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicSta }; for (int i=0; ids[i]; ++i) + { if (ids[i]==id) { std::string id (ids[i]); setBar (id, id + "T", value.getCurrent(), value.getModified()); + + // health, magicka, fatigue tooltip + MyGUI::Widget* w; + std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); + if (i==0) + { + getWidget(w, "Health"); + w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); + } + else if (i==1) + { + getWidget(w, "Magicka"); + w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + } + else if (i==2) + { + getWidget(w, "Fatigue"); + w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); + } } + } } void StatsWindow::setValue (const std::string& id, const std::string& value) @@ -193,13 +227,14 @@ void StatsWindow::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechani { float modified = value.getModified(), base = value.getBase(); std::string text = boost::lexical_cast(std::floor(modified)); - ColorStyle style = CS_Normal; + std::string state = "normal"; if (modified > base) - style = CS_Super; + state = "increased"; else if (modified < base) - style = CS_Sub; + state = "decreased"; - setStyledText(widget, style, text); + widget->setCaption(text); + widget->_setWidgetState(state); } } @@ -220,21 +255,50 @@ void StatsWindow::configureSkills (const std::vector& major, const std::vec if (skillSet.find(skill) == skillSet.end()) miscSkills.push_back(skill); } + + updateSkillArea(); } -void StatsWindow::setFactions (const std::vector& factions) +void StatsWindow::onFrame () { - this->factions = factions; + if (mMainWidget->getVisible()) + return; + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWMechanics::NpcStats PCstats = MWWorld::Class::get(player).getNpcStats(player); + + setFactions(PCstats.mFactionRank); + + setBirthSign(MWBase::Environment::get().getWorld()->getPlayer().getBirthsign()); + + if (mChanged) + updateSkillArea(); +} + +void StatsWindow::setFactions (const FactionList& factions) +{ + if (mFactions != factions) + { + mFactions = factions; + mChanged = true; + } } void StatsWindow::setBirthSign (const std::string& signId) { - birthSignId = signId; + if (signId != birthSignId) + { + birthSignId = signId; + mChanged = true; + } } void StatsWindow::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { - MyGUI::ImageBox* separator = skillClientWidget->createWidget("MW_HLine", MyGUI::IntCoord(10, coord1.top, coord1.width + coord2.width - 4, 18), MyGUI::Align::Default); + MyGUI::ImageBox* separator = skillClientWidget->createWidget("MW_HLine", + MyGUI::IntCoord(10, coord1.top, coord1.width + coord2.width - 4, 18), + MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); + separator->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); skillWidgets.push_back(separator); coord1.top += separator->getHeight(); @@ -243,23 +307,29 @@ void StatsWindow::addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) void StatsWindow::addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { - MyGUI::TextBox* groupWidget = skillClientWidget->createWidget("SandBrightText", MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), MyGUI::Align::Default); + MyGUI::TextBox* groupWidget = skillClientWidget->createWidget("SandBrightText", + MyGUI::IntCoord(0, coord1.top, coord1.width + coord2.width, coord1.height), + MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); groupWidget->setCaption(label); + groupWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); skillWidgets.push_back(groupWidget); coord1.top += lineHeight; coord2.top += lineHeight; } -MyGUI::TextBox* StatsWindow::addValueItem(const std::string text, const std::string &value, ColorStyle style, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) +MyGUI::TextBox* StatsWindow::addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::TextBox *skillNameWidget, *skillValueWidget; - skillNameWidget = skillClientWidget->createWidget("SandText", coord1, MyGUI::Align::Default); + skillNameWidget = skillClientWidget->createWidget("SandText", coord1, MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); skillNameWidget->setCaption(text); + skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); - skillValueWidget = skillClientWidget->createWidget("SandTextRight", coord2, MyGUI::Align::Default); - setStyledText(skillValueWidget, style, value); + skillValueWidget = skillClientWidget->createWidget("SandTextRight", coord2, MyGUI::Align::Right | MyGUI::Align::Top); + skillValueWidget->setCaption(value); + skillValueWidget->_setWidgetState(state); + skillValueWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); skillWidgets.push_back(skillNameWidget); skillWidgets.push_back(skillValueWidget); @@ -270,17 +340,20 @@ MyGUI::TextBox* StatsWindow::addValueItem(const std::string text, const std::str return skillValueWidget; } -void StatsWindow::addItem(const std::string text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) +MyGUI::Widget* StatsWindow::addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) { MyGUI::TextBox* skillNameWidget; skillNameWidget = skillClientWidget->createWidget("SandText", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default); skillNameWidget->setCaption(text); + skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); skillWidgets.push_back(skillNameWidget); coord1.top += lineHeight; coord2.top += lineHeight; + + return skillNameWidget; } void StatsWindow::addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2) @@ -304,25 +377,55 @@ void StatsWindow::addSkills(const SkillList &skills, const std::string &titleId, const MWMechanics::Stat &stat = skillValues.find(skillId)->second; float base = stat.getBase(); float modified = stat.getModified(); + int progressPercent = (modified - float(static_cast(modified))) * 100; - ColorStyle style = CS_Normal; + const ESM::Skill* skill = mWindowManager.getStore().skills.search(skillId); + assert(skill); + + std::string icon = "icons\\k\\" + ESM::Skill::sIconNames[skillId]; + + const ESM::Attribute* attr = mWindowManager.getStore().attributes.search(skill->data.attribute); + assert(attr); + + std::string state = "normal"; if (modified > base) - style = CS_Super; + state = "increased"; else if (modified < base) - style = CS_Sub; - MyGUI::TextBox* widget = addValueItem(mWindowManager.getGameSettingString(skillNameId, skillNameId), boost::lexical_cast(static_cast(modified)), style, coord1, coord2); + state = "decreased"; + MyGUI::TextBox* widget = addValueItem(mWindowManager.getGameSettingString(skillNameId, skillNameId), + boost::lexical_cast(static_cast(modified)), state, coord1, coord2); + + for (int i=0; i<2; ++i) + { + skillWidgets[skillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); + skillWidgets[skillWidgets.size()-1-i]->setUserString("ToolTipLayout", "SkillToolTip"); + skillWidgets[skillWidgets.size()-1-i]->setUserString("Caption_SkillName", "#{"+skillNameId+"}"); + skillWidgets[skillWidgets.size()-1-i]->setUserString("Caption_SkillDescription", skill->description); + skillWidgets[skillWidgets.size()-1-i]->setUserString("Caption_SkillAttribute", "#{sGoverningAttribute}: #{" + attr->name + "}"); + skillWidgets[skillWidgets.size()-1-i]->setUserString("ImageTexture_SkillImage", icon); + skillWidgets[skillWidgets.size()-1-i]->setUserString("Caption_SkillProgressText", boost::lexical_cast(progressPercent)+"/100"); + skillWidgets[skillWidgets.size()-1-i]->setUserString("Range_SkillProgress", "100"); + skillWidgets[skillWidgets.size()-1-i]->setUserString("RangePosition_SkillProgress", boost::lexical_cast(progressPercent)); + } + skillWidgetMap[skillId] = widget; } } void StatsWindow::updateSkillArea() { + mChanged = false; + for (std::vector::iterator it = skillWidgets.begin(); it != skillWidgets.end(); ++it) { MyGUI::Gui::getInstance().destroyWidget(*it); } skillWidgets.clear(); + skillScrollerWidget->setScrollPosition(0); + onScrollChangePosition(skillScrollerWidget, 0); + clientHeight = 0; + const int valueSize = 40; MyGUI::IntCoord coord1(10, 0, skillClientWidget->getWidth() - (10 + valueSize), 18); MyGUI::IntCoord coord2(coord1.left + coord1.width, coord1.top, valueSize, coord1.height); @@ -338,19 +441,75 @@ void StatsWindow::updateSkillArea() const ESMS::ESMStore &store = mWindowManager.getStore(); - if (!factions.empty()) + // race tooltip + const ESM::Race* playerRace = store.races.find (MWBase::Environment::get().getWorld()->getPlayer().getRace()); + MyGUI::Widget* raceWidget; + getWidget(raceWidget, "RaceText"); + ToolTips::createRaceToolTip(raceWidget, playerRace); + getWidget(raceWidget, "Race_str"); + ToolTips::createRaceToolTip(raceWidget, playerRace); + + // class tooltip + MyGUI::Widget* classWidget; + const ESM::Class& playerClass = MWBase::Environment::get().getWorld()->getPlayer().getClass(); + getWidget(classWidget, "ClassText"); + ToolTips::createClassToolTip(classWidget, playerClass); + getWidget(classWidget, "Class_str"); + ToolTips::createClassToolTip(classWidget, playerClass); + + if (!mFactions.empty()) { // Add a line separator if there are items above if (!skillWidgets.empty()) addSeparator(coord1, coord2); addGroup(mWindowManager.getGameSettingString("sFaction", "Faction"), coord1, coord2); - FactionList::const_iterator end = factions.end(); - for (FactionList::const_iterator it = factions.begin(); it != end; ++it) + FactionList::const_iterator end = mFactions.end(); + for (FactionList::const_iterator it = mFactions.begin(); it != end; ++it) { const ESM::Faction *faction = store.factions.find(it->first); - addItem(faction->name, coord1, coord2); - // TODO: Faction rank should be placed in tooltip + MyGUI::Widget* w = addItem(faction->name, coord1, coord2); + + std::string text; + + text += std::string("#DDC79E") + faction->name; + text += std::string("\n#BF9959") + faction->ranks[it->second]; + + if (it->second < 9) + { + // player doesn't have max rank yet + text += std::string("\n\n#DDC79E#{sNextRank} ") + faction->ranks[it->second+1]; + + ESM::RankData rankData = faction->data.rankData[it->second+1]; + const ESM::Attribute* attr1 = mWindowManager.getStore().attributes.search(faction->data.attribute1); + const ESM::Attribute* attr2 = mWindowManager.getStore().attributes.search(faction->data.attribute2); + assert(attr1 && attr2); + + text += "\n#BF9959#{" + attr1->name + "}: " + boost::lexical_cast(rankData.attribute1) + + ", #{" + attr2->name + "}: " + boost::lexical_cast(rankData.attribute2); + + text += "\n\n#DDC79E#{sFavoriteSkills}"; + text += "\n#BF9959"; + for (int i=0; i<6; ++i) + { + const ESM::Skill* skill = mWindowManager.getStore().skills.search(faction->data.skillID[i]); + assert(skill); + text += "#{"+ESM::Skill::sSkillNameIds[faction->data.skillID[i]]+"}"; + if (i<5) + text += ", "; + } + + text += "\n"; + + if (rankData.skill1 > 0) + text += "\n#{sNeedOneSkill} " + boost::lexical_cast(rankData.skill1); + if (rankData.skill2 > 0) + text += "\n#{sNeedTwoSkills} " + boost::lexical_cast(rankData.skill2); + } + + w->setUserString("ToolTipType", "Layout"); + w->setUserString("ToolTipLayout", "TextToolTip"); + w->setUserString("Caption_Text", text); } } @@ -360,17 +519,36 @@ void StatsWindow::updateSkillArea() if (!skillWidgets.empty()) addSeparator(coord1, coord2); - addGroup(mWindowManager.getGameSettingString("sSign", "Sign"), coord1, coord2); + addGroup(mWindowManager.getGameSettingString("sBirthSign", "Sign"), coord1, coord2); const ESM::BirthSign *sign = store.birthSigns.find(birthSignId); - addItem(sign->name, coord1, coord2); + MyGUI::Widget* w = addItem(sign->name, coord1, coord2); + + ToolTips::createBirthsignToolTip(w, birthSignId); } // Add a line separator if there are items above if (!skillWidgets.empty()) addSeparator(coord1, coord2); - addValueItem(mWindowManager.getGameSettingString("sReputation", "Reputation"), boost::lexical_cast(static_cast(reputation)), CS_Normal, coord1, coord2); - addValueItem(mWindowManager.getGameSettingString("sBounty", "Bounty"), boost::lexical_cast(static_cast(bounty)), CS_Normal, coord1, coord2); + addValueItem(mWindowManager.getGameSettingString("sReputation", "Reputation"), + boost::lexical_cast(static_cast(reputation)), "normal", coord1, coord2); + + for (int i=0; i<2; ++i) + { + skillWidgets[skillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); + skillWidgets[skillWidgets.size()-1-i]->setUserString("ToolTipLayout", "TextToolTip"); + skillWidgets[skillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sSkillsMenuReputationHelp}"); + } + + addValueItem(mWindowManager.getGameSettingString("sBounty", "Bounty"), + boost::lexical_cast(static_cast(bounty)), "normal", coord1, coord2); + + for (int i=0; i<2; ++i) + { + skillWidgets[skillWidgets.size()-1-i]->setUserString("ToolTipType", "Layout"); + skillWidgets[skillWidgets.size()-1-i]->setUserString("ToolTipLayout", "TextToolTip"); + skillWidgets[skillWidgets.size()-1-i]->setUserString("Caption_Text", "#{sCrimeHelp}"); + } clientHeight = coord1.top; updateScroller(); @@ -380,6 +558,8 @@ void StatsWindow::updateScroller() { skillScrollerWidget->setScrollRange(std::max(clientHeight - skillClientWidget->getHeight(), 0)); skillScrollerWidget->setScrollPage(std::max(skillClientWidget->getHeight() - lineHeight, 0)); + if (clientHeight != 0) + skillScrollerWidget->setTrackSize( (skillAreaWidget->getHeight() / float(clientHeight)) * skillScrollerWidget->getLineSize() ); } void StatsWindow::onPinToggled() diff --git a/apps/openmw/mwgui/stats_window.hpp b/apps/openmw/mwgui/stats_window.hpp index f2731e545c..10b794cc0e 100644 --- a/apps/openmw/mwgui/stats_window.hpp +++ b/apps/openmw/mwgui/stats_window.hpp @@ -18,13 +18,15 @@ namespace MWGui class StatsWindow : public WindowPinnableBase { public: - typedef std::pair Faction; - typedef std::vector FactionList; + typedef std::map FactionList; typedef std::vector SkillList; StatsWindow(WindowManager& parWindowManager); + /// automatically updates all the data in the stats window, but only if it has changed. + void onFrame(); + void setBar(const std::string& name, const std::string& tname, int val, int max); void setPlayerName(const std::string& playerName); @@ -36,32 +38,30 @@ namespace MWGui void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat& value); void configureSkills (const SkillList& major, const SkillList& minor); - void setFactions (const std::vector& factions); - void setBirthSign (const std::string &signId); void setReputation (int reputation) { this->reputation = reputation; } void setBounty (int bounty) { this->bounty = bounty; } void updateSkillArea(); private: - enum ColorStyle - { - CS_Sub, - CS_Normal, - CS_Super - }; - void setStyledText(MyGUI::TextBox* widget, ColorStyle style, const std::string &value); void addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addGroup(const std::string &label, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); - MyGUI::TextBox* addValueItem(const std::string text, const std::string &value, ColorStyle style, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); - void addItem(const std::string text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); + MyGUI::TextBox* addValueItem(const std::string& text, const std::string &value, const std::string& state, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); + MyGUI::Widget* addItem(const std::string& text, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void updateScroller(); + void setFactions (const FactionList& factions); + void setBirthSign (const std::string &signId); + void onScrollChangePosition(MyGUI::ScrollBar* scroller, size_t pos); void onWindowResize(MyGUI::Window* window); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); static const int lineHeight; + MyGUI::Widget* mLeftPane; + MyGUI::Widget* mRightPane; + MyGUI::WidgetPtr skillAreaWidget, skillClientWidget; MyGUI::ScrollBar* skillScrollerWidget; int lastPos, clientHeight; @@ -70,11 +70,13 @@ namespace MWGui std::map > skillValues; std::map skillWidgetMap; std::map factionWidgetMap; - FactionList factions; ///< Stores a list of factions and the current rank + FactionList mFactions; ///< Stores a list of factions and the current rank std::string birthSignId; int reputation, bounty; std::vector skillWidgets; //< Skills and other information + bool mChanged; + protected: virtual void onPinToggled(); }; diff --git a/apps/openmw/mwgui/text_input.cpp b/apps/openmw/mwgui/text_input.cpp index 8ac07e7668..7d84a9b9fd 100644 --- a/apps/openmw/mwgui/text_input.cpp +++ b/apps/openmw/mwgui/text_input.cpp @@ -12,7 +12,6 @@ TextInputDialog::TextInputDialog(WindowManager& parWindowManager) getWidget(textEdit, "TextEdit"); textEdit->eventEditSelectAccept += newDelegate(this, &TextInputDialog::onTextAccepted); - // TODO: These buttons should be managed by a Dialog class MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); okButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TextInputDialog::onOkClicked); @@ -25,16 +24,15 @@ void TextInputDialog::setNextButtonShow(bool shown) { MyGUI::ButtonPtr okButton; getWidget(okButton, "OKButton"); + if (shown) - { - okButton->setCaption("Next"); - okButton->setCoord(MyGUI::IntCoord(264 - 18, 60, 42 + 18, 23)); - } + okButton->setCaption(mWindowManager.getGameSettingString("sNext", "")); else - { - okButton->setCaption("OK"); - okButton->setCoord(MyGUI::IntCoord(264, 60, 42, 23)); - } + okButton->setCaption(mWindowManager.getGameSettingString("sOK", "")); + + int okButtonWidth = okButton->getTextSize().width + 24; + + okButton->setCoord(306 - okButtonWidth, 60, okButtonWidth, 23); } void TextInputDialog::setTextLabel(const std::string &label) diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp new file mode 100644 index 0000000000..7ec4441687 --- /dev/null +++ b/apps/openmw/mwgui/tooltips.cpp @@ -0,0 +1,691 @@ +#include "tooltips.hpp" + +#include "window_manager.hpp" +#include "widgets.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/world.hpp" +#include "../mwbase/environment.hpp" + +#include + +#include + +using namespace MWGui; +using namespace MyGUI; + +ToolTips::ToolTips(WindowManager* windowManager) : + Layout("openmw_tooltips.xml") + , mGameMode(true) + , mWindowManager(windowManager) + , mFullHelp(false) + , mEnabled(true) + , mFocusToolTipX(0.0) + , mFocusToolTipY(0.0) + , mDelay(0.0) + , mRemainingDelay(0.0) + , mLastMouseX(0) + , mLastMouseY(0) +{ + getWidget(mDynamicToolTipBox, "DynamicToolTipBox"); + + mDynamicToolTipBox->setVisible(false); + + // turn off mouse focus so that getMouseFocusWidget returns the correct widget, + // even if the mouse is over the tooltip + mDynamicToolTipBox->setNeedMouseFocus(false); + mMainWidget->setNeedMouseFocus(false); + + mDelay = Settings::Manager::getFloat("tooltip delay", "GUI"); + mRemainingDelay = mDelay; +} + +void ToolTips::setEnabled(bool enabled) +{ + mEnabled = enabled; +} + +void ToolTips::onFrame(float frameDuration) +{ + while (mDynamicToolTipBox->getChildCount()) + { + MyGUI::Gui::getInstance().destroyWidget(mDynamicToolTipBox->getChildAt(0)); + } + + // start by hiding everything + for (unsigned int i=0; i < mMainWidget->getChildCount(); ++i) + { + mMainWidget->getChildAt(i)->setVisible(false); + } + + const IntSize &viewSize = RenderManager::getInstance().getViewSize(); + + if (!mEnabled) + { + return; + } + + if (!mGameMode) + { + const MyGUI::IntPoint& mousePos = InputManager::getInstance().getMousePosition(); + + if (mWindowManager->getWorldMouseOver() && ((mWindowManager->getMode() == GM_Console) + || (mWindowManager->getMode() == GM_Container) + || (mWindowManager->getMode() == GM_Inventory))) + { + std::string handle = MWBase::Environment::get().getWorld()->getFacedHandle(); + try + { + mFocusObject = MWBase::Environment::get().getWorld()->getPtrViaHandle(handle); + } + catch (std::exception& e) + { + return; + } + + MyGUI::IntSize tooltipSize = getToolTipViaPtr(true); + + IntPoint tooltipPosition = InputManager::getInstance().getMousePosition() + IntPoint(0, 24); + + // make the tooltip stay completely in the viewport + if ((tooltipPosition.left + tooltipSize.width) > viewSize.width) + { + tooltipPosition.left = viewSize.width - tooltipSize.width; + } + if ((tooltipPosition.top + tooltipSize.height) > viewSize.height) + { + tooltipPosition.top = viewSize.height - tooltipSize.height; + } + + setCoord(tooltipPosition.left, tooltipPosition.top, tooltipSize.width, tooltipSize.height); + } + + else + { + const MyGUI::IntPoint& lastPressed = InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left); + + if (mousePos == lastPressed) // mouseclick makes tooltip disappear + return; + + if (mousePos.left == mLastMouseX && mousePos.top == mLastMouseY) + { + mRemainingDelay -= frameDuration; + } + else + { + mRemainingDelay = mDelay; + } + mLastMouseX = mousePos.left; + mLastMouseY = mousePos.top; + + if (mRemainingDelay > 0) + return; + + Widget* focus = InputManager::getInstance().getMouseFocusWidget(); + if (focus == 0) + { + return; + } + + IntSize tooltipSize; + + // try to go 1 level up until there is a widget that has tooltip + // this is necessary because some skin elements are actually separate widgets + int i=0; + while (!focus->isUserString("ToolTipType")) + { + focus = focus->getParent(); + if (!focus) + return; + ++i; + } + + std::string type = focus->getUserString("ToolTipType"); + std::string text = focus->getUserString("ToolTipText"); + + if (type == "") + { + return; + } + else if (type == "ItemPtr") + { + mFocusObject = *focus->getUserData(); + tooltipSize = getToolTipViaPtr(false); + } + else if (type == "Spell") + { + ToolTipInfo info; + const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().spells.find(focus->getUserString("Spell")); + info.caption = spell->name; + Widgets::SpellEffectList effects; + std::vector::const_iterator end = spell->effects.list.end(); + for (std::vector::const_iterator it = spell->effects.list.begin(); it != end; ++it) + { + Widgets::SpellEffectParams params; + params.mEffectID = it->effectID; + params.mSkill = it->skill; + params.mAttribute = it->attribute; + params.mDuration = it->duration; + params.mMagnMin = it->magnMin; + params.mMagnMax = it->magnMax; + params.mRange = it->range; + params.mIsConstant = (spell->data.type == ESM::Spell::ST_Ability); + effects.push_back(params); + } + info.effects = effects; + tooltipSize = createToolTip(info); + } + else if (type == "Layout") + { + // tooltip defined in the layout + MyGUI::Widget* tooltip; + getWidget(tooltip, focus->getUserString("ToolTipLayout")); + + tooltip->setVisible(true); + if (!tooltip->isUserString("DontResize")) + { + tooltip->setCoord(0, 0, 450, 300); // this is the maximum width of the tooltip before it starts word-wrapping + + tooltipSize = MyGUI::IntSize(0, tooltip->getSize().height); + } + else + tooltipSize = tooltip->getSize(); + + std::map userStrings = focus->getUserStrings(); + for (std::map::iterator it = userStrings.begin(); + it != userStrings.end(); ++it) + { + if (it->first == "ToolTipType" + || it->first == "ToolTipLayout") + continue; + + + size_t underscorePos = it->first.find("_"); + std::string propertyKey = it->first.substr(0, underscorePos); + std::string widgetName = it->first.substr(underscorePos+1, it->first.size()-(underscorePos+1)); + + MyGUI::Widget* w; + getWidget(w, widgetName); + w->setProperty(propertyKey, it->second); + } + + for (unsigned int i=0; igetChildCount(); ++i) + { + MyGUI::Widget* w = tooltip->getChildAt(i); + + if (w->isUserString("AutoResizeHorizontal")) + { + MyGUI::TextBox* text = w->castType(); + tooltipSize.width = std::max(tooltipSize.width, w->getLeft() + text->getTextSize().width + 8); + } + else if (!tooltip->isUserString("DontResize")) + tooltipSize.width = std::max(tooltipSize.width, w->getLeft() + w->getWidth() + 8); + + if (w->isUserString("AutoResizeVertical")) + { + MyGUI::TextBox* text = w->castType(); + int height = text->getTextSize().height; + if (height > w->getHeight()) + { + tooltipSize += MyGUI::IntSize(0, height - w->getHeight()); + } + if (height < w->getHeight()) + { + tooltipSize -= MyGUI::IntSize(0, w->getHeight() - height); + } + } + } + tooltip->setCoord(0, 0, tooltipSize.width, tooltipSize.height); + } + else + throw std::runtime_error ("unknown tooltip type"); + + IntPoint tooltipPosition = InputManager::getInstance().getMousePosition() + IntPoint(0, 24); + + // make the tooltip stay completely in the viewport + if ((tooltipPosition.left + tooltipSize.width) > viewSize.width) + { + tooltipPosition.left = viewSize.width - tooltipSize.width; + } + if ((tooltipPosition.top + tooltipSize.height) > viewSize.height) + { + tooltipPosition.top = viewSize.height - tooltipSize.height; + } + + setCoord(tooltipPosition.left, tooltipPosition.top, tooltipSize.width, tooltipSize.height); + } + } + else + { + if (!mFocusObject.isEmpty()) + { + IntSize tooltipSize = getToolTipViaPtr(); + + setCoord(viewSize.width/2 - tooltipSize.width/2, + std::max(0, int(mFocusToolTipY*viewSize.height - tooltipSize.height)), + tooltipSize.width, + tooltipSize.height); + + mDynamicToolTipBox->setVisible(true); + } + } +} + +void ToolTips::enterGameMode() +{ + mGameMode = true; +} + +void ToolTips::enterGuiMode() +{ + mGameMode = false; +} + +void ToolTips::setFocusObject(const MWWorld::Ptr& focus) +{ + mFocusObject = focus; +} + +IntSize ToolTips::getToolTipViaPtr (bool image) +{ + // this the maximum width of the tooltip before it starts word-wrapping + setCoord(0, 0, 300, 300); + + IntSize tooltipSize; + + const MWWorld::Class& object = MWWorld::Class::get (mFocusObject); + if (!object.hasToolTip(mFocusObject)) + { + mDynamicToolTipBox->setVisible(false); + } + else + { + mDynamicToolTipBox->setVisible(true); + + ToolTipInfo info = object.getToolTipInfo(mFocusObject); + if (!image) + info.icon = ""; + tooltipSize = createToolTip(info); + } + + return tooltipSize; +} + +void ToolTips::findImageExtension(std::string& image) +{ + int len = image.size(); + if (len < 4) return; + + if (!Ogre::ResourceGroupManager::getSingleton().resourceExists(Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME, image)) + { + // Change texture extension to .dds + image[len-3] = 'd'; + image[len-2] = 'd'; + image[len-1] = 's'; + } +} + +IntSize ToolTips::createToolTip(const MWGui::ToolTipInfo& info) +{ + mDynamicToolTipBox->setVisible(true); + + std::string caption = info.caption; + std::string image = info.icon; + int imageSize = (image != "") ? 32 : 0; + std::string text = info.text; + + // remove the first newline (easier this way) + if (text.size() > 0 && text[0] == '\n') + text.erase(0, 1); + + const ESM::Enchantment* enchant; + const ESMS::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + if (info.enchant != "") + { + enchant = store.enchants.search(info.enchant); + if (enchant->data.type == ESM::Enchantment::CastOnce) + text += "\n" + store.gameSettings.search("sItemCastOnce")->str; + else if (enchant->data.type == ESM::Enchantment::WhenStrikes) + text += "\n" + store.gameSettings.search("sItemCastWhenStrikes")->str; + else if (enchant->data.type == ESM::Enchantment::WhenUsed) + text += "\n" + store.gameSettings.search("sItemCastWhenUsed")->str; + else if (enchant->data.type == ESM::Enchantment::ConstantEffect) + text += "\n" + store.gameSettings.search("sItemCastConstant")->str; + } + + // this the maximum width of the tooltip before it starts word-wrapping + setCoord(0, 0, 300, 300); + + const IntPoint padding(8, 8); + + const int imageCaptionHPadding = (caption != "" ? 8 : 0); + const int imageCaptionVPadding = (caption != "" ? 4 : 0); + + std::string realImage = "icons\\" + image; + findImageExtension(realImage); + + EditBox* captionWidget = mDynamicToolTipBox->createWidget("NormalText", IntCoord(0, 0, 300, 300), Align::Left | Align::Top, "ToolTipCaption"); + captionWidget->setProperty("Static", "true"); + captionWidget->setCaption(caption); + IntSize captionSize = captionWidget->getTextSize(); + + int captionHeight = std::max(caption != "" ? captionSize.height : 0, imageSize); + + EditBox* textWidget = mDynamicToolTipBox->createWidget("SandText", IntCoord(0, captionHeight+imageCaptionVPadding, 300, 300-captionHeight-imageCaptionVPadding), Align::Stretch, "ToolTipText"); + textWidget->setProperty("Static", "true"); + textWidget->setProperty("MultiLine", "true"); + textWidget->setProperty("WordWrap", "true"); + textWidget->setCaption(text); + textWidget->setTextAlign(Align::HCenter | Align::Top); + IntSize textSize = textWidget->getTextSize(); + + captionSize += IntSize(imageSize, 0); // adjust for image + IntSize totalSize = IntSize( std::max(textSize.width, captionSize.width + ((image != "") ? imageCaptionHPadding : 0)), + ((text != "") ? textSize.height + imageCaptionVPadding : 0) + captionHeight ); + + if (!info.effects.empty()) + { + Widget* effectArea = mDynamicToolTipBox->createWidget("", + IntCoord(0, totalSize.height, 300, 300-totalSize.height), + Align::Stretch, "ToolTipEffectArea"); + + IntCoord coord(0, 6, totalSize.width, 24); + + /** + * \todo + * the various potion effects should appear in the tooltip depending if the player + * has enough skill in alchemy to know about the effects of this potion. + */ + + Widgets::MWEffectListPtr effectsWidget = effectArea->createWidget + ("MW_StatName", coord, Align::Default, "ToolTipEffectsWidget"); + effectsWidget->setWindowManager(mWindowManager); + effectsWidget->setEffectList(info.effects); + + std::vector effectItems; + effectsWidget->createEffectWidgets(effectItems, effectArea, coord, true, Widgets::MWEffectList::EF_NoTarget); + totalSize.height += coord.top-6; + totalSize.width = std::max(totalSize.width, coord.width); + } + + if (info.enchant != "") + { + Widget* enchantArea = mDynamicToolTipBox->createWidget("", + IntCoord(0, totalSize.height, 300, 300-totalSize.height), + Align::Stretch, "ToolTipEnchantArea"); + + IntCoord coord(0, 6, totalSize.width, 24); + + Widgets::MWEffectListPtr enchantWidget = enchantArea->createWidget + ("MW_StatName", coord, Align::Default, "ToolTipEnchantWidget"); + enchantWidget->setWindowManager(mWindowManager); + enchantWidget->setEffectList(Widgets::MWEffectList::effectListFromESM(&enchant->effects)); + + std::vector enchantEffectItems; + int flag = (enchant->data.type == ESM::Enchantment::ConstantEffect) ? Widgets::MWEffectList::EF_Constant : 0; + enchantWidget->createEffectWidgets(enchantEffectItems, enchantArea, coord, true, flag); + totalSize.height += coord.top-6; + totalSize.width = std::max(totalSize.width, coord.width); + + if (enchant->data.type == ESM::Enchantment::WhenStrikes + || enchant->data.type == ESM::Enchantment::WhenUsed) + { + /// \todo store the current enchantment charge somewhere + int charge = enchant->data.charge; + + const int chargeWidth = 204; + + TextBox* chargeText = enchantArea->createWidget("SandText", IntCoord(0, 0, 10, 18), Align::Default, "ToolTipEnchantChargeText"); + chargeText->setCaption(store.gameSettings.search("sCharges")->str); + const int chargeTextWidth = chargeText->getTextSize().width + 5; + + const int chargeAndTextWidth = chargeWidth + chargeTextWidth; + + totalSize.width = std::max(totalSize.width, chargeAndTextWidth); + + chargeText->setCoord((totalSize.width - chargeAndTextWidth)/2, coord.top+6, chargeTextWidth, 18); + + IntCoord chargeCoord; + if (totalSize.width < chargeWidth) + { + totalSize.width = chargeWidth; + chargeCoord = IntCoord(0, coord.top+6, chargeWidth, 18); + } + else + { + chargeCoord = IntCoord((totalSize.width - chargeAndTextWidth)/2 + chargeTextWidth, coord.top+6, chargeWidth, 18); + } + Widgets::MWDynamicStatPtr chargeWidget = enchantArea->createWidget + ("MW_ChargeBar", chargeCoord, Align::Default, "ToolTipEnchantCharge"); + chargeWidget->setValue(charge, charge); + totalSize.height += 24; + } + } + + captionWidget->setCoord( (totalSize.width - captionSize.width)/2 + imageSize, + (captionHeight-captionSize.height)/2, + captionSize.width-imageSize, + captionSize.height); + + captionWidget->setPosition (captionWidget->getPosition() + padding); + textWidget->setPosition (textWidget->getPosition() + IntPoint(0, padding.top)); // only apply vertical padding, the horizontal works automatically due to Align::HCenter + + if (image != "") + { + ImageBox* imageWidget = mDynamicToolTipBox->createWidget("ImageBox", + IntCoord((totalSize.width - captionSize.width - imageCaptionHPadding)/2, 0, imageSize, imageSize), + Align::Left | Align::Top, "ToolTipImage"); + imageWidget->setImageTexture(realImage); + imageWidget->setPosition (imageWidget->getPosition() + padding); + } + + totalSize += IntSize(padding.left*2, padding.top*2); + + return totalSize; +} + +std::string ToolTips::toString(const float value) +{ + std::ostringstream stream; + stream << std::setprecision(3) << value; + return stream.str(); +} + +std::string ToolTips::toString(const int value) +{ + std::ostringstream stream; + stream << value; + return stream.str(); +} + +std::string ToolTips::getValueString(const int value, const std::string& prefix) +{ + if (value == 0) + return ""; + else + return "\n" + prefix + ": " + toString(value); +} + +std::string ToolTips::getMiscString(const std::string& text, const std::string& prefix) +{ + if (text == "") + return ""; + else + return "\n" + prefix + ": " + text; +} + +std::string ToolTips::getCountString(const int value) +{ + if (value == 1) + return ""; + else + return " (" + boost::lexical_cast(value) + ")"; +} + +void ToolTips::toggleFullHelp() +{ + mFullHelp = !mFullHelp; +} + +bool ToolTips::getFullHelp() const +{ + return mFullHelp; +} + +void ToolTips::setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) +{ + mFocusToolTipX = (min_x + max_x) / 2; + mFocusToolTipY = min_y; +} + +void ToolTips::createSkillToolTip(MyGUI::Widget* widget, int skillId) +{ + if (skillId == -1) + return; + + const std::string &skillNameId = ESMS::Skill::sSkillNameIds[skillId]; + const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().skills.search(skillId); + assert(skill); + const ESM::Attribute* attr = MWBase::Environment::get().getWorld()->getStore().attributes.search(skill->data.attribute); + assert(attr); + std::string icon = "icons\\k\\" + ESM::Skill::sIconNames[skillId]; + + widget->setUserString("ToolTipType", "Layout"); + widget->setUserString("ToolTipLayout", "SkillNoProgressToolTip"); + widget->setUserString("Caption_SkillNoProgressName", "#{"+skillNameId+"}"); + widget->setUserString("Caption_SkillNoProgressDescription", skill->description); + widget->setUserString("Caption_SkillNoProgressAttribute", "#{sGoverningAttribute}: #{" + attr->name + "}"); + widget->setUserString("ImageTexture_SkillNoProgressImage", icon); + widget->setUserString("ToolTipLayout", "SkillNoProgressToolTip"); + widget->setUserString("ToolTipLayout", "SkillNoProgressToolTip"); +} + +void ToolTips::createAttributeToolTip(MyGUI::Widget* widget, int attributeId) +{ + if (attributeId == -1) + return; + + const ESM::Attribute* attr = MWBase::Environment::get().getWorld()->getStore().attributes.search(attributeId); + assert(attr); + std::string icon = ESM::Attribute::attributeIcons[attributeId]; + std::string name = ESM::Attribute::gmstAttributeIds[attributeId]; + std::string desc = ESM::Attribute::gmstAttributeDescIds[attributeId]; + + widget->setUserString("ToolTipType", "Layout"); + widget->setUserString("ToolTipLayout", "AttributeToolTip"); + widget->setUserString("Caption_AttributeName", "#{"+name+"}"); + widget->setUserString("Caption_AttributeDescription", "#{"+desc+"}"); + widget->setUserString("ImageTexture_AttributeImage", icon); +} + +void ToolTips::createSpecializationToolTip(MyGUI::Widget* widget, const std::string& name, int specId) +{ + widget->setUserString("Caption_CenteredCaption", name); + std::string specText; + // get all skills of this specialisation + std::map skills = MWBase::Environment::get().getWorld()->getStore().skills.list; + for (std::map::const_iterator it = skills.begin(); + it != skills.end(); ++it) + { + if (it->second.data.specialization == specId) + specText += std::string("\n#{") + ESM::Skill::sSkillNameIds[it->second.index] + "}"; + } + widget->setUserString("Caption_CenteredCaptionText", specText); + widget->setUserString("ToolTipLayout", "TextWithCenteredCaptionToolTip"); + widget->setUserString("ToolTipType", "Layout"); +} + +void ToolTips::createBirthsignToolTip(MyGUI::Widget* widget, const std::string& birthsignId) +{ + const ESM::BirthSign *sign = MWBase::Environment::get().getWorld()->getStore().birthSigns.find(birthsignId); + + widget->setUserString("ToolTipType", "Layout"); + widget->setUserString("ToolTipLayout", "BirthSignToolTip"); + std::string image = sign->texture; + image.replace(image.size()-3, 3, "dds"); + widget->setUserString("ImageTexture_BirthSignImage", "textures\\" + image); + std::string text; + + text += sign->name; + text += "\n#BF9959" + sign->description; + + std::vector abilities, powers, spells; + + std::vector::const_iterator it = sign->powers.list.begin(); + std::vector::const_iterator end = sign->powers.list.end(); + for (; it != end; ++it) + { + const std::string &spellId = *it; + const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().spells.search(spellId); + if (!spell) + continue; // Skip spells which cannot be found + ESM::Spell::SpellType type = static_cast(spell->data.type); + if (type != ESM::Spell::ST_Spell && type != ESM::Spell::ST_Ability && type != ESM::Spell::ST_Power) + continue; // We only want spell, ability and powers. + + if (type == ESM::Spell::ST_Ability) + abilities.push_back(spellId); + else if (type == ESM::Spell::ST_Power) + powers.push_back(spellId); + else if (type == ESM::Spell::ST_Spell) + spells.push_back(spellId); + } + + struct{ const std::vector &spells; std::string label; } categories[3] = { + {abilities, "sBirthsignmenu1"}, + {powers, "sPowers"}, + {spells, "sBirthsignmenu2"} + }; + + for (int category = 0; category < 3; ++category) + { + for (std::vector::const_iterator it = categories[category].spells.begin(); it != categories[category].spells.end(); ++it) + { + if (it == categories[category].spells.begin()) + { + text += std::string("\n#DDC79E") + std::string("#{") + categories[category].label + "}"; + } + + const std::string &spellId = *it; + + const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().spells.search(spellId); + text += "\n#BF9959" + spell->name; + } + } + + widget->setUserString("Caption_BirthSignText", text); +} + +void ToolTips::createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace) +{ + widget->setUserString("Caption_CenteredCaption", playerRace->name); + widget->setUserString("Caption_CenteredCaptionText", playerRace->description); + widget->setUserString("ToolTipType", "Layout"); + widget->setUserString("ToolTipLayout", "TextWithCenteredCaptionToolTip"); +} + +void ToolTips::createClassToolTip(MyGUI::Widget* widget, const ESM::Class& playerClass) +{ + if (playerClass.name == "") + return; + + int spec = playerClass.data.specialization; + std::string specStr; + if (spec == 0) + specStr = "#{sSpecializationCombat}"; + else if (spec == 1) + specStr = "#{sSpecializationMagic}"; + else if (spec == 2) + specStr = "#{sSpecializationStealth}"; + + widget->setUserString("Caption_ClassName", playerClass.name); + widget->setUserString("Caption_ClassDescription", playerClass.description); + widget->setUserString("Caption_ClassSpecialisation", "#{sSpecialization}: " + specStr); + widget->setUserString("ToolTipType", "Layout"); + widget->setUserString("ToolTipLayout", "ClassToolTip"); +} + +void ToolTips::setDelay(float delay) +{ + mDelay = delay; + mRemainingDelay = mDelay; +} diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp new file mode 100644 index 0000000000..036bdfaa34 --- /dev/null +++ b/apps/openmw/mwgui/tooltips.hpp @@ -0,0 +1,102 @@ + +#ifndef MWGUI_TOOLTIPS_H +#define MWGUI_TOOLTIPS_H + +#include +#include "../mwworld/ptr.hpp" + +#include "widgets.hpp" + +namespace MWGui +{ + class WindowManager; + + // Info about tooltip that is supplied by the MWWorld::Class object + struct ToolTipInfo + { + public: + std::string caption; + std::string text; + std::string icon; + + // enchantment (for cloth, armor, weapons) + std::string enchant; + + // effects (for potions, ingredients) + Widgets::SpellEffectList effects; + }; + + class ToolTips : public OEngine::GUI::Layout + { + public: + ToolTips(WindowManager* windowManager); + + void onFrame(float frameDuration); + + void enterGameMode(); + void enterGuiMode(); + + void setEnabled(bool enabled); + + void toggleFullHelp(); ///< show extra info in item tooltips (owner, script) + bool getFullHelp() const; + + void setDelay(float delay); + + void setFocusObject(const MWWorld::Ptr& focus); + void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y); + ///< set the screen-space position of the tooltip for focused object + + static std::string getValueString(const int value, const std::string& prefix); + ///< @return "prefix: value" or "" if value is 0 + + static std::string getMiscString(const std::string& text, const std::string& prefix); + ///< @return "prefix: text" or "" if text is empty + + static std::string toString(const float value); + static std::string toString(const int value); + + static std::string getCountString(const int value); + ///< @return blank string if count is 1, or else " (value)" + + // these do not create an actual tooltip, but they fill in the data that is required so the tooltip + // system knows what to show in case this widget is hovered + static void createSkillToolTip(MyGUI::Widget* widget, int skillId); + static void createAttributeToolTip(MyGUI::Widget* widget, int attributeId); + static void createSpecializationToolTip(MyGUI::Widget* widget, const std::string& name, int specId); + static void createBirthsignToolTip(MyGUI::Widget* widget, const std::string& birthsignId); + static void createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace); + static void createClassToolTip(MyGUI::Widget* widget, const ESM::Class& playerClass); + + private: + MyGUI::Widget* mDynamicToolTipBox; + + WindowManager* mWindowManager; + + MWWorld::Ptr mFocusObject; + + void findImageExtension(std::string& image); + + MyGUI::IntSize getToolTipViaPtr (bool image=true); + ///< @return requested tooltip size + + MyGUI::IntSize createToolTip(const ToolTipInfo& info); + ///< @return requested tooltip size + + float mFocusToolTipX; + float mFocusToolTipY; + + float mDelay; + float mRemainingDelay; // remaining time until tooltip will show + + int mLastMouseX; + int mLastMouseY; + + bool mGameMode; + + bool mEnabled; + + bool mFullHelp; + }; +} +#endif diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp new file mode 100644 index 0000000000..47d299fdc6 --- /dev/null +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -0,0 +1,370 @@ +#include "tradewindow.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/world.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwsound/soundmanager.hpp" + +#include "window_manager.hpp" +#include "inventorywindow.hpp" + +namespace MWGui +{ + TradeWindow::TradeWindow(WindowManager& parWindowManager) : + WindowBase("openmw_trade_window_layout.xml", parWindowManager) + , ContainerBase(NULL) // no drag&drop + , mCurrentBalance(0) + { + MyGUI::ScrollView* itemView; + MyGUI::Widget* containerWidget; + getWidget(containerWidget, "Items"); + getWidget(itemView, "ItemView"); + setWidgets(containerWidget, itemView); + + getWidget(mFilterAll, "AllButton"); + getWidget(mFilterWeapon, "WeaponButton"); + getWidget(mFilterApparel, "ApparelButton"); + getWidget(mFilterMagic, "MagicButton"); + getWidget(mFilterMisc, "MiscButton"); + + getWidget(mMaxSaleButton, "MaxSaleButton"); + getWidget(mCancelButton, "CancelButton"); + getWidget(mOfferButton, "OfferButton"); + getWidget(mPlayerGold, "PlayerGold"); + getWidget(mMerchantGold, "MerchantGold"); + getWidget(mIncreaseButton, "IncreaseButton"); + getWidget(mDecreaseButton, "DecreaseButton"); + getWidget(mTotalBalance, "TotalBalance"); + getWidget(mTotalBalanceLabel, "TotalBalanceLabel"); + getWidget(mBottomPane, "BottomPane"); + + // adjust size of buttons to fit text + int curX = 0; + mFilterAll->setSize( mFilterAll->getTextSize().width + 24, mFilterAll->getSize().height ); + curX += mFilterAll->getTextSize().width + 24 + 4; + + mFilterWeapon->setPosition(curX, mFilterWeapon->getPosition().top); + mFilterWeapon->setSize( mFilterWeapon->getTextSize().width + 24, mFilterWeapon->getSize().height ); + curX += mFilterWeapon->getTextSize().width + 24 + 4; + + mFilterApparel->setPosition(curX, mFilterApparel->getPosition().top); + mFilterApparel->setSize( mFilterApparel->getTextSize().width + 24, mFilterApparel->getSize().height ); + curX += mFilterApparel->getTextSize().width + 24 + 4; + + mFilterMagic->setPosition(curX, mFilterMagic->getPosition().top); + mFilterMagic->setSize( mFilterMagic->getTextSize().width + 24, mFilterMagic->getSize().height ); + curX += mFilterMagic->getTextSize().width + 24 + 4; + + mFilterMisc->setPosition(curX, mFilterMisc->getPosition().top); + mFilterMisc->setSize( mFilterMisc->getTextSize().width + 24, mFilterMisc->getSize().height ); + + mFilterAll->setStateSelected(true); + + mFilterAll->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged); + mFilterWeapon->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged); + mFilterApparel->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged); + mFilterMagic->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged); + mFilterMisc->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onFilterChanged); + + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onCancelButtonClicked); + mOfferButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TradeWindow::onOfferButtonClicked); + + mMaxSaleButton->setSize(MyGUI::IntSize(mMaxSaleButton->getTextSize().width + 24, mMaxSaleButton->getHeight())); + + int cancelButtonWidth = mCancelButton->getTextSize().width + 24; + mCancelButton->setCoord(mBottomPane->getWidth()-cancelButtonWidth, + mCancelButton->getTop(), + cancelButtonWidth, + mCancelButton->getHeight()); + + int offerButtonWidth = mOfferButton->getTextSize().width + 24; + mOfferButton->setCoord(mBottomPane->getWidth()-cancelButtonWidth-offerButtonWidth-8, + mOfferButton->getTop(), + offerButtonWidth, + mOfferButton->getHeight()); + + setCoord(400, 0, 400, 300); + + static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &TradeWindow::onWindowResize); + } + + void TradeWindow::startTrade(MWWorld::Ptr actor) + { + setTitle(MWWorld::Class::get(actor).getName(actor)); + + mCurrentBalance = 0; + + mWindowManager.getInventoryWindow()->startTrade(); + + mBoughtItems.clear(); + + ContainerBase::openContainer(actor); + + updateLabels(); + + drawItems(); + } + + void TradeWindow::onFilterChanged(MyGUI::Widget* _sender) + { + if (_sender == mFilterAll) + setFilter(ContainerBase::Filter_All); + else if (_sender == mFilterWeapon) + setFilter(ContainerBase::Filter_Weapon); + else if (_sender == mFilterApparel) + setFilter(ContainerBase::Filter_Apparel); + else if (_sender == mFilterMagic) + setFilter(ContainerBase::Filter_Magic); + else if (_sender == mFilterMisc) + setFilter(ContainerBase::Filter_Misc); + + mFilterAll->setStateSelected(false); + mFilterWeapon->setStateSelected(false); + mFilterApparel->setStateSelected(false); + mFilterMagic->setStateSelected(false); + mFilterMisc->setStateSelected(false); + + static_cast(_sender)->setStateSelected(true); + } + + void TradeWindow::onWindowResize(MyGUI::Window* _sender) + { + drawItems(); + } + + void TradeWindow::onOfferButtonClicked(MyGUI::Widget* _sender) + { + // were there any items traded at all? + MWWorld::ContainerStore& playerBought = mWindowManager.getInventoryWindow()->getBoughtItems(); + MWWorld::ContainerStore& merchantBought = getBoughtItems(); + if (playerBought.begin() == playerBought.end() && merchantBought.begin() == merchantBought.end()) + { + // user notification + MWBase::Environment::get().getWindowManager()-> + messageBox(MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sBarterDialog11")->str, std::vector()); + return; + } + + // check if the player can afford this + if (mCurrentBalance < 0 && mWindowManager.getInventoryWindow()->getPlayerGold() < std::abs(mCurrentBalance)) + { + // user notification + MWBase::Environment::get().getWindowManager()-> + messageBox(MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sBarterDialog1")->str, std::vector()); + return; + } + + // check if the merchant can afford this + int merchantgold; + if (mPtr.getTypeName() == typeid(ESM::NPC).name()) + { + ESMS::LiveCellRef* ref = mPtr.get(); + if (ref->base->npdt52.gold == -10) + merchantgold = ref->base->npdt12.gold; + else + merchantgold = ref->base->npdt52.gold; + } + else // ESM::Creature + { + ESMS::LiveCellRef* ref = mPtr.get(); + merchantgold = ref->base->data.gold; + } + if (mCurrentBalance > 0 && merchantgold < mCurrentBalance) + { + // user notification + MWBase::Environment::get().getWindowManager()-> + messageBox(MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sBarterDialog2")->str, std::vector()); + return; + } + + // success! make the item transfer. + transferBoughtItems(); + mWindowManager.getInventoryWindow()->transferBoughtItems(); + + // add or remove gold from the player. + bool goldFound = false; + MWWorld::Ptr gold; + MWWorld::ContainerStore& playerStore = mWindowManager.getInventoryWindow()->getContainerStore(); + for (MWWorld::ContainerStoreIterator it = playerStore.begin(); + it != playerStore.end(); ++it) + { + if (MWWorld::Class::get(*it).getName(*it) == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) + { + goldFound = true; + gold = *it; + } + } + if (goldFound) + { + gold.getRefData().setCount(gold.getRefData().getCount() + mCurrentBalance); + } + else + { + assert(mCurrentBalance > 0); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), "Gold_001"); + ref.getPtr().getRefData().setCount(mCurrentBalance); + playerStore.add(ref.getPtr()); + } + + std::string sound = "Item Gold Up"; + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); + + mWindowManager.popGuiMode(); + } + + void TradeWindow::onCancelButtonClicked(MyGUI::Widget* _sender) + { + // i give you back your stuff! + returnBoughtItems(mWindowManager.getInventoryWindow()->getContainerStore()); + // now gimme back my stuff! + mWindowManager.getInventoryWindow()->returnBoughtItems(MWWorld::Class::get(mPtr).getContainerStore(mPtr)); + + mWindowManager.popGuiMode(); + } + + void TradeWindow::updateLabels() + { + mPlayerGold->setCaption(MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sYourGold")->str + + " " + boost::lexical_cast(mWindowManager.getInventoryWindow()->getPlayerGold())); + + if (mCurrentBalance > 0) + { + mTotalBalanceLabel->setCaption(MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sTotalSold")->str); + mTotalBalance->setCaption(boost::lexical_cast(mCurrentBalance)); + } + else + { + mTotalBalanceLabel->setCaption(MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sTotalCost")->str); + mTotalBalance->setCaption(boost::lexical_cast(-mCurrentBalance)); + } + + int merchantgold; + if (mPtr.getTypeName() == typeid(ESM::NPC).name()) + { + ESMS::LiveCellRef* ref = mPtr.get(); + if (ref->base->npdt52.gold == -10) + merchantgold = ref->base->npdt12.gold; + else + merchantgold = ref->base->npdt52.gold; + } + else // ESM::Creature + { + ESMS::LiveCellRef* ref = mPtr.get(); + merchantgold = ref->base->data.gold; + } + + mMerchantGold->setCaption(MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sSellerGold")->str + + " " + boost::lexical_cast(merchantgold)); + } + + std::vector TradeWindow::getEquippedItems() + { + std::vector items; + + if (mPtr.getTypeName() == typeid(ESM::Creature).name()) + { + // creatures don't have equipment slots. + return items; + } + + MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + + for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + MWWorld::ContainerStoreIterator it = invStore.getSlot(slot); + if (it != invStore.end()) + { + items.push_back(*it); + } + } + + return items; + } + + bool TradeWindow::npcAcceptsItem(MWWorld::Ptr item) + { + int services = 0; + if (mPtr.getTypeName() == typeid(ESM::NPC).name()) + { + ESMS::LiveCellRef* ref = mPtr.get(); + if (ref->base->hasAI) + services = ref->base->AI.services; + } + else if (mPtr.getTypeName() == typeid(ESM::Creature).name()) + { + ESMS::LiveCellRef* ref = mPtr.get(); + if (ref->base->hasAI) + services = ref->base->AI.services; + } + + /// \todo what about potions, there doesn't seem to be a flag for them?? + + if (item.getTypeName() == typeid(ESM::Weapon).name()) + return services & ESM::NPC::Weapon; + else if (item.getTypeName() == typeid(ESM::Armor).name()) + return services & ESM::NPC::Armor; + else if (item.getTypeName() == typeid(ESM::Clothing).name()) + return services & ESM::NPC::Clothing; + else if (item.getTypeName() == typeid(ESM::Book).name()) + return services & ESM::NPC::Books; + else if (item.getTypeName() == typeid(ESM::Ingredient).name()) + return services & ESM::NPC::Ingredients; + else if (item.getTypeName() == typeid(ESM::Tool).name()) + return services & ESM::NPC::Picks; + else if (item.getTypeName() == typeid(ESM::Probe).name()) + return services & ESM::NPC::Probes; + else if (item.getTypeName() == typeid(ESM::Light).name()) + return services & ESM::NPC::Lights; + else if (item.getTypeName() == typeid(ESM::Apparatus).name()) + return services & ESM::NPC::Apparatus; + else if (item.getTypeName() == typeid(ESM::Repair).name()) + return services & ESM::NPC::RepairItem; + else if (item.getTypeName() == typeid(ESM::Miscellaneous).name()) + return services & ESM::NPC::Misc; + + return false; + } + + std::vector TradeWindow::itemsToIgnore() + { + std::vector items; + MWWorld::ContainerStore& invStore = MWWorld::Class::get(mPtr).getContainerStore(mPtr); + + for (MWWorld::ContainerStoreIterator it = invStore.begin(); + it != invStore.end(); ++it) + { + if (!npcAcceptsItem(*it)) + items.push_back(*it); + } + + return items; + } + + void TradeWindow::sellToNpc(MWWorld::Ptr item, int count) + { + /// \todo price adjustment depending on merchantile skill + + mCurrentBalance -= MWWorld::Class::get(item).getValue(item) * count; + + updateLabels(); + } + + void TradeWindow::buyFromNpc(MWWorld::Ptr item, int count) + { + /// \todo price adjustment depending on merchantile skill + + mCurrentBalance += MWWorld::Class::get(item).getValue(item) * count; + + updateLabels(); + } + + void TradeWindow::onReferenceUnavailable() + { + // remove both Trade and Dialogue (since you always trade with the NPC/creature that you have previously talked to) + mWindowManager.removeGuiMode(GM_Barter); + mWindowManager.removeGuiMode(GM_Dialogue); + } +} diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp new file mode 100644 index 0000000000..b391fd8fa2 --- /dev/null +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -0,0 +1,77 @@ +#ifndef MWGUI_TRADEWINDOW_H +#define MWGUI_TRADEWINDOW_H + +#include "container.hpp" +#include "window_base.hpp" + +#include "../mwworld/ptr.hpp" + +namespace MyGUI +{ + class Gui; + class Widget; +} + +namespace MWGui +{ + class WindowManager; +} + + +namespace MWGui +{ + class TradeWindow : public ContainerBase, public WindowBase + { + public: + TradeWindow(WindowManager& parWindowManager); + + void startTrade(MWWorld::Ptr actor); + + void sellToNpc(MWWorld::Ptr item, int count); ///< only used for adjusting the gold balance + void buyFromNpc(MWWorld::Ptr item, int count); ///< only used for adjusting the gold balance + + bool npcAcceptsItem(MWWorld::Ptr item); + + protected: + MyGUI::Button* mFilterAll; + MyGUI::Button* mFilterWeapon; + MyGUI::Button* mFilterApparel; + MyGUI::Button* mFilterMagic; + MyGUI::Button* mFilterMisc; + + MyGUI::Button* mIncreaseButton; + MyGUI::Button* mDecreaseButton; + MyGUI::TextBox* mTotalBalanceLabel; + MyGUI::TextBox* mTotalBalance; + + MyGUI::Widget* mBottomPane; + + MyGUI::Button* mMaxSaleButton; + MyGUI::Button* mCancelButton; + MyGUI::Button* mOfferButton; + MyGUI::TextBox* mPlayerGold; + MyGUI::TextBox* mMerchantGold; + + int mCurrentBalance; + + void onWindowResize(MyGUI::Window* _sender); + void onFilterChanged(MyGUI::Widget* _sender); + void onOfferButtonClicked(MyGUI::Widget* _sender); + void onCancelButtonClicked(MyGUI::Widget* _sender); + + // don't show items that the NPC has equipped in his trade-window. + virtual bool ignoreEquippedItems() { return true; } + virtual std::vector getEquippedItems(); + + virtual bool isTrading() { return true; } + virtual bool isTradeWindow() { return true; } + + virtual std::vector itemsToIgnore(); + + void updateLabels(); + + virtual void onReferenceUnavailable(); + }; +} + +#endif diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 74603aaf1c..65de7ce0a8 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -222,7 +222,7 @@ void MWSpell::setSpellId(const std::string &spellId) updateWidgets(); } -void MWSpell::createEffectWidgets(std::vector &effects, MyGUI::WidgetPtr creator, MyGUI::IntCoord &coord) +void MWSpell::createEffectWidgets(std::vector &effects, MyGUI::WidgetPtr creator, MyGUI::IntCoord &coord, int flags) { const ESMS::ESMStore &store = mWindowManager->getStore(); const ESM::Spell *spell = store.spells.search(id); @@ -234,9 +234,20 @@ void MWSpell::createEffectWidgets(std::vector &effects, MyGUI: { effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); effect->setWindowManager(mWindowManager); - effect->setSpellEffect(*it); + SpellEffectParams params; + params.mEffectID = it->effectID; + params.mSkill = it->skill; + params.mAttribute = it->attribute; + params.mDuration = it->duration; + params.mMagnMin = it->magnMin; + params.mMagnMax = it->magnMax; + params.mRange = it->range; + params.mIsConstant = (flags & MWEffectList::EF_Constant); + params.mNoTarget = (flags & MWEffectList::EF_NoTarget); + effect->setSpellEffect(params); effects.push_back(effect); coord.top += effect->getHeight(); + coord.width = std::max(coord.width, effect->getRequestedWidth()); } } @@ -264,18 +275,107 @@ MWSpell::~MWSpell() { } +/* MWEffectList */ + +MWEffectList::MWEffectList() + : mWindowManager(nullptr) + , mEffectList(0) +{ +} + +void MWEffectList::setEffectList(const SpellEffectList& list) +{ + mEffectList = list; + updateWidgets(); +} + +void MWEffectList::createEffectWidgets(std::vector &effects, MyGUI::WidgetPtr creator, MyGUI::IntCoord &coord, bool center, int flags) +{ + // We don't know the width of all the elements beforehand, so we do it in + // 2 steps: first, create all widgets and check their width.... + MWSpellEffectPtr effect = nullptr; + int maxwidth = coord.width; + + for (SpellEffectList::iterator it=mEffectList.begin(); + it != mEffectList.end(); ++it) + { + effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); + effect->setWindowManager(mWindowManager); + it->mIsConstant = (flags & EF_Constant) || it->mIsConstant; + it->mNoTarget = (flags & EF_NoTarget) || it->mNoTarget; + effect->setSpellEffect(*it); + effects.push_back(effect); + if (effect->getRequestedWidth() > maxwidth) + maxwidth = effect->getRequestedWidth(); + + coord.top += effect->getHeight(); + } + + // ... then adjust the size for all widgets + for (std::vector::iterator it = effects.begin(); it != effects.end(); ++it) + { + effect = static_cast(*it); + bool needcenter = center && (maxwidth > effect->getRequestedWidth()); + int diff = maxwidth - effect->getRequestedWidth(); + if (needcenter) + { + effect->setCoord(diff/2, effect->getCoord().top, effect->getRequestedWidth(), effect->getCoord().height); + } + else + { + effect->setCoord(0, effect->getCoord().top, effect->getRequestedWidth(), effect->getCoord().height); + } + } + + // inform the parent about width + coord.width = maxwidth; +} + +void MWEffectList::updateWidgets() +{ +} + +void MWEffectList::initialiseOverride() +{ + Base::initialiseOverride(); +} + +MWEffectList::~MWEffectList() +{ +} + +SpellEffectList MWEffectList::effectListFromESM(const ESM::EffectList* effects) +{ + SpellEffectList result; + std::vector::const_iterator end = effects->list.end(); + for (std::vector::const_iterator it = effects->list.begin(); it != end; ++it) + { + SpellEffectParams params; + params.mEffectID = it->effectID; + params.mSkill = it->skill; + params.mAttribute = it->attribute; + params.mDuration = it->duration; + params.mMagnMin = it->magnMin; + params.mMagnMax = it->magnMax; + params.mRange = it->range; + result.push_back(params); + } + return result; +} + /* MWSpellEffect */ MWSpellEffect::MWSpellEffect() : mWindowManager(nullptr) , imageWidget(nullptr) , textWidget(nullptr) + , mRequestedWidth(0) { } -void MWSpellEffect::setSpellEffect(SpellEffectValue value) +void MWSpellEffect::setSpellEffect(const SpellEffectParams& params) { - effect = value; + mEffectParams = params; updateWidgets(); } @@ -285,54 +385,71 @@ void MWSpellEffect::updateWidgets() return; const ESMS::ESMStore &store = mWindowManager->getStore(); - const ESM::MagicEffect *magicEffect = store.magicEffects.search(effect.effectID); + const ESM::MagicEffect *magicEffect = store.magicEffects.search(mEffectParams.mEffectID); + if (!magicEffect) + return; if (textWidget) { - if (magicEffect) + std::string pt = mWindowManager->getGameSettingString("spoint", ""); + std::string pts = mWindowManager->getGameSettingString("spoints", ""); + std::string to = " " + mWindowManager->getGameSettingString("sTo", "") + " "; + std::string sec = " " + mWindowManager->getGameSettingString("ssecond", ""); + std::string secs = " " + mWindowManager->getGameSettingString("sseconds", ""); + + std::string effectIDStr = effectIDToString(mEffectParams.mEffectID); + std::string spellLine = mWindowManager->getGameSettingString(effectIDStr, ""); + if (effectInvolvesSkill(effectIDStr) && mEffectParams.mSkill >= 0 && mEffectParams.mSkill < ESM::Skill::Length) { - // TODO: Get name of effect from GMST - std::string spellLine = ""; - if (effect.skill >= 0 && effect.skill < ESM::Skill::Length) - { - spellLine += " " + mWindowManager->getGameSettingString(ESM::Skill::sSkillNameIds[effect.skill], ""); - } - if (effect.attribute >= 0 && effect.attribute < 8) - { - static const char *attributes[8] = { - "sAttributeStrength", - "sAttributeIntelligence", - "sAttributeWillpower", - "sAttributeAgility", - "sAttributeSpeed", - "sAttributeEndurance", - "sAttributePersonality", - "sAttributeLuck" - }; - spellLine += " " + mWindowManager->getGameSettingString(attributes[effect.attribute], ""); - } - if (effect.magnMin >= 0 || effect.magnMax >= 0) - { - if (effect.magnMin == effect.magnMax) - spellLine += " " + boost::lexical_cast(effect.magnMin) + " pts"; - else - { - spellLine += " " + boost::lexical_cast(effect.magnMin) + " to " + boost::lexical_cast(effect.magnMin) + " pts"; - } - } - if (effect.duration >= 0) - { - spellLine += " for " + boost::lexical_cast(effect.duration) + " secs"; - } - if (effect.range == ESM::RT_Self) - spellLine += " on Self"; - else if (effect.range == ESM::RT_Touch) - spellLine += " on Touch"; - else if (effect.range == ESM::RT_Target) - spellLine += " on Target"; - static_cast(textWidget)->setCaption(spellLine); + spellLine += " " + mWindowManager->getGameSettingString(ESM::Skill::sSkillNameIds[mEffectParams.mSkill], ""); } - else - static_cast(textWidget)->setCaption(""); + if (effectInvolvesAttribute(effectIDStr) && mEffectParams.mAttribute >= 0 && mEffectParams.mAttribute < 8) + { + static const char *attributes[8] = { + "sAttributeStrength", + "sAttributeIntelligence", + "sAttributeWillpower", + "sAttributeAgility", + "sAttributeSpeed", + "sAttributeEndurance", + "sAttributePersonality", + "sAttributeLuck" + }; + spellLine += " " + mWindowManager->getGameSettingString(attributes[mEffectParams.mAttribute], ""); + } + + if ((mEffectParams.mMagnMin >= 0 || mEffectParams.mMagnMax >= 0) && effectHasMagnitude(effectIDStr)) + { + if (mEffectParams.mMagnMin == mEffectParams.mMagnMax) + spellLine += " " + boost::lexical_cast(mEffectParams.mMagnMin) + " " + ((mEffectParams.mMagnMin == 1) ? pt : pts); + else + { + spellLine += " " + boost::lexical_cast(mEffectParams.mMagnMin) + to + boost::lexical_cast(mEffectParams.mMagnMax) + " " + pts; + } + } + + // constant effects have no duration and no target + if (!mEffectParams.mIsConstant) + { + if (mEffectParams.mDuration >= 0 && effectHasDuration(effectIDStr)) + { + spellLine += " " + mWindowManager->getGameSettingString("sfor", "") + " " + boost::lexical_cast(mEffectParams.mDuration) + ((mEffectParams.mDuration == 1) ? sec : secs); + } + + // potions have no target + if (!mEffectParams.mNoTarget) + { + std::string on = mWindowManager->getGameSettingString("sonword", ""); + if (mEffectParams.mRange == ESM::RT_Self) + spellLine += " " + on + " " + mWindowManager->getGameSettingString("sRangeSelf", ""); + else if (mEffectParams.mRange == ESM::RT_Touch) + spellLine += " " + on + " " + mWindowManager->getGameSettingString("sRangeTouch", ""); + else if (mEffectParams.mRange == ESM::RT_Target) + spellLine += " " + on + " " + mWindowManager->getGameSettingString("sRangeTarget", ""); + } + } + + static_cast(textWidget)->setCaption(spellLine); + mRequestedWidth = textWidget->getTextSize().width + 24; } if (imageWidget) { @@ -342,6 +459,267 @@ void MWSpellEffect::updateWidgets() } } +std::string MWSpellEffect::effectIDToString(const short effectID) +{ + // Map effect ID to GMST name + // http://www.uesp.net/morrow/hints/mweffects.shtml + std::map names; + names[85] ="sEffectAbsorbAttribute"; + names[88] ="sEffectAbsorbFatigue"; + names[86] ="sEffectAbsorbHealth"; + names[87] ="sEffectAbsorbSpellPoints"; + names[89] ="sEffectAbsorbSkill"; + names[63] ="sEffectAlmsiviIntervention"; + names[47] ="sEffectBlind"; + names[123] ="sEffectBoundBattleAxe"; + names[129] ="sEffectBoundBoots"; + names[127] ="sEffectBoundCuirass"; + names[120] ="sEffectBoundDagger"; + names[131] ="sEffectBoundGloves"; + names[128] ="sEffectBoundHelm"; + names[125] ="sEffectBoundLongbow"; + names[121] ="sEffectBoundLongsword"; + names[122] ="sEffectBoundMace"; + names[130] ="sEffectBoundShield"; + names[124] ="sEffectBoundSpear"; + names[7] ="sEffectBurden"; + names[50] ="sEffectCalmCreature"; + names[49] ="sEffectCalmHumanoid"; + names[40] ="sEffectChameleon"; + names[44] ="sEffectCharm"; + names[118] ="sEffectCommandCreatures"; + names[119] ="sEffectCommandHumanoids"; + names[132] ="sEffectCorpus"; // NB this typo. (bethesda made it) + names[70] ="sEffectCureBlightDisease"; + names[69] ="sEffectCureCommonDisease"; + names[71] ="sEffectCureCorprusDisease"; + names[73] ="sEffectCureParalyzation"; + names[72] ="sEffectCurePoison"; + names[22] ="sEffectDamageAttribute"; + names[25] ="sEffectDamageFatigue"; + names[23] ="sEffectDamageHealth"; + names[24] ="sEffectDamageMagicka"; + names[26] ="sEffectDamageSkill"; + names[54] ="sEffectDemoralizeCreature"; + names[53] ="sEffectDemoralizeHumanoid"; + names[64] ="sEffectDetectAnimal"; + names[65] ="sEffectDetectEnchantment"; + names[66] ="sEffectDetectKey"; + names[38] ="sEffectDisintegrateArmor"; + names[37] ="sEffectDisintegrateWeapon"; + names[57] ="sEffectDispel"; + names[62] ="sEffectDivineIntervention"; + names[17] ="sEffectDrainAttribute"; + names[20] ="sEffectDrainFatigue"; + names[18] ="sEffectDrainHealth"; + names[19] ="sEffectDrainSpellpoints"; + names[21] ="sEffectDrainSkill"; + names[8] ="sEffectFeather"; + names[14] ="sEffectFireDamage"; + names[4] ="sEffectFireShield"; + names[117] ="sEffectFortifyAttackBonus"; + names[79] ="sEffectFortifyAttribute"; + names[82] ="sEffectFortifyFatigue"; + names[80] ="sEffectFortifyHealth"; + names[81] ="sEffectFortifySpellpoints"; + names[84] ="sEffectFortifyMagickaMultiplier"; + names[83] ="sEffectFortifySkill"; + names[52] ="sEffectFrenzyCreature"; + names[51] ="sEffectFrenzyHumanoid"; + names[16] ="sEffectFrostDamage"; + names[6] ="sEffectFrostShield"; + names[39] ="sEffectInvisibility"; + names[9] ="sEffectJump"; + names[10] ="sEffectLevitate"; + names[41] ="sEffectLight"; + names[5] ="sEffectLightningShield"; + names[12] ="sEffectLock"; + names[60] ="sEffectMark"; + names[43] ="sEffectNightEye"; + names[13] ="sEffectOpen"; + names[45] ="sEffectParalyze"; + names[27] ="sEffectPoison"; + names[56] ="sEffectRallyCreature"; + names[55] ="sEffectRallyHumanoid"; + names[61] ="sEffectRecall"; + names[68] ="sEffectReflect"; + names[100] ="sEffectRemoveCurse"; + names[95] ="sEffectResistBlightDisease"; + names[94] ="sEffectResistCommonDisease"; + names[96] ="sEffectResistCorprusDisease"; + names[90] ="sEffectResistFire"; + names[91] ="sEffectResistFrost"; + names[93] ="sEffectResistMagicka"; + names[98] ="sEffectResistNormalWeapons"; + names[99] ="sEffectResistParalysis"; + names[97] ="sEffectResistPoison"; + names[92] ="sEffectResistShock"; + names[74] ="sEffectRestoreAttribute"; + names[77] ="sEffectRestoreFatigue"; + names[75] ="sEffectRestoreHealth"; + names[76] ="sEffectRestoreSpellPoints"; + names[78] ="sEffectRestoreSkill"; + names[42] ="sEffectSanctuary"; + names[3] ="sEffectShield"; + names[15] ="sEffectShockDamage"; + names[46] ="sEffectSilence"; + names[11] ="sEffectSlowFall"; + names[58] ="sEffectSoultrap"; + names[48] ="sEffectSound"; + names[67] ="sEffectSpellAbsorption"; + names[136] ="sEffectStuntedMagicka"; + names[106] ="sEffectSummonAncestralGhost"; + names[110] ="sEffectSummonBonelord"; + names[108] ="sEffectSummonLeastBonewalker"; + names[134] ="sEffectSummonCenturionSphere"; + names[103] ="sEffectSummonClannfear"; + names[104] ="sEffectSummonDaedroth"; + names[105] ="sEffectSummonDremora"; + names[114] ="sEffectSummonFlameAtronach"; + names[115] ="sEffectSummonFrostAtronach"; + names[113] ="sEffectSummonGoldenSaint"; + names[109] ="sEffectSummonGreaterBonewalker"; + names[112] ="sEffectSummonHunger"; + names[102] ="sEffectSummonScamp"; + names[107] ="sEffectSummonSkeletalMinion"; + names[116] ="sEffectSummonStormAtronach"; + names[111] ="sEffectSummonWingedTwilight"; + names[135] ="sEffectSunDamage"; + names[1] ="sEffectSwiftSwim"; + names[59] ="sEffectTelekinesis"; + names[101] ="sEffectTurnUndead"; + names[133] ="sEffectVampirism"; + names[0] ="sEffectWaterBreathing"; + names[2] ="sEffectWaterWalking"; + names[33] ="sEffectWeaknesstoBlightDisease"; + names[32] ="sEffectWeaknesstoCommonDisease"; + names[34] ="sEffectWeaknesstoCorprusDisease"; + names[28] ="sEffectWeaknesstoFire"; + names[29] ="sEffectWeaknesstoFrost"; + names[31] ="sEffectWeaknesstoMagicka"; + names[36] ="sEffectWeaknesstoNormalWeapons"; + names[35] ="sEffectWeaknesstoPoison"; + names[30] ="sEffectWeaknesstoShock"; + + // bloodmoon + names[138] ="sEffectSummonCreature01"; + names[139] ="sEffectSummonCreature02"; + names[140] ="sEffectSummonCreature03"; + names[141] ="sEffectSummonCreature04"; + names[142] ="sEffectSummonCreature05"; + + // tribunal + names[137] ="sEffectSummonFabricant"; + + assert(names.find(effectID) != names.end() && "Unimplemented effect type"); + + return names[effectID]; +} + +bool MWSpellEffect::effectHasDuration(const std::string& effect) +{ + // lists effects that have no duration (e.g. open lock) + std::vector effectsWithoutDuration; + effectsWithoutDuration.push_back("sEffectOpen"); + effectsWithoutDuration.push_back("sEffectLock"); + effectsWithoutDuration.push_back("sEffectDispel"); + effectsWithoutDuration.push_back("sEffectSunDamage"); + effectsWithoutDuration.push_back("sEffectCorpus"); + effectsWithoutDuration.push_back("sEffectVampirism"); + effectsWithoutDuration.push_back("sEffectMark"); + effectsWithoutDuration.push_back("sEffectRecall"); + effectsWithoutDuration.push_back("sEffectDivineIntervention"); + effectsWithoutDuration.push_back("sEffectAlmsiviIntervention"); + effectsWithoutDuration.push_back("sEffectCureCommonDisease"); + effectsWithoutDuration.push_back("sEffectCureBlightDisease"); + effectsWithoutDuration.push_back("sEffectCureCorprusDisease"); + effectsWithoutDuration.push_back("sEffectCurePoison"); + effectsWithoutDuration.push_back("sEffectCureParalyzation"); + effectsWithoutDuration.push_back("sEffectRemoveCurse"); + effectsWithoutDuration.push_back("sEffectRestoreAttribute"); + + return (std::find(effectsWithoutDuration.begin(), effectsWithoutDuration.end(), effect) == effectsWithoutDuration.end()); +} + +bool MWSpellEffect::effectHasMagnitude(const std::string& effect) +{ + // lists effects that have no magnitude (e.g. invisiblity) + std::vector effectsWithoutMagnitude; + effectsWithoutMagnitude.push_back("sEffectInvisibility"); + effectsWithoutMagnitude.push_back("sEffectStuntedMagicka"); + effectsWithoutMagnitude.push_back("sEffectParalyze"); + effectsWithoutMagnitude.push_back("sEffectSoultrap"); + effectsWithoutMagnitude.push_back("sEffectSilence"); + effectsWithoutMagnitude.push_back("sEffectParalyze"); + effectsWithoutMagnitude.push_back("sEffectInvisibility"); + effectsWithoutMagnitude.push_back("sEffectWaterWalking"); + effectsWithoutMagnitude.push_back("sEffectWaterBreathing"); + effectsWithoutMagnitude.push_back("sEffectSummonScamp"); + effectsWithoutMagnitude.push_back("sEffectSummonClannfear"); + effectsWithoutMagnitude.push_back("sEffectSummonDaedroth"); + effectsWithoutMagnitude.push_back("sEffectSummonDremora"); + effectsWithoutMagnitude.push_back("sEffectSummonAncestralGhost"); + effectsWithoutMagnitude.push_back("sEffectSummonSkeletalMinion"); + effectsWithoutMagnitude.push_back("sEffectSummonBonewalker"); + effectsWithoutMagnitude.push_back("sEffectSummonGreaterBonewalker"); + effectsWithoutMagnitude.push_back("sEffectSummonBonelord"); + effectsWithoutMagnitude.push_back("sEffectSummonWingedTwilight"); + effectsWithoutMagnitude.push_back("sEffectSummonHunger"); + effectsWithoutMagnitude.push_back("sEffectSummonGoldenSaint"); + effectsWithoutMagnitude.push_back("sEffectSummonFlameAtronach"); + effectsWithoutMagnitude.push_back("sEffectSummonFrostAtronach"); + effectsWithoutMagnitude.push_back("sEffectSummonStormAtronach"); + effectsWithoutMagnitude.push_back("sEffectSummonCenturionSphere"); + effectsWithoutMagnitude.push_back("sEffectBoundDagger"); + effectsWithoutMagnitude.push_back("sEffectBoundLongsword"); + effectsWithoutMagnitude.push_back("sEffectBoundMace"); + effectsWithoutMagnitude.push_back("sEffectBoundBattleAxe"); + effectsWithoutMagnitude.push_back("sEffectBoundSpear"); + effectsWithoutMagnitude.push_back("sEffectBoundLongbow"); + effectsWithoutMagnitude.push_back("sEffectBoundCuirass"); + effectsWithoutMagnitude.push_back("sEffectBoundHelm"); + effectsWithoutMagnitude.push_back("sEffectBoundBoots"); + effectsWithoutMagnitude.push_back("sEffectBoundShield"); + effectsWithoutMagnitude.push_back("sEffectBoundGloves"); + effectsWithoutMagnitude.push_back("sEffectStuntedMagicka"); + effectsWithoutMagnitude.push_back("sEffectMark"); + effectsWithoutMagnitude.push_back("sEffectRecall"); + effectsWithoutMagnitude.push_back("sEffectDivineIntervention"); + effectsWithoutMagnitude.push_back("sEffectAlmsiviIntervention"); + effectsWithoutMagnitude.push_back("sEffectCureCommonDisease"); + effectsWithoutMagnitude.push_back("sEffectCureBlightDisease"); + effectsWithoutMagnitude.push_back("sEffectCureCorprusDisease"); + effectsWithoutMagnitude.push_back("sEffectCurePoison"); + effectsWithoutMagnitude.push_back("sEffectCureParalyzation"); + effectsWithoutMagnitude.push_back("sEffectRemoveCurse"); + effectsWithoutMagnitude.push_back("sEffectSummonCreature01"); + effectsWithoutMagnitude.push_back("sEffectSummonCreature02"); + effectsWithoutMagnitude.push_back("sEffectSummonCreature03"); + effectsWithoutMagnitude.push_back("sEffectSummonCreature04"); + effectsWithoutMagnitude.push_back("sEffectSummonCreature05"); + effectsWithoutMagnitude.push_back("sEffectSummonFabricant"); + + return (std::find(effectsWithoutMagnitude.begin(), effectsWithoutMagnitude.end(), effect) == effectsWithoutMagnitude.end()); +} + +bool MWSpellEffect::effectInvolvesAttribute (const std::string& effect) +{ + return (effect == "sEffectRestoreAttribute" + || effect == "sEffectAbsorbAttribute" + || effect == "sEffectDrainAttribute" + || effect == "sEffectFortifyAttribute" + || effect == "sEffectDamageAttribute"); +} + +bool MWSpellEffect::effectInvolvesSkill (const std::string& effect) +{ + return (effect == "sEffectRestoreSkill" + || effect == "sEffectAbsorbSkill" + || effect == "sEffectDrainSkill" + || effect == "sEffectFortifySkill" + || effect == "sEffectDamageSkill"); +} + MWSpellEffect::~MWSpellEffect() { } @@ -389,7 +767,7 @@ void MWDynamicStat::setValue(int cur, int max_) static_cast(barTextWidget)->setCaption(""); } } -void MWDynamicStat::setTitle(const std::string text) +void MWDynamicStat::setTitle(const std::string& text) { if (textWidget) static_cast(textWidget)->setCaption(text); diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index a7916285eb..5d00baf871 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -21,8 +21,58 @@ namespace MWGui namespace Widgets { + class MWEffectList; + void fixTexturePath(std::string &path); + struct SpellEffectParams + { + SpellEffectParams() + : mMagnMin(-1) + , mMagnMax(-1) + , mRange(-1) + , mDuration(-1) + , mSkill(-1) + , mAttribute(-1) + , mEffectID(-1) + , mNoTarget(false) + , mIsConstant(false) + { + } + + bool mNoTarget; // potion effects for example have no target (target is always the player) + bool mIsConstant; // constant effect means that duration will not be displayed + + // value of -1 here means the effect is unknown to the player + short mEffectID; + + // value of -1 here means there is no skill/attribute + signed char mSkill, mAttribute; + + // value of -1 here means the value is unavailable + int mMagnMin, mMagnMax, mRange, mDuration; + + bool operator==(const SpellEffectParams& other) const + { + if (mEffectID != other.mEffectID) + return false; + + bool involvesAttribute = (mEffectID == 74 // restore attribute + || mEffectID == 85 // absorb attribute + || mEffectID == 17 // drain attribute + || mEffectID == 79 // fortify attribute + || mEffectID == 22); // damage attribute + bool involvesSkill = (mEffectID == 78 // restore skill + || mEffectID == 89 // absorb skill + || mEffectID == 21 // drain skill + || mEffectID == 83 // fortify skill + || mEffectID == 26); // damage skill + return ((other.mSkill == mSkill) || !involvesSkill) && ((other.mAttribute == mAttribute) && !involvesAttribute); + } + }; + + typedef std::vector SpellEffectList; + class MYGUI_EXPORT MWSkill : public Widget { MYGUI_RTTI_DERIVED( MWSkill ); @@ -108,6 +158,9 @@ namespace MWGui }; typedef MWAttribute* MWAttributePtr; + /** + * @todo remove this class and use MWEffectList instead + */ class MWSpellEffect; class MYGUI_EXPORT MWSpell : public Widget { @@ -119,14 +172,22 @@ namespace MWGui void setWindowManager(WindowManager* parWindowManager) { mWindowManager = parWindowManager; } void setSpellId(const std::string &id); - void createEffectWidgets(std::vector &effects, MyGUI::WidgetPtr creator, MyGUI::IntCoord &coord); + + /** + * @param vector to store the created effect widgets + * @param parent widget + * @param coordinates to use, will be expanded if more space is needed + * @param spell category, if this is 0, this means the spell effects are permanent and won't display e.g. duration + * @param various flags, see MWEffectList::EffectFlags + */ + void createEffectWidgets(std::vector &effects, MyGUI::WidgetPtr creator, MyGUI::IntCoord &coord, int flags); const std::string &getSpellId() const { return id; } protected: virtual ~MWSpell(); - virtual void initialiseOverride(); + virtual void initialiseOverride(); private: void updateWidgets(); @@ -137,6 +198,47 @@ namespace MWGui }; typedef MWSpell* MWSpellPtr; + class MYGUI_EXPORT MWEffectList : public Widget + { + MYGUI_RTTI_DERIVED( MWEffectList ); + public: + MWEffectList(); + + typedef MWMechanics::Stat EnchantmentValue; + + enum EffectFlags + { + EF_NoTarget = 0x01, // potions have no target (target is always the player) + EF_Constant = 0x02 // constant effect means that duration will not be displayed + }; + + void setWindowManager(WindowManager* parWindowManager) { mWindowManager = parWindowManager; } + void setEffectList(const SpellEffectList& list); + + static SpellEffectList effectListFromESM(const ESM::EffectList* effects); + + /** + * @param vector to store the created effect widgets + * @param parent widget + * @param coordinates to use, will be expanded if more space is needed + * @param center the effect widgets horizontally + * @param various flags, see MWEffectList::EffectFlags + */ + void createEffectWidgets(std::vector &effects, MyGUI::WidgetPtr creator, MyGUI::IntCoord &coord, bool center, int flags); + + protected: + virtual ~MWEffectList(); + + virtual void initialiseOverride(); + + private: + void updateWidgets(); + + WindowManager* mWindowManager; + SpellEffectList mEffectList; + }; + typedef MWEffectList* MWEffectListPtr; + class MYGUI_EXPORT MWSpellEffect : public Widget { MYGUI_RTTI_DERIVED( MWSpellEffect ); @@ -146,9 +248,15 @@ namespace MWGui typedef ESM::ENAMstruct SpellEffectValue; void setWindowManager(WindowManager* parWindowManager) { mWindowManager = parWindowManager; } - void setSpellEffect(SpellEffectValue value); + void setSpellEffect(const SpellEffectParams& params); - const SpellEffectValue &getSpellEffect() const { return effect; } + std::string effectIDToString(const short effectID); + bool effectHasMagnitude (const std::string& effect); + bool effectHasDuration (const std::string& effect); + bool effectInvolvesAttribute (const std::string& effect); + bool effectInvolvesSkill (const std::string& effect); + + int getRequestedWidth() const { return mRequestedWidth; } protected: virtual ~MWSpellEffect(); @@ -160,9 +268,10 @@ namespace MWGui void updateWidgets(); WindowManager* mWindowManager; - SpellEffectValue effect; + SpellEffectParams mEffectParams; MyGUI::ImageBox* imageWidget; MyGUI::TextBox* textWidget; + int mRequestedWidth; }; typedef MWSpellEffect* MWSpellEffectPtr; @@ -173,7 +282,7 @@ namespace MWGui MWDynamicStat(); void setValue(int value, int max); - void setTitle(const std::string text); + void setTitle(const std::string& text); int getValue() const { return value; } int getMax() const { return max; } @@ -191,7 +300,6 @@ namespace MWGui MyGUI::TextBox* barTextWidget; }; typedef MWDynamicStat* MWDynamicStatPtr; - } } diff --git a/apps/openmw/mwgui/window_base.cpp b/apps/openmw/mwgui/window_base.cpp index 1eb126a74c..4330579311 100644 --- a/apps/openmw/mwgui/window_base.cpp +++ b/apps/openmw/mwgui/window_base.cpp @@ -1,6 +1,8 @@ #include "window_base.hpp" #include "window_manager.hpp" +#include + using namespace MWGui; WindowBase::WindowBase(const std::string& parLayout, WindowManager& parWindowManager) @@ -13,10 +15,25 @@ void WindowBase::open() { } +void WindowBase::setVisible(bool visible) +{ + bool wasVisible = mMainWidget->getVisible(); + mMainWidget->setVisible(visible); + + if (!wasVisible && visible) + open(); +} + void WindowBase::center() { // Centre dialog - MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize(); + + // MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize(); + // Note by scrawl: The following works more reliably in the case when the window was _just_ + // resized and MyGUI RenderManager doesn't know about the new size yet + MyGUI::IntSize gameWindowSize = MyGUI::IntSize(Settings::Manager::getInt("resolution x", "Video"), + Settings::Manager::getInt("resolution y", "Video")); + MyGUI::IntCoord coord = mMainWidget->getCoord(); coord.left = (gameWindowSize.width - coord.width)/2; coord.top = (gameWindowSize.height - coord.height)/2; diff --git a/apps/openmw/mwgui/window_base.hpp b/apps/openmw/mwgui/window_base.hpp index 99ddbe9181..9cfdbe2612 100644 --- a/apps/openmw/mwgui/window_base.hpp +++ b/apps/openmw/mwgui/window_base.hpp @@ -16,6 +16,7 @@ namespace MWGui typedef MyGUI::delegates::CMultiDelegate1 EventHandle_WindowBase; virtual void open(); + virtual void setVisible(bool visible); // calls open() if visible is true and was false before void center(); /** Event : Dialog finished, OK button clicked.\n diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index 0792b0fcb7..5b963d3ae1 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -1,5 +1,4 @@ #include "window_manager.hpp" -#include "layouts.hpp" #include "text_input.hpp" #include "review.hpp" #include "dialogue.hpp" @@ -7,6 +6,20 @@ #include "map_window.hpp" #include "stats_window.hpp" #include "messagebox.hpp" +#include "container.hpp" +#include "inventorywindow.hpp" +#include "tooltips.hpp" +#include "scrollwindow.hpp" +#include "bookwindow.hpp" +#include "list.hpp" +#include "hud.hpp" +#include "mainmenu.hpp" +#include "countdialog.hpp" +#include "tradewindow.hpp" +#include "settingswindow.hpp" +#include "confirmationdialog.hpp" +#include "alchemywindow.hpp" +#include "spellwindow.hpp" #include "../mwmechanics/mechanicsmanager.hpp" #include "../mwinput/inputmanager.hpp" @@ -26,21 +39,29 @@ using namespace MWGui; WindowManager::WindowManager( - const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string logpath) + const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string& logpath) : mGuiManager(NULL) , hud(NULL) , map(NULL) , menu(NULL) - , stats(NULL) + , mStatsWindow(NULL) + , mToolTips(NULL) , mMessageBoxManager(NULL) , console(NULL) , mJournal(NULL) - , dialogueWindow(nullptr) + , mDialogueWindow(NULL) + , mBookWindow(NULL) + , mScrollWindow(NULL) + , mCountDialog(NULL) + , mTradeWindow(NULL) + , mSettingsWindow(NULL) + , mConfirmationDialog(NULL) + , mAlchemyWindow(NULL) + , mSpellWindow(NULL) , mCharGen(NULL) , playerClass() , playerName() , playerRaceId() - , playerBirthSignId() , playerAttributes() , playerMajorSkills() , playerMinorSkills() @@ -49,9 +70,6 @@ WindowManager::WindowManager( , playerMagicka() , playerFatigue() , gui(NULL) - , mode(GM_Game) - , nextMode(GM_Game) - , needModeChange(false) , garbageDialogs() , shown(GW_ALL) , allowed(newGame ? GW_None : GW_ALL) @@ -64,23 +82,51 @@ WindowManager::WindowManager( // Set up the GUI system mGuiManager = new OEngine::GUI::MyGUIManager(mOgre->getWindow(), mOgre->getScene(), false, logpath); gui = mGuiManager->getGui(); - + //Register own widgets with MyGUI MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + + MyGUI::LanguageManager::getInstance().eventRequestTag = MyGUI::newDelegate(this, &WindowManager::onRetrieveTag); // Get size info from the Gui object assert(gui); int w = MyGUI::RenderManager::getInstance().getViewSize().width; int h = MyGUI::RenderManager::getInstance().getViewSize().height; - hud = new HUD(w,h, showFPSLevel); + MyGUI::Widget* dragAndDropWidget = gui->createWidgetT("Widget","",0,0,w,h,MyGUI::Align::Default,"DragAndDrop","DragAndDropWidget"); + dragAndDropWidget->setVisible(false); + + mDragAndDrop = new DragAndDrop(); + mDragAndDrop->mIsOnDragAndDrop = false; + mDragAndDrop->mDraggedWidget = 0; + mDragAndDrop->mDragAndDropWidget = dragAndDropWidget; + menu = new MainMenu(w,h); map = new MapWindow(*this); - stats = new StatsWindow(*this); + mStatsWindow = new StatsWindow(*this); console = new Console(w,h, extensions); mJournal = new JournalWindow(*this); mMessageBoxManager = new MessageBoxManager(this); - dialogueWindow = new DialogueWindow(*this); + mInventoryWindow = new InventoryWindow(*this,mDragAndDrop); + mTradeWindow = new TradeWindow(*this); + mDialogueWindow = new DialogueWindow(*this); + mContainerWindow = new ContainerWindow(*this,mDragAndDrop); + hud = new HUD(w,h, showFPSLevel, mDragAndDrop); + mToolTips = new ToolTips(this); + mScrollWindow = new ScrollWindow(*this); + mBookWindow = new BookWindow(*this); + mCountDialog = new CountDialog(*this); + mSettingsWindow = new SettingsWindow(*this); + mConfirmationDialog = new ConfirmationDialog(*this); + mAlchemyWindow = new AlchemyWindow(*this); + mSpellWindow = new SpellWindow(*this); // The HUD is always on hud->setVisible(true); @@ -98,11 +144,8 @@ WindowManager::WindowManager( playerSkillValues.insert(std::make_pair(ESM::Skill::skillIds[i], MWMechanics::Stat())); } - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); - MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + unsetSelectedSpell(); + unsetSelectedWeapon(); // Set up visibility updateVisible(); @@ -116,11 +159,21 @@ WindowManager::~WindowManager() delete hud; delete map; delete menu; - delete stats; + delete mStatsWindow; delete mJournal; - delete dialogueWindow; - + delete mDialogueWindow; + delete mContainerWindow; + delete mInventoryWindow; + delete mToolTips; delete mCharGen; + delete mDragAndDrop; + delete mBookWindow; + delete mScrollWindow; + delete mTradeWindow; + delete mSettingsWindow; + delete mConfirmationDialog; + delete mAlchemyWindow; + delete mSpellWindow; cleanupGarbage(); } @@ -141,29 +194,10 @@ void WindowManager::cleanupGarbage() void WindowManager::update() { cleanupGarbage(); - if (needModeChange) - { - needModeChange = false; - MWBase::Environment::get().getInputManager()->setGuiMode(nextMode); - nextMode = GM_Game; - } - if (showFPSLevel > 0) - { - hud->setFPS(mFPS); - hud->setTriangleCount(mTriangleCount); - hud->setBatchCount(mBatchCount); - } -} -void WindowManager::setNextMode(GuiMode newMode) -{ - nextMode = newMode; - needModeChange = true; -} - -void WindowManager::setGuiMode(GuiMode newMode) -{ - MWBase::Environment::get().getInputManager()->setGuiMode(newMode); + hud->setFPS(mFPS); + hud->setTriangleCount(mTriangleCount); + hud->setBatchCount(mBatchCount); } void WindowManager::updateVisible() @@ -171,24 +205,59 @@ void WindowManager::updateVisible() // Start out by hiding everything except the HUD map->setVisible(false); menu->setVisible(false); - stats->setVisible(false); + mStatsWindow->setVisible(false); console->disable(); mJournal->setVisible(false); - dialogueWindow->setVisible(false); + mDialogueWindow->setVisible(false); + mContainerWindow->setVisible(false); + mInventoryWindow->setVisible(false); + mScrollWindow->setVisible(false); + mBookWindow->setVisible(false); + mTradeWindow->setVisible(false); + mSettingsWindow->setVisible(false); + mAlchemyWindow->setVisible(false); + mSpellWindow->setVisible(false); // Mouse is visible whenever we're not in game mode MyGUI::PointerManager::getInstance().setVisible(isGuiMode()); + bool gameMode = !isGuiMode(); + + if (gameMode) + mToolTips->enterGameMode(); + else + mToolTips->enterGuiMode(); + + setMinimapVisibility((allowed & GW_Map) && !map->pinned()); + setWeaponVisibility((allowed & GW_Inventory) && !mInventoryWindow->pinned()); + setSpellVisibility((allowed & GW_Magic) && !mSpellWindow->pinned()); + setHMSVisibility((allowed & GW_Stats) && !mStatsWindow->pinned()); + + // If in game mode, don't show anything. + if (gameMode) + return; + + GuiMode mode = mGuiModes.back(); + switch(mode) { - case GM_Game: - // If in game mode, don't show anything. - break; case GM_MainMenu: menu->setVisible(true); break; + case GM_Settings: + mSettingsWindow->setVisible(true); + break; case GM_Console: console->enable(); break; + case GM_Scroll: + mScrollWindow->setVisible(true); + break; + case GM_Book: + mBookWindow->setVisible(true); + break; + case GM_Alchemy: + mAlchemyWindow->setVisible(true); + break; case GM_Name: case GM_Race: case GM_Class: @@ -208,17 +277,24 @@ void WindowManager::updateVisible() int eff = shown & allowed; // Show the windows we want - map -> setVisible( (eff & GW_Map) != 0 ); - stats -> setVisible( (eff & GW_Stats) != 0 ); + map -> setVisible(eff & GW_Map); + mStatsWindow -> setVisible(eff & GW_Stats); + mInventoryWindow->setVisible(eff & GW_Inventory); + mSpellWindow->setVisible(eff & GW_Magic); break; } + case GM_Container: + mContainerWindow->setVisible(true); + mInventoryWindow->setVisible(true); + break; case GM_Dialogue: - dialogueWindow->open(); + mDialogueWindow->setVisible(true); + break; + case GM_Barter: + mInventoryWindow->setVisible(true); + mTradeWindow->setVisible(true); break; case GM_InterMessageBox: - if(!mMessageBoxManager->isInteractiveMessageBox()) { - setGuiMode(GM_Game); - } break; case GM_Journal: mJournal->setVisible(true); @@ -226,16 +302,14 @@ void WindowManager::updateVisible() break; default: // Unsupported mode, switch back to game - // Note: The call will eventually end up this method again but - // will stop at the check if mode is GM_Game. - setGuiMode(GM_Game); break; } } void WindowManager::setValue (const std::string& id, const MWMechanics::Stat& value) { - stats->setValue (id, value); + mStatsWindow->setValue (id, value); + mCharGen->setValue(id, value); static const char *ids[] = { @@ -265,14 +339,16 @@ void WindowManager::setValue (const std::string& id, const MWMechanics::Stat& value) { - stats->setValue(parSkill, value); + mStatsWindow->setValue(parSkill, value); + mCharGen->setValue(parSkill, value); playerSkillValues[parSkill] = value; } void WindowManager::setValue (const std::string& id, const MWMechanics::DynamicStat& value) { - stats->setValue (id, value); + mStatsWindow->setValue (id, value); hud->setValue (id, value); + mCharGen->setValue(id, value); if (id == "HBar") { playerHealth = value; @@ -304,7 +380,7 @@ MWMechanics::DynamicStat WindowManager::getValue(const std::string& id) void WindowManager::setValue (const std::string& id, const std::string& value) { - stats->setValue (id, value); + mStatsWindow->setValue (id, value); if (id=="name") playerName = value; else if (id=="race") @@ -313,46 +389,36 @@ void WindowManager::setValue (const std::string& id, const std::string& value) void WindowManager::setValue (const std::string& id, int value) { - stats->setValue (id, value); + mStatsWindow->setValue (id, value); } void WindowManager::setPlayerClass (const ESM::Class &class_) { playerClass = class_; - stats->setValue("class", playerClass.name); + mStatsWindow->setValue("class", playerClass.name); } void WindowManager::configureSkills (const SkillList& major, const SkillList& minor) { - stats->configureSkills (major, minor); + mStatsWindow->configureSkills (major, minor); + mCharGen->configureSkills(major, minor); playerMajorSkills = major; playerMinorSkills = minor; } -void WindowManager::setFactions (const FactionList& factions) -{ - stats->setFactions (factions); -} - -void WindowManager::setBirthSign (const std::string &signId) -{ - stats->setBirthSign (signId); - playerBirthSignId = signId; -} - void WindowManager::setReputation (int reputation) { - stats->setReputation (reputation); + mStatsWindow->setReputation (reputation); } void WindowManager::setBounty (int bounty) { - stats->setBounty (bounty); + mStatsWindow->setBounty (bounty); } void WindowManager::updateSkillArea() { - stats->updateSkillArea(); + mStatsWindow->updateSkillArea(); } void WindowManager::removeDialog(OEngine::GUI::Layout*dialog) @@ -373,7 +439,7 @@ void WindowManager::messageBox (const std::string& message, const std::vectorcreateInteractiveMessageBox(message, buttons); - setGuiMode(GM_InterMessageBox); + pushGuiMode(GM_InterMessageBox); } } @@ -392,18 +458,36 @@ const std::string &WindowManager::getGameSettingString(const std::string &id, co void WindowManager::onDialogueWindowBye() { - if (dialogueWindow) + if (mDialogueWindow) { //FIXME set some state and stuff? //removeDialog(dialogueWindow); - dialogueWindow->setVisible(false); + mDialogueWindow->setVisible(false); } - setGuiMode(GM_Game); + popGuiMode(); } void WindowManager::onFrame (float frameDuration) { mMessageBoxManager->onFrame(frameDuration); + mToolTips->onFrame(frameDuration); + + if (mDragAndDrop->mIsOnDragAndDrop) + { + assert(mDragAndDrop->mDraggedWidget); + mDragAndDrop->mDraggedWidget->setPosition(MyGUI::InputManager::getInstance().getMousePosition()); + } + + mInventoryWindow->onFrame(); + + mStatsWindow->onFrame(); + + hud->onFrame(frameDuration); + + mDialogueWindow->checkReferenceAvailable(); + mTradeWindow->checkReferenceAvailable(); + mContainerWindow->checkReferenceAvailable(); + console->checkReferenceAvailable(); } const ESMS::ESMStore& WindowManager::getStore() const @@ -419,9 +503,16 @@ void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell) if (cell->cell->name != "") name = cell->cell->name; else - name = cell->cell->region; + { + const ESM::Region* region = MWBase::Environment::get().getWorld()->getStore().regions.search(cell->cell->region); + if (region) + name = region->name; + else + name = getGameSettingString("sDefaultCellname", "Wilderness"); + } map->setCellName( name ); + hud->setCellName( name ); map->setCellPrefix("Cell"); hud->setCellPrefix("Cell"); @@ -431,6 +522,7 @@ void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell) else { map->setCellName( cell->cell->name ); + hud->setCellName( cell->cell->name ); map->setCellPrefix( cell->cell->name ); hud->setCellPrefix( cell->cell->name ); } @@ -471,10 +563,172 @@ void WindowManager::toggleFogOfWar() hud->toggleFogOfWar(); } -int WindowManager::toggleFps() +void WindowManager::setFocusObject(const MWWorld::Ptr& focus) { - showFPSLevel = (showFPSLevel+1)%3; - hud->setFpsLevel(showFPSLevel); - Settings::Manager::setInt("fps", "HUD", showFPSLevel); - return showFPSLevel; + mToolTips->setFocusObject(focus); +} + +void WindowManager::setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y) +{ + mToolTips->setFocusObjectScreenCoords(min_x, min_y, max_x, max_y); +} + +void WindowManager::toggleFullHelp() +{ + mToolTips->toggleFullHelp(); +} + +bool WindowManager::getFullHelp() const +{ + return mToolTips->getFullHelp(); +} + +void WindowManager::setWeaponVisibility(bool visible) +{ + hud->setBottomLeftVisibility(hud->health->getVisible(), visible, hud->spellBox->getVisible()); +} + +void WindowManager::setSpellVisibility(bool visible) +{ + hud->setBottomLeftVisibility(hud->health->getVisible(), hud->weapBox->getVisible(), visible); + hud->setBottomRightVisibility(visible, hud->minimapBox->getVisible()); +} + +void WindowManager::setMouseVisible(bool visible) +{ + MyGUI::PointerManager::getInstance().setVisible(visible); +} + +void WindowManager::setDragDrop(bool dragDrop) +{ + mToolTips->setEnabled(!dragDrop); + MWBase::Environment::get().getInputManager()->setDragDrop(dragDrop); +} + +void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) +{ + const ESM::GameSetting *setting = MWBase::Environment::get().getWorld()->getStore().gameSettings.search(_tag); + if (setting && setting->type == ESM::VT_String) + _result = setting->str; + else + _result = _tag; +} + +void WindowManager::processChangedSettings(const Settings::CategorySettingVector& changed) +{ + hud->setFpsLevel(Settings::Manager::getInt("fps", "HUD")); + mToolTips->setDelay(Settings::Manager::getFloat("tooltip delay", "GUI")); + + bool changeRes = false; + for (Settings::CategorySettingVector::const_iterator it = changed.begin(); + it != changed.end(); ++it) + { + if (it->first == "Video" && ( + it->second == "resolution x" + || it->second == "resolution y")) + { + changeRes = true; + } + } + + if (changeRes) + { + int x = Settings::Manager::getInt("resolution x", "Video"); + int y = Settings::Manager::getInt("resolution y", "Video"); + hud->onResChange(x, y); + console->onResChange(x, y); + mSettingsWindow->center(); + mDragAndDrop->mDragAndDropWidget->setSize(MyGUI::IntSize(x, y)); + } +} + +void WindowManager::pushGuiMode(GuiMode mode) +{ + if (mode==GM_Inventory && allowed==GW_None) + return; + + mGuiModes.push_back(mode); + + bool gameMode = !isGuiMode(); + MWBase::Environment::get().getInputManager()->changeInputMode(!gameMode); + + updateVisible(); +} + +void WindowManager::popGuiMode() +{ + if (!mGuiModes.empty()) + mGuiModes.pop_back(); + + bool gameMode = !isGuiMode(); + MWBase::Environment::get().getInputManager()->changeInputMode(!gameMode); + + updateVisible(); +} + +void WindowManager::removeGuiMode(GuiMode mode) +{ + std::vector::iterator it = mGuiModes.begin(); + while (it != mGuiModes.end()) + { + if (*it == mode) + it = mGuiModes.erase(it); + else + ++it; + } + + updateVisible(); +} + +void WindowManager::setSelectedSpell(const std::string& spellId, int successChancePercent) +{ + hud->setSelectedSpell(spellId, successChancePercent); + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(spellId); + mSpellWindow->setTitle(spell->name); +} + +void WindowManager::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent) +{ + hud->setSelectedEnchantItem(item, chargePercent); + mSpellWindow->setTitle(MWWorld::Class::get(item).getName(item)); +} + +void WindowManager::setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent) +{ + hud->setSelectedWeapon(item, durabilityPercent); + mInventoryWindow->setTitle(MWWorld::Class::get(item).getName(item)); +} + +void WindowManager::unsetSelectedSpell() +{ + hud->unsetSelectedSpell(); + mSpellWindow->setTitle("#{sNone}"); +} + +void WindowManager::unsetSelectedWeapon() +{ + hud->unsetSelectedWeapon(); + mInventoryWindow->setTitle("#{sSkillHandtohand}"); +} + +void WindowManager::getMousePosition(int &x, int &y) +{ + const MyGUI::IntPoint& pos = MyGUI::InputManager::getInstance().getMousePosition(); + x = pos.left; + y = pos.top; +} + +void WindowManager::getMousePosition(float &x, float &y) +{ + const MyGUI::IntPoint& pos = MyGUI::InputManager::getInstance().getMousePosition(); + x = pos.left; + y = pos.top; + const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + x /= viewSize.width; + y /= viewSize.height; +} + +bool WindowManager::getWorldMouseOver() +{ + return hud->getWorldMouseOver(); } diff --git a/apps/openmw/mwgui/window_manager.hpp b/apps/openmw/mwgui/window_manager.hpp index 6e446387e0..37f30c59a4 100644 --- a/apps/openmw/mwgui/window_manager.hpp +++ b/apps/openmw/mwgui/window_manager.hpp @@ -14,11 +14,16 @@ #include #include +#include "MyGUI_UString.h" + #include +#include #include #include + #include "../mwmechanics/stat.hpp" #include "../mwworld/ptr.hpp" + #include "mode.hpp" namespace MyGUI @@ -61,11 +66,22 @@ namespace MWGui class Console; class JournalWindow; class CharacterCreation; - + class ContainerWindow; + class DragAndDrop; + class InventoryWindow; + class ToolTips; + class ScrollWindow; + class BookWindow; class TextInputDialog; class InfoBoxDialog; class DialogueWindow; class MessageBoxManager; + class CountDialog; + class TradeWindow; + class SettingsWindow; + class ConfirmationDialog; + class AlchemyWindow; + class SpellWindow; struct ClassPoint { @@ -82,11 +98,9 @@ namespace MWGui typedef std::vector FactionList; typedef std::vector SkillList; - WindowManager(const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string logpath); + WindowManager(const Compiler::Extensions& extensions, int fpsLevel, bool newGame, OEngine::Render::OgreRenderer *mOgre, const std::string& logpath); virtual ~WindowManager(); - void setGuiMode(GuiMode newMode); - /** * Should be called each frame to update windows/gui elements. * This could mean updating sizes of gui elements or opening @@ -94,19 +108,24 @@ namespace MWGui */ void update(); - void setMode(GuiMode newMode) + void pushGuiMode(GuiMode mode); + void popGuiMode(); + void removeGuiMode(GuiMode mode); ///< can be anywhere in the stack + + GuiMode getMode() const { - if (newMode==GM_Inventory && allowed==GW_None) - return; - - mode = newMode; - updateVisible(); + if (mGuiModes.empty()) + throw std::runtime_error ("getMode() called, but there is no active mode"); + return mGuiModes.back(); } - void setNextMode(GuiMode newMode); - GuiMode getMode() const { return mode; } + bool isGuiMode() const { return !mGuiModes.empty(); } - bool isGuiMode() const { return getMode() != GM_Game; } // Everything that is not game mode is considered "gui mode" + void toggleVisible(GuiWindow wnd) + { + shown = (shown & wnd) ? (GuiWindow) (shown & ~wnd) : (GuiWindow) (shown | wnd); + updateVisible(); + } // Disallow all inventory mode windows void disallowAll() @@ -122,7 +141,21 @@ namespace MWGui updateVisible(); } - MWGui::DialogueWindow* getDialogueWindow() {return dialogueWindow;} + bool isAllowed(GuiWindow wnd) const + { + return allowed & wnd; + } + + MWGui::DialogueWindow* getDialogueWindow() {return mDialogueWindow;} + MWGui::ContainerWindow* getContainerWindow() {return mContainerWindow;} + MWGui::InventoryWindow* getInventoryWindow() {return mInventoryWindow;} + MWGui::BookWindow* getBookWindow() {return mBookWindow;} + MWGui::ScrollWindow* getScrollWindow() {return mScrollWindow;} + MWGui::CountDialog* getCountDialog() {return mCountDialog;} + MWGui::ConfirmationDialog* getConfirmationDialog() {return mConfirmationDialog;} + MWGui::TradeWindow* getTradeWindow() {return mTradeWindow;} + MWGui::SpellWindow* getSpellWindow() {return mSpellWindow;} + MWGui::Console* getConsole() {return console;} MyGUI::Gui* getGui() const { return gui; } @@ -144,8 +177,6 @@ namespace MWGui void setPlayerClass (const ESM::Class &class_); ///< set current class of player void configureSkills (const SkillList& major, const SkillList& minor); ///< configure skill groups, each set contains the skill ID for that group. - void setFactions (const FactionList& factions); ///< set faction and rank to display on stat window, use an empty vector to disable - void setBirthSign (const std::string &signId); ///< set birth sign to display on stat window, use an empty string to disable. void setReputation (int reputation); ///< set the current reputation value void setBounty (int bounty); ///< set the current bounty value void updateSkillArea(); ///< update display of skills, factions, birth sign, reputation and bounty @@ -154,10 +185,18 @@ namespace MWGui void setPlayerPos(const float x, const float y); ///< set player position in map space void setPlayerDir(const float x, const float y); ///< set player view direction in map space - void toggleFogOfWar(); + void setFocusObject(const MWWorld::Ptr& focus); + void setFocusObjectScreenCoords(float min_x, float min_y, float max_x, float max_y); - int toggleFps(); - ///< toggle fps display @return resulting fps level + void setMouseVisible(bool visible); + void getMousePosition(int &x, int &y); + void getMousePosition(float &x, float &y); + void setDragDrop(bool dragDrop); + bool getWorldMouseOver(); + + void toggleFogOfWar(); + void toggleFullHelp(); ///< show extra info in item tooltips (owner, script) + bool getFullHelp() const; void setInteriorMapTexture(const int x, const int y); ///< set the index of the map texture that should be used (for interiors) @@ -166,6 +205,14 @@ namespace MWGui void setHMSVisibility(bool visible); // sets the visibility of the hud minimap void setMinimapVisibility(bool visible); + void setWeaponVisibility(bool visible); + void setSpellVisibility(bool visible); + + void setSelectedSpell(const std::string& spellId, int successChancePercent); + void setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent); + void setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent); + void unsetSelectedSpell(); + void unsetSelectedWeapon(); template void removeDialog(T*& dialog); ///< Casts to OEngine::GUI::Layout and calls removeDialog, then resets pointer to nullptr. @@ -176,6 +223,11 @@ namespace MWGui void onFrame (float frameDuration); + std::map > getPlayerSkillValues() { return playerSkillValues; } + std::map > getPlayerAttributeValues() { return playerAttributes; } + SkillList getPlayerMinorSkills() { return playerMinorSkills; } + SkillList getPlayerMajorSkills() { return playerMajorSkills; } + /** * Fetches a GMST string from the store, if there is no setting with the given * ID or it is not a string the default string is returned. @@ -187,16 +239,30 @@ namespace MWGui const ESMS::ESMStore& getStore() const; + void processChangedSettings(const Settings::CategorySettingVector& changed); + private: OEngine::GUI::MyGUIManager *mGuiManager; HUD *hud; MapWindow *map; MainMenu *menu; - StatsWindow *stats; + ToolTips *mToolTips; + StatsWindow *mStatsWindow; MessageBoxManager *mMessageBoxManager; Console *console; JournalWindow* mJournal; - DialogueWindow *dialogueWindow; + DialogueWindow *mDialogueWindow; + ContainerWindow *mContainerWindow; + DragAndDrop* mDragAndDrop; + InventoryWindow *mInventoryWindow; + ScrollWindow* mScrollWindow; + BookWindow* mBookWindow; + CountDialog* mCountDialog; + TradeWindow* mTradeWindow; + SettingsWindow* mSettingsWindow; + ConfirmationDialog* mConfirmationDialog; + AlchemyWindow* mAlchemyWindow; + SpellWindow* mSpellWindow; CharacterCreation* mCharGen; @@ -204,7 +270,6 @@ namespace MWGui ESM::Class playerClass; std::string playerName; std::string playerRaceId; - std::string playerBirthSignId; std::map > playerAttributes; SkillList playerMajorSkills, playerMinorSkills; std::map > playerSkillValues; @@ -212,9 +277,7 @@ namespace MWGui MyGUI::Gui *gui; // Gui - GuiMode mode; // Current gui mode - GuiMode nextMode; // Next mode to activate in update() - bool needModeChange; //Whether a mode change is needed in update() [will use nextMode] + std::vector mGuiModes; std::vector garbageDialogs; void cleanupGarbage(); @@ -225,9 +288,6 @@ namespace MWGui the start of the game, when windows are enabled one by one through script commands. You can manipulate this through using allow() and disableAll(). - - The setting should also affect visibility of certain HUD - elements, but this is not done yet. */ GuiWindow allowed; @@ -239,6 +299,12 @@ namespace MWGui size_t mBatchCount; void onDialogueWindowBye(); + + /** + * Called when MyGUI tries to retrieve a tag. This usually corresponds to a GMST string, + * so this method will retrieve the GMST with the name \a _tag and place the result in \a _result + */ + void onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result); }; template diff --git a/apps/openmw/mwgui/window_pinnable_base.hpp b/apps/openmw/mwgui/window_pinnable_base.hpp index 8ef38c3867..86bc3b85c8 100644 --- a/apps/openmw/mwgui/window_pinnable_base.hpp +++ b/apps/openmw/mwgui/window_pinnable_base.hpp @@ -12,6 +12,7 @@ namespace MWGui public: WindowPinnableBase(const std::string& parLayout, WindowManager& parWindowManager); void setVisible(bool b); + bool pinned() { return mPinned; } private: void onWindowButtonPressed(MyGUI::Window* sender, const std::string& eventName); diff --git a/apps/openmw/mwinput/inputmanager.cpp b/apps/openmw/mwinput/inputmanager.cpp index c2233f6265..a2bafcbf78 100644 --- a/apps/openmw/mwinput/inputmanager.cpp +++ b/apps/openmw/mwinput/inputmanager.cpp @@ -60,6 +60,7 @@ namespace MWInput A_CycleWeaponRight, A_ToggleSneak, //Toggles Sneak, add Push-Sneak later A_ToggleWalk, //Toggle Walking/Running + A_Crouch, A_QuickSave, A_QuickLoad, @@ -68,7 +69,7 @@ namespace MWInput A_ToggleWeapon, A_ToggleSpell, - A_ToggleFps, // Toggle FPS display (this is temporary) + A_Settings, // Temporary hotkey A_LAST // Marker for the last item }; @@ -87,14 +88,16 @@ namespace MWInput MWGui::WindowManager &windows; OMW::Engine& mEngine; + bool mDragDrop; + /* InputImpl Methods */ - - void toggleFps() +public: + void adjustMouseRegion(int width, int height) { - windows.toggleFps(); + input.adjustMouseClippingSize(width, height); } - +private: void toggleSpell() { if (windows.isGuiMode()) return; @@ -132,22 +135,37 @@ namespace MWInput void screenshot() { mEngine.screenshot(); + + std::vector empty; + windows.messageBox ("Screenshot saved", empty); + } + + void showSettings() + { + if (mDragDrop) + return; + + if (!windows.isGuiMode() || windows.getMode() != MWGui::GM_Settings) + windows.pushGuiMode(MWGui::GM_Settings); } /* toggleInventory() is called when the user presses the button to toggle the inventory screen. */ void toggleInventory() { - using namespace MWGui; + using namespace MWGui; - GuiMode mode = windows.getMode(); + if (mDragDrop) + return; - // Toggle between game mode and inventory mode - if(mode == GM_Game) - setGuiMode(GM_Inventory); - else if(mode == GM_Inventory) - setGuiMode(GM_Game); + bool gameMode = !windows.isGuiMode(); - // .. but don't touch any other mode. + // Toggle between game mode and inventory mode + if(gameMode) + windows.pushGuiMode(GM_Inventory); + else if(windows.getMode() == GM_Inventory) + windows.popGuiMode(); + + // .. but don't touch any other mode. } // Toggle console @@ -155,28 +173,36 @@ namespace MWInput { using namespace MWGui; - GuiMode mode = windows.getMode(); + if (mDragDrop) + return; + + bool gameMode = !windows.isGuiMode(); // Switch to console mode no matter what mode we are currently // in, except of course if we are already in console mode - if(mode == GM_Console) - setGuiMode(GM_Game); - else setGuiMode(GM_Console); + if (!gameMode) + { + if (windows.getMode() == GM_Console) + windows.popGuiMode(); + else + windows.pushGuiMode(GM_Console); + } + else + windows.pushGuiMode(GM_Console); } void toggleJournal() { - using namespace MWGui; + using namespace MWGui; - GuiMode mode = windows.getMode(); + // Toggle between game mode and journal mode + bool gameMode = !windows.isGuiMode(); - // Toggle between game mode and journal mode - if(mode == GM_Game) - setGuiMode(GM_Journal); - else if(mode == GM_Journal) - setGuiMode(GM_Game); - - // .. but don't touch any other mode. + if(gameMode) + windows.pushGuiMode(GM_Journal); + else if(windows.getMode() == GM_Journal) + windows.popGuiMode(); + // .. but don't touch any other mode. } void activate() @@ -215,7 +241,8 @@ namespace MWInput poller(input), player(_player), windows(_windows), - mEngine (engine) + mEngine (engine), + mDragDrop(false) { using namespace OEngine::Input; using namespace OEngine::Render; @@ -246,8 +273,8 @@ namespace MWInput "Draw Weapon"); disp->funcs.bind(A_ToggleSpell,boost::bind(&InputImpl::toggleSpell,this), "Ready hands"); - disp->funcs.bind(A_ToggleFps, boost::bind(&InputImpl::toggleFps, this), - "Toggle FPS display"); + disp->funcs.bind(A_Settings, boost::bind(&InputImpl::showSettings, this), + "Show settings window"); // Add the exit listener ogre.getRoot()->addFrameListener(&exit); @@ -269,8 +296,7 @@ namespace MWInput lst->add(guiEvents,Event::EV_ALL); } - // Start out in game mode - setGuiMode(MWGui::GM_Game); + changeInputMode(false); /********************************** Key binding section @@ -294,7 +320,7 @@ namespace MWInput disp->bind(A_ToggleWalk, KC_C); disp->bind(A_ToggleWeapon,KC_F); disp->bind(A_ToggleSpell,KC_R); - disp->bind(A_ToggleFps, KC_F10); + disp->bind(A_Settings, KC_F2); // Key bindings for polled keys // NOTE: These keys are constantly being polled. Only add keys that must be checked each frame. @@ -310,6 +336,14 @@ namespace MWInput poller.bind(A_MoveRight, KC_D); poller.bind(A_MoveForward, KC_W); poller.bind(A_MoveBackward, KC_S); + + poller.bind(A_Jump, KC_E); + poller.bind(A_Crouch, KC_LCONTROL); + } + + void setDragDrop(bool dragDrop) + { + mDragDrop = dragDrop; } //NOTE: Used to check for movement keys @@ -327,6 +361,7 @@ namespace MWInput windows.update(); // Disable movement in Gui mode + if (windows.isGuiMode()) return; // Configure player movement according to keyboard input. Actual movement will @@ -356,18 +391,21 @@ namespace MWInput } else player.setForwardBackward (0); + + if (poller.isDown(A_Jump)) + player.setUpDown (1); + else if (poller.isDown(A_Crouch)) + player.setUpDown (-1); + else + player.setUpDown (0); } // Switch between gui modes. Besides controlling the Gui windows // this also makes sure input is directed to the right place - void setGuiMode(MWGui::GuiMode mode) + void changeInputMode(bool guiMode) { - // Tell the GUI what to show (this also takes care of the mouse - // pointer) - windows.setMode(mode); - // Are we in GUI mode now? - if(windows.isGuiMode()) + if(guiMode) { // Disable mouse look mouse->setCamera(NULL); @@ -403,13 +441,34 @@ namespace MWInput delete impl; } - void MWInputManager::setGuiMode(MWGui::GuiMode mode) - { - impl->setGuiMode(mode); - } - void MWInputManager::update() { impl->update(); } + + void MWInputManager::setDragDrop(bool dragDrop) + { + impl->setDragDrop(dragDrop); + } + + void MWInputManager::changeInputMode(bool guiMode) + { + impl->changeInputMode(guiMode); + } + + void MWInputManager::processChangedSettings(const Settings::CategorySettingVector& changed) + { + bool changeRes = false; + for (Settings::CategorySettingVector::const_iterator it = changed.begin(); + it != changed.end(); ++it) + { + if (it->first == "Video" && ( + it->second == "resolution x" + || it->second == "resolution y")) + changeRes = true; + } + + if (changeRes) + impl->adjustMouseRegion(Settings::Manager::getInt("resolution x", "Video"), Settings::Manager::getInt("resolution y", "Video")); + } } diff --git a/apps/openmw/mwinput/inputmanager.hpp b/apps/openmw/mwinput/inputmanager.hpp index 721c77d9fd..4ef3df1371 100644 --- a/apps/openmw/mwinput/inputmanager.hpp +++ b/apps/openmw/mwinput/inputmanager.hpp @@ -3,6 +3,8 @@ #include "../mwgui/mode.hpp" +#include + namespace OEngine { namespace Render @@ -50,7 +52,11 @@ namespace MWInput void update(); - void setGuiMode(MWGui::GuiMode mode); + void changeInputMode(bool guiMode); + + void processChangedSettings(const Settings::CategorySettingVector& changed); + + void setDragDrop(bool dragDrop); }; } #endif diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp new file mode 100644 index 0000000000..ced2a5c3fe --- /dev/null +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -0,0 +1,162 @@ + +#include "activespells.hpp" + +#include + +#include "../mwbase/environment.hpp" + +#include "../mwworld/world.hpp" + +namespace MWMechanics +{ + void ActiveSpells::update() const + { + bool rebuild = false; + + MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp(); + + if (mLastUpdate!=now) + { + TContainer::iterator iter (mSpells.begin()); + while (iter!=mSpells.end()) + if (!timeToExpire (iter)) + { + mSpells.erase (iter++); + rebuild = true; + } + else + ++iter; + + mLastUpdate = now; + } + + if (mSpellsChanged) + { + mSpellsChanged = false; + rebuild = true; + } + + if (rebuild) + { + mEffects = MagicEffects(); + + for (TIterator iter (begin()); iter!=end(); ++iter) + { + const ESM::Spell& spell = + *MWBase::Environment::get().getWorld()->getStore().spells.find (iter->first); + + const MWWorld::TimeStamp& start = iter->second.first; + float magnitude = iter->second.second; + + for (std::vector::const_iterator iter (spell.effects.list.begin()); + iter!=spell.effects.list.end(); ++iter) + { + if (iter->duration) + { + MWWorld::TimeStamp end = start; + end += static_cast (iter->duration)* + MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); + + if (end>now) + { + EffectParam param; + param.mMagnitude = static_cast ( + (iter->magnMax-iter->magnMin+1)*magnitude + iter->magnMin); + mEffects.add (*iter, param); + } + } + } + } + } + } + + ActiveSpells::ActiveSpells() + : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) + {} + + void ActiveSpells::addSpell (const std::string& id) + { + const ESM::Spell& spell = *MWBase::Environment::get().getWorld()->getStore().spells.find (id); + + bool found = false; + + for (std::vector::const_iterator iter (spell.effects.list.begin()); + iter!=spell.effects.list.end(); ++iter) + { + if (iter->duration) + { + found = true; + break; + } + } + + if (!found) + return; + + TContainer::iterator iter = mSpells.find (id); + + float random = static_cast (std::rand()) / RAND_MAX; + + if (iter==mSpells.end()) + mSpells.insert (std::make_pair (id, + std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random))); + else + iter->second = std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random); + + mSpellsChanged = true; + } + + void ActiveSpells::removeSpell (const std::string& id) + { + TContainer::iterator iter = mSpells.find (id); + + if (iter!=mSpells.end()) + { + mSpells.erase (iter); + mSpellsChanged = true; + } + } + + const MagicEffects& ActiveSpells::getMagicEffects() const + { + update(); + return mEffects; + } + + ActiveSpells::TIterator ActiveSpells::begin() const + { + update(); + return mSpells.begin(); + } + + ActiveSpells::TIterator ActiveSpells::end() const + { + update(); + return mSpells.end(); + } + + double ActiveSpells::timeToExpire (const TIterator& iterator) const + { + const ESM::Spell& spell = + *MWBase::Environment::get().getWorld()->getStore().spells.find (iterator->first); + + int duration = 0; + + for (std::vector::const_iterator iter (spell.effects.list.begin()); + iter!=spell.effects.list.end(); ++iter) + { + if (iter->duration>duration) + duration = iter->duration; + } + + double scaledDuration = duration * + MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); + + double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp()-iterator->second.first; + + if (usedUp>=scaledDuration) + return 0; + + return scaledDuration-usedUp; + } +} diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp new file mode 100644 index 0000000000..179321c58a --- /dev/null +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -0,0 +1,58 @@ +#ifndef GAME_MWMECHANICS_ACTIVESPELLS_H +#define GAME_MWMECHANICS_ACTIVESPELLS_H + +#include +#include +#include + +#include "../mwworld/timestamp.hpp" + +#include "magiceffects.hpp" + +namespace ESM +{ + struct Spell; +} + +namespace MWMechanics +{ + /// \brief Lasting spell effects + class ActiveSpells + { + public: + + typedef std::map > TContainer; + typedef TContainer::const_iterator TIterator; + + private: + + mutable TContainer mSpells; // spellId, (time of casting, relative magnitude) + mutable MagicEffects mEffects; + mutable bool mSpellsChanged; + mutable MWWorld::TimeStamp mLastUpdate; + + void update() const; + + public: + + ActiveSpells(); + + void addSpell (const std::string& id); + ///< Overwrites an existing spell with the same ID. If the spell does not have any + /// non-instant effects, it is ignored. + + void removeSpell (const std::string& id); + + const MagicEffects& getMagicEffects() const; + + TIterator begin() const; + + TIterator end() const; + + double timeToExpire (const TIterator& iterator) const; + ///< Returns time (in in-game hours) until the spell pointed to by \a iterator + /// expires. + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 849ab8ea4c..8f8fd68710 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -8,11 +8,30 @@ #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" +#include "creaturestats.hpp" + namespace MWMechanics { void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) { + // magic effects + adjustMagicEffects (ptr); + CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + + // calculate dynamic stats + int strength = creatureStats.mAttributes[0].getBase(); + int intelligence = creatureStats.mAttributes[1].getBase(); + int willpower = creatureStats.mAttributes[2].getBase(); + int agility = creatureStats.mAttributes[3].getBase(); + int endurance = creatureStats.mAttributes[5].getBase(); + + double magickaFactor = creatureStats.mMagicEffects.get (EffectKey (84)).mMagnitude*0.1 + 0.5; + + creatureStats.mDynamic[0].setBase (static_cast (0.5 * (strength + endurance))); + creatureStats.mDynamic[1].setBase (static_cast (intelligence + + magickaFactor * intelligence)); + creatureStats.mDynamic[2].setBase (strength+willpower+agility+endurance); } void Actors::updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused) @@ -22,6 +41,27 @@ namespace MWMechanics MWWorld::Class::get (ptr).getNpcStats (ptr)); } + void Actors::adjustMagicEffects (const MWWorld::Ptr& creature) + { + CreatureStats& creatureStats = MWWorld::Class::get (creature).getCreatureStats (creature); + + MagicEffects now = creatureStats.mSpells.getMagicEffects(); + + if (creature.getTypeName()==typeid (ESM::NPC).name()) + { + MWWorld::InventoryStore& store = MWWorld::Class::get (creature).getInventoryStore (creature); + now += store.getMagicEffects(); + } + + now += creatureStats.mActiveSpells.getMagicEffects(); + + MagicEffects diff = MagicEffects::diff (creatureStats.mMagicEffects, now); + + creatureStats.mMagicEffects = now; + + // TODO apply diff to other stats + } + Actors::Actors() : mDuration (0) {} void Actors::addActor (const MWWorld::Ptr& ptr) @@ -31,7 +71,10 @@ namespace MWMechanics void Actors::removeActor (const MWWorld::Ptr& ptr) { - mActors.erase (ptr); + std::set::iterator iter = mActors.find (ptr); + + if (iter!=mActors.end()) + mActors.erase (iter); } void Actors::dropActors (const MWWorld::Ptr::CellStore *cellStore) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index ae93fb52ee..1be29463f0 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -19,10 +19,10 @@ namespace MWMechanics std::set mActors; float mDuration; - void updateActor (const MWWorld::Ptr& ptr, float duration); - void updateNpc (const MWWorld::Ptr& ptr, float duration, bool paused); + void adjustMagicEffects (const MWWorld::Ptr& creature); + public: Actors(); @@ -32,6 +32,8 @@ namespace MWMechanics void removeActor (const MWWorld::Ptr& ptr); ///< Deregister an actor for stats management + /// + /// \note Ignored, if \a ptr is not a registered actor. void dropActors (const MWWorld::Ptr::CellStore *cellStore); ///< Deregister all actors in the given cell. @@ -39,6 +41,11 @@ namespace MWMechanics void update (std::vector >& movement, float duration, bool paused); ///< Update actor stats and store desired velocity vectors in \a movement + + void updateActor (const MWWorld::Ptr& ptr, float duration); + ///< This function is normally called automatically during the update process, but it can + /// also be called explicitly at any time to force an update. + }; } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index ab008da9e8..cc3c409da2 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -7,6 +7,7 @@ #include "stat.hpp" #include "magiceffects.hpp" #include "spells.hpp" +#include "activespells.hpp" namespace MWMechanics { @@ -16,6 +17,7 @@ namespace MWMechanics DynamicStat mDynamic[3]; // health, magicka, fatigue int mLevel; Spells mSpells; + ActiveSpells mActiveSpells; MagicEffects mMagicEffects; }; } diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 1c73bf1b15..a77e199661 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -1,6 +1,8 @@ #include "magiceffects.hpp" +#include + #include #include @@ -66,6 +68,46 @@ namespace MWMechanics } } + void MagicEffects::add (const ESM::EffectList& list) + { + for (std::vector::const_iterator iter (list.list.begin()); iter!=list.list.end(); + ++iter) + { + EffectParam param; + + if (iter->magnMin>=iter->magnMax) + param.mMagnitude = iter->magnMin; + else + param.mMagnitude = static_cast ( + (iter->magnMax-iter->magnMin+1)* + (static_cast (std::rand()) / RAND_MAX) + iter->magnMin); + + add (*iter, param); + } + } + + MagicEffects& MagicEffects::operator+= (const MagicEffects& effects) + { + if (this==&effects) + { + MagicEffects temp (effects); + *this += temp; + return *this; + } + + for (Collection::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) + { + Collection::iterator result = mCollection.find (iter->first); + + if (result!=mCollection.end()) + result->second += iter->second; + else + mCollection.insert (*iter); + } + + return *this; + } + EffectParam MagicEffects::get (const EffectKey& key) const { Collection::const_iterator iter = mCollection.find (key); @@ -85,11 +127,11 @@ namespace MWMechanics MagicEffects result; // adding/changing - for (Collection::const_iterator iter (now.Begin()); iter!=now.End(); ++iter) + for (Collection::const_iterator iter (now.begin()); iter!=now.end(); ++iter) { Collection::const_iterator other = prev.mCollection.find (iter->first); - if (other==prev.End()) + if (other==prev.end()) { // adding result.add (iter->first, iter->second); @@ -102,17 +144,16 @@ namespace MWMechanics } // removing - for (Collection::const_iterator iter (prev.Begin()); iter!=prev.End(); ++iter) + for (Collection::const_iterator iter (prev.begin()); iter!=prev.end(); ++iter) { Collection::const_iterator other = now.mCollection.find (iter->first); - if (other==prev.End()) + if (other==prev.end()) { result.add (iter->first, EffectParam() - iter->second); } } return result; - } } diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 3d36ea813d..2f61d7eeb4 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -6,6 +6,7 @@ namespace ESM { struct ENAMstruct; + struct EffectList; } namespace MWMechanics @@ -60,12 +61,16 @@ namespace MWMechanics public: - Collection::const_iterator Begin() const { return mCollection.begin(); } + Collection::const_iterator begin() const { return mCollection.begin(); } - Collection::const_iterator End() const { return mCollection.end(); } + Collection::const_iterator end() const { return mCollection.end(); } void add (const EffectKey& key, const EffectParam& param); + void add (const ESM::EffectList& list); + + MagicEffects& operator+= (const MagicEffects& effects); + EffectParam get (const EffectKey& key) const; ///< This function can safely be used for keys that are not present. diff --git a/apps/openmw/mwmechanics/mechanicsmanager.cpp b/apps/openmw/mwmechanics/mechanicsmanager.cpp index 8bc408be6f..331074ef78 100644 --- a/apps/openmw/mwmechanics/mechanicsmanager.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanager.cpp @@ -30,6 +30,15 @@ namespace MWMechanics for (int i=0; i<27; ++i) npcStats.mSkill[i].setBase (player->npdt52.skills[i]); + creatureStats.mAttributes[0].setBase (player->npdt52.strength); + creatureStats.mAttributes[1].setBase (player->npdt52.intelligence); + creatureStats.mAttributes[2].setBase (player->npdt52.willpower); + creatureStats.mAttributes[3].setBase (player->npdt52.agility); + creatureStats.mAttributes[4].setBase (player->npdt52.speed); + creatureStats.mAttributes[5].setBase (player->npdt52.endurance); + creatureStats.mAttributes[6].setBase (player->npdt52.personality); + creatureStats.mAttributes[7].setBase (player->npdt52.luck); + // race if (mRaceSelected) { @@ -139,42 +148,13 @@ namespace MWMechanics } } - // magic effects - adjustMagicEffects (ptr); - - // calculate dynamic stats - int strength = creatureStats.mAttributes[0].getBase(); - int intelligence = creatureStats.mAttributes[1].getBase(); - int willpower = creatureStats.mAttributes[2].getBase(); - int agility = creatureStats.mAttributes[3].getBase(); - int endurance = creatureStats.mAttributes[5].getBase(); - - double magickaFactor = creatureStats.mMagicEffects.get (EffectKey (84)).mMagnitude*0.1 + 0.5; - - creatureStats.mDynamic[0].setBase (static_cast (0.5 * (strength + endurance))); - creatureStats.mDynamic[1].setBase (static_cast (intelligence + - magickaFactor * intelligence)); - creatureStats.mDynamic[2].setBase (strength+willpower+agility+endurance); + // forced update and current value adjustments + mActors.updateActor (ptr, 0); for (int i=0; i<3; ++i) creatureStats.mDynamic[i].setCurrent (creatureStats.mDynamic[i].getModified()); } - void MechanicsManager::adjustMagicEffects (MWWorld::Ptr& creature) - { - MWMechanics::CreatureStats& creatureStats = - MWWorld::Class::get (creature).getCreatureStats (creature); - - MagicEffects now = creatureStats.mSpells.getMagicEffects(); - - /// \todo add effects from active spells and equipment - - MagicEffects diff = MagicEffects::diff (creatureStats.mMagicEffects, now); - - creatureStats.mMagicEffects = now; - - // TODO apply diff to other stats - } MechanicsManager::MechanicsManager() : mUpdatePlayer (true), mClassSelected (false), diff --git a/apps/openmw/mwmechanics/mechanicsmanager.hpp b/apps/openmw/mwmechanics/mechanicsmanager.hpp index a26fb98cd9..62bb4cf7e2 100644 --- a/apps/openmw/mwmechanics/mechanicsmanager.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanager.hpp @@ -31,8 +31,6 @@ namespace MWMechanics ///< build player according to stored class/race/birthsign information. Will /// default to the values of the ESM::NPC object, if no explicit information is given. - void adjustMagicEffects (MWWorld::Ptr& creature); - public: MechanicsManager (); diff --git a/apps/openmw/mwmechanics/movement.hpp b/apps/openmw/mwmechanics/movement.hpp index a555ac010a..11eb83151e 100644 --- a/apps/openmw/mwmechanics/movement.hpp +++ b/apps/openmw/mwmechanics/movement.hpp @@ -8,8 +8,9 @@ namespace MWMechanics { signed char mLeftRight; // 1: wants to move left, -1: wants to move right signed char mForwardBackward; // 1:wants to move forward, -1: wants to move backward + signed char mUpDown; - Movement() : mLeftRight (0), mForwardBackward (0) {} + Movement() : mLeftRight (0), mForwardBackward (0), mUpDown(0) {} }; } diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index feac5d4d3c..972863b728 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -20,6 +20,7 @@ namespace MWMechanics { // NPCs other than the player can only have one faction. But for the sake of consistency // we use the same data structure for the PC and the NPCs. + /// \note the faction key must be in lowercase std::map mFactionRank; Stat mSkill[27]; diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index a53c75dc22..70eb786392 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -13,13 +13,7 @@ namespace MWMechanics { void Spells::addSpell (const ESM::Spell *spell, MagicEffects& effects) const { - for (std::vector::const_iterator iter = spell->effects.list.begin(); - iter!=spell->effects.list.end(); ++iter) - { - EffectParam param; - param.mMagnitude = iter->magnMax; /// \todo calculate magnitude - effects.add (EffectKey (*iter), param); - } + effects.add (spell->effects); } Spells::TIterator Spells::begin() const @@ -34,7 +28,7 @@ namespace MWMechanics void Spells::add (const std::string& spellId) { - if (std::find (mSpells.begin(), mSpells.end(), spellId)!=mSpells.end()) + if (std::find (mSpells.begin(), mSpells.end(), spellId)==mSpells.end()) mSpells.push_back (spellId); } diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellsuccess.hpp new file mode 100644 index 0000000000..11ac7cda76 --- /dev/null +++ b/apps/openmw/mwmechanics/spellsuccess.hpp @@ -0,0 +1,99 @@ +#ifndef MWMECHANICS_SPELLSUCCESS_H +#define MWMECHANICS_SPELLSUCCESS_H + +#include "../mwworld/ptr.hpp" +#include "../mwworld/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwmechanics/creaturestats.hpp" + +#include "npcstats.hpp" + +namespace MWMechanics +{ + inline int spellSchoolToSkill(int school) + { + std::map schoolSkillMap; // maps spell school to skill id + schoolSkillMap[0] = 11; // alteration + schoolSkillMap[1] = 13; // conjuration + schoolSkillMap[3] = 12; // illusion + schoolSkillMap[2] = 10; // destruction + schoolSkillMap[4] = 14; // mysticism + schoolSkillMap[5] = 15; // restoration + assert(schoolSkillMap.find(school) != schoolSkillMap.end()); + return schoolSkillMap[school]; + } + + inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(spellId); + NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); + + // determine the spell's school + // this is always the school where the player's respective skill is the least advanced + // out of all the magic effects' schools + const std::vector& effects = spell->effects.list; + int school = -1; + int skillLevel = -1; + for (std::vector::const_iterator it = effects.begin(); + it != effects.end(); ++it) + { + const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().magicEffects.find(it->effectID); + int _school = effect->data.school; + int _skillLevel = stats.mSkill[spellSchoolToSkill(_school)].getModified(); + + if (school == -1) + { + school = _school; + skillLevel = _skillLevel; + } + else if (_skillLevel < skillLevel) + { + school = _school; + skillLevel = _skillLevel; + } + } + + return school; + } + + + // UESP wiki / Morrowind/Spells: + // Chance of success is (Spell's skill * 2 + Willpower / 5 + Luck / 10 - Spell cost - Sound magnitude) * (Current fatigue + Maximum Fatigue * 1.5) / Maximum fatigue * 2 + /** + * @param spellId ID of spell + * @param actor calculate spell success chance for this actor (depends on actor's skills) + * @attention actor has to be an NPC and not a creature! + * @return success chance from 0 to 100 (in percent) + */ + inline float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().spells.find(spellId); + + if (spell->data.flags & ESM::Spell::F_Always // spells with this flag always succeed (usually birthsign spells) + || spell->data.type == ESM::Spell::ST_Power) // powers always succeed, but can be cast only once per day + return 100.0; + + NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); + CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor); + + int skillLevel = stats.mSkill[getSpellSchool(spellId, actor)].getModified(); + + // Sound magic effect (reduces spell casting chance) + int soundMagnitude = creatureStats.mMagicEffects.get (MWMechanics::EffectKey (48)).mMagnitude; + + int willpower = creatureStats.mAttributes[ESM::Attribute::Willpower].getModified(); + int luck = creatureStats.mAttributes[ESM::Attribute::Luck].getModified(); + int currentFatigue = creatureStats.mDynamic[2].getCurrent(); + int maxFatigue = creatureStats.mDynamic[2].getModified(); + int spellCost = spell->data.cost; + + // There we go, all needed variables are there, lets go + float chance = (float(skillLevel * 2) + float(willpower)/5.0 + float(luck)/ 10.0 - spellCost - soundMagnitude) * (float(currentFatigue + maxFatigue * 1.5)) / float(maxFatigue * 2.0); + + chance = std::max(0.0f, std::min(100.0f, chance)); // clamp to 0 .. 100 + + return chance; + } +} + +#endif diff --git a/apps/openmw/mwrender/compositors.cpp b/apps/openmw/mwrender/compositors.cpp new file mode 100644 index 0000000000..6f97269ab7 --- /dev/null +++ b/apps/openmw/mwrender/compositors.cpp @@ -0,0 +1,71 @@ +#include "compositors.hpp" + +#include +#include + +using namespace MWRender; + +Compositors::Compositors(Ogre::Viewport* vp) : + mViewport(vp) + , mEnabled(true) +{ +} + +Compositors::~Compositors() +{ + Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport); +} + +void Compositors::setEnabled (const bool enabled) +{ + for (CompositorMap::iterator it=mCompositors.begin(); + it != mCompositors.end(); ++it) + { + Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, it->first, enabled && it->second.first); + } + mEnabled = enabled; +} + +void Compositors::recreate() +{ + Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport); + + CompositorMap temp = mCompositors; + mCompositors.clear(); + + for (CompositorMap::iterator it=temp.begin(); + it != temp.end(); ++it) + { + addCompositor(it->first, it->second.second); + setCompositorEnabled(it->first, mEnabled && it->second.first); + } +} + +void Compositors::addCompositor (const std::string& name, const int priority) +{ + int id = 0; + + for (CompositorMap::iterator it=mCompositors.begin(); + it != mCompositors.end(); ++it) + { + if (it->second.second > priority) + break; + ++id; + } + Ogre::CompositorManager::getSingleton().addCompositor (mViewport, name, id); + + mCompositors[name] = std::make_pair(false, priority); +} + +void Compositors::setCompositorEnabled (const std::string& name, const bool enabled) +{ + mCompositors[name].first = enabled; + Ogre::CompositorManager::getSingleton().setCompositorEnabled (mViewport, name, enabled && mEnabled); +} + +void Compositors::removeAll() +{ + Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport); + + mCompositors.clear(); +} diff --git a/apps/openmw/mwrender/compositors.hpp b/apps/openmw/mwrender/compositors.hpp new file mode 100644 index 0000000000..bbd838b8ee --- /dev/null +++ b/apps/openmw/mwrender/compositors.hpp @@ -0,0 +1,60 @@ +#ifndef GAME_MWRENDER_COMPOSITORS_H +#define GAME_MWRENDER_COMPOSITORS_H + +#include +#include + +namespace Ogre +{ + class Viewport; +} + +namespace MWRender +{ + typedef std::map < std::string, std::pair > CompositorMap; + + /// \brief Manages a set of compositors for one viewport + class Compositors + { + public: + Compositors(Ogre::Viewport* vp); + virtual ~Compositors(); + + /** + * enable or disable all compositors globally + */ + void setEnabled (const bool enabled); + + void setViewport(Ogre::Viewport* vp) { mViewport = vp; } + + /// recreate compositors (call this after viewport size changes) + void recreate(); + + bool toggle() { setEnabled(!mEnabled); return mEnabled; } + + /** + * enable or disable a specific compositor + * @note enable has no effect if all compositors are globally disabled + */ + void setCompositorEnabled (const std::string& name, const bool enabled); + + /** + * @param name of compositor + * @param priority, lower number will be first in the chain + */ + void addCompositor (const std::string& name, const int priority); + + void removeAll (); + + protected: + /// maps compositor name to its "enabled" state + CompositorMap mCompositors; + + bool mEnabled; + + Ogre::Viewport* mViewport; + }; + +} + +#endif diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp index be8afbae65..9086a9bc49 100644 --- a/apps/openmw/mwrender/debugging.cpp +++ b/apps/openmw/mwrender/debugging.cpp @@ -68,7 +68,7 @@ ManualObject *Debugging::createPathgridLines(const ESM::Pathgrid *pathgrid) result->begin(PATHGRID_LINE_MATERIAL, RenderOperation::OT_LINE_LIST); for(ESM::Pathgrid::EdgeList::const_iterator it = pathgrid->edges.begin(); it != pathgrid->edges.end(); - it++) + ++it) { const ESM::Pathgrid::Edge &edge = *it; const ESM::Pathgrid::Point &p1 = pathgrid->points[edge.v0], &p2 = pathgrid->points[edge.v1]; @@ -197,7 +197,7 @@ void Debugging::togglePathgrid() // add path grid meshes to already loaded cells mPathGridRoot = mMwRoot->createChildSceneNode(); - for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); it++) + for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); ++it) { enableCellPathgrid(*it); } @@ -205,7 +205,7 @@ void Debugging::togglePathgrid() else { // remove path grid meshes from already loaded cells - for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); it++) + for(CellList::iterator it = mActiveCells.begin(); it != mActiveCells.end(); ++it) { disableCellPathgrid(*it); } diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index ea89024433..2442700bb5 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -307,50 +307,81 @@ void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Quaterni // convert from world coordinates to texture UV coordinates float u,v; - std::string texName; + std::string texBaseName; if (!mInterior) { u = std::abs((pos.x - (sSize*x))/sSize); v = 1-std::abs((pos.y + (sSize*y))/sSize); - texName = "Cell_"+coordStr(x,y); + texBaseName = "Cell_"; } else { u = (pos.x - min.x - sSize*x)/sSize; v = (pos.y - min.y - sSize*y)/sSize; - texName = mInteriorName + "_" + coordStr(x,y); + texBaseName = mInteriorName + "_"; } MWBase::Environment::get().getWindowManager()->setPlayerPos(u, v); MWBase::Environment::get().getWindowManager()->setPlayerDir(playerdirection.x, -playerdirection.z); // explore radius (squared) - const float sqrExploreRadius = 0.01 * sFogOfWarResolution*sFogOfWarResolution; + const float sqrExploreRadius = (mInterior ? 0.01 : 0.09) * sFogOfWarResolution*sFogOfWarResolution; + const float exploreRadius = (mInterior ? 0.1 : 0.3) * sFogOfWarResolution; // explore radius from 0 to sFogOfWarResolution + const float exploreRadiusUV = exploreRadius / sFogOfWarResolution; // explore radius from 0 to 1 (UV space) - // get the appropriate fog of war texture - TexturePtr tex = TextureManager::getSingleton().getByName(texName+"_fog"); - if (!tex.isNull()) + int intExtMult = mInterior ? 1 : -1; // interior and exterior have reversed Y coordinates (interior: top to bottom) + + // change the affected fog of war textures (in a 3x3 grid around the player) + for (int mx = -1; mx<2; ++mx) { - // get its buffer - if (mBuffers.find(texName) == mBuffers.end()) return; - int i=0; - for (int texV = 0; texV> 24); - alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); - mBuffers[texName][i] = (uint32) (alpha << 24); - ++i; + // is this texture affected at all? + bool affected = false; + if (mx == 0 && my == 0) // the player is always in the center of the 3x3 grid + affected = true; + else + { + bool affectsX = (mx > 0)? (u + exploreRadiusUV > 1) : (u - exploreRadiusUV < 0); + bool affectsY = (my > 0)? (v + exploreRadiusUV > 1) : (v - exploreRadiusUV < 0); + affected = (affectsX && (my == 0)) || (affectsY && mx == 0) || (affectsX && affectsY); + } + + if (!affected) + continue; + + std::string texName = texBaseName + coordStr(x+mx,y+my*intExtMult); + + TexturePtr tex = TextureManager::getSingleton().getByName(texName+"_fog"); + if (!tex.isNull()) + { + // get its buffer + if (mBuffers.find(texName) == mBuffers.end()) return; + int i=0; + for (int texV = 0; texV> 24); + alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) ); + mBuffers[texName][i] = (uint32) (alpha << 24); + + ++i; + } + } + + // copy to the texture + memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &mBuffers[texName][0], sFogOfWarResolution*sFogOfWarResolution*4); + tex->getBuffer()->unlock(); } } - - // copy to the texture - memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &mBuffers[texName][0], sFogOfWarResolution*sFogOfWarResolution*4); - tex->getBuffer()->unlock(); } } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index c35fff1cee..5ceafca365 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -528,7 +528,7 @@ void NpcAnimation::insertFootPart(int type, const std::string &mesh){ } -std::pair*> NpcAnimation::insertFreePart(const std::string &mesh, const std::string suffix){ +std::pair*> NpcAnimation::insertFreePart(const std::string &mesh, const std::string& suffix){ std::string meshNumbered = mesh + getUniqueID(mesh + suffix) + suffix; NIFLoader::load(meshNumbered); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index a37becc26d..8f4f8181d0 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -84,7 +84,7 @@ private: NpcAnimation(const MWWorld::Ptr& ptr, OEngine::Render::OgreRenderer& _rend, MWWorld::InventoryStore& _inv); virtual ~NpcAnimation(); Ogre::Entity* insertBoundedPart(const std::string &mesh, std::string bonename); - std::pair*> insertFreePart(const std::string &mesh, const std::string suffix); + std::pair*> insertFreePart(const std::string &mesh, const std::string& suffix); void insertFootPart(int type, const std::string &mesh); virtual void runAnimation(float timepassed); void updateParts(); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 5922086a01..b9efcd3f5d 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -53,8 +53,10 @@ void Objects::insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_) Ogre::SceneNode* insert = cellnode->createChildSceneNode(); const float *f = ptr.getRefData().getPosition().pos; + insert->setPosition(f[0], f[1], f[2]); insert->setScale(ptr.getCellRef().scale, ptr.getCellRef().scale, ptr.getCellRef().scale); + // Convert MW rotation to a quaternion: f = ptr.getCellRef().pos.rot; @@ -217,7 +219,7 @@ void Objects::insertLight (const MWWorld::Ptr& ptr, float r, float g, float b, f info.type = LT_Normal; // random starting phase for the animation - info.time = Ogre::Math::RangeRandom(0, 2 * M_PI); + info.time = Ogre::Math::RangeRandom(0, 2 * Ogre::Math::PI); // adjust the lights depending if we're in an interior or exterior cell // quadratic means the light intensity falls off quite fast, resulting in a @@ -365,7 +367,7 @@ void Objects::update(const float dt) // Light animation (pulse & flicker) it->time += dt; - const float phase = std::fmod(static_cast (it->time), (32 * 2 * M_PI)) * 20; + const float phase = std::fmod(static_cast (it->time), static_cast(32 * 2 * Ogre::Math::PI)) * 20; float pulseConstant; // These formulas are just guesswork, but they work pretty well diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index fb26808b96..6132879e69 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -39,7 +39,8 @@ struct LightInfo LightInfo() : flickerVariation(0), resetTime(0.5), - flickerSlowVariation(0), time(0), interior(true) + flickerSlowVariation(0), time(0), interior(true), + type(LT_Normal), radius(1.0) { } }; @@ -68,7 +69,7 @@ class Objects{ ///< Remove all movable objects from \a node. public: - Objects(OEngine::Render::OgreRenderer& renderer): mRenderer (renderer) {} + Objects(OEngine::Render::OgreRenderer& renderer): mRenderer (renderer), mIsStatic(false) {} ~Objects(){} void insertBegin (const MWWorld::Ptr& ptr, bool enabled, bool static_); void insertMesh (const MWWorld::Ptr& ptr, const std::string& mesh); diff --git a/apps/openmw/mwrender/player.cpp b/apps/openmw/mwrender/player.cpp index 7ed921218b..d6baac4b5a 100644 --- a/apps/openmw/mwrender/player.cpp +++ b/apps/openmw/mwrender/player.cpp @@ -6,4 +6,22 @@ namespace MWRender Player::Player (Ogre::Camera *camera, Ogre::SceneNode* node) : mCamera (camera), mNode (node) {} + + void Player::setRot(float x, float y, float z) + { + Ogre::SceneNode *sceneNode = mNode; + Ogre::Node* yawNode = sceneNode->getChildIterator().getNext(); + Ogre::Node* pitchNode = yawNode->getChildIterator().getNext(); + + // we are only interested in X and Y rotation + + // Rotate around X axis + Ogre::Quaternion xr(Ogre::Radian(x), Ogre::Vector3::UNIT_X); + + // Rotate around Y axis + Ogre::Quaternion yr(Ogre::Radian(-z), Ogre::Vector3::UNIT_Y); + + pitchNode->setOrientation(xr); + yawNode->setOrientation(yr); + } } diff --git a/apps/openmw/mwrender/player.hpp b/apps/openmw/mwrender/player.hpp index 4306b8a95e..406bedb0aa 100644 --- a/apps/openmw/mwrender/player.hpp +++ b/apps/openmw/mwrender/player.hpp @@ -23,6 +23,9 @@ namespace MWRender Ogre::Camera *getCamera() { return mCamera; } + /// Set where the player is looking at. Uses Morrowind (euler) angles + void setRot(float x, float y, float z); + std::string getHandle() const { return mNode->getName(); } Ogre::SceneNode* getNode() {return mNode;} }; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a95a179c6c..266362f9b4 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -11,6 +11,8 @@ #include "../mwworld/world.hpp" // these includes can be removed once the static-hack is gone #include "../mwworld/ptr.hpp" +#include "../mwworld/player.hpp" +#include "../mwbase/environment.hpp" #include #include @@ -18,6 +20,10 @@ #include "shaderhelper.hpp" #include "localmap.hpp" #include "water.hpp" +#include "compositors.hpp" + +#include "../mwgui/window_manager.hpp" // FIXME +#include "../mwinput/inputmanager.hpp" // FIXME using namespace MWRender; using namespace Ogre; @@ -28,6 +34,9 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const :mRendering(_rend), mObjects(mRendering), mActors(mRendering), mAmbientMode(0), mSunEnabled(0) { mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 5); + mRendering.setWindowEventListener(this); + + mCompositors = new Compositors(mRendering.getViewport()); mWater = 0; @@ -58,25 +67,13 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const // disable unsupported effects const RenderSystemCapabilities* caps = Root::getSingleton().getRenderSystem()->getCapabilities(); - if (caps->getNumMultiRenderTargets() < 2 || !Settings::Manager::getBool("shaders", "Objects")) + if (!waterShaderSupported()) Settings::Manager::setBool("shader", "Water", false); if ( !(caps->isShaderProfileSupported("fp40") || caps->isShaderProfileSupported("ps_4_0")) || !Settings::Manager::getBool("shaders", "Objects")) Settings::Manager::setBool("enabled", "Shadows", false); - // note that the order is important here - if (useMRT()) - { - CompositorManager::getSingleton().addCompositor(mRendering.getViewport(), "gbuffer"); - CompositorManager::getSingleton().setCompositorEnabled(mRendering.getViewport(), "gbuffer", true); - CompositorManager::getSingleton().addCompositor(mRendering.getViewport(), "Underwater"); - CompositorManager::getSingleton().addCompositor(mRendering.getViewport(), "gbufferFinalizer"); - CompositorManager::getSingleton().setCompositorEnabled(mRendering.getViewport(), "gbufferFinalizer", true); - } - else - { - CompositorManager::getSingleton().addCompositor(mRendering.getViewport(), "UnderwaterNoMRT"); - } + applyCompositors(); // Turn the entire scene (represented by the 'root' node) -90 // degrees around the x axis. This makes Z go upwards, and Y go into @@ -100,7 +97,6 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mTerrainManager = new TerrainManager(mRendering.getScene(), this); - //mSkyManager = 0; mSkyManager = new SkyManager(mMwRoot, mRendering.getCamera()); mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); @@ -110,16 +106,24 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mDebugging = new Debugging(mMwRoot, engine); mLocalMap = new MWRender::LocalMap(&mRendering, this); + + setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI")); } RenderingManager::~RenderingManager () { + mRendering.removeWindowEventListener(this); + delete mPlayer; delete mSkyManager; delete mDebugging; + delete mShaderHelper; + delete mShadows; delete mTerrainManager; delete mLocalMap; delete mOcclusionQuery; + delete mCompositors; + delete mWater; } MWRender::SkyManager* RenderingManager::getSkyManager() @@ -229,16 +233,18 @@ void RenderingManager::update (float duration){ mWater->update(); } void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store){ - if(store->cell->data.flags & store->cell->HasWater){ + if(store->cell->data.flags & store->cell->HasWater + || ((!(store->cell->data.flags & ESM::Cell::Interior)) + && !MWBase::Environment::get().getWorld()->getStore().lands.search(store->cell->data.gridX,store->cell->data.gridY) )) // always use water, if the cell does not have land. + { if(mWater == 0) - mWater = new MWRender::Water(mRendering.getCamera(), mSkyManager, store->cell); + mWater = new MWRender::Water(mRendering.getCamera(), this, store->cell); else mWater->changeCell(store->cell); mWater->setActive(true); } else removeWater(); - } void RenderingManager::setWaterHeight(const float height) @@ -292,35 +298,29 @@ void RenderingManager::skySetMoonColour (bool red){ bool RenderingManager::toggleRenderMode(int mode) { - if (mode != MWWorld::World::Render_Wireframe) + if (mode == MWWorld::World::Render_CollisionDebug || mode == MWWorld::World::Render_Pathgrid) return mDebugging->toggleRenderMode(mode); - else // if (mode == MWWorld::World::Render_Wireframe) + else if (mode == MWWorld::World::Render_Wireframe) { if (mRendering.getCamera()->getPolygonMode() == PM_SOLID) { - // disable compositors - if (useMRT()) - { - CompositorManager::getSingleton().setCompositorEnabled(mRendering.getViewport(), "gbuffer", false); - CompositorManager::getSingleton().setCompositorEnabled(mRendering.getViewport(), "gbufferFinalizer", false); - } + mCompositors->setEnabled(false); mRendering.getCamera()->setPolygonMode(PM_WIREFRAME); return true; } else { - // re-enable compositors - if (useMRT()) - { - CompositorManager::getSingleton().setCompositorEnabled(mRendering.getViewport(), "gbuffer", true); - CompositorManager::getSingleton().setCompositorEnabled(mRendering.getViewport(), "gbufferFinalizer", true); - } + mCompositors->setEnabled(true); mRendering.getCamera()->setPolygonMode(PM_SOLID); return false; } } + else //if (mode == MWWorld::World::Render_Compositors) + { + return mCompositors->toggle(); + } } void RenderingManager::configureFog(ESMS::CellStore &mCell) @@ -518,4 +518,163 @@ void RenderingManager::switchToExterior() mRendering.getScene()->setCameraRelativeRendering(true); } +Ogre::Vector4 RenderingManager::boundingBoxToScreen(Ogre::AxisAlignedBox bounds) +{ + Ogre::Matrix4 mat = mRendering.getCamera()->getViewMatrix(); + + const Ogre::Vector3* corners = bounds.getAllCorners(); + + float min_x = 1.0f, max_x = 0.0f, min_y = 1.0f, max_y = 0.0f; + + // expand the screen-space bounding-box so that it completely encloses + // the object's AABB + for (int i=0; i<8; i++) + { + Ogre::Vector3 corner = corners[i]; + + // multiply the AABB corner vertex by the view matrix to + // get a camera-space vertex + corner = mat * corner; + + // make 2D relative/normalized coords from the view-space vertex + // by dividing out the Z (depth) factor -- this is an approximation + float x = corner.x / corner.z + 0.5; + float y = corner.y / corner.z + 0.5; + + if (x < min_x) + min_x = x; + + if (x > max_x) + max_x = x; + + if (y < min_y) + min_y = y; + + if (y > max_y) + max_y = y; + } + + return Vector4(min_x, min_y, max_x, max_y); +} + +Compositors* RenderingManager::getCompositors() +{ + return mCompositors; +} + +void RenderingManager::processChangedSettings(const Settings::CategorySettingVector& settings) +{ + bool changeRes = false; + for (Settings::CategorySettingVector::const_iterator it=settings.begin(); + it != settings.end(); ++it) + { + if (it->second == "menu transparency" && it->first == "GUI") + { + setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI")); + } + else if (it->second == "max viewing distance" && it->first == "Viewing distance") + { + if (!MWBase::Environment::get().getWorld()->isCellExterior() && !MWBase::Environment::get().getWorld()->isCellQuasiExterior()) + configureFog(*MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell()); + } + else if (it->first == "Video" && ( + it->second == "resolution x" + || it->second == "resolution y" + || it->second == "fullscreen")) + changeRes = true; + else if (it->second == "field of view" && it->first == "General") + mRendering.setFov(Settings::Manager::getFloat("field of view", "General")); + else if ((it->second == "texture filtering" && it->first == "General") + || (it->second == "anisotropy" && it->first == "General")) + { + TextureFilterOptions tfo; + std::string filter = Settings::Manager::getString("texture filtering", "General"); + if (filter == "anisotropic") tfo = TFO_ANISOTROPIC; + else if (filter == "trilinear") tfo = TFO_TRILINEAR; + else if (filter == "bilinear") tfo = TFO_BILINEAR; + else if (filter == "none") tfo = TFO_NONE; + + MaterialManager::getSingleton().setDefaultTextureFiltering(tfo); + MaterialManager::getSingleton().setDefaultAnisotropy( (filter == "anisotropic") ? Settings::Manager::getInt("anisotropy", "General") : 1 ); + } + else if (it->second == "shader" && it->first == "Water") + { + applyCompositors(); + mShaderHelper->applyShaders(); + } + } + + if (changeRes) + { + unsigned int x = Settings::Manager::getInt("resolution x", "Video"); + unsigned int y = Settings::Manager::getInt("resolution y", "Video"); + + if (x != mRendering.getWindow()->getWidth() || y != mRendering.getWindow()->getHeight()) + { + mRendering.getWindow()->resize(x, y); + } + mRendering.getWindow()->setFullscreen(Settings::Manager::getBool("fullscreen", "Video"), x, y); + } + + if (mWater) + mWater->processChangedSettings(settings); +} + +void RenderingManager::setMenuTransparency(float val) +{ + Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().getByName("transparent.png"); + std::vector buffer; + buffer.resize(1); + buffer[0] = (int(255*val) << 24); + memcpy(tex->getBuffer()->lock(Ogre::HardwareBuffer::HBL_DISCARD), &buffer[0], 1*4); + tex->getBuffer()->unlock(); +} + +void RenderingManager::windowResized(Ogre::RenderWindow* rw) +{ + Settings::Manager::setInt("resolution x", "Video", rw->getWidth()); + Settings::Manager::setInt("resolution y", "Video", rw->getHeight()); + + + mRendering.adjustViewport(); + mCompositors->recreate(); + mWater->assignTextures(); + + const Settings::CategorySettingVector& changed = Settings::Manager::apply(); + MWBase::Environment::get().getInputManager()->processChangedSettings(changed); //FIXME + MWBase::Environment::get().getWindowManager()->processChangedSettings(changed); // FIXME +} + +void RenderingManager::windowClosed(Ogre::RenderWindow* rw) +{ +} + +bool RenderingManager::waterShaderSupported() +{ + const RenderSystemCapabilities* caps = Root::getSingleton().getRenderSystem()->getCapabilities(); + if (caps->getNumMultiRenderTargets() < 2 || !Settings::Manager::getBool("shaders", "Objects")) + return false; + return true; +} + +void RenderingManager::applyCompositors() +{ + mCompositors->removeAll(); + if (useMRT()) + { + mCompositors->addCompositor("gbuffer", 0); + mCompositors->setCompositorEnabled("gbuffer", true); + mCompositors->addCompositor("Underwater", 1); + mCompositors->addCompositor("gbufferFinalizer", 2); + mCompositors->setCompositorEnabled("gbufferFinalizer", true); + } + else + { + mCompositors->addCompositor("UnderwaterNoMRT", 0); + } + + if (mWater) + mWater->assignTextures(); +} + } // namespace diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 0d11b3d57d..642c42930c 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -8,11 +8,15 @@ #include "../mwworld/class.hpp" +#include + #include #include #include #include +#include + #include #include @@ -47,8 +51,9 @@ namespace MWRender class ShaderHelper; class LocalMap; class Water; + class Compositors; -class RenderingManager: private RenderingInterface { +class RenderingManager: private RenderingInterface, public Ogre::WindowEventListener { private: @@ -67,6 +72,7 @@ class RenderingManager: private RenderingInterface { /// to internal details of the rendering system anymore SkyManager* getSkyManager(); + Compositors* getCompositors(); void toggleLight(); bool toggleRenderMode(int mode); @@ -151,10 +157,28 @@ class RenderingManager: private RenderingInterface { ///< Skip the animation for the given MW-reference for one frame. Calls to this function for /// references that are currently not in the rendered scene should be ignored. + Ogre::Vector4 boundingBoxToScreen(Ogre::AxisAlignedBox bounds); + ///< transform the specified bounding box (in world coordinates) into screen coordinates. + /// @return packed vector4 (min_x, min_y, max_x, max_y) + + void processChangedSettings(const Settings::CategorySettingVector& settings); + + Ogre::Viewport* getViewport() { return mRendering.getViewport(); } + + static bool waterShaderSupported(); + + protected: + virtual void windowResized(Ogre::RenderWindow* rw); + virtual void windowClosed(Ogre::RenderWindow* rw); + private: void setAmbientMode(); + void setMenuTransparency(float val); + + void applyCompositors(); + bool mSunEnabled; SkyManager* mSkyManager; @@ -192,6 +216,8 @@ class RenderingManager: private RenderingInterface { MWRender::Shadows* mShadows; MWRender::ShaderHelper* mShaderHelper; + + MWRender::Compositors* mCompositors; }; } diff --git a/apps/openmw/mwrender/shaderhelper.cpp b/apps/openmw/mwrender/shaderhelper.cpp index 1d29be2b8d..59154a295c 100644 --- a/apps/openmw/mwrender/shaderhelper.cpp +++ b/apps/openmw/mwrender/shaderhelper.cpp @@ -258,7 +258,7 @@ void ShaderHelper::createShader(const bool mrt, const bool shadows, const bool s outStream << " float3 lightingFinal = lightColour.xyz * diffuse.xyz + ambient.xyz * lightAmbient.xyz + emissive.xyz; \n" " float fogValue = saturate((iDepth - fogParams.y) * fogParams.w); \n" - " oColor.xyz = lerp(lightingFinal * tex.xyz * vertexColour.xyz, fogColour.xyz, fogValue); \n" + " oColor.xyz = saturate(lerp(lightingFinal * tex.xyz * vertexColour.xyz, fogColour.xyz, fogValue)); \n" // saturate output to prevent negative output colors - TODO: replace this once there is HDR-rendering " oColor.a = tex.a * diffuse.a * vertexColour.a; \n"; if (mrt) outStream << diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index bf5602f438..9a4ae7243c 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -66,7 +66,10 @@ void Shadows::recreate() if (split) { mPSSMSetup = new PSSMShadowCameraSetup(); - mPSSMSetup->setSplitPadding(5); + + // Make sure to keep this in sync with the camera's near clip distance! + mPSSMSetup->setSplitPadding(mRendering->getCamera()->getNearClipDistance()); + mPSSMSetup->calculateSplitPoints(3, mRendering->getCamera()->getNearClipDistance(), mShadowFar); const Real adjustFactors[3] = {64, 64, 64}; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index da2e7446ab..19e45c189d 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -113,6 +113,7 @@ void BillboardObject::init(const String& textureName, p->setSelfIllumination(1.0,1.0,1.0); p->setDiffuse(0.0,0.0,0.0,1.0); p->setAmbient(0.0,0.0,0.0); + p->setPolygonModeOverrideable(false); p->createTextureUnitState(textureName); mBBSet->setMaterialName("BillboardMaterial"+StringConverter::toString(bodyCount)); @@ -373,7 +374,7 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera) , mSunGlare(NULL) , mMasser(NULL) , mSecunda(NULL) - , mViewport(NULL) + , mCamera(pCamera) , mRootNode(NULL) , mSceneMgr(NULL) , mAtmosphereDay(NULL) @@ -398,9 +399,8 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera) , mSecundaEnabled(true) , mCreated(false) { - mViewport = pCamera->getViewport(); mSceneMgr = pMwRoot->getCreator(); - mRootNode = pCamera->getParentSceneNode()->createChildSceneNode(); + mRootNode = mCamera->getParentSceneNode()->createChildSceneNode(); mRootNode->pitch(Degree(-90)); // convert MW to ogre coordinates mRootNode->setInheritOrientation(false); } @@ -520,6 +520,7 @@ void SkyManager::create() mp->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); mp->getTechnique(0)->getPass(0)->setVertexProgram(stars_vp->getName()); mp->getTechnique(0)->getPass(0)->setFragmentProgram(stars_fp->getName()); + mp->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); mStarsMaterials[i] = mp; } @@ -535,6 +536,7 @@ void SkyManager::create() mAtmosphereDay = mRootNode->createChildSceneNode(); mAtmosphereDay->attachObject(atmosphere_ent); mAtmosphereMaterial = atmosphere_ent->getSubEntity(0)->getMaterial(); + mAtmosphereMaterial->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); // Atmosphere shader HighLevelGpuProgramPtr vshader = mgr.createProgram("Atmosphere_VP", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, @@ -598,6 +600,7 @@ void SkyManager::create() SceneNode* clouds_node = mRootNode->createChildSceneNode(); clouds_node->attachObject(clouds_ent); mCloudMaterial = clouds_ent->getSubEntity(0)->getMaterial(); + mCloudMaterial->getTechnique(0)->getPass(0)->setPolygonModeOverrideable(false); clouds_ent->setCastShadows(false); // Clouds vertex shader @@ -737,7 +740,7 @@ void SkyManager::update(float duration) // on how directly the player is looking at the sun Vector3 sun = mSunGlare->getPosition(); sun = Vector3(sun.x, sun.z, -sun.y); - Vector3 cam = mViewport->getCamera()->getRealDirection(); + Vector3 cam = mCamera->getRealDirection(); const Degree angle = sun.angleBetween( cam ); float val = 1- (angle.valueDegrees() / 180.f); val = (val*val*val*val)*2; diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index da89d0eb04..e11745e828 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -185,7 +185,7 @@ namespace MWRender Moon* mMasser; Moon* mSecunda; - Ogre::Viewport* mViewport; + Ogre::Camera* mCamera; Ogre::SceneNode* mRootNode; Ogre::SceneManager* mSceneMgr; diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp index 90d853f752..4dac750c7c 100644 --- a/apps/openmw/mwrender/terrain.cpp +++ b/apps/openmw/mwrender/terrain.cpp @@ -22,30 +22,30 @@ namespace MWRender TerrainManager::TerrainManager(Ogre::SceneManager* mgr, RenderingManager* rend) : mTerrainGroup(TerrainGroup(mgr, Terrain::ALIGN_X_Z, mLandSize, mWorldSize)), mRendering(rend) { - + mTerrainGlobals = OGRE_NEW TerrainGlobalOptions(); TerrainMaterialGeneratorPtr matGen; TerrainMaterialGeneratorB* matGenP = new TerrainMaterialGeneratorB(); matGen.bind(matGenP); - mTerrainGlobals.setDefaultMaterialGenerator(matGen); + mTerrainGlobals->setDefaultMaterialGenerator(matGen); TerrainMaterialGenerator::Profile* const activeProfile = - mTerrainGlobals.getDefaultMaterialGenerator() + mTerrainGlobals->getDefaultMaterialGenerator() ->getActiveProfile(); mActiveProfile = static_cast(activeProfile); //The pixel error should be as high as possible without it being noticed //as it governs how fast mesh quality decreases. - mTerrainGlobals.setMaxPixelError(8); + mTerrainGlobals->setMaxPixelError(8); - mTerrainGlobals.setLayerBlendMapSize(32); - mTerrainGlobals.setDefaultGlobalColourMapSize(65); + mTerrainGlobals->setLayerBlendMapSize(32); + mTerrainGlobals->setDefaultGlobalColourMapSize(65); //10 (default) didn't seem to be quite enough - mTerrainGlobals.setSkirtSize(128); + mTerrainGlobals->setSkirtSize(128); //due to the sudden flick between composite and non composite textures, //this seemed the distance where it wasn't too noticeable - mTerrainGlobals.setCompositeMapDistance(mWorldSize*2); + mTerrainGlobals->setCompositeMapDistance(mWorldSize*2); mActiveProfile->setLightmapEnabled(false); mActiveProfile->setLayerSpecularMappingEnabled(false); @@ -86,20 +86,21 @@ namespace MWRender TerrainManager::~TerrainManager() { + OGRE_DELETE mTerrainGlobals; } //---------------------------------------------------------------------------------------------- void TerrainManager::setDiffuse(const ColourValue& diffuse) { - mTerrainGlobals.setCompositeMapDiffuse(diffuse); + mTerrainGlobals->setCompositeMapDiffuse(diffuse); } //---------------------------------------------------------------------------------------------- void TerrainManager::setAmbient(const ColourValue& ambient) { - mTerrainGlobals.setCompositeMapAmbient(ambient); + mTerrainGlobals->setCompositeMapAmbient(ambient); } //---------------------------------------------------------------------------------------------- @@ -110,12 +111,12 @@ namespace MWRender const int cellY = store->cell->getGridY(); ESM::Land* land = MWBase::Environment::get().getWorld()->getStore().lands.search(cellX, cellY); - if ( land != NULL ) + if (land == NULL) // no land data means we're not going to create any terrain. + return; + + if (!land->dataLoaded) { - if (!land->dataLoaded) - { - land->loadData(); - } + land->loadData(); } //split the cell terrain into four segments @@ -138,25 +139,18 @@ namespace MWRender mLandSize*mLandSize, MEMCATEGORY_GEOMETRY); - if ( land != NULL ) + //copy the height data row by row + for ( int terrainCopyY = 0; terrainCopyY < mLandSize; terrainCopyY++ ) { - //copy the height data row by row - for ( int terrainCopyY = 0; terrainCopyY < mLandSize; terrainCopyY++ ) - { - //the offset of the current segment - const size_t yOffset = y * (mLandSize-1) * ESM::Land::LAND_SIZE + - //offset of the row - terrainCopyY * ESM::Land::LAND_SIZE; - const size_t xOffset = x * (mLandSize-1); + //the offset of the current segment + const size_t yOffset = y * (mLandSize-1) * ESM::Land::LAND_SIZE + + //offset of the row + terrainCopyY * ESM::Land::LAND_SIZE; + const size_t xOffset = x * (mLandSize-1); - memcpy(&terrainData.inputFloat[terrainCopyY*mLandSize], - &land->landData->heights[yOffset + xOffset], - mLandSize*sizeof(float)); - } - } - else - { - memset(terrainData.inputFloat, 0, mLandSize*mLandSize*sizeof(float)); + memcpy(&terrainData.inputFloat[terrainCopyY*mLandSize], + &land->landData->heights[yOffset + xOffset], + mLandSize*sizeof(float)); } std::map indexes; @@ -179,7 +173,7 @@ namespace MWRender terrain->setVisibilityFlags(RV_Terrain); terrain->setRenderQueueGroup(RQG_Main); - if ( land && land->landData->usingColours ) + if ( land->landData->usingColours ) { // disable or enable global colour map (depends on available vertex colours) mActiveProfile->setGlobalColourMapEnabled(true); @@ -196,10 +190,6 @@ namespace MWRender //mat = terrain->_getCompositeMapMaterial(); //mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); } - else - { - mActiveProfile->setGlobalColourMapEnabled(false); - } } } } @@ -215,8 +205,10 @@ namespace MWRender { for ( int y = 0; y < 2; y++ ) { - mTerrainGroup.unloadTerrain(store->cell->getGridX() * 2 + x, - store->cell->getGridY() * 2 + y); + int terrainX = store->cell->getGridX() * 2 + x; + int terrainY = store->cell->getGridY() * 2 + y; + if (mTerrainGroup.getTerrain(terrainX, terrainY) != NULL) + mTerrainGroup.unloadTerrain(terrainX, terrainY); } } } diff --git a/apps/openmw/mwrender/terrain.hpp b/apps/openmw/mwrender/terrain.hpp index c4ff053a6f..273ede0849 100644 --- a/apps/openmw/mwrender/terrain.hpp +++ b/apps/openmw/mwrender/terrain.hpp @@ -33,7 +33,7 @@ namespace MWRender{ void cellAdded(MWWorld::Ptr::CellStore* store); void cellRemoved(MWWorld::Ptr::CellStore* store); private: - Ogre::TerrainGlobalOptions mTerrainGlobals; + Ogre::TerrainGlobalOptions* mTerrainGlobals; Ogre::TerrainGroup mTerrainGroup; RenderingManager* mRendering; diff --git a/apps/openmw/mwrender/terrainmaterial.hpp b/apps/openmw/mwrender/terrainmaterial.hpp index db916bf253..e94e20b7e9 100644 --- a/apps/openmw/mwrender/terrainmaterial.hpp +++ b/apps/openmw/mwrender/terrainmaterial.hpp @@ -167,7 +167,7 @@ namespace Ogre class ShaderHelper : public TerrainAlloc { public: - ShaderHelper() {} + ShaderHelper() : mShadowSamplerStartHi(0), mShadowSamplerStartLo(0) {} virtual ~ShaderHelper() {} virtual HighLevelGpuProgramPtr generateVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); virtual HighLevelGpuProgramPtr generateFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index c81f23f548..dc9e1fbee1 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -1,25 +1,21 @@ #include "water.hpp" -#include + #include "sky.hpp" #include "renderingmanager.hpp" +#include "compositors.hpp" using namespace Ogre; namespace MWRender { -Water::Water (Ogre::Camera *camera, SkyManager* sky, const ESM::Cell* cell) : - mCamera (camera), mViewport (camera->getViewport()), mSceneManager (camera->getSceneManager()), +Water::Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cell) : + mCamera (camera), mSceneManager (camera->getSceneManager()), mIsUnderwater(false), mVisibilityFlags(0), mReflectionTarget(0), mActive(1), mToggled(1), - mReflectionRenderActive(false) + mReflectionRenderActive(false), mRendering(rend) { - mSky = sky; - - try - { - CompositorManager::getSingleton().setCompositorEnabled(mViewport, "Water", false); - } catch(...) {} + mSky = rend->getSkyManager(); mTop = cell->water; @@ -34,13 +30,6 @@ Water::Water (Ogre::Camera *camera, SkyManager* sky, const ESM::Cell* cell) : mWater->setRenderQueueGroup(RQG_Water); mWater->setCastShadows(false); - mVisibilityFlags = RV_Terrain * Settings::Manager::getBool("reflect terrain", "Water") - + RV_Statics * Settings::Manager::getBool("reflect statics", "Water") - + RV_StaticsSmall * Settings::Manager::getBool("reflect small statics", "Water") - + RV_Actors * Settings::Manager::getBool("reflect actors", "Water") - + RV_Misc * Settings::Manager::getBool("reflect misc", "Water") - + RV_Sky; - mWaterNode = mSceneManager->getRootSceneNode()->createChildSceneNode(); mWaterNode->setPosition(0, mTop, 0); @@ -52,30 +41,9 @@ Water::Water (Ogre::Camera *camera, SkyManager* sky, const ESM::Cell* cell) : } mWaterNode->attachObject(mWater); - // Create rendertarget for reflection - int rttsize = Settings::Manager::getInt("rtt size", "Water"); + applyRTT(); + applyVisibilityMask(); - TexturePtr tex; - if (Settings::Manager::getBool("shader", "Water")) - { - tex = TextureManager::getSingleton().createManual("WaterReflection", - ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, rttsize, rttsize, 0, PF_FLOAT16_RGBA, TU_RENDERTARGET); - - RenderTarget* rtt = tex->getBuffer()->getRenderTarget(); - Viewport* vp = rtt->addViewport(mReflectionCamera); - vp->setOverlaysEnabled(false); - vp->setBackgroundColour(ColourValue(0.8f, 0.9f, 1.0f)); - vp->setShadowsEnabled(false); - vp->setVisibilityMask( mVisibilityFlags ); - // use fallback techniques without shadows and without mrt (currently not implemented for sky and terrain) - //vp->setMaterialScheme("Fallback"); - rtt->addListener(this); - rtt->setActive(true); - - mReflectionTarget = rtt; - } - - mCompositorName = RenderingManager::useMRT() ? "Underwater" : "UnderwaterNoMRT"; createMaterial(); mWater->setMaterial(mMaterial); @@ -84,6 +52,8 @@ Water::Water (Ogre::Camera *camera, SkyManager* sky, const ESM::Cell* cell) : mSceneManager->addRenderQueueListener(this); + assignTextures(); + // ---------------------------------------------------------------------------------------------- // ---------------------------------- reflection debug overlay ---------------------------------- @@ -144,21 +114,22 @@ Water::~Water() { MeshManager::getSingleton().remove("water"); + if (mReflectionTarget) + mReflectionTexture->getBuffer()->getRenderTarget()->removeListener(this); + mWaterNode->detachObject(mWater); mSceneManager->destroyEntity(mWater); mSceneManager->destroySceneNode(mWaterNode); - - CompositorManager::getSingleton().removeCompositorChain(mViewport); } void Water::changeCell(const ESM::Cell* cell) { mTop = cell->water; + setHeight(mTop); + if(!(cell->data.flags & cell->Interior)) mWaterNode->setPosition(getSceneNodeCoordinates(cell->data.gridX, cell->data.gridY)); - else - setHeight(mTop); } void Water::setHeight(const float height) @@ -178,13 +149,13 @@ void Water::checkUnderwater(float y) { if (!mActive) { - CompositorManager::getSingleton().setCompositorEnabled(mViewport, mCompositorName, false); + mRendering->getCompositors()->setCompositorEnabled(mCompositorName, false); return; } if ((mIsUnderwater && y > mTop) || !mWater->isVisible() || mCamera->getPolygonMode() != Ogre::PM_SOLID) { - CompositorManager::getSingleton().setCompositorEnabled(mViewport, mCompositorName, false); + mRendering->getCompositors()->setCompositorEnabled(mCompositorName, false); // tell the shader we are not underwater Ogre::Pass* pass = mMaterial->getTechnique(0)->getPass(0); @@ -199,7 +170,7 @@ void Water::checkUnderwater(float y) if (!mIsUnderwater && y < mTop && mWater->isVisible() && mCamera->getPolygonMode() == Ogre::PM_SOLID) { if (mUnderwaterEffect) - CompositorManager::getSingleton().setCompositorEnabled(mViewport, mCompositorName, true); + mRendering->getCompositors()->setCompositorEnabled(mCompositorName, true); // tell the shader we are underwater Ogre::Pass* pass = mMaterial->getTechnique(0)->getPass(0); @@ -255,7 +226,15 @@ void Water::postRenderTargetUpdate(const RenderTargetEvent& evt) void Water::createMaterial() { - mMaterial = MaterialManager::getSingleton().getByName("Water"); + if (mReflectionTarget == 0) + { + mMaterial = MaterialManager::getSingleton().getByName("Water_Fallback"); + } + else + { + mMaterial = MaterialManager::getSingleton().getByName("Water"); + mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTexture(mReflectionTexture); + } // these have to be set in code std::string textureNames[32]; @@ -263,15 +242,20 @@ void Water::createMaterial() { textureNames[i] = "textures\\water\\water" + StringConverter::toString(i, 2, '0') + ".dds"; } - mMaterial->getTechnique(1)->getPass(0)->getTextureUnitState(0)->setAnimatedTextureName(textureNames, 32, 2); - - // use technique without shaders if reflection is disabled + Ogre::Technique* tech; if (mReflectionTarget == 0) - mMaterial->removeTechnique(0); + tech = mMaterial->getTechnique(0); + else + tech = mMaterial->getTechnique(1); + tech->getPass(0)->getTextureUnitState(0)->setAnimatedTextureName(textureNames, 32, 2); +} + +void Water::assignTextures() +{ if (Settings::Manager::getBool("shader", "Water")) { - CompositorInstance* compositor = CompositorManager::getSingleton().getCompositorChain(mViewport)->getCompositor("gbuffer"); + CompositorInstance* compositor = CompositorManager::getSingleton().getCompositorChain(mRendering->getViewport())->getCompositor("gbuffer"); TexturePtr colorTexture = compositor->getTextureInstance("mrt_output", 0); TextureUnitState* tus = mMaterial->getTechnique(0)->getPass(0)->getTextureUnitState("refractionMap"); @@ -321,4 +305,84 @@ void Water::update() { } +void Water::applyRTT() +{ + if (mReflectionTarget) + { + TextureManager::getSingleton().remove("WaterReflection"); + mReflectionTarget = 0; + } + + // Create rendertarget for reflection + int rttsize = Settings::Manager::getInt("rtt size", "Water"); + + if (Settings::Manager::getBool("shader", "Water")) + { + mReflectionTexture = TextureManager::getSingleton().createManual("WaterReflection", + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, rttsize, rttsize, 0, PF_FLOAT16_RGBA, TU_RENDERTARGET); + + RenderTarget* rtt = mReflectionTexture->getBuffer()->getRenderTarget(); + Viewport* vp = rtt->addViewport(mReflectionCamera); + vp->setOverlaysEnabled(false); + vp->setBackgroundColour(ColourValue(0.8f, 0.9f, 1.0f)); + vp->setShadowsEnabled(false); + // use fallback techniques without shadows and without mrt (currently not implemented for sky and terrain) + //vp->setMaterialScheme("Fallback"); + rtt->addListener(this); + rtt->setActive(true); + + mReflectionTarget = rtt; + } + + mCompositorName = RenderingManager::useMRT() ? "Underwater" : "UnderwaterNoMRT"; +} + +void Water::applyVisibilityMask() +{ + mVisibilityFlags = RV_Terrain * Settings::Manager::getBool("reflect terrain", "Water") + + RV_Statics * Settings::Manager::getBool("reflect statics", "Water") + + RV_StaticsSmall * Settings::Manager::getBool("reflect small statics", "Water") + + RV_Actors * Settings::Manager::getBool("reflect actors", "Water") + + RV_Misc * Settings::Manager::getBool("reflect misc", "Water") + + RV_Sky; + + if (mReflectionTarget) + { + mReflectionTexture->getBuffer()->getRenderTarget()->getViewport(0)->setVisibilityMask(mVisibilityFlags); + } +} + +void Water::processChangedSettings(const Settings::CategorySettingVector& settings) +{ + bool applyRT = false; + bool applyVisMask = false; + for (Settings::CategorySettingVector::const_iterator it=settings.begin(); + it != settings.end(); ++it) + { + if ( it->first == "Water" && ( + it->second == "shader" + || it->second == "rtt size")) + applyRT = true; + + if ( it->first == "Water" && ( + it->second == "reflect actors" + || it->second == "reflect terrain" + || it->second == "reflect misc" + || it->second == "reflect small statics" + || it->second == "reflect statics")) + applyVisMask = true; + } + + if(applyRT) + { + applyRTT(); + applyVisibilityMask(); + createMaterial(); + mWater->setMaterial(mMaterial); + assignTextures(); + } + if (applyVisMask) + applyVisibilityMask(); +} + } // namespace diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index c8b8d311ed..4d617ea472 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -3,12 +3,15 @@ #include #include +#include #include "renderconst.hpp" + namespace MWRender { class SkyManager; + class RenderingManager; /// Water rendering class Water : public Ogre::RenderTargetListener, public Ogre::RenderQueueListener @@ -16,7 +19,6 @@ namespace MWRender { static const int CELL_SIZE = 8192; Ogre::Camera *mCamera; Ogre::SceneManager *mSceneManager; - Ogre::Viewport *mViewport; Ogre::Plane mWaterPlane; Ogre::SceneNode *mWaterNode; @@ -38,8 +40,12 @@ namespace MWRender { void renderQueueStarted (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation); void renderQueueEnded (Ogre::uint8 queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation); + void applyRTT(); + void applyVisibilityMask(); + void updateVisible(); + RenderingManager* mRendering; SkyManager* mSky; std::string mCompositorName; @@ -49,13 +55,14 @@ namespace MWRender { Ogre::Camera* mReflectionCamera; + Ogre::TexturePtr mReflectionTexture; Ogre::RenderTarget* mReflectionTarget; bool mUnderwaterEffect; int mVisibilityFlags; public: - Water (Ogre::Camera *camera, SkyManager* sky, const ESM::Cell* cell); + Water (Ogre::Camera *camera, RenderingManager* rend, const ESM::Cell* cell); ~Water(); void setActive(bool active); @@ -63,8 +70,12 @@ namespace MWRender { void toggle(); void update(); + void assignTextures(); + void setViewportBackground(const Ogre::ColourValue& bg); + void processChangedSettings(const Settings::CategorySettingVector& settings); + void checkUnderwater(float y); void changeCell(const ESM::Cell* cell); void setHeight(const float height); diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 3e8658bf85..b916ba72cc 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -18,6 +18,19 @@ #include "interpretercontext.hpp" #include "ref.hpp" +namespace +{ + std::string toLower (const std::string& name) + { + std::string lowerCase; + + std::transform (name.begin(), name.end(), std::back_inserter (lowerCase), + (int(*)(int)) std::tolower); + + return lowerCase; + } +} + namespace MWScript { namespace Container @@ -65,7 +78,7 @@ namespace MWScript Interpreter::Type_Integer sum = 0; for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) - if (iter->getCellRef().refID==item) + if (toLower(iter->getCellRef().refID) == toLower(item)) sum += iter->getRefData().getCount(); runtime.push (sum); @@ -95,7 +108,7 @@ namespace MWScript for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end() && count; ++iter) { - if (iter->getCellRef().refID==item) + if (toLower(iter->getCellRef().refID) == toLower(item)) { if (iter->getRefData().getCount()<=count) { diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index b99d55999c..ec8ab59b41 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -115,6 +115,16 @@ namespace MWScript } }; + class OpGoodbye : public Interpreter::Opcode0 + { + public: + + virtual void execute(Interpreter::Runtime& runtime) + { + MWBase::Environment::get().getDialogueManager()->goodbye(); + } + }; + const int opcodeJournal = 0x2000133; const int opcodeSetJournalIndex = 0x2000134; const int opcodeGetJournalIndex = 0x2000135; @@ -122,6 +132,7 @@ namespace MWScript const int opcodeChoice = 0x2000a; const int opcodeForceGreeting = 0x200014f; const int opcodeForceGreetingExplicit = 0x2000150; + const int opcodeGoodbye = 0x2000152; void registerExtensions (Compiler::Extensions& extensions) { @@ -133,6 +144,7 @@ namespace MWScript extensions.registerInstruction("forcegreeting","",opcodeForceGreeting); extensions.registerInstruction("forcegreeting","",opcodeForceGreeting, opcodeForceGreetingExplicit); + extensions.registerInstruction("goodbye", "", opcodeGoodbye); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -144,6 +156,7 @@ namespace MWScript interpreter.installSegment3 (opcodeChoice,new OpChoice); interpreter.installSegment5 (opcodeForceGreeting, new OpForceGreeting); interpreter.installSegment5 (opcodeForceGreetingExplicit, new OpForceGreeting); + interpreter.installSegment5 (opcodeGoodbye, new OpGoodbye); } } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 58960aac47..4423f17cfb 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -144,4 +144,7 @@ op 0x200014d: ModDisposition op 0x200014e: ModDisposition, explicit reference op 0x200014f: ForceGreeting op 0x2000150: ForceGreeting, explicit reference -opcodes 0x2000151-0x3ffffff unused +op 0x2000151: ToggleFullHelp +op 0x2000152: Goodbye +op 0x2000153: DontSaveObject (left unimplemented) +opcodes 0x2000154-0x3ffffff unused diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index a37595ea82..b267cc6e49 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -44,7 +44,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWBase::Environment::get().getInputManager()->setGuiMode(mDialogue); + MWBase::Environment::get().getWindowManager()->pushGuiMode(mDialogue); } }; @@ -73,6 +73,16 @@ namespace MWScript } }; + class OpToggleFullHelp : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::Environment::get().getWindowManager()->toggleFullHelp(); + } + }; + const int opcodeEnableBirthMenu = 0x200000e; const int opcodeEnableClassMenu = 0x200000f; const int opcodeEnableNameMenu = 0x2000010; @@ -86,6 +96,7 @@ namespace MWScript const int opcodeShowRestMenu = 0x2000018; const int opcodeGetButtonPressed = 0x2000137; const int opcodeToggleFogOfWar = 0x2000145; + const int opcodeToggleFullHelp = 0x2000151; void registerExtensions (Compiler::Extensions& extensions) { @@ -94,7 +105,7 @@ namespace MWScript extensions.registerInstruction ("enablenamemenu", "", opcodeEnableNameMenu); extensions.registerInstruction ("enableracemenu", "", opcodeEnableRaceMenu); extensions.registerInstruction ("enablestatsreviewmenu", "", - opcodeEnableStatsReviewMenu); +opcodeEnableStatsReviewMenu); extensions.registerInstruction ("enableinventorymenu", "", opcodeEnableInventoryMenu); extensions.registerInstruction ("enablemagicmenu", "", opcodeEnableMagicMenu); @@ -110,6 +121,9 @@ namespace MWScript extensions.registerInstruction ("togglefogofwar", "", opcodeToggleFogOfWar); extensions.registerInstruction ("tfow", "", opcodeToggleFogOfWar); + + extensions.registerInstruction ("togglefullhelp", "", opcodeToggleFullHelp); + extensions.registerInstruction ("tfh", "", opcodeToggleFullHelp); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -147,6 +161,8 @@ namespace MWScript interpreter.installSegment5 (opcodeGetButtonPressed, new OpGetButtonPressed); interpreter.installSegment5 (opcodeToggleFogOfWar, new OpToggleFogOfWar); + + interpreter.installSegment5 (opcodeToggleFullHelp, new OpToggleFullHelp); } } } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 4ba523937c..c7569ccdda 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -194,6 +194,17 @@ namespace MWScript } }; + class OpDontSaveObject : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + // We are ignoring the DontSaveObject statement for now. Probably not worth + /// bothering with. The incompatibility we are creating should be marginal at most. + } + }; + const int opcodeXBox = 0x200000c; const int opcodeOnActivate = 0x200000d; const int opcodeActivate = 0x2000075; @@ -208,6 +219,7 @@ namespace MWScript const int opcodeFadeTo = 0x200013e; const int opcodeToggleWater = 0x2000144; const int opcodeTogglePathgrid = 0x2000146; + const int opcodeDontSaveObject = 0x2000153; void registerExtensions (Compiler::Extensions& extensions) { @@ -229,6 +241,7 @@ namespace MWScript extensions.registerInstruction ("twa", "", opcodeToggleWater); extensions.registerInstruction ("togglepathgrid", "", opcodeTogglePathgrid); extensions.registerInstruction ("tpg", "", opcodeTogglePathgrid); + extensions.registerInstruction ("dontsaveobject", "", opcodeDontSaveObject); } void installOpcodes (Interpreter::Interpreter& interpreter) @@ -247,6 +260,7 @@ namespace MWScript interpreter.installSegment5 (opcodeFadeTo, new OpFadeTo); interpreter.installSegment5 (opcodeTogglePathgrid, new OpTogglePathgrid); interpreter.installSegment5 (opcodeToggleWater, new OpToggleWater); + interpreter.installSegment5 (opcodeDontSaveObject, new OpDontSaveObject); } } } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index cad52e5930..d235192817 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1,6 +1,8 @@ #include "statsextensions.hpp" +#include + #include #include @@ -362,6 +364,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } + boost::algorithm::to_lower(factionID); if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); @@ -390,6 +393,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } + boost::algorithm::to_lower(factionID); if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); @@ -422,6 +426,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } + boost::algorithm::to_lower(factionID); if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); @@ -459,6 +464,7 @@ namespace MWScript factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).mFactionRank.begin()->first; } } + boost::algorithm::to_lower(factionID); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); if(factionID!="") { diff --git a/apps/openmw/mwsound/mpgsnd_decoder.cpp b/apps/openmw/mwsound/mpgsnd_decoder.cpp index 9b91b4e74e..7f7a84889a 100644 --- a/apps/openmw/mwsound/mpgsnd_decoder.cpp +++ b/apps/openmw/mwsound/mpgsnd_decoder.cpp @@ -189,7 +189,7 @@ void MpgSnd_Decoder::readAll(std::vector &output) { size_t pos = output.size(); output.resize(pos + mSndInfo.frames*mSndInfo.channels*2); - sf_readf_short(mSndFile, (short*)(output.data()+pos), mSndInfo.frames); + sf_readf_short(mSndFile, (short*)(&output[0]+pos), mSndInfo.frames); return; } // Fallback in case we don't know the total already diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 615def7019..ac4baa8b29 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -222,8 +222,8 @@ void OpenAL_SoundStream::play() for(ALuint i = 0;i < sNumBuffers;i++) { size_t got; - got = mDecoder->read(data.data(), data.size()); - alBufferData(mBuffers[i], mFormat, data.data(), got, mSampleRate); + got = mDecoder->read(&data[0], data.size()); + alBufferData(mBuffers[i], mFormat, &data[0], got, mSampleRate); } throwALerror(); @@ -299,11 +299,11 @@ bool OpenAL_SoundStream::process() if(finished) continue; - got = mDecoder->read(data.data(), data.size()); + got = mDecoder->read(&data[0], data.size()); finished = (got < data.size()); if(got > 0) { - alBufferData(bufid, mFormat, data.data(), got, mSampleRate); + alBufferData(bufid, mFormat, &data[0], got, mSampleRate); alSourceQueueBuffers(mSource, 1, &bufid); } } while(processed > 0); @@ -595,7 +595,7 @@ ALuint OpenAL_Output::getBuffer(const std::string &fname) alGenBuffers(1, &buf); throwALerror(); - alBufferData(buf, format, data.data(), data.size(), srate); + alBufferData(buf, format, &data[0], data.size(), srate); mBufferCache[fname] = buf; mBufferRefs[buf] = 1; diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 7efed81293..100b2208c4 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -46,7 +46,7 @@ namespace MWSound public: virtual ~Sound_Output() { } - bool isInitialized() { return mInitialized; } + bool isInitialized() const { return mInitialized; } friend class OpenAL_Output; friend class SoundManager; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 4656efb6ed..ff618ac334 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -7,7 +7,6 @@ #include #include -#include #include "../mwbase/environment.hpp" @@ -53,6 +52,8 @@ namespace MWSound , mMasterVolume(1.0f) , mSFXVolume(1.0f) , mMusicVolume(1.0f) + , mFootstepsVolume(1.0f) + , mVoiceVolume(1.0f) { if(!useSound) return; @@ -63,6 +64,10 @@ namespace MWSound mSFXVolume = std::min(std::max(mSFXVolume, 0.0f), 1.0f); mMusicVolume = Settings::Manager::getFloat("music volume", "Sound"); mMusicVolume = std::min(std::max(mMusicVolume, 0.0f), 1.0f); + mVoiceVolume = Settings::Manager::getFloat("voice volume", "Sound"); + mVoiceVolume = std::min(std::max(mVoiceVolume, 0.0f), 1.0f); + mFootstepsVolume = Settings::Manager::getFloat("footsteps volume", "Sound"); + mFootstepsVolume = std::min(std::max(mFootstepsVolume, 0.0f), 1.0f); std::cout << "Sound output: " << SOUND_OUT << std::endl; std::cout << "Sound decoder: " << SOUND_IN << std::endl; @@ -210,7 +215,7 @@ namespace MWSound try { // The range values are not tested - float basevol = mMasterVolume * mSFXVolume; + float basevol = mMasterVolume * mVoiceVolume; std::string filePath = "Sound/"+filename; const ESM::Position &pos = ptr.getCellRef().pos; const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); @@ -228,11 +233,47 @@ namespace MWSound } } + void SoundManager::say(const std::string& filename) + { + if(!mOutput->isInitialized()) + return; + try + { + float basevol = mMasterVolume * mVoiceVolume; + std::string filePath = "Sound/"+filename; + + SoundPtr sound = mOutput->playSound(filePath, basevol, 1.0f, Play_Normal); + sound->mBaseVolume = basevol; + + mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), std::string("_say_sound")); + } + catch(std::exception &e) + { + std::cout <<"Sound Error: "<second.first == ptr && snditer->second.second == "_say_sound") + { + snditer->first->stop(); + mActiveSounds.erase(snditer++); + } + else + snditer++; + } + } + + SoundPtr SoundManager::playSound(const std::string& soundId, float volume, float pitch, int mode) { @@ -397,7 +438,10 @@ namespace MWSound total = 0; } - const ESM::Region *regn = MWBase::Environment::get().getWorld()->getStore().regions.find(regionName); + const ESM::Region *regn = MWBase::Environment::get().getWorld()->getStore().regions.search(regionName); + if (regn == NULL) + return; + std::vector::const_iterator soundIter; if(total == 0) { @@ -488,6 +532,39 @@ namespace MWSound } + void SoundManager::processChangedSettings(const Settings::CategorySettingVector& settings) + { + mMasterVolume = Settings::Manager::getFloat("master volume", "Sound"); + mMusicVolume = Settings::Manager::getFloat("music volume", "Sound"); + mSFXVolume = Settings::Manager::getFloat("sfx volume", "Sound"); + mFootstepsVolume = Settings::Manager::getFloat("footsteps volume", "Sound"); + mVoiceVolume = Settings::Manager::getFloat("voice volume", "Sound"); + + SoundMap::iterator snditer = mActiveSounds.begin(); + while(snditer != mActiveSounds.end()) + { + if(snditer->second.second != "_say_sound") + { + float basevol = mMasterVolume * mSFXVolume; + float min, max; + lookup(snditer->second.second, basevol, min, max); + snditer->first->mBaseVolume = basevol; + } + else + { + float basevol = mMasterVolume * mVoiceVolume; + snditer->first->mBaseVolume = basevol; + } + snditer->first->update(); + snditer++; + } + if(mMusic) + { + mMusic->mBaseVolume = mMasterVolume * mMusicVolume; + mMusic->update(); + } + } + // Default readAll implementation, for decoders that can't do anything // better void Sound_Decoder::readAll(std::vector &output) diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 8afd488e11..562b2af002 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -7,6 +7,8 @@ #include +#include + #include "../mwworld/ptr.hpp" @@ -52,6 +54,10 @@ namespace MWSound float mMasterVolume; float mSFXVolume; float mMusicVolume; + float mVoiceVolume; + + // not implemented + float mFootstepsVolume; boost::shared_ptr mMusic; std::string mCurrentPlaylist; @@ -78,6 +84,8 @@ namespace MWSound SoundManager(bool useSound); ~SoundManager(); + void processChangedSettings(const Settings::CategorySettingVector& settings); + void stopMusic(); ///< Stops music if it's playing @@ -97,11 +105,18 @@ namespace MWSound void say(MWWorld::Ptr reference, const std::string& filename); ///< Make an actor say some text. - /// \param filename name of a sound file in "Sound/Vo/" in the data directory. + /// \param filename name of a sound file in "Sound/" in the data directory. - bool sayDone(MWWorld::Ptr reference) const; + void say(const std::string& filename); + ///< Say some text, without an actor ref + /// \param filename name of a sound file in "Sound/" in the data directory. + + bool sayDone(MWWorld::Ptr reference=MWWorld::Ptr()) const; ///< Is actor not speaking? + void stopSay(MWWorld::Ptr reference=MWWorld::Ptr()); + ///< Stop an actor speaking + SoundPtr playSound(const std::string& soundId, float volume, float pitch, int mode=Play_Normal); ///< Play a sound, independently of 3D-position diff --git a/apps/openmw/mwworld/actionalchemy.cpp b/apps/openmw/mwworld/actionalchemy.cpp new file mode 100644 index 0000000000..a2f3bd1e4e --- /dev/null +++ b/apps/openmw/mwworld/actionalchemy.cpp @@ -0,0 +1,13 @@ +#include "actionalchemy.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwgui/window_manager.hpp" + +namespace MWWorld +{ + void ActionAlchemy::execute() + { + MWBase::Environment::get().getWindowManager()->popGuiMode(); + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Alchemy); + } +} diff --git a/apps/openmw/mwworld/actionalchemy.hpp b/apps/openmw/mwworld/actionalchemy.hpp new file mode 100644 index 0000000000..739de419b4 --- /dev/null +++ b/apps/openmw/mwworld/actionalchemy.hpp @@ -0,0 +1,15 @@ +#ifndef GAME_MWWORLD_ACTIONALCHEMY_H +#define GAME_MWWORLD_ACTIONALCHEMY_H + +#include "action.hpp" + +namespace MWWorld +{ + class ActionAlchemy : public Action + { + public: + virtual void execute (); + }; +} + +#endif diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp new file mode 100644 index 0000000000..f3bb256fd8 --- /dev/null +++ b/apps/openmw/mwworld/actionequip.cpp @@ -0,0 +1,54 @@ +#include "actionequip.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwworld/world.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/player.hpp" + +namespace MWWorld +{ + ActionEquip::ActionEquip (const MWWorld::Ptr& object) : mObject (object) + { + } + + void ActionEquip::execute () + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::InventoryStore& invStore = static_cast(MWWorld::Class::get(player).getContainerStore(player)); + + // slots that this item can be equipped in + std::pair, bool> slots = MWWorld::Class::get(mObject).getEquipmentSlots(mObject); + + // retrieve ContainerStoreIterator to the item + MWWorld::ContainerStoreIterator it = invStore.begin(); + for (; it != invStore.end(); ++it) + { + if (*it == mObject) + { + break; + } + } + + assert(it != invStore.end()); + + // equip the item in the first free slot + for (std::vector::const_iterator slot=slots.first.begin(); + slot!=slots.first.end(); ++slot) + { + // if all slots are occupied, replace the last slot + if (slot == --slots.first.end()) + { + invStore.equip(*slot, it); + break; + } + + if (invStore.getSlot(*slot) == invStore.end()) + { + // slot is not occupied + invStore.equip(*slot, it); + break; + } + } + } +} + diff --git a/apps/openmw/mwworld/actionequip.hpp b/apps/openmw/mwworld/actionequip.hpp new file mode 100644 index 0000000000..6cf3640f8b --- /dev/null +++ b/apps/openmw/mwworld/actionequip.hpp @@ -0,0 +1,21 @@ +#ifndef GAME_MWWORLD_ACTIONEQUIP_H +#define GAME_MWWORLD_ACTIONEQUIP_H + +#include "action.hpp" +#include "ptr.hpp" + +namespace MWWorld +{ + class ActionEquip : public Action + { + Ptr mObject; + + public: + /// @param item to equip + ActionEquip (const Ptr& object); + + virtual void execute (); + }; +} + +#endif diff --git a/apps/openmw/mwworld/actionopen.cpp b/apps/openmw/mwworld/actionopen.cpp new file mode 100644 index 0000000000..dd36487dc9 --- /dev/null +++ b/apps/openmw/mwworld/actionopen.cpp @@ -0,0 +1,25 @@ +#include "actionopen.hpp" + +#include "../mwbase/environment.hpp" +#include "class.hpp" +#include "world.hpp" +#include "containerstore.hpp" +#include "../mwclass/container.hpp" +#include "../mwgui/window_manager.hpp" +#include "../mwgui/container.hpp" + +namespace MWWorld +{ + ActionOpen::ActionOpen (const MWWorld::Ptr& container) : mContainer (container) { + mContainer = container; + } + + void ActionOpen::execute () + { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return; + + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container); + MWBase::Environment::get().getWindowManager()->getContainerWindow()->open(mContainer); + } +} diff --git a/apps/openmw/mwworld/actionopen.hpp b/apps/openmw/mwworld/actionopen.hpp new file mode 100644 index 0000000000..eff26c78c1 --- /dev/null +++ b/apps/openmw/mwworld/actionopen.hpp @@ -0,0 +1,22 @@ + +#ifndef GAME_MWWORLD_ACTIONOPEN_H +#define GAME_MWWORLD_ACTIONOPEN_H + +#include "action.hpp" +#include "ptr.hpp" + + +namespace MWWorld +{ + class ActionOpen : public Action + { + Ptr mContainer; + + public: + ActionOpen (const Ptr& container); + ///< \param The Container the Player has activated. + virtual void execute (); + }; +} + +#endif // ACTIONOPEN_H diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp new file mode 100644 index 0000000000..34e95cbd07 --- /dev/null +++ b/apps/openmw/mwworld/actionread.cpp @@ -0,0 +1,31 @@ +#include "actionread.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwgui/window_manager.hpp" +#include "../mwgui/bookwindow.hpp" +#include "../mwgui/scrollwindow.hpp" + +namespace MWWorld +{ + ActionRead::ActionRead (const MWWorld::Ptr& object) : mObject (object) + { + } + + void ActionRead::execute () + { + ESMS::LiveCellRef *ref = + mObject.get(); + + if (ref->base->data.isScroll) + { + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Scroll); + MWBase::Environment::get().getWindowManager()->getScrollWindow()->open(mObject); + } + else + { + MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Book); + MWBase::Environment::get().getWindowManager()->getBookWindow()->open(mObject); + } + } +} + diff --git a/apps/openmw/mwworld/actionread.hpp b/apps/openmw/mwworld/actionread.hpp new file mode 100644 index 0000000000..a4b495f794 --- /dev/null +++ b/apps/openmw/mwworld/actionread.hpp @@ -0,0 +1,21 @@ +#ifndef GAME_MWWORLD_ACTIONREAD_H +#define GAME_MWWORLD_ACTIONREAD_H + +#include "action.hpp" +#include "ptr.hpp" + +namespace MWWorld +{ + class ActionRead : public Action + { + Ptr mObject; // book or scroll to read + + public: + /// @param book or scroll to read + ActionRead (const Ptr& object); + + virtual void execute (); + }; +} + +#endif // ACTIONOPEN_H diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp index b1e2e1fc3d..9cff42812b 100644 --- a/apps/openmw/mwworld/actiontake.cpp +++ b/apps/openmw/mwworld/actiontake.cpp @@ -2,6 +2,7 @@ #include "actiontake.hpp" #include "../mwbase/environment.hpp" +#include "../mwgui/window_manager.hpp" #include "class.hpp" #include "world.hpp" @@ -13,12 +14,16 @@ namespace MWWorld void ActionTake::execute() { + if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory)) + return; + // insert into player's inventory MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPtr ("player", true); MWWorld::Class::get (player).getContainerStore (player).add (mObject); - // remove from world - MWBase::Environment::get().getWorld()->deleteObject (mObject); + // remove from world, if the item is currently in the world (it could also be in a container) + if (mObject.isInCell()) + MWBase::Environment::get().getWorld()->deleteObject (mObject); } } diff --git a/apps/openmw/mwworld/actiontalk.cpp b/apps/openmw/mwworld/actiontalk.cpp index b33b788323..a0f9d8c4c6 100644 --- a/apps/openmw/mwworld/actiontalk.cpp +++ b/apps/openmw/mwworld/actiontalk.cpp @@ -2,7 +2,7 @@ #include "actiontalk.hpp" #include "../mwbase/environment.hpp" - +#include "../mwgui/window_manager.hpp" #include "../mwdialogue/dialoguemanager.hpp" namespace MWWorld diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 8c657a5c83..b6d3e38ce7 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -78,8 +78,6 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) std::map, Ptr::CellStore>::iterator result = mExteriors.find (std::make_pair (x, y)); - bool fill = false; - if (result==mExteriors.end()) { const ESM::Cell *cell = mStore.cells.searchExt (x, y); @@ -100,15 +98,13 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) result = mExteriors.insert (std::make_pair ( std::make_pair (x, y), Ptr::CellStore (cell))).first; - - fill = true; } if (result->second.mState!=Ptr::CellStore::State_Loaded) + { result->second.load (mStore, mReader); - - if (fill) fillContainers (result->second); + } return &result->second; } @@ -117,22 +113,18 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getInterior (const std::string& name) { std::map::iterator result = mInteriors.find (name); - bool fill = false; - if (result==mInteriors.end()) { const ESM::Cell *cell = mStore.cells.findInt (name); result = mInteriors.insert (std::make_pair (name, Ptr::CellStore (cell))).first; - - fill = true; } if (result->second.mState!=Ptr::CellStore::State_Loaded) + { result->second.load (mStore, mReader); - - if (fill) fillContainers (result->second); + } return &result->second; } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index c53ebcfd04..fe39406fe1 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -32,16 +32,6 @@ namespace MWWorld } - void Class::enable (const Ptr& ptr) const - { - - } - - void Class::disable (const Ptr& ptr) const - { - - } - MWMechanics::CreatureStats& Class::getCreatureStats (const Ptr& ptr) const { throw std::runtime_error ("class does not have creature stats"); @@ -142,6 +132,16 @@ namespace MWWorld throw std::logic_error ("value not supported by this class"); } + float Class::getCapacity (const MWWorld::Ptr& ptr) const + { + throw std::runtime_error ("capacity not supported by this class"); + } + + float Class::getEncumbrance (const MWWorld::Ptr& ptr) const + { + throw std::runtime_error ("encumbrance not supported by class"); + } + const Class& Class::get (const std::string& key) { std::map >::const_iterator iter = sClasses.find (key); @@ -171,4 +171,25 @@ namespace MWWorld { throw std::runtime_error ("class does not have an down sound"); } + + + std::string Class::getInventoryIcon (const MWWorld::Ptr& ptr) const + { + throw std::runtime_error ("class does not have any inventory icon"); + } + + MWGui::ToolTipInfo Class::getToolTipInfo (const Ptr& ptr) const + { + throw std::runtime_error ("class does not have a tool tip"); + } + + bool Class::hasToolTip (const Ptr& ptr) const + { + return false; + } + + std::string Class::getEnchantment (const Ptr& ptr) const + { + return ""; + } } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 622d9eeaee..46781d5160 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -12,6 +12,7 @@ #include "physicssystem.hpp" #include "../mwrender/renderinginterface.hpp" +#include "../mwgui/tooltips.hpp" namespace Ogre { @@ -67,16 +68,6 @@ namespace MWWorld virtual void insertObject(const Ptr& ptr, MWWorld::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). - virtual void enable (const Ptr& ptr) const; - ///< Enable reference; only does the non-rendering part (default implementation: ignore) - /// \attention This is not the same as the script instruction with the same name. References - /// should only be enabled while in an active cell. - - virtual void disable (const Ptr& ptr) const; - ///< Enable reference; only does the non-rendering part (default implementation: ignore) - /// \attention This is not the same as the script instruction with the same name. References - /// should only be enabled while in an active cell. - virtual std::string getName (const Ptr& ptr) const = 0; ///< \return name (the one that is to be presented to the user; not the internal one); /// can return an empty string. @@ -85,6 +76,12 @@ namespace MWWorld ///< Return creature stats or throw an exception, if class does not have creature stats /// (default implementation: throw an exceoption) + virtual bool hasToolTip (const Ptr& ptr) const; + ///< @return true if this object has a tooltip when focused (default implementation: false) + + virtual MWGui::ToolTipInfo getToolTipInfo (const Ptr& ptr) const; + ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. + virtual MWMechanics::NpcStats& getNpcStats (const Ptr& ptr) const; ///< Return NPC stats or throw an exception, if class does not have NPC stats /// (default implementation: throw an exceoption) @@ -129,7 +126,7 @@ namespace MWWorld ///< Set or unset a stance. virtual bool getStance (const Ptr& ptr, Stance stance, bool ignoreForce = false) const; - ////< Check if a stance is active or not. + ///< Check if a stance is active or not. virtual float getSpeed (const Ptr& ptr) const; ///< Return movement speed. @@ -157,6 +154,16 @@ namespace MWWorld ///< Return trade value of the object. Throws an exception, if the object can't be traded. /// (default implementation: throws an exception) + virtual float getCapacity (const MWWorld::Ptr& ptr) const; + ///< Return total weight that fits into the object. Throws an exception, if the object can't + /// hold other objects. + /// (default implementation: throws an exception) + + virtual float getEncumbrance (const MWWorld::Ptr& ptr) const; + ///< Returns total weight of objects inside this object (including modifications from magic + /// effects). Throws an exception, if the object can't hold other objects. + /// (default implementation: throws an exception) + static const Class& get (const std::string& key); ///< If there is no class for this \a key, an exception is thrown. @@ -172,6 +179,13 @@ namespace MWWorld virtual std::string getDownSoundId (const Ptr& ptr) const; ///< Return the down sound ID of \a ptr or throw an exception, if class does not support ID retrieval /// (default implementation: throw an exception) + + virtual std::string getInventoryIcon (const MWWorld::Ptr& ptr) const; + ///< Return name of inventory icon. + + virtual std::string getEnchantment (const MWWorld::Ptr& ptr) const; + ///< @return the enchantment ID if the object is enchanted, otherwise an empty string + /// (default implementation: return empty string) }; } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 2800b6f3c1..3304d0e6d0 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -5,10 +5,16 @@ #include #include +#include + #include +#include "../mwbase/environment.hpp" +#include "../mwworld/world.hpp" + #include "manualref.hpp" #include "refdata.hpp" +#include "class.hpp" namespace { @@ -28,6 +34,12 @@ namespace return sum; } + + bool compare_string_ci(std::string str1, std::string str2) + { + boost::algorithm::to_lower(str1); + return str1 == str2; + } } MWWorld::ContainerStore::ContainerStore() : mStateId (0), mCachedWeight (0), mWeightUpToDate (false) {} @@ -44,27 +56,94 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end() return ContainerStoreIterator (this); } -void MWWorld::ContainerStore::add (const Ptr& ptr) +bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) { - /// \todo implement item stacking + /// \todo add current weapon/armor health, remaining lockpick/repair uses, current enchantment charge here as soon as they are implemented + if ( ptr1.mCellRef->refID == ptr2.mCellRef->refID + && MWWorld::Class::get(ptr1).getScript(ptr1) == "" // item with a script never stacks + && MWWorld::Class::get(ptr1).getEnchantment(ptr1) == "" // item with enchantment never stacks (we could revisit this later, but for now it makes selecting items in the spell window much easier) + && ptr1.mCellRef->owner == ptr2.mCellRef->owner + && ptr1.mCellRef->soul == ptr2.mCellRef->soul + && ptr1.mCellRef->charge == ptr2.mCellRef->charge) + return true; - switch (getType (ptr)) + return false; +} + +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& ptr) +{ + int type = getType(ptr); + + // gold needs special handling: when it is inserted into a container, the base object automatically becomes Gold_001 + // this ensures that gold piles of different sizes stack with each other (also, several scripts rely on Gold_001 for detecting player gold) + if (MWWorld::Class::get(ptr).getName(ptr) == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) { - case Type_Potion: potions.list.push_back (*ptr.get()); break; - case Type_Apparatus: appas.list.push_back (*ptr.get()); break; - case Type_Armor: armors.list.push_back (*ptr.get()); break; - case Type_Book: books.list.push_back (*ptr.get()); break; - case Type_Clothing: clothes.list.push_back (*ptr.get()); break; - case Type_Ingredient: ingreds.list.push_back (*ptr.get()); break; - case Type_Light: lights.list.push_back (*ptr.get()); break; - case Type_Lockpick: lockpicks.list.push_back (*ptr.get()); break; - case Type_Miscellaneous: miscItems.list.push_back (*ptr.get()); break; - case Type_Probe: probes.list.push_back (*ptr.get()); break; - case Type_Repair: repairs.list.push_back (*ptr.get()); break; - case Type_Weapon: weapons.list.push_back (*ptr.get()); break; + ESMS::LiveCellRef *gold = + ptr.get(); + + if (compare_string_ci(gold->ref.refID, "gold_001") + || compare_string_ci(gold->ref.refID, "gold_005") + || compare_string_ci(gold->ref.refID, "gold_010") + || compare_string_ci(gold->ref.refID, "gold_025") + || compare_string_ci(gold->ref.refID, "gold_100")) + { + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), "Gold_001"); + + int count = (ptr.getRefData().getCount() == 1) ? gold->base->data.value : ptr.getRefData().getCount(); + ref.getPtr().getRefData().setCount(count); + for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) + { + if (compare_string_ci((*iter).get()->ref.refID, "gold_001")) + { + (*iter).getRefData().setCount( (*iter).getRefData().getCount() + count); + flagAsModified(); + return iter; + } + } + + return addImpl(ref.getPtr()); + } + } + + // determine whether to stack or not + for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) + { + if (stacks(*iter, ptr)) + { + // stack + iter->getRefData().setCount( iter->getRefData().getCount() + ptr.getRefData().getCount() ); + + flagAsModified(); + return iter; + } + } + + // if we got here, this means no stacking + return addImpl(ptr); +} + +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImpl (const Ptr& ptr) +{ + ContainerStoreIterator it = begin(); + + switch (getType(ptr)) + { + case Type_Potion: potions.list.push_back (*ptr.get()); it = ContainerStoreIterator(this, --potions.list.end()); break; + case Type_Apparatus: appas.list.push_back (*ptr.get()); it = ContainerStoreIterator(this, --appas.list.end()); break; + case Type_Armor: armors.list.push_back (*ptr.get()); it = ContainerStoreIterator(this, --armors.list.end()); break; + case Type_Book: books.list.push_back (*ptr.get()); it = ContainerStoreIterator(this, --books.list.end()); break; + case Type_Clothing: clothes.list.push_back (*ptr.get()); it = ContainerStoreIterator(this, --clothes.list.end()); break; + case Type_Ingredient: ingreds.list.push_back (*ptr.get()); it = ContainerStoreIterator(this, --ingreds.list.end()); break; + case Type_Light: lights.list.push_back (*ptr.get()); it = ContainerStoreIterator(this, --lights.list.end()); break; + case Type_Lockpick: lockpicks.list.push_back (*ptr.get()); it = ContainerStoreIterator(this, --lockpicks.list.end()); break; + case Type_Miscellaneous: miscItems.list.push_back (*ptr.get()); it = ContainerStoreIterator(this, --miscItems.list.end()); break; + case Type_Probe: probes.list.push_back (*ptr.get()); it = ContainerStoreIterator(this, --probes.list.end()); break; + case Type_Repair: repairs.list.push_back (*ptr.get()); it = ContainerStoreIterator(this, --repairs.list.end()); break; + case Type_Weapon: weapons.list.push_back (*ptr.get()); it = ContainerStoreIterator(this, --weapons.list.end()); break; } flagAsModified(); + return it; } void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const ESMS::ESMStore& store) @@ -80,7 +159,7 @@ void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const ESMS: continue; } - ref.getPtr().getRefData().setCount (iter->count); + ref.getPtr().getRefData().setCount (std::abs(iter->count)); /// \todo implement item restocking (indicated by negative count) add (ref.getPtr()); } @@ -195,8 +274,38 @@ MWWorld::ContainerStoreIterator::ContainerStoreIterator (int mask, ContainerStor : mType (0), mMask (mask), mContainer (container) { nextType(); + + if (mType==-1 || (**this).getRefData().getCount()) + return; + + ++*this; } +MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator iterator) + : mType(MWWorld::ContainerStore::Type_Potion), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mPotion(iterator){} +MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator iterator) + : mType(MWWorld::ContainerStore::Type_Apparatus), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mApparatus(iterator){} +MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator iterator) + : mType(MWWorld::ContainerStore::Type_Armor), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mArmor(iterator){} +MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator iterator) + : mType(MWWorld::ContainerStore::Type_Book), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mBook(iterator){} +MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator iterator) + : mType(MWWorld::ContainerStore::Type_Clothing), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mClothing(iterator){} +MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator iterator) + : mType(MWWorld::ContainerStore::Type_Ingredient), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mIngredient(iterator){} +MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator iterator) + : mType(MWWorld::ContainerStore::Type_Light), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mLight(iterator){} +MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator iterator) + : mType(MWWorld::ContainerStore::Type_Lockpick), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mLockpick(iterator){} +MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator iterator) + : mType(MWWorld::ContainerStore::Type_Miscellaneous), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mMiscellaneous(iterator){} +MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator iterator) + : mType(MWWorld::ContainerStore::Type_Probe), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mProbe(iterator){} +MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator iterator) + : mType(MWWorld::ContainerStore::Type_Repair), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mRepair(iterator){} +MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator iterator) + : mType(MWWorld::ContainerStore::Type_Weapon), mMask(MWWorld::ContainerStore::Type_All), mContainer(container), mWeapon(iterator){} + void MWWorld::ContainerStoreIterator::incType() { if (mType==0) @@ -216,7 +325,7 @@ void MWWorld::ContainerStoreIterator::nextType() { incType(); - if (mType & mMask) + if ((mType & mMask) && mType>0) if (resetIterator()) break; } diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index da5424fe08..15b553f9dd 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -66,13 +66,25 @@ namespace MWWorld ContainerStoreIterator end(); - void add (const Ptr& ptr); - ///< Add the item pointed to by \a ptr to this container. + ContainerStoreIterator add (const Ptr& ptr); + ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) /// /// \note The item pointed to is not required to exist beyond this function call. /// /// \attention Do not add items to an existing stack by increasing the count instead of /// calling this function! + /// + /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. + + protected: + ContainerStoreIterator addImpl (const Ptr& ptr); + ///< Add the item to this container (no stacking) + + virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); + ///< @return true if the two specified objects can stack with each other + /// @note ptr1 is the item that is already in this container + + public: void fill (const ESM::InventoryList& items, const ESMS::ESMStore& store); ///< Insert items into *this. @@ -80,7 +92,7 @@ namespace MWWorld void clear(); ///< Empty container. - void flagAsModified(); + virtual void flagAsModified(); ///< \attention This function is internal to the world model and should not be called from /// outside. @@ -131,6 +143,20 @@ namespace MWWorld ContainerStoreIterator (int mask, ContainerStore *container); ///< Begin-iterator + // construct iterator using a CellRefList iterator + ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator); + ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator); + ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator); + ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator); + ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator); + ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator); + ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator); + ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator); + ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator); + ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator); + ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator); + ContainerStoreIterator (ContainerStore *container, ESMS::CellRefList::List::iterator); + void incType(); void nextType(); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 576f2371f9..6df73004be 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -4,6 +4,12 @@ #include #include +#include + +#include "../mwbase/environment.hpp" + +#include "../mwworld/world.hpp" + #include "../mwmechanics/npcstats.hpp" #include "class.hpp" @@ -32,19 +38,26 @@ void MWWorld::InventoryStore::initSlots (TSlots& slots) slots.push_back (end()); } -MWWorld::InventoryStore::InventoryStore() +MWWorld::InventoryStore::InventoryStore() : mMagicEffectsUpToDate (false) + , mSelectedEnchantItem(end()) { initSlots (mSlots); } MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) : ContainerStore (store) + , mSelectedEnchantItem(end()) { + mMagicEffects = store.mMagicEffects; + mMagicEffectsUpToDate = store.mMagicEffectsUpToDate; + mSelectedEnchantItem = store.mSelectedEnchantItem; copySlots (store); } MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store) { + mMagicEffects = store.mMagicEffects; + mMagicEffectsUpToDate = store.mMagicEffectsUpToDate; ContainerStore::operator= (store); mSlots.clear(); copySlots (store); @@ -59,17 +72,38 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite if (iterator.getContainerStore()!=this) throw std::runtime_error ("attempt to equip an item that is not in the inventory"); + std::pair, bool> slots; if (iterator!=end()) { - std::pair, bool> slots = Class::get (*iterator).getEquipmentSlots (*iterator); + slots = Class::get (*iterator).getEquipmentSlots (*iterator); if (std::find (slots.first.begin(), slots.first.end(), slot)==slots.first.end()) throw std::runtime_error ("invalid slot"); } - /// \todo restack item previously in this slot (if required) + // restack item previously in this slot (if required) + if (mSlots[slot] != end()) + { + for (MWWorld::ContainerStoreIterator iter (begin()); iter!=end(); ++iter) + { + if (stacks(*iter, *mSlots[slot])) + { + iter->getRefData().setCount( iter->getRefData().getCount() + mSlots[slot]->getRefData().getCount() ); + mSlots[slot]->getRefData().setCount(0); + break; + } + } + } - /// \todo unstack item pointed to by iterator if required) + // unstack item pointed to by iterator if required + if (iterator!=end() && !slots.second && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped + { + // add the item again with a count of count-1, then set the count of the original (that will be equipped) to 1 + int count = iterator->getRefData().getCount(); + iterator->getRefData().setCount(count-1); + addImpl(*iterator); + iterator->getRefData().setCount(1); + } mSlots[slot] = iterator; @@ -147,7 +181,18 @@ void MWWorld::InventoryStore::autoEquip (const MWMechanics::NpcStats& stats) } } - /// \todo unstack, if reqquired (itemsSlots.second) + if (!itemsSlots.second) // if itemsSlots.second is true, item can stay stacked when equipped + { + // unstack item pointed to by iterator if required + if (iter->getRefData().getCount() > 1) + { + // add the item again with a count of count-1, then set the count of the original (that will be equipped) to 1 + int count = iter->getRefData().getCount(); + iter->getRefData().setCount(count-1); + addImpl(*iter); + iter->getRefData().setCount(1); + } + } slots[*iter2] = iter; break; @@ -168,3 +213,63 @@ void MWWorld::InventoryStore::autoEquip (const MWMechanics::NpcStats& stats) flagAsModified(); } } + +const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() +{ + if (!mMagicEffectsUpToDate) + { + mMagicEffects = MWMechanics::MagicEffects(); + + for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) + if (*iter!=end()) + { + std::string enchantmentId = MWWorld::Class::get (**iter).getEnchantment (**iter); + + if (!enchantmentId.empty()) + { + const ESM::Enchantment& enchantment = + *MWBase::Environment::get().getWorld()->getStore().enchants.find (enchantmentId); + + if (enchantment.data.type==ESM::Enchantment::ConstantEffect) + mMagicEffects.add (enchantment.effects); + } + } + + mMagicEffectsUpToDate = true; + } + + return mMagicEffects; +} + +void MWWorld::InventoryStore::flagAsModified() +{ + ContainerStore::flagAsModified(); + mMagicEffectsUpToDate = false; +} + +bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2) +{ + bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2); + if (!canStack) + return false; + + // don't stack if the item being checked against is currently equipped. + for (TSlots::const_iterator iter (mSlots.begin()); + iter!=mSlots.end(); ++iter) + { + if (*iter != end() && ptr1 == **iter) + return false; + } + + return true; +} + +void MWWorld::InventoryStore::setSelectedEnchantItem(const ContainerStoreIterator& iterator) +{ + mSelectedEnchantItem = iterator; +} + +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem() +{ + return mSelectedEnchantItem; +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 5eeaf570d0..45b3cab268 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -3,6 +3,8 @@ #include "containerstore.hpp" +#include "../mwmechanics/magiceffects.hpp" + namespace MWMechanics { struct NpcStats; @@ -41,10 +43,16 @@ namespace MWWorld private: + mutable MWMechanics::MagicEffects mMagicEffects; + mutable bool mMagicEffectsUpToDate; + typedef std::vector TSlots; mutable TSlots mSlots; + // selected magic item (for using enchantments of type "Cast once" or "Cast when used") + ContainerStoreIterator mSelectedEnchantItem; + void copySlots (const InventoryStore& store); void initSlots (TSlots& slots); @@ -58,12 +66,36 @@ namespace MWWorld InventoryStore& operator= (const InventoryStore& store); void equip (int slot, const ContainerStoreIterator& iterator); - ///< \note \a iteartor can be an end-iterator + ///< \note \a iterator can be an end-iterator + + void setSelectedEnchantItem(const ContainerStoreIterator& iterator); + ///< set the selected magic item (for using enchantments of type "Cast once" or "Cast when used") + /// \note to unset the selected item, call this method with end() iterator + + ContainerStoreIterator getSelectedEnchantItem(); + ///< @return selected magic item (for using enchantments of type "Cast once" or "Cast when used") + /// \note if no item selected, return end() iterator ContainerStoreIterator getSlot (int slot); void autoEquip (const MWMechanics::NpcStats& stats); ///< Auto equip items according to stats and item value. + + const MWMechanics::MagicEffects& getMagicEffects(); + ///< Return magic effects from worn items. + /// + /// \todo make this const again, after the constness of Ptrs and iterators has been addressed. + + virtual void flagAsModified(); + ///< \attention This function is internal to the world model and should not be called from + /// outside. + + protected: + + virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); + ///< @return true if the two specified objects can stack with each other + /// @note ptr1 is the item that is already in this container + }; } diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index ed96f47d62..7c1ff31b40 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -21,23 +21,25 @@ namespace MWWorld PhysicsSystem::PhysicsSystem(OEngine::Render::OgreRenderer &_rend) : mRender(_rend), mEngine(0), mFreeFly (true) { - + + playerphysics = new playerMove; // Create physics. shapeLoader is deleted by the physic engine NifBullet::ManualBulletShapeLoader* shapeLoader = new NifBullet::ManualBulletShapeLoader(); mEngine = new OEngine::Physic::PhysicEngine(shapeLoader); - + playerphysics->mEngine = mEngine; } PhysicsSystem::~PhysicsSystem() { delete mEngine; - + delete playerphysics; + } OEngine::Physic::PhysicEngine* PhysicsSystem::getEngine() { return mEngine; } - + std::pair PhysicsSystem::getFacedHandle (MWWorld::World& world) { std::string handle = ""; @@ -66,44 +68,98 @@ namespace MWWorld return mEngine->rayTest2(from,to); } + std::vector < std::pair > PhysicsSystem::getFacedObjects (float mouseX, float mouseY) + { + Ray ray = mRender.getCamera()->getCameraToViewportRay(mouseX, mouseY); + Ogre::Vector3 from = ray.getOrigin(); + Ogre::Vector3 to = ray.getPoint(500); /// \todo make this distance (ray length) configurable + + btVector3 _from, _to; + // OGRE to MW coordinates + _from = btVector3(from.x, -from.z, from.y); + _to = btVector3(to.x, -to.z, to.y); + + return mEngine->rayTest2(_from,_to); + } + + void PhysicsSystem::setCurrentWater(bool hasWater, int waterHeight) + { + playerphysics->hasWater = hasWater; + if(hasWater){ + playerphysics->waterHeight = waterHeight; + } + + } + btVector3 PhysicsSystem::getRayPoint(float extent) { //get a ray pointing to the center of the viewport Ray centerRay = mRender.getCamera()->getCameraToViewportRay( mRender.getViewport()->getWidth()/2, mRender.getViewport()->getHeight()/2); - btVector3 result(centerRay.getPoint(500*extent).x,-centerRay.getPoint(500*extent).z,centerRay.getPoint(500*extent).y); + btVector3 result(centerRay.getPoint(500*extent).x,-centerRay.getPoint(500*extent).z,centerRay.getPoint(500*extent).y); /// \todo make this distance (ray length) configurable return result; } - + + btVector3 PhysicsSystem::getRayPoint(float extent, float mouseX, float mouseY) + { + //get a ray pointing to the center of the viewport + Ray centerRay = mRender.getCamera()->getCameraToViewportRay(mouseX, mouseY); + btVector3 result(centerRay.getPoint(500*extent).x,-centerRay.getPoint(500*extent).z,centerRay.getPoint(500*extent).y); /// \todo make this distance (ray length) configurable + return result; + } + bool PhysicsSystem::castRay(const Vector3& from, const Vector3& to) { btVector3 _from, _to; _from = btVector3(from.x, from.y, from.z); _to = btVector3(to.x, to.y, to.z); - + std::pair result = mEngine->rayTest(_from, _to); - + return !(result.first == ""); } + std::pair PhysicsSystem::castRay(float mouseX, float mouseY) + { + Ogre::Ray ray = mRender.getCamera()->getCameraToViewportRay( + mouseX, + mouseY); + Ogre::Vector3 from = ray.getOrigin(); + Ogre::Vector3 to = ray.getPoint(200); /// \todo make this distance (ray length) configurable - std::vector< std::pair > PhysicsSystem::doPhysics (float duration, - const std::vector >& actors) + btVector3 _from, _to; + // OGRE to MW coordinates + _from = btVector3(from.x, -from.z, from.y); + _to = btVector3(to.x, -to.z, to.y); + + std::pair result = mEngine->rayTest(_from, _to); + + if (result.first == "") + return std::make_pair(false, Ogre::Vector3()); + else + { + return std::make_pair(true, ray.getPoint(200*result.second)); /// \todo make this distance (ray length) configurable + } + } + + void PhysicsSystem::doPhysics(float dt, const std::vector >& actors) { //set the DebugRenderingMode. To disable it,set it to 0 //eng->setDebugRenderingMode(1); - - + //set the walkdirection to 0 (no movement) for every actor) for(std::map::iterator it = mEngine->PhysicActorMap.begin(); it != mEngine->PhysicActorMap.end();it++) { OEngine::Physic::PhysicActor* act = it->second; act->setWalkDirection(btVector3(0,0,0)); } - - - + playerMove::playercmd& pm_ref = playerphysics->cmd; + + pm_ref.rightmove = 0; + pm_ref.forwardmove = 0; + pm_ref.upmove = 0; + //playerphysics->ps.move_type = PM_NOCLIP; for (std::vector >::const_iterator iter (actors.begin()); iter!=actors.end(); ++iter) @@ -118,21 +174,24 @@ namespace MWWorld Ogre::Node* yawNode = sceneNode->getChildIterator().getNext(); Ogre::Node* pitchNode = yawNode->getChildIterator().getNext(); Ogre::Quaternion yawQuat = yawNode->getOrientation(); - Ogre::Quaternion pitchQuat = pitchNode->getOrientation(); - Ogre::Quaternion both = yawQuat * pitchQuat; - - - //playerphysics->ps.viewangles.z = both.getPitch().valueDegrees(); - - + Ogre::Quaternion pitchQuat = pitchNode->getOrientation(); + + // unused + //Ogre::Quaternion both = yawQuat * pitchQuat; + + playerphysics->ps.viewangles.x = pitchQuat.getPitch().valueDegrees(); + playerphysics->ps.viewangles.z = 0; + playerphysics->ps.viewangles.y = yawQuat.getYaw().valueDegrees() *-1 + 90; + if(mFreeFly) { - Ogre::Vector3 dir1(iter->second.x,iter->second.z,-iter->second.y); - - - - + + pm_ref.rightmove = -dir1.x; + pm_ref.forwardmove = dir1.z; + pm_ref.upmove = dir1.y; + + //std::cout << "Current angle" << yawQuat.getYaw().valueDegrees() - 90<< "\n"; //playerphysics->ps.viewangles.x = pitchQuat.getPitch().valueDegrees(); //std::cout << "Pitch: " << yawQuat.getPitch() << "Yaw:" << yawQuat.getYaw() << "Roll: " << yawQuat.getRoll() << "\n"; @@ -140,31 +199,65 @@ namespace MWWorld } else { + Ogre::Quaternion quat = yawNode->getOrientation(); Ogre::Vector3 dir1(iter->second.x,iter->second.z,-iter->second.y); - - + + pm_ref.rightmove = -dir1.x; + pm_ref.forwardmove = dir1.z; + pm_ref.upmove = dir1.y; + + + dir = 0.025*(quat*dir1); } - + //set the walk direction act->setWalkDirection(btVector3(dir.x,-dir.z,dir.y)); } - mEngine->stepSimulation(duration); - + mEngine->stepSimulation(dt); + } + + std::vector< std::pair > PhysicsSystem::doPhysicsFixed ( + const std::vector >& actors) + { + Pmove(playerphysics); + std::vector< std::pair > response; for(std::map::iterator it = mEngine->PhysicActorMap.begin(); it != mEngine->PhysicActorMap.end();it++) { btVector3 newPos = it->second->getPosition(); - + Ogre::Vector3 coord(newPos.x(), newPos.y(), newPos.z()); - + if(it->first == "player"){ + + coord = playerphysics->ps.origin; + //std::cout << "ZCoord: " << coord.z << "\n"; + //std::cout << "Coord" << coord << "\n"; + //coord = Ogre::Vector3(coord.x, coord.z, coord.y); //x, z, -y + + } + + response.push_back(std::pair(it->first, coord)); } - + return response; } + + void PhysicsSystem::addHeightField (float* heights, + int x, int y, float yoffset, + float triSize, float sqrtVerts) + { + mEngine->addHeightField(heights, x, y, yoffset, triSize, sqrtVerts); + } + + void PhysicsSystem::removeHeightField (int x, int y) + { + mEngine->removeHeightField(x, y); + } + void PhysicsSystem::addObject (const std::string& handle, const std::string& mesh, const Ogre::Quaternion& rotation, float scale, const Ogre::Vector3& position) { @@ -207,7 +300,14 @@ namespace MWWorld { // TODO very dirty hack to avoid crash during setup -> needs cleaning up to allow // start positions others than 0, 0, 0 - act->setPosition(btVector3(position.x,position.y,position.z)); + if (handle == "player") + { + playerphysics->ps.origin = position; + } + else + { + act->setPosition(btVector3(position.x,position.y,position.z)); + } } } @@ -228,6 +328,11 @@ namespace MWWorld bool PhysicsSystem::toggleCollisionMode() { + if(playerphysics->ps.move_type==PM_NOCLIP) + playerphysics->ps.move_type=PM_NORMAL; + + else + playerphysics->ps.move_type=PM_NOCLIP; for(std::map::iterator it = mEngine->PhysicActorMap.begin(); it != mEngine->PhysicActorMap.end();it++) { if (it->first=="player") @@ -258,7 +363,12 @@ namespace MWWorld } void PhysicsSystem::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string model){ + Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); + + // unused + //Ogre::Vector3 objPos = node->getPosition(); + addObject (node->getName(), model, node->getOrientation(), node->getScale().x, node->getPosition()); } diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index b88ff23b16..1a8bd87ae4 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -5,6 +5,7 @@ #include #include #include "ptr.hpp" +#include namespace MWWorld { @@ -15,8 +16,11 @@ namespace MWWorld PhysicsSystem (OEngine::Render::OgreRenderer &_rend); ~PhysicsSystem (); - std::vector< std::pair > doPhysics (float duration, - const std::vector >& actors); + void doPhysics(float duration, const std::vector >& actors); + ///< do physics with dt - Usage: first call doPhysics with frame dt, then call doPhysicsFixed as often as time steps have passed + + std::vector< std::pair > doPhysicsFixed (const std::vector >& actors); + ///< do physics with fixed timestep - Usage: first call doPhysics with frame dt, then call doPhysicsFixed as often as time steps have passed void addObject (const std::string& handle, const std::string& mesh, const Ogre::Quaternion& rotation, float scale, const Ogre::Vector3& position); @@ -24,6 +28,12 @@ namespace MWWorld void addActor (const std::string& handle, const std::string& mesh, const Ogre::Vector3& position); + void addHeightField (float* heights, + int x, int y, float yoffset, + float triSize, float sqrtVerts); + + void removeHeightField (int x, int y); + void removeObject (const std::string& handle); void moveObject (const std::string& handle, const Ogre::Vector3& position); @@ -37,23 +47,31 @@ namespace MWWorld std::pair getFacedHandle (MWWorld::World& world); btVector3 getRayPoint(float extent); + btVector3 getRayPoint(float extent, float mouseX, float mouseY); std::vector < std::pair > getFacedObjects (); + std::vector < std::pair > getFacedObjects (float mouseX, float mouseY); + // cast ray, return true if it hit something bool castRay(const Ogre::Vector3& from, const Ogre::Vector3& to); + std::pair castRay(float mouseX, float mouseY); + ///< cast ray from the mouse, return true if it hit something and the first result (in OGRE coordinates) + void insertObjectPhysics(const MWWorld::Ptr& ptr, std::string model); void insertActorPhysics(const MWWorld::Ptr&, std::string model); OEngine::Physic::PhysicEngine* getEngine(); + void setCurrentWater(bool hasWater, int waterHeight); + private: OEngine::Render::OgreRenderer &mRender; OEngine::Physic::PhysicEngine* mEngine; bool mFreeFly; - + playerMove* playerphysics; PhysicsSystem (const PhysicsSystem&); PhysicsSystem& operator= (const PhysicsSystem&); diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 5ed9aeaffc..91b030d1c8 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -40,6 +40,11 @@ namespace MWWorld mWorld.moveObject (getPlayer(), x, y, z); } + void Player::setRot(float x, float y, float z) + { + mRenderer->setRot(x, y, z); + } + void Player::setClass (const ESM::Class& class_) { ESM::Class *new_class = new ESM::Class (class_); @@ -85,6 +90,14 @@ namespace MWWorld MWWorld::Class::get (ptr).getMovementSettings (ptr).mForwardBackward = value; } + void Player::setUpDown(int value) + { + MWWorld::Ptr ptr = getPlayer(); + + + + MWWorld::Class::get (ptr).getMovementSettings (ptr).mUpDown = value; + } void Player::toggleRunning() { @@ -100,4 +113,5 @@ namespace MWWorld MWWorld::Ptr ptr = getPlayer(); return MWWorld::Class::get(ptr).getNpcStats(ptr).mDrawState; } + } diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 8dcd9fcc65..a5c5ff308b 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -42,6 +42,9 @@ namespace MWWorld /// Set the player position. Uses Morrowind coordinates. void setPos(float x, float y, float z); + /// Set where the player is looking at. Uses Morrowind (euler) angles + void setRot(float x, float y, float z); + void setCell (MWWorld::Ptr::CellStore *cellStore) { mCellStore = cellStore; @@ -104,7 +107,7 @@ namespace MWWorld return *mClass; } - bool getAutoMove() + bool getAutoMove() const { return mAutoMove; } @@ -116,6 +119,7 @@ namespace MWWorld void setLeftRight (int value); void setForwardBackward (int value); + void setUpDown(int value); void toggleRunning(); }; diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index d6e485f419..4cf3e98da4 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -77,6 +77,11 @@ namespace MWWorld return mCell; } + bool isInCell() const + { + return (mCell != 0); + } + void setContainerStore (ContainerStore *store); ///< Must not be called on references that are in a cell. diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 528f49c33e..e1c14b907e 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -123,7 +123,7 @@ namespace MWWorld void RefData::disable() { - mEnabled = true; + mEnabled = false; } ESM::Position& RefData::getPosition() diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 5e4556fc70..a47137d257 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -9,45 +9,48 @@ #include "../mwgui/window_manager.hpp" +#include "../mwworld/world.hpp" /// FIXME +#include "../mwworld/manualref.hpp" /// FIXME + #include "ptr.hpp" #include "player.hpp" #include "class.hpp" #include "cellfunctors.hpp" -namespace { - -template -void insertCellRefList(MWRender::RenderingManager& rendering, - T& cellRefList, ESMS::CellStore &cell, MWWorld::PhysicsSystem& physics) +namespace { - if (!cellRefList.list.empty()) + + template + void insertCellRefList(MWRender::RenderingManager& rendering, + T& cellRefList, ESMS::CellStore &cell, MWWorld::PhysicsSystem& physics) { - const MWWorld::Class& class_ = - MWWorld::Class::get (MWWorld::Ptr (&*cellRefList.list.begin(), &cell)); - - for (typename T::List::iterator it = cellRefList.list.begin(); - it != cellRefList.list.end(); it++) + if (!cellRefList.list.empty()) { - if (it->mData.getCount() || it->mData.isEnabled()) - { - MWWorld::Ptr ptr (&*it, &cell); + const MWWorld::Class& class_ = + MWWorld::Class::get (MWWorld::Ptr (&*cellRefList.list.begin(), &cell)); - try + for (typename T::List::iterator it = cellRefList.list.begin(); + it != cellRefList.list.end(); it++) + { + if (it->mData.getCount() || it->mData.isEnabled()) { - rendering.addObject(ptr); - class_.insertObject(ptr, physics); - class_.enable (ptr); - } - catch (const std::exception& e) - { - std::string error ("error during rendering: "); - std::cerr << error + e.what() << std::endl; + MWWorld::Ptr ptr (&*it, &cell); + + try + { + rendering.addObject(ptr); + class_.insertObject(ptr, physics); + } + catch (const std::exception& e) + { + std::string error ("error during rendering: "); + std::cerr << error + e.what() << std::endl; + } } } } } -} } @@ -75,11 +78,18 @@ namespace MWWorld // silence annoying g++ warning - for (std::vector::const_iterator iter (functor.mHandles.begin()); - iter!=functor.mHandles.end(); ++iter){ - Ogre::SceneNode* node = *iter; + for (std::vector::const_iterator iter2 (functor.mHandles.begin()); + iter2!=functor.mHandles.end(); ++iter2){ + Ogre::SceneNode* node = *iter2; mPhysics->removeObject (node->getName()); } + + if (!((*iter)->cell->data.flags & ESM::Cell::Interior)) + { + ESM::Land* land = mWorld->getStore().lands.search((*iter)->cell->data.gridX,(*iter)->cell->data.gridY); + if (land) + mPhysics->removeHeightField( (*iter)->cell->data.gridX, (*iter)->cell->data.gridY ); + } } mRendering.removeCell(*iter); @@ -103,25 +113,45 @@ namespace MWWorld std::pair result = mActiveCells.insert(cell); - if(result.second){ - insertCell(*cell); - mRendering.cellAdded(cell); - mRendering.configureAmbient(*cell); - mRendering.requestMap(cell); - mRendering.configureAmbient(*cell); - } + if(result.second) + { + insertCell(*cell); + mRendering.cellAdded (cell); + + float verts = ESM::Land::LAND_SIZE; + float worldsize = ESM::Land::REAL_SIZE; + + if (!(cell->cell->data.flags & ESM::Cell::Interior)) + { + ESM::Land* land = mWorld->getStore().lands.search(cell->cell->data.gridX,cell->cell->data.gridY); + if (land) + mPhysics->addHeightField (land->landData->heights, + cell->cell->data.gridX, cell->cell->data.gridY, + 0, ( worldsize/(verts-1) ), verts); + } + + mRendering.configureAmbient(*cell); + mRendering.requestMap(cell); + mRendering.configureAmbient(*cell); + + } } void Scene::playerCellChange (Ptr::CellStore *cell, const ESM::Position& position, bool adjustPlayerPos) { + bool hasWater = cell->cell->data.flags & cell->cell->HasWater; + mPhysics->setCurrentWater(hasWater, cell->cell->water); if (adjustPlayerPos) + { mWorld->getPlayer().setPos (position.pos[0], position.pos[1], position.pos[2]); + mWorld->getPlayer().setRot (position.rot[0], position.rot[1], position.rot[2]); + } mWorld->getPlayer().setCell (cell); - // TODO orientation + MWBase::Environment::get().getMechanicsManager()->addActor (mWorld->getPlayer().getPlayer()); MWBase::Environment::get().getMechanicsManager()->watchActor (mWorld->getPlayer().getPlayer()); @@ -232,6 +262,9 @@ namespace MWWorld void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { std::cout << "Changing to interior\n"; + + Ptr::CellStore *cell = mWorld->getInterior(cellName); + // remove active CellStoreCollection::iterator active = mActiveCells.begin(); @@ -242,11 +275,9 @@ namespace MWWorld // Load cell. std::cout << "cellName:" << cellName << std::endl; - Ptr::CellStore *cell = mWorld->getInterior(cellName); loadCell (cell); - // adjust player mCurrentCell = cell; playerCellChange (cell, position); @@ -281,30 +312,169 @@ namespace MWWorld mCellChanged = false; } -void Scene::insertCell(ESMS::CellStore &cell) -{ - // Loop through all references in the cell - insertCellRefList(mRendering, cell.activators, cell, *mPhysics); - insertCellRefList(mRendering, cell.potions, cell, *mPhysics); - insertCellRefList(mRendering, cell.appas, cell, *mPhysics); - insertCellRefList(mRendering, cell.armors, cell, *mPhysics); - insertCellRefList(mRendering, cell.books, cell, *mPhysics); - insertCellRefList(mRendering, cell.clothes, cell, *mPhysics); - insertCellRefList(mRendering, cell.containers, cell, *mPhysics); - insertCellRefList(mRendering, cell.creatures, cell, *mPhysics); - insertCellRefList(mRendering, cell.doors, cell, *mPhysics); - insertCellRefList(mRendering, cell.ingreds, cell, *mPhysics); - insertCellRefList(mRendering, cell.creatureLists, cell, *mPhysics); - insertCellRefList(mRendering, cell.itemLists, cell, *mPhysics); - insertCellRefList(mRendering, cell.lights, cell, *mPhysics); - insertCellRefList(mRendering, cell.lockpicks, cell, *mPhysics); - insertCellRefList(mRendering, cell.miscItems, cell, *mPhysics); - insertCellRefList(mRendering, cell.npcs, cell, *mPhysics); - insertCellRefList(mRendering, cell.probes, cell, *mPhysics); - insertCellRefList(mRendering, cell.repairs, cell, *mPhysics); - insertCellRefList(mRendering, cell.statics, cell, *mPhysics); - insertCellRefList(mRendering, cell.weapons, cell, *mPhysics); -} + void Scene::insertCell(ESMS::CellStore &cell) + { + // Loop through all references in the cell + insertCellRefList(mRendering, cell.activators, cell, *mPhysics); + insertCellRefList(mRendering, cell.potions, cell, *mPhysics); + insertCellRefList(mRendering, cell.appas, cell, *mPhysics); + insertCellRefList(mRendering, cell.armors, cell, *mPhysics); + insertCellRefList(mRendering, cell.books, cell, *mPhysics); + insertCellRefList(mRendering, cell.clothes, cell, *mPhysics); + insertCellRefList(mRendering, cell.containers, cell, *mPhysics); + insertCellRefList(mRendering, cell.creatures, cell, *mPhysics); + insertCellRefList(mRendering, cell.doors, cell, *mPhysics); + insertCellRefList(mRendering, cell.ingreds, cell, *mPhysics); + insertCellRefList(mRendering, cell.creatureLists, cell, *mPhysics); + insertCellRefList(mRendering, cell.itemLists, cell, *mPhysics); + insertCellRefList(mRendering, cell.lights, cell, *mPhysics); + insertCellRefList(mRendering, cell.lockpicks, cell, *mPhysics); + insertCellRefList(mRendering, cell.miscItems, cell, *mPhysics); + insertCellRefList(mRendering, cell.npcs, cell, *mPhysics); + insertCellRefList(mRendering, cell.probes, cell, *mPhysics); + insertCellRefList(mRendering, cell.repairs, cell, *mPhysics); + insertCellRefList(mRendering, cell.statics, cell, *mPhysics); + insertCellRefList(mRendering, cell.weapons, cell, *mPhysics); + } + /// \todo this whole code needs major clean up, and doesn't belong in this class. + void Scene::insertObject(MWWorld::Ptr ptr, Ptr::CellStore* cell) + { + std::string type = ptr.getTypeName(); + + MWWorld::Ptr newPtr; + + // insert into the correct CellRefList + if (type == typeid(ESM::Potion).name()) + { + ESMS::LiveCellRef* ref = ptr.get(); + cell->potions.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->potions.list.back(), cell); + } + else if (type == typeid(ESM::Apparatus).name()) + { + ESMS::LiveCellRef* ref = ptr.get(); + cell->appas.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->appas.list.back(), cell); + } + else if (type == typeid(ESM::Armor).name()) + { + ESMS::LiveCellRef* ref = ptr.get(); + cell->armors.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->armors.list.back(), cell); + } + else if (type == typeid(ESM::Book).name()) + { + ESMS::LiveCellRef* ref = ptr.get(); + cell->books.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->books.list.back(), cell); + } + else if (type == typeid(ESM::Clothing).name()) + { + ESMS::LiveCellRef* ref = ptr.get(); + cell->clothes.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->clothes.list.back(), cell); + } + else if (type == typeid(ESM::Ingredient).name()) + { + ESMS::LiveCellRef* ref = ptr.get(); + cell->ingreds.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->ingreds.list.back(), cell); + } + else if (type == typeid(ESM::Light).name()) + { + ESMS::LiveCellRef* ref = ptr.get(); + cell->lights.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->lights.list.back(), cell); + } + else if (type == typeid(ESM::Tool).name()) + { + ESMS::LiveCellRef* ref = ptr.get(); + cell->lockpicks.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->lockpicks.list.back(), cell); + } + else if (type == typeid(ESM::Repair).name()) + { + ESMS::LiveCellRef* ref = ptr.get(); + cell->repairs.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->repairs.list.back(), cell); + } + else if (type == typeid(ESM::Probe).name()) + { + ESMS::LiveCellRef* ref = ptr.get(); + cell->probes.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->probes.list.back(), cell); + } + else if (type == typeid(ESM::Weapon).name()) + { + ESMS::LiveCellRef* ref = ptr.get(); + cell->weapons.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->weapons.list.back(), cell); + } + else if (type == typeid(ESM::Miscellaneous).name()) + { + + // if this is gold, we need to fetch the correct mesh depending on the amount of gold. + if (MWWorld::Class::get(ptr).getName(ptr) == MWBase::Environment::get().getWorld()->getStore().gameSettings.search("sGold")->str) + { + int goldAmount = ptr.getRefData().getCount(); + + std::string base = "Gold_001"; + if (goldAmount >= 100) + base = "Gold_100"; + else if (goldAmount >= 25) + base = "Gold_025"; + else if (goldAmount >= 10) + base = "Gold_010"; + else if (goldAmount >= 5) + base = "Gold_005"; + + MWWorld::ManualRef newRef (MWBase::Environment::get().getWorld()->getStore(), base); + + ESMS::LiveCellRef* ref = newRef.getPtr().get(); + + cell->miscItems.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->miscItems.list.back(), cell); + + ESM::Position& p = newPtr.getRefData().getPosition(); + p.pos[0] = ptr.getRefData().getPosition().pos[0]; + p.pos[1] = ptr.getRefData().getPosition().pos[1]; + p.pos[2] = ptr.getRefData().getPosition().pos[2]; + } + else + { + ESMS::LiveCellRef* ref = ptr.get(); + + cell->miscItems.list.push_back( *ref ); + newPtr = MWWorld::Ptr(&cell->miscItems.list.back(), cell); + } + } + else + throw std::runtime_error("Trying to insert object of unhandled type"); + + + + newPtr.getRefData().setCount(ptr.getRefData().getCount()); + ptr.getRefData().setCount(0); + newPtr.getRefData().enable(); + + mRendering.addObject(newPtr); + MWWorld::Class::get(newPtr).insertObject(newPtr, *mPhysics); + + } + + void Scene::addObjectToScene (const Ptr& ptr) + { + mRendering.addObject (ptr); + MWWorld::Class::get (ptr).insertObject (ptr, *mPhysics); + } + + void Scene::removeObjectFromScene (const Ptr& ptr) + { + MWBase::Environment::get().getMechanicsManager()->removeActor (ptr); + MWBase::Environment::get().getSoundManager()->stopSound3D (ptr); + mPhysics->removeObject (ptr.getRefData().getHandle()); + mRendering.removeObject (ptr); + } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 1a9f2f271a..906580ff4f 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -100,7 +100,17 @@ namespace MWWorld void insertCell(ESMS::CellStore &cell); + /// this method is only meant for dropping objects into the gameworld from a container + /// and thus only handles object types that can be placed in a container + void insertObject(MWWorld::Ptr object, Ptr::CellStore* cell); + void update (float duration); + + void addObjectToScene (const Ptr& ptr); + ///< Add an object that already exists in the world model to the scene. + + void removeObjectFromScene (const Ptr& ptr); + ///< Remove an object from the scene, but not from the world model. }; } diff --git a/apps/openmw/mwworld/timestamp.cpp b/apps/openmw/mwworld/timestamp.cpp new file mode 100644 index 0000000000..e2f3d6c634 --- /dev/null +++ b/apps/openmw/mwworld/timestamp.cpp @@ -0,0 +1,108 @@ + +#include "timestamp.hpp" + +#include + +#include + +namespace MWWorld +{ + TimeStamp::TimeStamp (float hour, int day) + : mHour (hour), mDay (day) + { + if (hour<0 || hour>=24 || day<0) + throw std::runtime_error ("invalid time stamp"); + } + + float TimeStamp::getHour() const + { + return mHour; + } + + int TimeStamp::getDay() const + { + return mDay; + } + + TimeStamp& TimeStamp::operator+= (double hours) + { + if (hours<0) + throw std::runtime_error ("can't move time stamp backwards in time"); + + hours += mHour; + + mHour = static_cast (std::fmod (hours, 24)); + + mDay += hours / 24; + + return *this; + } + + bool operator== (const TimeStamp& left, const TimeStamp& right) + { + return left.getHour()==right.getHour() && left.getDay()==right.getDay(); + } + + bool operator!= (const TimeStamp& left, const TimeStamp& right) + { + return !(left==right); + } + + bool operator< (const TimeStamp& left, const TimeStamp& right) + { + if (left.getDay()right.getDay()) + return false; + + return left.getHour() (const TimeStamp& left, const TimeStamp& right) + { + return !(left<=right); + } + + bool operator>= (const TimeStamp& left, const TimeStamp& right) + { + return !(left=0 + + float getHour() const; + + int getDay() const; + + TimeStamp& operator+= (double hours); + ///< \param hours >=0 + }; + + bool operator== (const TimeStamp& left, const TimeStamp& right); + bool operator!= (const TimeStamp& left, const TimeStamp& right); + + bool operator< (const TimeStamp& left, const TimeStamp& right); + bool operator<= (const TimeStamp& left, const TimeStamp& right); + + bool operator> (const TimeStamp& left, const TimeStamp& right); + bool operator>= (const TimeStamp& left, const TimeStamp& right); + + TimeStamp operator+ (const TimeStamp& stamp, double hours); + TimeStamp operator+ (double hours, const TimeStamp& stamp); + + double operator- (const TimeStamp& left, const TimeStamp& right); + ///< Returns the difference between \a left and \a right in in-game hours. +} + +#endif diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index bcbb96eecf..3d6547d6ab 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -38,7 +38,8 @@ const float WeatherGlobals::mThunderSoundDelay = 0.25; WeatherManager::WeatherManager(MWRender::RenderingManager* rendering) : mHour(14), mCurrentWeather("clear"), mFirstUpdate(true), mWeatherUpdateTime(0), - mThunderFlash(0), mThunderChance(0), mThunderChanceNeeded(50), mThunderSoundDelay(0) + mThunderFlash(0), mThunderChance(0), mThunderChanceNeeded(50), mThunderSoundDelay(0), + mRemainingTransitionTime(0), mMonth(0), mDay(0) { mRendering = rendering; @@ -499,51 +500,54 @@ void WeatherManager::update(float duration) mCurrentRegion = regionstr; mWeatherUpdateTime = WeatherGlobals::mWeatherUpdateTime*3600; - std::string weather; + std::string weather = "clear"; if (mRegionOverrides.find(regionstr) != mRegionOverrides.end()) weather = mRegionOverrides[regionstr]; else { // get weather probabilities for the current region - const ESM::Region *region = MWBase::Environment::get().getWorld()->getStore().regions.find (regionstr); + const ESM::Region *region = MWBase::Environment::get().getWorld()->getStore().regions.search (regionstr); - float clear = region->data.clear/255.f; - float cloudy = region->data.cloudy/255.f; - float foggy = region->data.foggy/255.f; - float overcast = region->data.overcast/255.f; - float rain = region->data.rain/255.f; - float thunder = region->data.thunder/255.f; - float ash = region->data.ash/255.f; - float blight = region->data.blight/255.f; - //float snow = region->data.a/255.f; - //float blizzard = region->data.b/255.f; + if (region != 0) + { + float clear = region->data.clear/255.f; + float cloudy = region->data.cloudy/255.f; + float foggy = region->data.foggy/255.f; + float overcast = region->data.overcast/255.f; + float rain = region->data.rain/255.f; + float thunder = region->data.thunder/255.f; + float ash = region->data.ash/255.f; + float blight = region->data.blight/255.f; + //float snow = region->data.a/255.f; + //float blizzard = region->data.b/255.f; - // re-scale to 100 percent - const float total = clear+cloudy+foggy+overcast+rain+thunder+ash+blight;//+snow+blizzard; + // re-scale to 100 percent + const float total = clear+cloudy+foggy+overcast+rain+thunder+ash+blight;//+snow+blizzard; - float random = ((rand()%100)/100.f) * total; + float random = ((rand()%100)/100.f) * total; - //if (random >= snow+blight+ash+thunder+rain+overcast+foggy+cloudy+clear) - // weather = "blizzard"; - //else if (random >= blight+ash+thunder+rain+overcast+foggy+cloudy+clear) - // weather = "snow"; - /*else*/ if (random >= ash+thunder+rain+overcast+foggy+cloudy+clear) - weather = "blight"; - else if (random >= thunder+rain+overcast+foggy+cloudy+clear) - weather = "ashstorm"; - else if (random >= rain+overcast+foggy+cloudy+clear) - weather = "thunderstorm"; - else if (random >= overcast+foggy+cloudy+clear) - weather = "rain"; - else if (random >= foggy+cloudy+clear) - weather = "overcast"; - else if (random >= cloudy+clear) - weather = "foggy"; - else if (random >= clear) - weather = "cloudy"; - else - weather = "clear"; + //if (random >= snow+blight+ash+thunder+rain+overcast+foggy+cloudy+clear) + // weather = "blizzard"; + //else if (random >= blight+ash+thunder+rain+overcast+foggy+cloudy+clear) + // weather = "snow"; + /*else*/ if (random >= ash+thunder+rain+overcast+foggy+cloudy+clear) + weather = "blight"; + else if (random >= thunder+rain+overcast+foggy+cloudy+clear) + weather = "ashstorm"; + else if (random >= rain+overcast+foggy+cloudy+clear) + weather = "thunderstorm"; + else if (random >= overcast+foggy+cloudy+clear) + weather = "rain"; + else if (random >= foggy+cloudy+clear) + weather = "overcast"; + else if (random >= cloudy+clear) + weather = "foggy"; + else if (random >= clear) + weather = "cloudy"; + else + weather = "clear"; + } } setWeather(weather, false); diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index 66eedcb845..e8d555689d 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -15,12 +15,13 @@ #include "../mwsound/soundmanager.hpp" +#include "../mwgui/window_manager.hpp" #include "ptr.hpp" #include "class.hpp" #include "player.hpp" #include "weather.hpp" - +#include "manualref.hpp" #include "refdata.hpp" #include "globals.hpp" #include "cellfunctors.hpp" @@ -214,6 +215,7 @@ namespace MWWorld setFallbackValues(fallbackMap); + lastTick = mTimer.getMilliseconds(); } @@ -355,12 +357,8 @@ namespace MWWorld { reference.getRefData().enable(); - - //render->enable (reference.getRefData().getHandle()); - if(mWorldScene->getActiveCells().find (reference.getCell()) != mWorldScene->getActiveCells().end()) - Class::get (reference).enable (reference); - - + if(mWorldScene->getActiveCells().find (reference.getCell()) != mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) + mWorldScene->addObjectToScene (reference); } } @@ -370,14 +368,8 @@ namespace MWWorld { reference.getRefData().disable(); - - //render->disable (reference.getRefData().getHandle()); - if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end()){ - Class::get (reference).disable (reference); - MWBase::Environment::get().getSoundManager()->stopSound3D (reference); - } - - + if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) + mWorldScene->removeObjectFromScene (reference); } } @@ -469,6 +461,12 @@ namespace MWWorld mRendering->skySetDate (mGlobalVariables->getInt ("day"), month); } + TimeStamp World::getTimeStamp() const + { + return TimeStamp (mGlobalVariables->getFloat ("gamehour"), + mGlobalVariables->getInt ("dayspassed")); + } + bool World::toggleSky() { if (mSky) @@ -545,21 +543,18 @@ namespace MWWorld { ptr.getRefData().setCount (0); - - if (mWorldScene->getActiveCells().find (ptr.getCell())!=mWorldScene->getActiveCells().end()){ -// Class::get (ptr).disable (ptr, mEnvironment); /// \todo this line needs to be removed - MWBase::Environment::get().getSoundManager()->stopSound3D (ptr); - - mPhysics->removeObject (ptr.getRefData().getHandle()); - mRendering->removeObject(ptr); - - mLocalScripts.remove (ptr); - } + if (mWorldScene->getActiveCells().find (ptr.getCell())!=mWorldScene->getActiveCells().end() && + ptr.getRefData().isEnabled()) + { + mWorldScene->removeObjectFromScene (ptr); + mLocalScripts.remove (ptr); + } } } - void World::moveObjectImp (Ptr ptr, float x, float y, float z) + bool World::moveObjectImp (Ptr ptr, float x, float y, float z) { + bool ret = false; ptr.getRefData().getPosition().pos[0] = x; ptr.getRefData().getPosition().pos[1] = y; ptr.getRefData().getPosition().pos[2] = z; @@ -581,6 +576,7 @@ namespace MWWorld if (currentCell->cell->data.gridX!=cellX || currentCell->cell->data.gridY!=cellY) { mWorldScene->changeCell (cellX, cellY, mPlayer->getPlayer().getRefData().getPosition(), false); + ret = true; } } @@ -590,6 +586,8 @@ namespace MWWorld /// \todo cell change for non-player ref mRendering->moveObject (ptr, Ogre::Vector3 (x, y, z)); + + return ret; } void World::moveObject (Ptr ptr, float x, float y, float z) @@ -631,29 +629,50 @@ namespace MWWorld void World::doPhysics (const std::vector >& actors, float duration) { - std::vector< std::pair > vectors = mPhysics->doPhysics (duration, actors); + mPhysics->doPhysics(duration, actors); - std::vector< std::pair >::iterator player = vectors.end(); + const int tick = 16; // 16 ms ^= 60 Hz - for (std::vector< std::pair >::iterator it = vectors.begin(); - it!= vectors.end(); ++it) + // Game clock part of the loop, contains everything that has to be executed in a fixed timestep + long long dt = mTimer.getMilliseconds() - lastTick; + if (dt >= 100) { - if (it->first=="player") + // throw away wall clock time if necessary to keep the framerate above the minimum of 10 fps + lastTick += (dt - 100); + dt = 100; + } + while (dt >= tick) + { + dt -= tick; + lastTick += tick; + + std::vector< std::pair > vectors = mPhysics->doPhysicsFixed (actors); + + std::vector< std::pair >::iterator player = vectors.end(); + + for (std::vector< std::pair >::iterator it = vectors.begin(); + it!= vectors.end(); ++it) { - player = it; + if (it->first=="player") + { + player = it; + } + else + { + MWWorld::Ptr ptr = getPtrViaHandle (it->first); + moveObjectImp (ptr, it->second.x, it->second.y, it->second.z); + } } - else + + // Make sure player is moved last (otherwise the cell might change in the middle of an update + // loop) + if (player!=vectors.end()) { - MWWorld::Ptr ptr = getPtrViaHandle (it->first); - moveObjectImp (ptr, it->second.x, it->second.y, it->second.z); + if (moveObjectImp (getPtrViaHandle (player->first), + player->second.x, player->second.y, player->second.z) == true) + return; // abort the current loop if the cell has changed } } - - // Make sure player is moved last (otherwise the cell might change in the middle of an update - // loop) - if (player!=vectors.end()) - moveObjectImp (getPtrViaHandle (player->first), - player->second.x, player->second.y, player->second.z); } bool World::toggleCollisionMode() @@ -738,6 +757,34 @@ namespace MWWorld mWeatherManager->update (duration); + // inform the GUI about focused object + try + { + MWWorld::Ptr object = getPtrViaHandle(mFacedHandle); + MWBase::Environment::get().getWindowManager()->setFocusObject(object); + + // retrieve object dimensions so we know where to place the floating label + Ogre::SceneNode* node = object.getRefData().getBaseNode(); + Ogre::AxisAlignedBox bounds; + int i; + for (i=0; inumAttachedObjects(); ++i) + { + Ogre::MovableObject* ob = node->getAttachedObject(i); + bounds.merge(ob->getWorldBoundingBox()); + } + if (bounds.isFinite()) + { + Vector4 screenCoords = mRendering->boundingBoxToScreen(bounds); + MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords( + screenCoords[0], screenCoords[1], screenCoords[2], screenCoords[3]); + } + } + catch (std::runtime_error&) + { + MWWorld::Ptr null; + MWBase::Environment::get().getWindowManager()->setFocusObject(null); + } + if (!mRendering->occlusionQuerySupported()) { // cast a ray from player to sun to detect if the sun is visible @@ -771,13 +818,22 @@ namespace MWWorld // send new query // figure out which object we want to test against - std::vector < std::pair < float, std::string > > results = mPhysics->getFacedObjects(); + std::vector < std::pair < float, std::string > > results; + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) + { + float x, y; + MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); + results = mPhysics->getFacedObjects(x, y); + } + else + results = mPhysics->getFacedObjects(); // ignore the player and other things we're not interested in std::vector < std::pair < float, std::string > >::iterator it = results.begin(); while (it != results.end()) { - if ( getPtrViaHandle((*it).second) == mPlayer->getPlayer() ) + if ( (*it).second.find("HeightField") != std::string::npos // not interested in terrain + || getPtrViaHandle((*it).second) == mPlayer->getPlayer() ) // not interested in player (unless you want to talk to yourself) { it = results.erase(it); } @@ -795,7 +851,15 @@ namespace MWWorld mFaced1Name = results.front().second; mNumFacing = 1; - btVector3 p = mPhysics->getRayPoint(results.front().first); + btVector3 p; + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) + { + float x, y; + MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); + p = mPhysics->getRayPoint(results.front().first, x, y); + } + else + p = mPhysics->getRayPoint(results.front().first); Ogre::Vector3 pos(p.x(), p.z(), -p.y()); Ogre::SceneNode* node = mFaced1.getRefData().getBaseNode(); @@ -812,7 +876,15 @@ namespace MWWorld mFaced2 = getPtrViaHandle(results[1].second); mNumFacing = 2; - btVector3 p = mPhysics->getRayPoint(results[1].first); + btVector3 p; + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) + { + float x, y; + MWBase::Environment::get().getWindowManager()->getMousePosition(x, y); + p = mPhysics->getRayPoint(results[1].first, x, y); + } + else + p = mPhysics->getRayPoint(results[1].first); Ogre::Vector3 pos(p.x(), p.z(), -p.y()); Ogre::SceneNode* node1 = mFaced1.getRefData().getBaseNode(); Ogre::SceneNode* node2 = mFaced2.getRefData().getBaseNode(); @@ -909,4 +981,62 @@ namespace MWWorld mRendering->toggleWater(); } + bool World::placeObject(MWWorld::Ptr object, float cursorX, float cursorY) + { + std::pair result = mPhysics->castRay(cursorX, cursorY); + + if (!result.first) + return false; + + MWWorld::Ptr::CellStore* cell; + if (isCellExterior()) + { + int cellX, cellY; + positionToIndex(result.second[0], -result.second[2], cellX, cellY); + cell = mCells.getExterior(cellX, cellY); + } + else + cell = getPlayer().getPlayer().getCell(); + + ESM::Position& pos = object.getRefData().getPosition(); + pos.pos[0] = result.second[0]; + pos.pos[1] = -result.second[2]; + pos.pos[2] = result.second[1]; + + mWorldScene->insertObject(object, cell); + + /// \todo retrieve the bounds of the object and translate it accordingly + + return true; + } + + bool World::canPlaceObject(float cursorX, float cursorY) + { + std::pair result = mPhysics->castRay(cursorX, cursorY); + + /// \todo also check if the wanted position is on a flat surface, and not e.g. against a vertical wall! + + if (!result.first) + return false; + return true; + } + + void World::dropObjectOnGround(MWWorld::Ptr object) + { + MWWorld::Ptr::CellStore* cell = getPlayer().getPlayer().getCell(); + + float* playerPos = getPlayer().getPlayer().getRefData().getPosition().pos; + + ESM::Position& pos = object.getRefData().getPosition(); + pos.pos[0] = playerPos[0]; + pos.pos[1] = playerPos[1]; + pos.pos[2] = playerPos[2]; + + mWorldScene->insertObject(object, cell); + } + + void World::processChangedSettings(const Settings::CategorySettingVector& settings) + { + mRendering->processChangedSettings(settings); + } } diff --git a/apps/openmw/mwworld/world.hpp b/apps/openmw/mwworld/world.hpp index 77e5bcef62..e8391773b1 100644 --- a/apps/openmw/mwworld/world.hpp +++ b/apps/openmw/mwworld/world.hpp @@ -7,6 +7,7 @@ #include #include +#include #include "../mwrender/debugging.hpp" #include "../mwrender/renderingmanager.hpp" @@ -18,10 +19,13 @@ #include "physicssystem.hpp" #include "cells.hpp" #include "localscripts.hpp" +#include "timestamp.hpp" #include #include +#include + namespace Ogre { class Vector3; @@ -63,7 +67,8 @@ namespace MWWorld { Render_CollisionDebug, Render_Wireframe, - Render_Pathgrid + Render_Pathgrid, + Render_Compositors }; private: @@ -100,9 +105,13 @@ namespace MWWorld int mNumFacing; std::map mFallback; + unsigned long lastTick; + Ogre::Timer mTimer; + int getDaysPerMonth (int month) const; - void moveObjectImp (Ptr ptr, float x, float y, float z); + bool moveObjectImp (Ptr ptr, float x, float y, float z); + ///< @return true if the active cell (cell player is in) changed public: @@ -179,6 +188,9 @@ namespace MWWorld void setDay (int day); ///< Set in-game time day. + TimeStamp getTimeStamp() const; + ///< Return current in-game time stamp. + bool toggleSky(); ///< \return Resulting mode @@ -257,6 +269,19 @@ namespace MWWorld void update (float duration); + bool placeObject(MWWorld::Ptr object, float cursorX, float cursorY); + ///< place an object into the gameworld at the specified cursor position + /// @param object + /// @param cursor X (relative 0-1) + /// @param cursor Y (relative 0-1) + /// @return true if the object was placed, or false if it was rejected because the position is too far away + + void dropObjectOnGround(MWWorld::Ptr object); + + bool canPlaceObject(float cursorX, float cursorY); + ///< @return true if it is possible to place on object at specified cursor location + + void processChangedSettings(const Settings::CategorySettingVector& settings); }; } diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index fa197d9605..d3b75d4ae4 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -93,12 +93,12 @@ class DirArchive: public Ogre::FileSystemArchive return true; std::string folder; - int delimiter = 0; + //int delimiter = 0; size_t lastSlash = copy.rfind('/'); if (lastSlash != std::string::npos) { folder = copy.substr(0, lastSlash); - delimiter = lastSlash+1; + //delimiter = lastSlash+1; } std::vector current; diff --git a/components/compiler/generator.cpp b/components/compiler/generator.cpp index ee9876a147..26a80387b1 100644 --- a/components/compiler/generator.cpp +++ b/components/compiler/generator.cpp @@ -727,7 +727,7 @@ namespace Compiler opStopScript (code); } - void getDistance (CodeContainer& code, Literals& literals, const std::string id) + void getDistance (CodeContainer& code, Literals& literals, const std::string& id) { if (id.empty()) { @@ -746,7 +746,7 @@ namespace Compiler opGetSecondsPassed (code); } - void getDisabled (CodeContainer& code, Literals& literals, const std::string id) + void getDisabled (CodeContainer& code, Literals& literals, const std::string& id) { if (id.empty()) { @@ -760,7 +760,7 @@ namespace Compiler } } - void enable (CodeContainer& code, Literals& literals, const std::string id) + void enable (CodeContainer& code, Literals& literals, const std::string& id) { if (id.empty()) { @@ -774,7 +774,7 @@ namespace Compiler } } - void disable (CodeContainer& code, Literals& literals, const std::string id) + void disable (CodeContainer& code, Literals& literals, const std::string& id) { if (id.empty()) { diff --git a/components/compiler/generator.hpp b/components/compiler/generator.hpp index fd1f954cad..89e1984314 100644 --- a/components/compiler/generator.hpp +++ b/components/compiler/generator.hpp @@ -109,15 +109,15 @@ namespace Compiler void stopScript (CodeContainer& code); - void getDistance (CodeContainer& code, Literals& literals, const std::string id); + void getDistance (CodeContainer& code, Literals& literals, const std::string& id); void getSecondsPassed (CodeContainer& code); - void getDisabled (CodeContainer& code, Literals& literals, const std::string id); + void getDisabled (CodeContainer& code, Literals& literals, const std::string& id); - void enable (CodeContainer& code, Literals& literals, const std::string id); + void enable (CodeContainer& code, Literals& literals, const std::string& id); - void disable (CodeContainer& code, Literals& literals, const std::string id); + void disable (CodeContainer& code, Literals& literals, const std::string& id); } } diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 080ca7d987..d0397e8cf1 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -368,8 +368,10 @@ namespace Compiler special = S_cmpEQ; else { + special = S_cmpEQ; putback (c); - return false; +// return false; +// Allow = as synonym for ==. \todo optionally disable for post-1.0 scripting improvements. } } else diff --git a/components/esm/attr.cpp b/components/esm/attr.cpp index 8c410e57a0..b077ef499c 100644 --- a/components/esm/attr.cpp +++ b/components/esm/attr.cpp @@ -34,3 +34,14 @@ const std::string Attribute::gmstAttributeDescIds[Attribute::Length] = { "sPerDesc", "sLucDesc" }; + +const std::string Attribute::attributeIcons[Attribute::Length] = { + "icons\\k\\attribute_strength.dds", + "icons\\k\\attribute_int.dds", + "icons\\k\\attribute_wilpower.dds", + "icons\\k\\attribute_agility.dds", + "icons\\k\\attribute_speed.dds", + "icons\\k\\attribute_endurance.dds", + "icons\\k\\attribute_personality.dds", + "icons\\k\\attribute_luck.dds" +}; diff --git a/components/esm/attr.hpp b/components/esm/attr.hpp index 183fb9e0e0..620683dbb1 100644 --- a/components/esm/attr.hpp +++ b/components/esm/attr.hpp @@ -30,6 +30,7 @@ struct Attribute static const AttributeID attributeIds[Length]; static const std::string gmstAttributeIds[Length]; static const std::string gmstAttributeDescIds[Length]; + static const std::string attributeIcons[Length]; Attribute(AttributeID id, const std::string &name, const std::string &description) : id(id) diff --git a/components/esm/esm_reader.hpp b/components/esm/esm_reader.hpp index 3e69aa5281..121642bfe2 100644 --- a/components/esm/esm_reader.hpp +++ b/components/esm/esm_reader.hpp @@ -58,16 +58,16 @@ public: * *************************************************************************/ - int getVer() { return mCtx.header.version; } + int getVer() const { return mCtx.header.version; } float getFVer() { if(mCtx.header.version == VER_12) return 1.2; else return 1.3; } int getSpecial() { return mSpf; } int getType() { return mCtx.header.type; } const std::string getAuthor() { return mCtx.header.author.toString(); } const std::string getDesc() { return mCtx.header.desc.toString(); } - const SaveData &getSaveData() { return mSaveData; } + const SaveData &getSaveData() const { return mSaveData; } const MasterList &getMasters() { return mMasters; } const NAME &retSubName() { return mCtx.subName; } - uint32_t getSubSize() { return mCtx.leftSub; } + uint32_t getSubSize() const { return mCtx.leftSub; } /************************************************************************* * @@ -230,8 +230,8 @@ public: void getRecHeader() { uint32_t u; getRecHeader(u); } void getRecHeader(uint32_t &flags); - bool hasMoreRecs() { return mCtx.leftFile > 0; } - bool hasMoreSubs() { return mCtx.leftRec > 0; } + bool hasMoreRecs() const { return mCtx.leftFile > 0; } + bool hasMoreSubs() const { return mCtx.leftRec > 0; } /************************************************************************* diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index c4f9dca487..e7c19d2705 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -19,9 +19,15 @@ void Creature::load(ESMReader &esm) spells.load(esm); aiData.load(esm); - // More subrecords: + if (esm.isNextSub("AIDT")) + { + esm.getHExact(&AI, sizeof(AI)); + hasAI = true; + } + else + hasAI = false; - // AIDT - data (12 bytes, unknown) + // More subrecords: // AI_W - wander (14 bytes, i don't understand it) // short distance // byte duration @@ -33,8 +39,8 @@ void Creature::load(ESMReader &esm) // AI_F - follow? // AI_E - escort? // AI_A - activate? - esm.skipRecord(); + } void Creature::save(ESMWriter &esm) diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index 355065aeb7..a9e1d646f3 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -54,6 +54,15 @@ struct Creature : public Record int gold; }; // 96 bytes + struct AIDTstruct + { + // These are probabilities + char hello, u1, fight, flee, alarm, u2, u3, u4; + // The last u's might be the skills that this NPC can train you + // in? + int services; // See the NPC::Services enum + }; // 12 bytes + NPDTstruct data; AIData aiData; @@ -66,9 +75,15 @@ struct Creature : public Record InventoryList inventory; SpellList spells; + void load(ESMReader &esm); void save(ESMWriter &esm); + bool hasAI; + AIDTstruct AI; + + std::string mId; + int getName() { return REC_CREA; } }; } diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp index adce2f361c..f948397173 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -31,6 +31,35 @@ namespace ESM "sSkillSpeechcraft", "sSkillHandtohand", }; + const std::string Skill::sIconNames[Length] = { + "combat_block.dds", + "combat_armor.dds", + "combat_mediumarmor.dds", + "combat_heavyarmor.dds", + "combat_blunt.dds", + "combat_longblade.dds", + "combat_axe.dds", + "combat_spear.dds", + "combat_athletics.dds", + "magic_enchant.dds", + "magic_destruction.dds", + "magic_alteration.dds", + "magic_illusion.dds", + "magic_conjuration.dds", + "magic_mysticism.dds", + "magic_restoration.dds", + "magic_alchemy.dds", + "magic_unarmored.dds", + "stealth_security.dds", + "stealth_sneak.dds", + "stealth_acrobatics.dds", + "stealth_lightarmor.dds", + "stealth_shortblade.dds", + "stealth_marksman.dds", + "stealth_mercantile.dds", + "stealth_speechcraft.dds", + "stealth_handtohand.dds", + }; const boost::array Skill::skillIds = {{ Block, Armorer, diff --git a/components/esm/loadskil.hpp b/components/esm/loadskil.hpp index 68ada6020b..01f8df4e96 100644 --- a/components/esm/loadskil.hpp +++ b/components/esm/loadskil.hpp @@ -66,6 +66,7 @@ struct Skill : public Record Length }; static const std::string sSkillNameIds[Length]; + static const std::string sIconNames[Length]; static const boost::array skillIds; void load(ESMReader &esm); diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index ef45b6543e..150a4fcd88 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -15,7 +15,6 @@ namespace Files { static const char* const openmwCfgFile = "openmw.cfg"; -static const char* const ogreCfgFile = "ogre.cfg"; static const char* const pluginsCfgFile = "plugins.cfg"; const char* const mwToken = "?mw?"; @@ -28,10 +27,10 @@ ConfigurationManager::ConfigurationManager() { setupTokensMapping(); - mPluginsCfgPath = mFixedPath.getGlobalPath() / pluginsCfgFile; + mPluginsCfgPath = mFixedPath.getLocalPath() / pluginsCfgFile; if (!boost::filesystem::is_regular_file(mPluginsCfgPath)) { - mPluginsCfgPath = mFixedPath.getLocalPath() / pluginsCfgFile; + mPluginsCfgPath = mFixedPath.getGlobalPath() / pluginsCfgFile; if (!boost::filesystem::is_regular_file(mPluginsCfgPath)) { std::cerr << "Failed to find " << pluginsCfgFile << " file!" << std::endl; @@ -39,7 +38,6 @@ ConfigurationManager::ConfigurationManager() } } - mOgreCfgPath = mFixedPath.getUserPath() / ogreCfgFile; mLogPath = mFixedPath.getUserPath(); } @@ -164,11 +162,6 @@ const boost::filesystem::path& ConfigurationManager::getInstallPath() const return mFixedPath.getInstallPath(); } -const boost::filesystem::path& ConfigurationManager::getOgreConfigPath() const -{ - return mOgreCfgPath; -} - const boost::filesystem::path& ConfigurationManager::getPluginsConfigPath() const { return mPluginsCfgPath; diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 7fb3793c64..af9d02b912 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -41,7 +41,6 @@ struct ConfigurationManager const boost::filesystem::path& getLocalDataPath() const; const boost::filesystem::path& getInstallPath() const; - const boost::filesystem::path& getOgreConfigPath() const; const boost::filesystem::path& getPluginsConfigPath() const; const boost::filesystem::path& getLogPath() const; @@ -59,7 +58,6 @@ struct ConfigurationManager FixedPathType mFixedPath; - boost::filesystem::path mOgreCfgPath; boost::filesystem::path mPluginsCfgPath; boost::filesystem::path mLogPath; diff --git a/components/files/filelibrary.cpp b/components/files/filelibrary.cpp index c114159291..ce67f0c667 100644 --- a/components/files/filelibrary.cpp +++ b/components/files/filelibrary.cpp @@ -11,7 +11,7 @@ namespace Files bool containsVectorString(const StringVector& list, const std::string& str) { for (StringVector::const_iterator iter = list.begin(); - iter != list.end(); iter++) + iter != list.end(); ++iter) { if (*iter == str) return true; @@ -112,7 +112,7 @@ namespace Files void FileLibrary::printSections() { for(StringPathContMap::const_iterator mapIter = mMap.begin(); - mapIter != mMap.end(); mapIter++) + mapIter != mMap.end(); ++mapIter) { std::cout << mapIter->first <collide = hasCollisionNode&&cShape->collide; + struct TriangleMeshShape : public btBvhTriangleMeshShape { TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression) diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index 05d35b85f6..331701c2a4 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -326,6 +326,8 @@ void NIFLoader::createMaterial(const String &name, { material->getTechnique(0)->getPass(0)->setVertexProgram("main_vp"); material->getTechnique(0)->getPass(0)->setFragmentProgram("main_fp"); + + material->getTechnique(0)->getPass(0)->setFog(true); // force-disable fixed function fog, it is calculated in shader } // Create a fallback technique without shadows and without mrt @@ -338,6 +340,7 @@ void NIFLoader::createMaterial(const String &name, { pass2->setVertexProgram("main_fallback_vp"); pass2->setFragmentProgram("main_fallback_fp"); + pass2->setFog(true); // force-disable fixed function fog, it is calculated in shader } // Add material bells and whistles @@ -681,11 +684,11 @@ void NIFLoader::handleNiTriShape(NiTriShape *shape, int flags, BoundsFinder &bou Property *pr = &list[i]; if (pr->recType == RC_NiTexturingProperty) - t = (NiTexturingProperty*)pr; + t = static_cast(pr); else if (pr->recType == RC_NiMaterialProperty) - m = (NiMaterialProperty*)pr; + m = static_cast(pr); else if (pr->recType == RC_NiAlphaProperty) - a = (NiAlphaProperty*)pr; + a = static_cast(pr); } // Texture diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 28201eda2a..9b941e2534 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -131,7 +131,11 @@ void Manager::setString (const std::string& setting, const std::string& category } } else + { + if (mDefaultFile.getSetting(setting, category) != value) + mChangedSettings.push_back(std::make_pair(category, setting)); mNewSettings[s] = value; + } } } diff --git a/files/gbuffer/gbuffer.compositor b/files/gbuffer/gbuffer.compositor index 316003af6b..6ca35df87b 100644 --- a/files/gbuffer/gbuffer.compositor +++ b/files/gbuffer/gbuffer.compositor @@ -11,11 +11,8 @@ compositor gbuffer input none pass clear { - clear - { - // make sure to set this to the viewport background color from outside - colour_value 0 0 0 1 - } + // make sure to set this to the viewport background color from outside + colour_value 0 0 0 1 } pass render_scene { @@ -59,11 +56,8 @@ compositor gbufferFinalizer shadows off pass clear { - clear - { - buffers colour - colour_value 0 0 0 0 - } + buffers colour + colour_value 0 0 0 0 } pass render_quad { @@ -81,9 +75,6 @@ compositor gbufferFinalizer input none pass clear { - clear - { - } } pass render_quad { diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index e3a7b9999c..dad4afb466 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -9,7 +9,7 @@ configure_file("${SDIR}/black.png" "${DDIR}/black.png" COPYONLY) configure_file("${SDIR}/core.skin" "${DDIR}/core.skin" COPYONLY) configure_file("${SDIR}/core.xml" "${DDIR}/core.xml" COPYONLY) configure_file("${SDIR}/mwgui.png" "${DDIR}/mwgui.png" COPYONLY) -configure_file("${SDIR}/openmw_images.xml" "${DDIR}/openmw_images.xml" COPYONLY) +configure_file("${SDIR}/openmw_resources.xml" "${DDIR}/openmw_resources.xml" COPYONLY) configure_file("${SDIR}/openmw_settings.xml" "${DDIR}/openmw_settings.xml" COPYONLY) configure_file("${SDIR}/openmw_box.skin.xml" "${DDIR}/openmw_box.skin.xml" COPYONLY) configure_file("${SDIR}/openmw_button.skin.xml" "${DDIR}/openmw_button.skin.xml" COPYONLY) @@ -36,6 +36,7 @@ configure_file("${SDIR}/openmw_chargen_review_layout.xml" "${DDIR}/openmw_charge configure_file("${SDIR}/openmw_dialogue_window_layout.xml" "${DDIR}/openmw_dialogue_window_layout.xml" COPYONLY) configure_file("${SDIR}/openmw_dialogue_window_skin.xml" "${DDIR}/openmw_dialogue_window_skin.xml" COPYONLY) configure_file("${SDIR}/openmw_inventory_window_layout.xml" "${DDIR}/openmw_inventory_window_layout.xml" COPYONLY) +configure_file("${SDIR}/openmw_container_window_layout.xml" "${DDIR}/openmw_container_window_layout.xml" COPYONLY) configure_file("${SDIR}/openmw_layers.xml" "${DDIR}/openmw_layers.xml" COPYONLY) configure_file("${SDIR}/openmw_mainmenu_layout.xml" "${DDIR}/openmw_mainmenu_layout.xml" COPYONLY) configure_file("${SDIR}/openmw_mainmenu_skin.xml" "${DDIR}/openmw_mainmenu_skin.xml" COPYONLY) @@ -50,7 +51,18 @@ configure_file("${SDIR}/openmw_messagebox_layout.xml" "${DDIR}/openmw_messagebox configure_file("${SDIR}/openmw_interactive_messagebox_layout.xml" "${DDIR}/openmw_interactive_messagebox_layout.xml" COPYONLY) configure_file("${SDIR}/openmw_journal_layout.xml" "${DDIR}/openmw_journal_layout.xml" COPYONLY) configure_file("${SDIR}/openmw_journal_skin.xml" "${DDIR}/openmw_journal_skin.xml" COPYONLY) +configure_file("${SDIR}/openmw_tooltips.xml" "${DDIR}/openmw_tooltips.xml" COPYONLY) +configure_file("${SDIR}/openmw_scroll_layout.xml" "${DDIR}/openmw_scroll_layout.xml" COPYONLY) +configure_file("${SDIR}/openmw_scroll_skin.xml" "${DDIR}/openmw_scroll_skin.xml" COPYONLY) +configure_file("${SDIR}/openmw_book_layout.xml" "${DDIR}/openmw_book_layout.xml" COPYONLY) +configure_file("${SDIR}/openmw_count_window_layout.xml" "${DDIR}/openmw_count_window_layout.xml" COPYONLY) +configure_file("${SDIR}/openmw_trade_window_layout.xml" "${DDIR}/openmw_trade_window_layout.xml" COPYONLY) +configure_file("${SDIR}/openmw_settings_window_layout.xml" "${DDIR}/openmw_settings_window_layout.xml" COPYONLY) +configure_file("${SDIR}/openmw_confirmation_dialog_layout.xml" "${DDIR}/openmw_confirmation_dialog_layout.xml" COPYONLY) +configure_file("${SDIR}/openmw_alchemy_window_layout.xml" "${DDIR}/openmw_alchemy_window_layout.xml" COPYONLY) +configure_file("${SDIR}/openmw_spell_window_layout.xml" "${DDIR}/openmw_spell_window_layout.xml" COPYONLY) +configure_file("${SDIR}/atlas1.cfg" "${DDIR}/atlas1.cfg" COPYONLY) configure_file("${SDIR}/smallbars.png" "${DDIR}/smallbars.png" COPYONLY) -configure_file("${SDIR}/transparent.png" "${DDIR}/transparent.png" COPYONLY) configure_file("${SDIR}/EBGaramond-Regular.ttf" "${DDIR}/EBGaramond-Regular.ttf" COPYONLY) +configure_file("${SDIR}/Obliviontt.zip" "${DDIR}/Obliviontt.zip" COPYONLY) configure_file("${SDIR}/VeraMono.ttf" "${DDIR}/VeraMono.ttf" COPYONLY) diff --git a/files/mygui/Obliviontt.zip b/files/mygui/Obliviontt.zip new file mode 100644 index 0000000000..af4f809fd8 Binary files /dev/null and b/files/mygui/Obliviontt.zip differ diff --git a/files/mygui/atlas1.cfg b/files/mygui/atlas1.cfg new file mode 100644 index 0000000000..d1e05e0412 --- /dev/null +++ b/files/mygui/atlas1.cfg @@ -0,0 +1,51 @@ +[settings] + size_x = 512 + size_y = 512 + +[tx_menubook_close_idle.dds] + x = 0 + y = 0 + +[tx_menubook_close_over.dds] + x = 128 + y = 0 + +[tx_menubook_close_pressed.dds] + x = 256 + y = 0 + +[tx_menubook_take_idle.dds] + x = 384 + y = 0 + +[tx_menubook_take_over.dds] + x = 0 + y = 32 + +[tx_menubook_take_pressed.dds] + x = 128 + y = 32 + +[tx_menubook_next_idle.dds] + x = 256 + y = 32 + +[tx_menubook_next_over.dds] + x = 384 + y = 32 + +[tx_menubook_next_pressed.dds] + x = 0 + y = 64 + +[tx_menubook_prev_idle.dds] + x = 128 + y = 64 + +[tx_menubook_prev_over.dds] + x = 256 + y = 64 + +[tx_menubook_prev_pressed.dds] + x = 384 + y = 64 diff --git a/files/mygui/core.xml b/files/mygui/core.xml index 7417328cf1..e1fb1b5e21 100644 --- a/files/mygui/core.xml +++ b/files/mygui/core.xml @@ -2,7 +2,7 @@ - + @@ -20,6 +20,7 @@ + diff --git a/files/mygui/openmw.font.xml b/files/mygui/openmw.font.xml index e7d0f50c8a..73d491e04e 100644 --- a/files/mygui/openmw.font.xml +++ b/files/mygui/openmw.font.xml @@ -6,26 +6,39 @@ - + + + + + + + + + + + + + + + - @@ -37,4 +50,4 @@ - \ No newline at end of file + diff --git a/files/mygui/openmw.pointer.xml b/files/mygui/openmw.pointer.xml index 0fbef2fdf5..42ee5d4351 100644 --- a/files/mygui/openmw.pointer.xml +++ b/files/mygui/openmw.pointer.xml @@ -26,4 +26,9 @@ + + + + + diff --git a/files/mygui/openmw_alchemy_window_layout.xml b/files/mygui/openmw_alchemy_window_layout.xml new file mode 100644 index 0000000000..68df748b16 --- /dev/null +++ b/files/mygui/openmw_alchemy_window_layout.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_book_layout.xml b/files/mygui/openmw_book_layout.xml new file mode 100644 index 0000000000..6c708cdd3d --- /dev/null +++ b/files/mygui/openmw_book_layout.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_box.skin.xml b/files/mygui/openmw_box.skin.xml index c1952794c1..2a54edd60b 100644 --- a/files/mygui/openmw_box.skin.xml +++ b/files/mygui/openmw_box.skin.xml @@ -6,23 +6,39 @@ as around the sections of the stats window, or around popup info windows --> - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + diff --git a/files/mygui/openmw_button.skin.xml b/files/mygui/openmw_button.skin.xml index 1c68930264..491b3f47b7 100644 --- a/files/mygui/openmw_button.skin.xml +++ b/files/mygui/openmw_button.skin.xml @@ -3,23 +3,39 @@ - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + @@ -47,7 +63,6 @@ - @@ -59,11 +74,14 @@ - - - - - + + + + + + + + diff --git a/files/mygui/openmw_chargen_birth_layout.xml b/files/mygui/openmw_chargen_birth_layout.xml index bce3f585dc..0599ab8638 100644 --- a/files/mygui/openmw_chargen_birth_layout.xml +++ b/files/mygui/openmw_chargen_birth_layout.xml @@ -15,7 +15,7 @@ - + diff --git a/files/mygui/openmw_chargen_class_layout.xml b/files/mygui/openmw_chargen_class_layout.xml index bf49810c80..613143d542 100644 --- a/files/mygui/openmw_chargen_class_layout.xml +++ b/files/mygui/openmw_chargen_class_layout.xml @@ -14,8 +14,11 @@ - + + + + @@ -23,16 +26,19 @@ - + + + + - + @@ -42,7 +48,7 @@ - + @@ -55,10 +61,10 @@ - + - + diff --git a/files/mygui/openmw_chargen_create_class_layout.xml b/files/mygui/openmw_chargen_create_class_layout.xml index 3b7d91b00c..dde74a6a2c 100644 --- a/files/mygui/openmw_chargen_create_class_layout.xml +++ b/files/mygui/openmw_chargen_create_class_layout.xml @@ -12,17 +12,23 @@ - + + + + - + - + + + + @@ -56,10 +62,10 @@ - + - + diff --git a/files/mygui/openmw_chargen_generate_class_result_layout.xml b/files/mygui/openmw_chargen_generate_class_result_layout.xml index 7ec926eb05..1cef9c9ce6 100644 --- a/files/mygui/openmw_chargen_generate_class_result_layout.xml +++ b/files/mygui/openmw_chargen_generate_class_result_layout.xml @@ -1,28 +1,30 @@ - + - - + + + + - + - - + + - - + + diff --git a/files/mygui/openmw_chargen_race_layout.xml b/files/mygui/openmw_chargen_race_layout.xml index 6887b12c5c..df0d29f78c 100644 --- a/files/mygui/openmw_chargen_race_layout.xml +++ b/files/mygui/openmw_chargen_race_layout.xml @@ -17,27 +17,23 @@ - - - - @@ -45,15 +41,13 @@ - - + - @@ -61,7 +55,7 @@ - + diff --git a/files/mygui/openmw_chargen_review_layout.xml b/files/mygui/openmw_chargen_review_layout.xml index c713eb4772..2071cac88d 100644 --- a/files/mygui/openmw_chargen_review_layout.xml +++ b/files/mygui/openmw_chargen_review_layout.xml @@ -5,10 +5,18 @@ - - - - + + + + + + + + + + + + @@ -17,21 +25,84 @@ - - - + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -42,10 +113,10 @@ - + - + diff --git a/files/mygui/openmw_chargen_select_specialization_layout.xml b/files/mygui/openmw_chargen_select_specialization_layout.xml index 53980b1335..1b5295bc9e 100644 --- a/files/mygui/openmw_chargen_select_specialization_layout.xml +++ b/files/mygui/openmw_chargen_select_specialization_layout.xml @@ -11,13 +11,13 @@ - + - + - + diff --git a/files/mygui/openmw_confirmation_dialog_layout.xml b/files/mygui/openmw_confirmation_dialog_layout.xml new file mode 100644 index 0000000000..7b8bd2a1fd --- /dev/null +++ b/files/mygui/openmw_confirmation_dialog_layout.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_console.skin.xml b/files/mygui/openmw_console.skin.xml index 598252734f..1758c728de 100644 --- a/files/mygui/openmw_console.skin.xml +++ b/files/mygui/openmw_console.skin.xml @@ -1,41 +1,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/files/mygui/openmw_console_layout.xml b/files/mygui/openmw_console_layout.xml index a2b883cdb5..732684ad12 100644 --- a/files/mygui/openmw_console_layout.xml +++ b/files/mygui/openmw_console_layout.xml @@ -1,17 +1,17 @@  - - + + - + - + diff --git a/files/mygui/openmw_container_window_layout.xml b/files/mygui/openmw_container_window_layout.xml new file mode 100644 index 0000000000..ae9b0bfdf9 --- /dev/null +++ b/files/mygui/openmw_container_window_layout.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_count_window_layout.xml b/files/mygui/openmw_count_window_layout.xml new file mode 100644 index 0000000000..ae6635dff3 --- /dev/null +++ b/files/mygui/openmw_count_window_layout.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_dialogue_window_layout.xml b/files/mygui/openmw_dialogue_window_layout.xml index 29a3b511ec..1271a287b6 100644 --- a/files/mygui/openmw_dialogue_window_layout.xml +++ b/files/mygui/openmw_dialogue_window_layout.xml @@ -16,15 +16,15 @@ - + - + - + diff --git a/files/mygui/openmw_dialogue_window_skin.xml b/files/mygui/openmw_dialogue_window_skin.xml index ecdec8a5c0..31ce626be0 100644 --- a/files/mygui/openmw_dialogue_window_skin.xml +++ b/files/mygui/openmw_dialogue_window_skin.xml @@ -8,12 +8,12 @@ - - + + - + diff --git a/files/mygui/openmw_edit.skin.xml b/files/mygui/openmw_edit.skin.xml index a86317d620..02fee4b179 100644 --- a/files/mygui/openmw_edit.skin.xml +++ b/files/mygui/openmw_edit.skin.xml @@ -13,7 +13,7 @@ - + @@ -34,7 +34,7 @@ - + diff --git a/files/mygui/openmw_hud_box.skin.xml b/files/mygui/openmw_hud_box.skin.xml index bf1b0056a0..ce231e5bb9 100644 --- a/files/mygui/openmw_hud_box.skin.xml +++ b/files/mygui/openmw_hud_box.skin.xml @@ -3,19 +3,76 @@ - - + + + + + + - - + + + + + + + - - + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_hud_layout.xml b/files/mygui/openmw_hud_layout.xml index 2dafa72988..cf353a205e 100644 --- a/files/mygui/openmw_hud_layout.xml +++ b/files/mygui/openmw_hud_layout.xml @@ -3,91 +3,98 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + - - + - + + + + + - - + + + + + + + + - + + + - - - - - - + + + - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/files/mygui/openmw_infobox_layout.xml b/files/mygui/openmw_infobox_layout.xml index de10308304..89031a6162 100644 --- a/files/mygui/openmw_infobox_layout.xml +++ b/files/mygui/openmw_infobox_layout.xml @@ -4,7 +4,7 @@ - + diff --git a/files/mygui/openmw_interactive_messagebox_layout.xml b/files/mygui/openmw_interactive_messagebox_layout.xml index 744f212276..b8a71c670d 100644 --- a/files/mygui/openmw_interactive_messagebox_layout.xml +++ b/files/mygui/openmw_interactive_messagebox_layout.xml @@ -5,7 +5,7 @@ - + diff --git a/files/mygui/openmw_inventory_window_layout.xml b/files/mygui/openmw_inventory_window_layout.xml index c69b6d8fe2..b38e15fc79 100644 --- a/files/mygui/openmw_inventory_window_layout.xml +++ b/files/mygui/openmw_inventory_window_layout.xml @@ -1,28 +1,53 @@ - + + + + + + + + + + + + + + + - - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - diff --git a/files/mygui/openmw_journal_layout.xml b/files/mygui/openmw_journal_layout.xml index da4fe658bd..e4c3c7e472 100644 --- a/files/mygui/openmw_journal_layout.xml +++ b/files/mygui/openmw_journal_layout.xml @@ -7,14 +7,14 @@ - + + - - + + - - - + + diff --git a/files/mygui/openmw_journal_skin.xml b/files/mygui/openmw_journal_skin.xml index 0ef87852f4..9f82e907ff 100644 --- a/files/mygui/openmw_journal_skin.xml +++ b/files/mygui/openmw_journal_skin.xml @@ -1,18 +1,6 @@ - - - - - - - - - - - - diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index a83eb970a8..56f800ea34 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -7,7 +7,8 @@ + + - diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml index 0ac8e03ba6..64435451ad 100644 --- a/files/mygui/openmw_list.skin.xml +++ b/files/mygui/openmw_list.skin.xml @@ -6,13 +6,32 @@ - - + + + + + + + + + + + + + + + + + + + + + @@ -131,6 +150,8 @@ + + @@ -143,13 +164,35 @@ - + + + + + + + + + + + + + + + + + + + + + + + @@ -180,10 +223,14 @@ - - - - + + + + + + + + @@ -208,6 +255,20 @@ + + + + + + + + + + + + + + @@ -219,7 +280,7 @@ - <_BasisSkin type="MainSkin" offset = "0 0 0 0" align = "ALIGN_LEFT ALIGN_TOP"/> + diff --git a/files/mygui/openmw_map_window_layout.xml b/files/mygui/openmw_map_window_layout.xml index fbba8ddf47..63fd03b140 100644 --- a/files/mygui/openmw_map_window_layout.xml +++ b/files/mygui/openmw_map_window_layout.xml @@ -11,44 +11,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/files/mygui/openmw_messagebox_layout.xml b/files/mygui/openmw_messagebox_layout.xml index 81d1c0a57e..c5a7464ef6 100644 --- a/files/mygui/openmw_messagebox_layout.xml +++ b/files/mygui/openmw_messagebox_layout.xml @@ -4,11 +4,11 @@ - + - + diff --git a/files/mygui/openmw_resources.xml b/files/mygui/openmw_resources.xml new file mode 100644 index 0000000000..f1a8b0dfc7 --- /dev/null +++ b/files/mygui/openmw_resources.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_scroll_layout.xml b/files/mygui/openmw_scroll_layout.xml new file mode 100644 index 0000000000..0f4a0be3e8 --- /dev/null +++ b/files/mygui/openmw_scroll_layout.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_scroll_skin.xml b/files/mygui/openmw_scroll_skin.xml new file mode 100644 index 0000000000..70fad3f4b4 --- /dev/null +++ b/files/mygui/openmw_scroll_skin.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_settings_window_layout.xml b/files/mygui/openmw_settings_window_layout.xml new file mode 100644 index 0000000000..7c508b1e97 --- /dev/null +++ b/files/mygui/openmw_settings_window_layout.xml @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_spell_window_layout.xml b/files/mygui/openmw_spell_window_layout.xml new file mode 100644 index 0000000000..d489f41b8a --- /dev/null +++ b/files/mygui/openmw_spell_window_layout.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_stats_window_layout.xml b/files/mygui/openmw_stats_window_layout.xml index fd99f863e5..81d70f7042 100644 --- a/files/mygui/openmw_stats_window_layout.xml +++ b/files/mygui/openmw_stats_window_layout.xml @@ -3,59 +3,226 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/files/mygui/openmw_text.skin.xml b/files/mygui/openmw_text.skin.xml index 6ae14c558b..e29483e35b 100644 --- a/files/mygui/openmw_text.skin.xml +++ b/files/mygui/openmw_text.skin.xml @@ -1,6 +1,8 @@ + + @@ -15,6 +17,7 @@ + @@ -62,26 +65,75 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + @@ -93,20 +145,25 @@ + + + + + - + - + - + diff --git a/files/mygui/openmw_text_input_layout.xml b/files/mygui/openmw_text_input_layout.xml index 6a7ad27f01..c8f76b2575 100644 --- a/files/mygui/openmw_text_input_layout.xml +++ b/files/mygui/openmw_text_input_layout.xml @@ -4,10 +4,10 @@ - + - + diff --git a/files/mygui/openmw_tooltips.xml b/files/mygui/openmw_tooltips.xml new file mode 100644 index 0000000000..148e98064f --- /dev/null +++ b/files/mygui/openmw_tooltips.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_trade_window_layout.xml b/files/mygui/openmw_trade_window_layout.xml new file mode 100644 index 0000000000..c8a9f25234 --- /dev/null +++ b/files/mygui/openmw_trade_window_layout.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index 7c194ea5d0..b379f84ced 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -17,14 +17,22 @@ - - + + + + + + - - + + + + + + @@ -66,29 +74,45 @@ - - + + + + + + - - + + + + + + - - + + + + + + - - + + + + + + @@ -120,20 +144,32 @@ - - + + + + + + - - + + + + + + - - + + + + + + @@ -171,12 +207,8 @@ - - - - - - + + @@ -218,6 +250,7 @@ + @@ -294,6 +327,7 @@ + diff --git a/files/mygui/transparent.png b/files/mygui/transparent.png deleted file mode 100644 index 32d2ed1975..0000000000 Binary files a/files/mygui/transparent.png and /dev/null differ diff --git a/files/settings-default.cfg b/files/settings-default.cfg index e4a0c020a5..883f32ae07 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1,6 +1,36 @@ # WARNING: Editing this file might have no effect, as these # settings are overwritten by your user settings file. +[Video] +resolution x = 800 +resolution y = 600 + +fullscreen = false + +# Render system +# blank means default +# Valid values: +# OpenGL Rendering Subsystem +# Direct3D9 Rendering Subsystem +render system = + +# Valid values: +# none +# MSAA 2 +# MSAA 4 +# MSAA 8 +# MSAA 16 +antialiasing = none + +vsync = false + +[GUI] +# 1 is fully opaque +menu transparency = 0.84 + +# 0 - instantly, 1 - max. delay +tooltip delay = 0.2 + [General] # Camera field of view field of view = 55 @@ -104,7 +134,9 @@ underwater effect = true # Device name. Blank means default device = -# Volumes. Sfx and music volumes are both affected by the master volume +# Volumes. master volume affects all other volumes. master volume = 1.0 sfx volume = 1.0 music volume = 0.4 +footsteps volume = 0.6 +voice volume = 1.0 diff --git a/files/shadows/depthshadowcaster.material b/files/shadows/depthshadowcaster.material index 9ff51c5b17..f645cad019 100644 --- a/files/shadows/depthshadowcaster.material +++ b/files/shadows/depthshadowcaster.material @@ -1,67 +1,73 @@ vertex_program depth_shadow_caster_vs cg { - source depthshadowcaster.cg - profiles vs_1_1 arbvp1 - entry_point main_vp + source depthshadowcaster.cg + profiles vs_1_1 arbvp1 + entry_point main_vp - default_params - { - param_named_auto wvpMat worldviewproj_matrix - } + default_params + { + param_named_auto wvpMat worldviewproj_matrix + } } fragment_program depth_shadow_caster_ps cg { - source depthshadowcaster.cg - profiles ps_2_0 arbfp1 - entry_point main_fp + source depthshadowcaster.cg + profiles ps_2_0 arbfp1 + entry_point main_fp - default_params - { - } + default_params + { + } } fragment_program depth_shadow_caster_ps_noalpha cg { - source depthshadowcaster.cg - profiles ps_2_0 arbfp1 - entry_point main_fp_noalpha + source depthshadowcaster.cg + profiles ps_2_0 arbfp1 + entry_point main_fp_noalpha - default_params - { - } + default_params + { + } } material depth_shadow_caster { - technique - { - pass - { - vertex_program_ref depth_shadow_caster_vs - { - } + technique + { + pass + { + // force-disable fog (relevant for DirectX profiles below SM3 that always apply fixed function fog) + fog_override true - fragment_program_ref depth_shadow_caster_ps - { - } - } - } + vertex_program_ref depth_shadow_caster_vs + { + } + + fragment_program_ref depth_shadow_caster_ps + { + } + } + } } material depth_shadow_caster_noalpha { - technique - { - pass - { - vertex_program_ref depth_shadow_caster_vs - { - } + technique + { + pass + { + // force-disable fog (relevant for DirectX profiles below SM3 that always apply fixed function fog) + fog_override true - fragment_program_ref depth_shadow_caster_ps_noalpha - { - } - } - } + vertex_program_ref depth_shadow_caster_vs + { + } + + fragment_program_ref depth_shadow_caster_ps_noalpha + { + } + } + } } diff --git a/files/water/water.material b/files/water/water.material index 8b4ff96f56..a2a6b3e2dc 100644 --- a/files/water/water.material +++ b/files/water/water.material @@ -90,6 +90,31 @@ material Water } } } + + technique + { + scheme Fallback + pass + { + cull_hardware none + scene_blend alpha_blend + depth_write off + diffuse 0 0 0 1 + emissive 0.6 0.7 1.0 + ambient 0 0 0 + texture_unit + { + // texture names set via code + scale 0.1 0.1 + alpha_op_ex source1 src_manual src_current 0.7 + } + } + } + +} + +material Water_Fallback +{ technique { scheme Fallback diff --git a/libs/mangle/input/servers/ois_driver.cpp b/libs/mangle/input/servers/ois_driver.cpp index 2071b91ea6..b8e4f5eb9a 100644 --- a/libs/mangle/input/servers/ois_driver.cpp +++ b/libs/mangle/input/servers/ois_driver.cpp @@ -121,8 +121,7 @@ OISDriver::OISDriver(Ogre::RenderWindow *window, bool exclusive) OISDriver::~OISDriver() { // Delete the listener object - if(listener) - delete listener; + delete listener; if(inputMgr == NULL) return; @@ -146,3 +145,10 @@ bool OISDriver::isDown(int index) // TODO: Extend to mouse buttons as well return keyboard->isKeyDown((OIS::KeyCode)index); } + +void OISDriver::adjustMouseClippingSize(int width, int height) +{ + const OIS::MouseState &ms = mouse->getMouseState(); + ms.width = width; + ms.height = height; +} diff --git a/libs/mangle/input/servers/ois_driver.hpp b/libs/mangle/input/servers/ois_driver.hpp index ba780c39e6..81633542fb 100644 --- a/libs/mangle/input/servers/ois_driver.hpp +++ b/libs/mangle/input/servers/ois_driver.hpp @@ -31,6 +31,8 @@ namespace Mangle OISDriver(Ogre::RenderWindow *window, bool exclusive=true); ~OISDriver(); + void adjustMouseClippingSize(int width, int height); + void capture(); bool isDown(int index); /// Not currently supported. diff --git a/libs/openengine/bullet/CMotionState.cpp b/libs/openengine/bullet/CMotionState.cpp index 5ddef51752..d7746cbc52 100644 --- a/libs/openengine/bullet/CMotionState.cpp +++ b/libs/openengine/bullet/CMotionState.cpp @@ -11,6 +11,8 @@ namespace Physic { CMotionState::CMotionState(PhysicEngine* eng,std::string name) + : isPC(false) + , isNPC(true) { pEng = eng; tr.setIdentity(); diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index e7da9f0850..a94434e5b5 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -1,6 +1,7 @@ #include "physic.hpp" #include #include +#include #include //#include #include "CMotionState.h" @@ -10,6 +11,8 @@ #include "BtOgreGP.h" #include "BtOgreExtras.h" +#include + #define BIT(x) (1<<(x)) namespace OEngine { @@ -19,7 +22,8 @@ namespace Physic COL_NOTHING = 0, //setInternalGhostPairCallback( new btGhostPairCallback() ); - broadphase = new btDbvtBroadphase(pairCache); + broadphase = new btDbvtBroadphase(); // The world. dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration); @@ -253,6 +259,69 @@ namespace Physic delete mShapeLoader; } + void PhysicEngine::addHeightField(float* heights, + int x, int y, float yoffset, + float triSize, float sqrtVerts) + { + const std::string name = "HeightField_" + + boost::lexical_cast(x) + "_" + + boost::lexical_cast(y); + + // find the minimum and maximum heights (needed for bullet) + float minh; + float maxh; + for (int i=0; imaxh) maxh = h; + if (hsetUseDiamondSubdivision(true); + + btVector3 scl(triSize, triSize, 1); + hfShape->setLocalScaling(scl); + + CMotionState* newMotionState = new CMotionState(this,name); + + btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo(0,newMotionState,hfShape); + RigidBody* body = new RigidBody(CI,name); + body->collide = true; + body->getWorldTransform().setOrigin(btVector3( (x+0.5)*triSize*(sqrtVerts-1), (y+0.5)*triSize*(sqrtVerts-1), (maxh+minh)/2.f)); + + HeightField hf; + hf.mBody = body; + hf.mShape = hfShape; + + mHeightFieldMap [name] = hf; + + dynamicsWorld->addRigidBody(body,COL_WORLD,COL_WORLD|COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL); + } + + void PhysicEngine::removeHeightField(int x, int y) + { + const std::string name = "HeightField_" + + boost::lexical_cast(x) + "_" + + boost::lexical_cast(y); + + HeightField hf = mHeightFieldMap [name]; + + dynamicsWorld->removeRigidBody(hf.mBody); + delete hf.mShape; + delete hf.mBody; + } + RigidBody* PhysicEngine::createRigidBody(std::string mesh,std::string name,float scale) { //get the shape from the .nif @@ -269,27 +338,31 @@ namespace Physic RigidBody* body = new RigidBody(CI,name); body->collide = shape->collide; return body; + } void PhysicEngine::addRigidBody(RigidBody* body) { - if(body->collide) + if(body) { - dynamicsWorld->addRigidBody(body,COL_WORLD,COL_WORLD|COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL); - } - else - { - dynamicsWorld->addRigidBody(body,COL_WORLD,COL_NOTHING); - } - body->setActivationState(DISABLE_DEACTIVATION); - RigidBody* oldBody = RigidBodyMap[body->mName]; - if (oldBody != NULL) - { - dynamicsWorld->removeRigidBody(oldBody); - delete oldBody; - } + if(body->collide) + { + dynamicsWorld->addRigidBody(body,COL_WORLD,COL_WORLD|COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL); + } + else + { + dynamicsWorld->addRigidBody(body,COL_RAYCASTING,COL_RAYCASTING|COL_WORLD); + } + body->setActivationState(DISABLE_DEACTIVATION); + RigidBody* oldBody = RigidBodyMap[body->mName]; + if (oldBody != NULL) + { + dynamicsWorld->removeRigidBody(oldBody); + delete oldBody; + } - RigidBodyMap[body->mName] = body; + RigidBodyMap[body->mName] = body; + } } void PhysicEngine::removeRigidBody(std::string name) @@ -334,7 +407,7 @@ namespace Physic void PhysicEngine::stepSimulation(double deltaT) { - dynamicsWorld->stepSimulation(deltaT,1,1/50.); + dynamicsWorld->stepSimulation(deltaT,10, 1/60.0); if(isDebugCreated) { mDebugDrawer->step(); @@ -401,7 +474,7 @@ namespace Physic float d1 = 10000.; btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); - resultCallback1.m_collisionFilterMask = COL_WORLD; + resultCallback1.m_collisionFilterMask = COL_WORLD|COL_RAYCASTING; dynamicsWorld->rayTest(from, to, resultCallback1); if (resultCallback1.hasHit()) { @@ -430,7 +503,7 @@ namespace Physic std::vector< std::pair > PhysicEngine::rayTest2(btVector3& from, btVector3& to) { MyRayResultCallback resultCallback1; - resultCallback1.m_collisionFilterMask = COL_WORLD; + resultCallback1.m_collisionFilterMask = COL_WORLD|COL_RAYCASTING; dynamicsWorld->rayTest(from, to, resultCallback1); std::vector< std::pair > results = resultCallback1.results; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 8d177efda8..8115723201 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -15,6 +15,7 @@ class btSequentialImpulseConstraintSolver; class btCollisionDispatcher; class btDiscreteDynamicsWorld; class btKinematicCharacterController; +class btHeightfieldTerrainShape; namespace BtOgre { @@ -115,6 +116,12 @@ namespace Physic bool collide; }; + struct HeightField + { + btHeightfieldTerrainShape* mShape; + RigidBody* mBody; + }; + /** * The PhysicEngine class contain everything which is needed for Physic. * It's needed that Ogre Resources are set up before the PhysicEngine is created. @@ -140,6 +147,18 @@ namespace Physic */ RigidBody* createRigidBody(std::string mesh,std::string name,float scale); + /** + * Add a HeightField to the simulation + */ + void addHeightField(float* heights, + int x, int y, float yoffset, + float triSize, float sqrtVerts); + + /** + * Remove a HeightField from the simulation + */ + void removeHeightField(int x, int y); + /** * Add a RigidBody to the simulation */ @@ -228,6 +247,9 @@ namespace Physic //the NIF file loader. BulletShapeLoader* mShapeLoader; + typedef std::map HeightFieldContainer; + HeightFieldContainer mHeightFieldMap; + typedef std::map RigidBodyContainer; RigidBodyContainer RigidBodyMap; diff --git a/libs/openengine/bullet/pmove.cpp b/libs/openengine/bullet/pmove.cpp new file mode 100644 index 0000000000..591f1869f8 --- /dev/null +++ b/libs/openengine/bullet/pmove.cpp @@ -0,0 +1,2095 @@ +/* +This source file is a *modified* version of bg_pmove.c from the Quake 3 Arena source code, +which was released under the GNU GPL (v2) in 2005. +Quake 3 Arena is copyright (C) 1999-2005 Id Software, Inc. +*/ + + +#include "pmove.h" + + + +//#include "bprintf.h" + +//#include "..\..\ESMParser\ESMParser\CELL.h" + +//#include "GameTime.h" + +//#include "Object.h" + +//#include "Sound.h" + +//#include "..\..\ESMParser\ESMParser\SNDG.h" +//#include "..\..\ESMParser\ESMParser\SOUN.h" + +#include + +//SceneInstance* global_lastscene = NULL; + +// Forward declaration: +void PM_AirMove(); + +static playerMove* pm = NULL; + +//extern std::map ExtCellLookup; + +static struct playermoveLocal +{ + playermoveLocal() : frametime(1.0f / 20.0f), groundPlane(true), walking(true), msec(50) + { + forward = Ogre::Vector3(0.0f, 0.0f, 0.0f); + right = Ogre::Vector3(0.0f, 0.0f, 0.0f); + up = Ogre::Vector3(0.0f, 0.0f, 0.0f); + + previous_origin = Ogre::Vector3(0.0f, 0.0f, 0.0f); + previous_velocity = Ogre::Vector3(0.0f, 0.0f, 0.0f); + } + + traceResults groundTrace; + + //SceneInstance* scene; + + float frametime; // in seconds (usually something like 0.01f) + float impactSpeed; + + Ogre::Vector3 forward; + Ogre::Vector3 right; + Ogre::Vector3 up; + + int msec; + + Ogre::Vector3 previous_origin, previous_velocity; + + int previous_waterlevel; // the waterlevel before this pmove + + bool groundPlane; // if we're standing on a groundplane this frame + + bool walking; + int waterHeight; + bool hasWater; + bool isInterior; + //Object* traceObj; + +} pml; + +static inline void PM_ClipVelocity(const Ogre::Vector3& in, const Ogre::Vector3& normal, Ogre::Vector3& out, const float overbounce) +{ + float backoff; + //float change; + //int i; + + // backoff = in dot normal + //backoff = DotProduct (in, normal); + backoff = in.dotProduct(normal); + + if ( backoff < 0 ) + backoff *= overbounce; + else + backoff /= overbounce; + + // change = normal * backoff + // out = in - change + /*for ( i=0 ; i<3 ; i++ ) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + + }*/ + float changex = normal.x * backoff; + out.x = in.x - changex; + float changey = normal.y * backoff; + out.y = in.y - changey; + float changez = normal.z * backoff; + out.z = in.z - changez; +} + +float VectorNormalize2( const Ogre::Vector3& v, Ogre::Vector3& out) +{ + float length, ilength; + + length = v.x * v.x+ v.y * v.y + v.z * v.z; + length = sqrt(length); + + if (length) + { +#ifndef Q3_VM // bk0101022 - FPE related +// assert( ((Q_fabs(v[0])!=0.0f) || (Q_fabs(v[1])!=0.0f) || (Q_fabs(v[2])!=0.0f)) ); +#endif + ilength = 1 / length; + out.x= v.x * ilength; + out.y = v.y * ilength; + out.z = v.z * ilength; + } else + { +#ifndef Q3_VM // bk0101022 - FPE related +// assert( ((Q_fabs(v[0])==0.0f) && (Q_fabs(v[1])==0.0f) && (Q_fabs(v[2])==0.0f)) ); +#endif + //VectorClear( out ); + out.x = 0; out.y = 0; out.z = 0; + } + + return length; + +} + + +float VectorNormalize(Ogre::Vector3& out) +{ + float length, ilength; + + length = out.x * out.x + out.y * out.y + out.z * out.z; + length = sqrt(length); + + if (length) + { +#ifndef Q3_VM // bk0101022 - FPE related +// assert( ((Q_fabs(v[0])!=0.0f) || (Q_fabs(v[1])!=0.0f) || (Q_fabs(v[2])!=0.0f)) ); +#endif + ilength = 1 / length; + out.x = out.x * ilength; + out.y = out.y * ilength; + out.z = out.z * ilength; + } + + return length; + +} + +/* +================== +PM_SlideMove + +Returns qtrue if the velocity was clipped in some way +================== +*/ + +bool PM_SlideMove( bool gravity ) +{ + int bumpcount, numbumps; + Ogre::Vector3 dir; + float d; + int numplanes; + Ogre::Vector3 planes[MAX_CLIP_PLANES]; + Ogre::Vector3 primal_velocity; + Ogre::Vector3 clipVelocity; + int i, j, k; + struct traceResults trace; + Ogre::Vector3 end; + float time_left; + float into; + Ogre::Vector3 endVelocity; + Ogre::Vector3 endClipVelocity; + + numbumps = 4; + + // primal_velocity = pm->ps->velocity + //VectorCopy (pm->ps->velocity, primal_velocity); + primal_velocity = pm->ps.velocity; + + if ( gravity ) + { + // endVelocity = pm->ps->velocity - vec3(0, 0, pm->ps->gravity * pml.frametime) + //VectorCopy( pm->ps->velocity, endVelocity ); + endVelocity = pm->ps.velocity; + //endVelocity[2] -= pm->ps->gravity * pml.frametime; + endVelocity.z -= pm->ps.gravity * pml.frametime; + + // pm->ps->velocity = avg(pm->ps->velocity.z, endVelocity.z) + //pm->ps->velocity[2] = ( pm->ps->velocity[2] + endVelocity[2] ) * 0.5; + pm->ps.velocity.z= (pm->ps.velocity.z + endVelocity.z) * 0.5f; + + //primal_velocity[2] = endVelocity[2]; + primal_velocity.z = endVelocity.z; + + if ( pml.groundPlane ) + // slide along the ground plane + //PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity, OVERCLIP ); + PM_ClipVelocity(pm->ps.velocity, pml.groundTrace.planenormal, pm->ps.velocity, OVERCLIP); + } + + time_left = pml.frametime; + + // never turn against the ground plane + if ( pml.groundPlane ) + { + numplanes = 1; + + // planes[0] = pml.groundTrace.plane.normal + //VectorCopy( pml.groundTrace.plane.normal, planes[0] ); + planes[0] = pml.groundTrace.planenormal; + } else + numplanes = 0; + + // never turn against original velocity + VectorNormalize2( pm->ps.velocity, planes[numplanes] ); + numplanes++; + + for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) + { + + // calculate position we are trying to move to + //VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); + end = pm->ps.origin + pm->ps.velocity * time_left; + + // see if we can make it there + //pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask); + //tracefunc(&trace, *(const D3DXVECTOR3* const)&(pm->ps.origin), *(const D3DXVECTOR3* const)&(end), *(const D3DXVECTOR3* const)&(pm->ps.velocity), 0, pml.traceObj); + newtrace(&trace, pm->ps.origin, end, halfExtents, Ogre::Math::DegreesToRadians (pm->ps.viewangles.y), pm->isInterior, pm->mEngine); + + if (trace.allsolid) + { + // entity is completely trapped in another solid + //pm->ps->velocity[2] = 0; // don't build up falling damage, but allow sideways acceleration + pm->ps.velocity.z = 0; + return true; + } + + if (trace.fraction > 0) + // actually covered some distance + //VectorCopy (trace.endpos, pm->ps->origin); + pm->ps.origin = trace.endpos; + + if (trace.fraction == 1) + break; // moved the entire distance + + // save entity for contact8 + //PM_AddTouchEnt( trace.entityNum ); + + time_left -= time_left * trace.fraction; + + if (numplanes >= MAX_CLIP_PLANES) + { + // this shouldn't really happen + //VectorClear( pm->ps->velocity ); + pm->ps.velocity = Ogre::Vector3(0,0,0); + return true; + } + + // + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + // + for ( i = 0 ; i < numplanes ; i++ ) + { + if (trace.planenormal.dotProduct(planes[i]) > 0.99) //OGRE::VECTOR3 ? + //if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) + { + // pm->ps->velocity += (trace.plane.normal + pm->ps->velocity) + //VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity ); + pm->ps.velocity = (trace.planenormal + pm->ps.velocity); + break; + } + } + + if ( i < numplanes ) + continue; + + //VectorCopy (trace.plane.normal, planes[numplanes]); + planes[numplanes] = trace.planenormal; + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for ( i = 0 ; i < numplanes ; i++ ) + { + //into = DotProduct( pm->ps->velocity, planes[i] ); + into = pm->ps.velocity.dotProduct(planes[i]); + if ( into >= 0.1 ) + continue; // move doesn't interact with the plane + + // see how hard we are hitting things + if ( -into > pml.impactSpeed ) + pml.impactSpeed = -into; + + // slide along the plane + //PM_ClipVelocity (pm->ps->velocity, planes[i], clipVelocity, OVERCLIP ); + PM_ClipVelocity(pm->ps.velocity, planes[i], clipVelocity, OVERCLIP); + + // slide along the plane + PM_ClipVelocity (endVelocity, planes[i], endClipVelocity, OVERCLIP ); + + // see if there is a second plane that the new move enters + for ( j = 0 ; j < numplanes ; j++ ) + { + if ( j == i ) + continue; + + if (clipVelocity.dotProduct(planes[j]) >= 0.1) + //if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) + continue; // move doesn't interact with the plane + + // try clipping the move to the plane + PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP ); + PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); + + // see if it goes back into the first clip plane + if (clipVelocity.dotProduct(planes[i]) >= 0) + //if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) + continue; + + + // slide the original velocity along the crease + //dProduct (planes[i], planes[j], dir); + dir = planes[i].crossProduct(planes[j]) ; + + //VectorNormalize( dir ); + //D3DXVec3Normalize( (D3DXVECTOR3* const)&dir, (const D3DXVECTOR3* const)&dir); + VectorNormalize(dir); + + //d = DotProduct( dir, pm->ps->velocity ); + d = dir.dotProduct(pm->ps.velocity); + + //VectorScale( dir, d, clipVelocity ); + clipVelocity = dir * d; + + //CrossProduct (planes[i], planes[j], dir); + dir = planes[i].crossProduct(planes[j]) ; + + + //VectorNormalize( dir ); + //D3DXVec3Normalize( (D3DXVECTOR3* const)&dir, (const D3DXVECTOR3* const)&dir); + VectorNormalize(dir); + + //d = DotProduct( dir, endVelocity ); + d = dir.dotProduct(endVelocity); + + //VectorScale( dir, d, endClipVelocity ); + endClipVelocity = dir * d; + + // see if there is a third plane the the new move enters + for ( k = 0 ; k < numplanes ; k++ ) + { + if ( k == i || k == j ) + continue; + + if (clipVelocity.dotProduct(planes[k]) >= 0.1) + //if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) + continue; // move doesn't interact with the plane + + // stop dead at a tripple plane interaction + //VectorClear( pm->ps->velocity ); + //printf("Stop dead at a triple plane interaction\n"); + pm->ps.velocity = Ogre::Vector3(0,0,0); + return true; + } + } + + // if we have fixed all interactions, try another move + //VectorCopy( clipVelocity, pm->ps->velocity ); + pm->ps.velocity = clipVelocity; + + //VectorCopy( endClipVelocity, endVelocity ); + endVelocity = endClipVelocity; + break; + } + } + + if ( gravity ) + //VectorCopy( endVelocity, pm->ps->velocity ); + pm->ps.velocity = endVelocity; + + // don't change velocity if in a timer (FIXME: is this correct?) + if ( pm->ps.pm_time ) + //VectorCopy( primal_velocity, pm->ps->velocity ); + pm->ps.velocity = primal_velocity; + + //return ( (qboolean)(bumpcount != 0) ); + return bumpcount != 0; +} + +/* +================== +PM_StepSlideMove + +================== +*/ +int PM_StepSlideMove( bool gravity ) +{ + Ogre::Vector3 start_o, start_v; + Ogre::Vector3 down_o, down_v; + traceResults trace; +// float down_dist, up_dist; +// vec3_t delta, delta2; + Ogre::Vector3 up, down; + float stepSize; + + //std::cout << "StepSlideMove\n"; + // start_o = pm->ps->origin + //VectorCopy (pm->ps->origin, start_o); + start_o = pm->ps.origin; + + // start_v = pm->ps->velocity + //VectorCopy (pm->ps->velocity, start_v); + start_v = pm->ps.velocity; + + if ( PM_SlideMove( gravity ) == false ) + return 1; // we got exactly where we wanted to go first try + + + // down = start_o - vec3(0, 0, STEPSIZE) + //VectorCopy(start_o, down); + down = start_o; + down.z -= STEPSIZE; + + //pm->trace (&trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); + //tracefunc(&trace, start_o, down, , 0, pml.scene); + //tracefunc(&trace, *(const D3DXVECTOR3* const)&start_o, *(const D3DXVECTOR3* const)&down, D3DXVECTOR3(0.0f, -STEPSIZE, 0.0f), 0, pml.traceObj); + newtrace(&trace, start_o, down, halfExtents, Ogre::Math::DegreesToRadians(pm->ps.viewangles.y), pm->isInterior, pm->mEngine); + + // up = vec3(0, 0, 1) + //VectorSet(up, 0, 0, 1); + up = Ogre::Vector3(0.0f, 0.0f, 1.0f); + + // never step up when you still have up velocity + //if ( pm->ps->velocity[2] > 0 && (trace.fraction == 1.0 || DotProduct(trace.plane.normal, up) < 0.7)) + if (pm->ps.velocity.z > 0 && ( + trace.fraction == 1.0 || trace.planenormal.dotProduct(up) < 0.7 + ) ) + return 2; + + // down_o = pm->ps->origin + //VectorCopy (pm->ps->origin, down_o); + down_o = pm->ps.origin; + + // down_v = pm->ps->velocity + //VectorCopy (pm->ps->velocity, down_v); + down_v = pm->ps.velocity; + + // up = start_o + vec3(0, 0, STEPSIZE) + //VectorCopy (start_o, up); + up = start_o; + //up[2] += STEPSIZE; + up.z += STEPSIZE; + + // test the player position if they were a stepheight higher + //pm->trace (&trace, start_o, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask); + //tracefunc(&trace, *(const D3DXVECTOR3* const)&start_o, *(const D3DXVECTOR3* const)&up, D3DXVECTOR3(0.0f, STEPSIZE, 0.0f), 0, pml.traceObj); + newtrace(&trace, start_o, up, halfExtents, Ogre::Math::DegreesToRadians(pm->ps.viewangles.y), pm->isInterior, pm->mEngine); + if ( trace.allsolid ) + { + //if ( pm->debugLevel ) + //Com_Printf("%i:bend can't step\n", c_pmove); + //bprintf("bend can't step\n"); + return 3; // can't step up + } + + //stepSize = trace.endpos[2] - start_o[2]; + stepSize = trace.endpos.z - start_o.z; + + // try slidemove from this position + //VectorCopy (trace.endpos, pm->ps->origin); // pm->ps->origin = trace.endpos + pm->ps.origin = trace.endpos; + //VectorCopy (start_v, pm->ps->velocity); // pm->ps->velocity = start_v + pm->ps.velocity = start_v; + + PM_SlideMove( gravity ); + + // push down the final amount + + // down = pm->ps->origin - vec3(0, 0, stepSize) + //VectorCopy (pm->ps->origin, down); + down = pm->ps.origin; + //down[2] -= stepSize; + down.z -= stepSize; + + + //pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); + //tracefunc(&trace, *(const D3DXVECTOR3* const)&(pm->ps.origin), *(const D3DXVECTOR3* const)&down, D3DXVECTOR3(0.0f, -STEPSIZE, 0.0f), 0, pml.traceObj); + newtrace(&trace, pm->ps.origin, down, halfExtents, Ogre::Math::DegreesToRadians(pm->ps.viewangles.y), pm->isInterior, pm->mEngine); + if ( !trace.allsolid ) + //VectorCopy (trace.endpos, pm->ps->origin); + pm->ps.origin = trace.endpos; + + if ( trace.fraction < 1.0 ) + //PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + PM_ClipVelocity(pm->ps.velocity, trace.planenormal, pm->ps.velocity, OVERCLIP); + + { + // use the step move + float delta; + + //delta = pm->ps->origin[2] - start_o[2]; + delta = pm->ps.origin.z - start_o.z; + if ( delta > 2 ) + { + pm->ps.counter = 10; + + /* + if (gravity) + printf("g on: %f ", delta); + else + printf("g off: %f ", delta); + + if ( delta < 7 ) + printf("stepped 3 < x < 7\n"); + //PM_AddEvent( EV_STEP_4 ); + else if ( delta < 11 ) + printf("stepped 7 < x < 11\n"); + //PM_AddEvent( EV_STEP_8 ); + else if ( delta < 15 ) + printf("stepped 11 < x < 15\n"); + //PM_AddEvent( EV_STEP_12 ); + else + printf("stepped 15+\n"); + //PM_AddEvent( EV_STEP_16 ); + */ + } + /*if ( pm->debugLevel ) + Com_Printf("%i:stepped\n", c_pmove);*/ + } + + return 4; +} + +void PM_Friction(void) +{ + + Ogre::Vector3 vec; + float* vel; + float speed, newspeed, control; + float drop; + + vel = &(pm->ps.velocity.x); + + // vec = vel + //VectorCopy( vel, vec ); + vec = pm->ps.velocity; + + if ( pml.walking ) + //vec[2] = 0; // ignore slope movement + vec.z = 0; + + //speed = VectorLength(vec); + speed = vec.length(); + if (speed < 1) + { + vel[0] = 0; + vel[1] = 0; // allow sinking underwater + // FIXME: still have z friction underwater? + //bprintf("Static friction (vec = [%f, %f, %f]) (vec.length = %f)\n", vec.x, vec.y, vec.z, speed); + return; + } + + drop = 0; + + // apply ground friction + if ( pm->ps.waterlevel <= 1 ) + { + if ( pml.walking )//&& !(pml.groundTrace.surfaceFlags & SURF_SLICK) ) + { + // if getting knocked back, no friction + //if ( ! (pm->ps->pm_flags & PMF_TIME_KNOCKBACK) ) + { + control = (speed < pm_stopspeed) ? pm_stopspeed : speed; + drop += control * pm_friction * pml.frametime; + } + } + } + + // apply water friction even if just wading + if ( pm->ps.waterlevel ) + drop += speed * pm_waterfriction * pm->ps.waterlevel * pml.frametime; + + // apply flying friction + /*if ( pm->ps->powerups[PW_FLIGHT]) + drop += speed * pm_flightfriction * pml.frametime; + + if ( pm->ps->pm_type == PM_SPECTATOR) + drop += speed * pm_spectatorfriction * pml.frametime;*/ + if (pm->ps.move_type == PM_SPECTATOR) + drop += speed * pm_flightfriction * pml.frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + + newspeed /= speed; + + // vel *= newspeed + vel[0] = vel[0] * newspeed; + vel[1] = vel[1] * newspeed; + vel[2] = vel[2] * newspeed; +} + +float PM_CmdScale(playerMove::playercmd* const cmd) +{ + int max; + float total; + float scale; + + max = abs( cmd->forwardmove ); + if ( abs( cmd->rightmove ) > max ) + max = abs( cmd->rightmove ); + + if ( abs( cmd->upmove ) > max ) + max = abs( cmd->upmove ); + + if ( !max ) + return 0; + + total = sqrtf( (const float)(cmd->forwardmove * cmd->forwardmove + + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove) ); + scale = (float)pm->ps.speed * max / ( 127.0f * total ); + if(pm->ps.move_type == PM_NOCLIP) + scale *= 10; + + return scale; +} + +static void PM_Accelerate( Ogre::Vector3& wishdir, float wishspeed, float accel ) +{ +// int i; + float addspeed, accelspeed, currentspeed; + + // currentspeed = pm->ps->velocity dot wishdir + //currentspeed = DotProduct (pm->ps->velocity, wishdir); + currentspeed = pm->ps.velocity.dotProduct(wishdir); + + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) + return; + + accelspeed = accel * pml.frametime * wishspeed; + + // Clamp accelspeed at addspeed + if (accelspeed > addspeed) + accelspeed = addspeed; + + // pm->ps->velocity += accelspeed * wishdir + //for (i=0 ; i<3 ; i++) + //pm->ps->velocity[i] += accelspeed * wishdir[i]; + pm->ps.velocity += (wishdir * accelspeed); +} + +static bool PM_CheckJump(void) +{ + //if ( pm->ps->pm_flags & PMF_RESPAWNED ) + //return qfalse; // don't allow jump until all buttons are up + + if ( pm->cmd.upmove < 10 ) + // not holding jump + return false; + + pm->cmd.upmove = 0; + + // must wait for jump to be released + /*if ( pm->ps->pm_flags & PMF_JUMP_HELD ) + { + // clear upmove so cmdscale doesn't lower running speed + pm->cmd.upmove = 0; + return false; + }*/ + + pml.groundPlane = false; // jumping away + pml.walking = false; + //pm->ps->pm_flags |= PMF_JUMP_HELD; + + pm->ps.groundEntityNum = ENTITYNUM_NONE; + pm->ps.velocity.z = JUMP_VELOCITY; + pm->ps.bSnap = false; + //PM_AddEvent( EV_JUMP ); + + /*if ( pm->cmd.forwardmove >= 0 ) + { + PM_ForceLegsAnim( LEGS_JUMP ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + PM_ForceLegsAnim( LEGS_JUMPB ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + }*/ + + return true; +} + +static void PM_WaterMove( playerMove* const pm ) +{ + //int i; + //vec3_t wishvel; + Ogre::Vector3 wishvel; + float wishspeed; + //vec3_t wishdir; + Ogre::Vector3 wishdir; + float scale; + float vel; + + pm->ps.bSnap = false; + + /*if ( PM_CheckWaterJump() ) + { + PM_WaterJumpMove(); + return; + }*/ +#if 0 + // jump = head for surface + if ( pm->cmd.upmove >= 10 ) { + if (pm->ps->velocity[2] > -300) { + if ( pm->watertype == CONTENTS_WATER ) { + pm->ps->velocity[2] = 100; + } else if (pm->watertype == CONTENTS_SLIME) { + pm->ps->velocity[2] = 80; + } else { + pm->ps->velocity[2] = 50; + } + } + } +#endif + PM_Friction (); + + if (pm->cmd.forwardmove || pm->cmd.rightmove) + { + //NEEDS TO BE REWRITTEN FOR OGRE TIME--------------------------------------------------- + /* + static const TimeTicks footstep_duration = GetTimeFreq(); // make each splash last 1.0s + static TimeTicks lastStepTime = 0; + const TimeTicks thisStepTime = GetTimeQPC(); + static bool lastWasLeft = false; + if (thisStepTime > lastStepTime) + { + if (pm->cmd.ducking) + lastStepTime = thisStepTime + footstep_duration * 2; // splashes while ducking are twice as slow + else + lastStepTime = thisStepTime + footstep_duration; + + lastWasLeft = !lastWasLeft; + */ + //-----------------jhooks1 + + /* + namestruct defaultCreature; + const SNDG* const sndg = SNDG::GetFromMap(defaultCreature, lastWasLeft ? SNDG::r_swim : SNDG::l_swim); + if (sndg) + { + const namestruct& SOUNID = sndg->soundID; + const SOUN* const soun = SOUN::GetSound(SOUNID); + if (soun) + { + PlaySound2D(soun->soundFilename, soun->soundData->GetVolumeFloat() ); + } + }*/ + //Sound, ignore for now -- jhooks1 + //} + } + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if ( !scale ) + { + /*wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = -60; // sink towards bottom + */ + wishvel.x = 0; + wishvel.z = -60; + wishvel.y = 0; + } + else + { + /*for (i=0 ; i<3 ; i++) + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove;*/ + wishvel = pml.forward * scale * pm->cmd.forwardmove + pml.right * scale * pm->cmd.rightmove; + + //wishvel[2] += scale * pm->cmd.upmove; + wishvel.z += pm->cmd.upmove * scale; + } + + //VectorCopy (wishvel, wishdir); + wishdir = wishvel; + wishspeed = VectorNormalize(wishdir); + + if ( wishspeed > pm->ps.speed * pm_swimScale ) + wishspeed = pm->ps.speed * pm_swimScale; + + PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate); + + // make sure we can go up slopes easily under water + //if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) + if (pml.groundPlane && pm->ps.velocity.dotProduct(pml.groundTrace.planenormal) < 0.0f) + { + //vel = VectorLength(pm->ps->velocity); + vel = pm->ps.velocity.length(); + + // slide along the ground plane + //PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity, OVERCLIP ); + PM_ClipVelocity(pm->ps.velocity, pml.groundTrace.planenormal, pm->ps.velocity, OVERCLIP); + + VectorNormalize(pm->ps.velocity); + //VectorScale(pm->ps->velocity, vel, pm->ps->velocity); + pm->ps.velocity = pm->ps.velocity * vel; + } + + PM_SlideMove( false ); +} + +/* +=================== +PM_WalkMove + +=================== +*/ +static void PM_WalkMove( playerMove* const pmove ) +{ +// int i; + Ogre::Vector3 wishvel; + float fmove, smove; + Ogre::Vector3 wishdir; + float wishspeed; + float scale; + playerMove::playercmd cmd; + float accelerate; + float vel; + //pm->ps.gravity = 4000; + + if ( pm->ps.waterlevel > 2 && //DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) + pml.forward.dotProduct(pml.groundTrace.planenormal) > 0.0f) + { + // begin swimming + PM_WaterMove(pmove); + return; + } + + + if ( PM_CheckJump () ) + { + + // jumped away + if ( pm->ps.waterlevel > 1 ) + PM_WaterMove(pmove); + else + PM_AirMove(); + //printf("Jumped away\n"); + return; + } + + // Footsteps time + if (pmove->cmd.forwardmove || pmove->cmd.rightmove) + { + bool step_underwater = false; + //if (pmove->traceObj) + //{ + + + //jhooks1 - Water handling, deal with later + + + + if (pmove->hasWater) + { + if (pmove->hasWater ) + { + const float waterHeight = pmove->waterHeight; + const float waterSoundStepHeight = waterHeight + halfExtents.y; + if (pmove->ps.origin.y < waterSoundStepHeight) + step_underwater = true; + } + } + //} + + /* + static const TimeTicks footstep_duration = GetTimeFreq() / 2; // make each footstep last 500ms + static TimeTicks lastStepTime = 0; + const TimeTicks thisStepTime = GetTimeQPC(); + static bool lastWasLeft = false; + if (thisStepTime > lastStepTime) + { + if (pmove->cmd.ducking) + lastStepTime = thisStepTime + footstep_duration * 2; // footsteps while ducking are twice as slow + else + lastStepTime = thisStepTime + footstep_duration; + + lastWasLeft = !lastWasLeft; + */ + + if (step_underwater) + { + /* + const namestruct ns(lastWasLeft ? "FootWaterRight" : "FootWaterLeft"); + const SOUN* const soun = SOUN::GetSound(ns); + if (soun) + { + PlaySound2D(soun->soundFilename, soun->soundData->GetVolumeFloat() ); + }*/ + } + else + { + /* + namestruct defaultCreature; + const SNDG* const sndg = SNDG::GetFromMap(defaultCreature, lastWasLeft ? SNDG::r_foot : SNDG::l_foot); + if (sndg) + { + const namestruct& SOUNID = sndg->soundID; + const SOUN* const soun = SOUN::GetSound(SOUNID); + if (soun) + { + PlaySound2D(soun->soundFilename, soun->soundData->GetVolumeFloat() ); + } + }*/ + } + } + + + PM_Friction (); + + + //bprintf("vel: (%f, %f, %f)\n", pm->ps.velocity.x, pm->ps.velocity.y, pm->ps.velocity.z); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + //PM_SetMovementDir(); + + // project moves down to flat plane + //pml.forward[2] = 0; + pml.forward.z = 0; + + //pml.right[2] = 0; + pml.right.z = 0; + //std::cout << "Further down" << pm->ps.velocity << "\n"; + + + // project the forward and right directions onto the ground plane + PM_ClipVelocity (pml.forward, pml.groundTrace.planenormal, pml.forward, OVERCLIP ); + PM_ClipVelocity (pml.right, pml.groundTrace.planenormal, pml.right, OVERCLIP ); + //std::cout << "Clip velocity" << pm->ps.velocity << "\n"; + // + + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + //pml.forward = pml.forward.normalise(); + //pml.right = pml.right.normalise(); + //std::cout << "forward2" << pml.forward << "\n"; + //std::cout << "right2" << pml.right << "\n"; + + + // wishvel = (pml.forward * fmove) + (pml.right * smove); + //for ( i = 0 ; i < 3 ; i++ ) + //wishvel[i] = pml.forward[i] * fmove + pml.right[i] * smove; + wishvel = pml.forward * fmove + pml.right * smove; + + + //bprintf("f: (%f, %f, %f), s: (%f, %f, %f)\n", fmove, smove); + + + // when going up or down slopes the wish velocity should Not be zero +// wishvel[2] = 0; + + // wishdir = wishvel + //VectorCopy (wishvel, wishdir); + //wishvel = wishdir; + wishdir = wishvel; + + wishspeed = VectorNormalize(wishdir); + //std::cout << "Wishspeed: " << wishspeed << "\n"; + wishspeed *= scale; + //std::cout << "Wishspeed scaled:" << wishspeed << "\n"; + + // clamp the speed lower if ducking + if ( pm->cmd.ducking ) + if ( wishspeed > pm->ps.speed * pm_duckScale ) + wishspeed = pm->ps.speed * pm_duckScale; + + // clamp the speed lower if wading or walking on the bottom + if ( pm->ps.waterlevel ) + { + float waterScale; + + waterScale = pm->ps.waterlevel / 3.0f; + waterScale = 1.0f - ( 1.0f - pm_swimScale ) * waterScale; + if ( wishspeed > pm->ps.speed * waterScale ) + wishspeed = pm->ps.speed * waterScale; + } + + // when a player gets hit, they temporarily lose + // full control, which allows them to be moved a bit + //if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) + //accelerate = pm_airaccelerate; + //else + accelerate = pm_accelerate; + + + PM_Accelerate (wishdir, wishspeed, accelerate); + //std::cout << "Velocityafter: " << pm->ps.velocity << "\n"; + + //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]); + //Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity)); + + //if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) + //pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + //else + //{ + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + //} + + //vel = VectorLength(pm->ps->velocity); + vel = pm->ps.velocity.length(); + //std::cout << "The length" << vel << "\n"; + + // slide along the ground plane + PM_ClipVelocity (pm->ps.velocity, pml.groundTrace.planenormal, + pm->ps.velocity, OVERCLIP ); + //std::cout << "Velocity clipped" << pm->ps.velocity << "\n"; + + // don't decrease velocity when going up or down a slope + VectorNormalize(pm->ps.velocity); + //pm->ps.velocity = pm->ps.velocity.normalise(); + + //std::cout << "Final:" << pm->ps.velocity << "\n"; + //VectorScale(pm->ps->velocity, vel, pm->ps->velocity); + pm->ps.velocity = pm->ps.velocity * vel; + + // don't do anything if standing still + //if (!pm->ps->velocity[0] && !pm->ps->velocity[1]) + if (!pm->ps.velocity.x && !pm->ps.velocity.z) + return; + + PM_StepSlideMove( false ); + + //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); + + +} + +void PM_UpdateViewAngles( playerMove::playerStruct* const ps, playerMove::playercmd* const cmd ) +{ + short temp; + int i; + + //while(1); + + //if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION) + //return; // no view changes at all + + //if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) + //return; // no view changes at all + + // circularly clamp the angles with deltas + //bprintf("View angles: %i, %i, %i\n", cmd->angles[0], cmd->angles[1], cmd->angles[2]); + for (i = 0 ; i < 3 ; i++) + { + temp = cmd->angles[i];// + ps->delta_angles[i]; + //if ( i == PITCH ) + { + // don't let the player look up or down more than 90 degrees + /*if ( temp > 16000 ) + { + ps->delta_angles[i] = 16000 - cmd->angles[i]; + temp = 16000; + } + else if ( temp < -16000 ) + { + ps->delta_angles[i] = -16000 - cmd->angles[i]; + temp = -16000; + }*/ + } + (&(ps->viewangles.x) )[i] = SHORT2ANGLE(temp); + //cmd->angles[i] += ps->delta_angles[i]; + } + //ps->delta_angles[0] = ps->delta_angles[1] = ps->delta_angles[2] = 0; + +} + +void AngleVectors( const Ogre::Vector3& angles, Ogre::Vector3* const forward, Ogre::Vector3* const right, Ogre::Vector3* const up) +{ + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + //angle = angles[YAW] * (M_PI*2 / 360); + angle = angles.x * (M_PI * 2.0f / 360.0f); + sp = sinf(angle); + cp = cosf(angle); + + //angle = angles[PITCH] * (M_PI*2 / 360); + angle = angles.y * (-M_PI * 2.0f / 360.0f); + sy = sinf(angle); + cy = cosf(angle); + + //angle = angles[ROLL] * (M_PI*2 / 360); + angle = angles.z * (M_PI * 2.0f / 360.0f); + sr = sinf(angle); + cr = cosf(angle); + + if (forward) + { + forward->x = cp * cy; + forward->y = cp * sy; + forward->z = -sp; + } + if (right) + { + right->x = (-1 * sr * sp * cy + -1 * cr * -sy); + right->y = (-1 * sr * sp * sy + -1 * cr * cy); + right->z = 0; + } + if (up) + { + up->x =(cr * sp * cy + -sr * -sy); + up->y=(cr * sp * sy + -sr * cy); + up->z = cr * cp; + } + +} + +void PM_GroundTraceMissed() +{ + traceResults trace; + Ogre::Vector3 point; + //std::cout << "Ground trace missed\n"; + // we just transitioned into freefall + //if ( pm->debugLevel ) + //Com_Printf("%i:lift\n", c_pmove); + + // if they aren't in a jumping animation and the ground is a ways away, force into it + // if we didn't do the trace, the player would be backflipping down staircases + //VectorCopy( pm->ps->origin, point ); + point = pm->ps.origin; + //point[2] -= 64; + point.z -= 32; + + //pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + //tracefunc(&trace, *(const D3DXVECTOR3* const)&(pm->ps.origin), *(const D3DXVECTOR3* const)&point, D3DXVECTOR3(0.0f, -64.0f, 0.0f), 0, pml.traceObj); + newtrace(&trace, pm->ps.origin, point, halfExtents, Ogre::Math::DegreesToRadians(pm->ps.viewangles.y), pm->isInterior, pm->mEngine); + //It hit the ground below + if ( trace.fraction < 1.0 && pm->ps.origin.z > trace.endpos.z) + { + pm->ps.origin = trace.endpos; + pml.walking = true; + pml.groundPlane = true; + pm->ps.groundEntityNum = trace.entityNum; + + } + else{ + pm->ps.groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = false; + pml.walking = false; + pm->ps.bSnap = false; + } + + +} + +static bool PM_CorrectAllSolid(traceResults* const trace) +{ + int i, j, k; + Ogre::Vector3 point; + + //if ( pm->debugLevel ) + //Com_Printf("%i:allsolid\n", c_pmove); + //bprintf("allsolid\n"); + + // jitter around + for (i = -1; i <= 1; i++) + { + for (j = -1; j <= 1; j++) + { + for (k = -1; k <= 1; k++) + { + //VectorCopy(pm->ps->origin, point); + point = pm->ps.origin; + + /*point[0] += (float) i; + point[1] += (float) j; + point[2] += (float) k;*/ + point += Ogre::Vector3( (const float)i, (const float)j, (const float)k); + + //pm->trace (trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + //tracefunc(trace, *(const D3DXVECTOR3* const)&point, *(const D3DXVECTOR3* const)&point, D3DXVECTOR3(0.0f, 0.0f, 0.0f), 0, pml.traceObj); + newtrace(trace, point, point, halfExtents, Ogre::Math::DegreesToRadians(pm->ps.viewangles.y), pm->isInterior, pm->mEngine); + + if ( !trace->allsolid ) + { + /*point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25;*/ + point = pm->ps.origin; + point.z -= 0.25f; + + //pm->trace (trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + //tracefunc(trace, *(const D3DXVECTOR3* const)&(pm->ps.origin), *(const D3DXVECTOR3* const)&point, D3DXVECTOR3(0.0f, -0.25f, 0.0f), 0, pml.traceObj); + newtrace(trace, pm->ps.origin, point, halfExtents, Ogre::Math::DegreesToRadians(pm->ps.viewangles.y), pm->isInterior, pm->mEngine); + pml.groundTrace = *trace; + return true; + } + } + } + } + + //pm->ps->groundEntityNum = ENTITYNUM_NONE; + pm->ps.groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = false; + pml.walking = false; + + return false; +} + +static void PM_CrashLand( void ) +{ + float delta; + float dist ; + float vel, acc; + float t; + float a, b, c, den; + + // decide which landing animation to use + /*if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) + PM_ForceLegsAnim( LEGS_LANDB ); + else + PM_ForceLegsAnim( LEGS_LAND ); + + pm->ps->legsTimer = TIMER_LAND;*/ + + // calculate the exact velocity on landing + //dist = pm->ps->origin[2] - pml.previous_origin[2]; + + dist = pm->ps.origin.z - pml.previous_origin.z; + + //vel = pml.previous_velocity[2]; + vel = pml.previous_velocity.z; + + //acc = -pm->ps->gravity; + acc = -pm->ps.gravity; + + a = acc / 2; + b = vel; + c = -dist; + + den = b * b - 4 * a * c; + if ( den < 0 ) + return; + + t = (-b - sqrtf( den ) ) / ( 2 * a ); + + delta = vel + t * acc; + delta = delta * delta * 0.0001f; + + // ducking while falling doubles damage + /*if ( pm->ps->pm_flags & PMF_DUCKED ) + delta *= 2;*/ + if (pm->cmd.upmove < -20) + delta *= 2; + + // never take falling damage if completely underwater + if ( pm->ps.waterlevel == 3 ) + return; + + // reduce falling damage if there is standing water + if ( pm->ps.waterlevel == 2 ) + delta *= 0.25; + if ( pm->ps.waterlevel == 1 ) + delta *= 0.5; + + if ( delta < 1 ) + return; +/* + if (delta > 60) + printf("Far crashland: %f\n", delta); + else if (delta > 40) + printf("Medium crashland: %f\n", delta); + else if (delta > 4) + printf("Short crashland: %f\n", delta); +*/ + if (delta > 60) + { + /* + static const namestruct healthDamage("Health Damage"); + const SOUN* const soun = SOUN::GetSound(healthDamage); + if (soun) + { + PlaySound2D(soun->soundFilename, soun->soundData->GetVolumeFloat() ); + }*/ + } + + if (delta > 3) // We need at least a short crashland to proc the sound effects: + { + bool splashSound = false; + + if (pm->hasWater) + { + + const float waterHeight = pm->waterHeight; + const float waterHeightSplash = waterHeight + halfExtents.y; + if (pm->ps.origin.z < waterHeightSplash) + { + splashSound = true; + } + + } + + + if (splashSound) + { + //Change this later----------------------------------- + /* + const namestruct ns("DefaultLandWater"); + const SOUN* const soun = SOUN::GetSound(ns); + if (soun) + { + PlaySound2D(soun->soundFilename, soun->soundDatga->GetVolumeFloat() ); + }*/ + } + else + { + //Change this later--------------------------------- + /* + namestruct defaultCreature; + const SNDG* const sndg = SNDG::GetFromMap(defaultCreature, SNDG::land); + if (sndg) + { + const namestruct& SOUNID = sndg->soundID; + const SOUN* const soun = SOUN::GetSound(SOUNID); + if (soun) + { + PlaySound2D(soun->soundFilename, soun->soundData->GetVolumeFloat() ); + } + }*/ + } + } + + // create a local entity event to play the sound + + // SURF_NODAMAGE is used for bounce pads where you don't ever + // want to take damage or play a crunch sound + //if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) + { + /*if ( delta > 60 ) + PM_AddEvent( EV_FALL_FAR ); + else if ( delta > 40 ) + { + // this is a pain grunt, so don't play it if dead + if ( pm->ps->stats[STAT_HEALTH] > 0 ) + PM_AddEvent( EV_FALL_MEDIUM ); + } + else if ( delta > 7 ) + PM_AddEvent( EV_FALL_SHORT ); + else + PM_AddEvent( PM_FootstepForSurface() );*/ + } + + // start footstep cycle over + //pm->ps->bobCycle = 0; +} + +static void PM_GroundTrace( void ) +{ + Ogre::Vector3 point; + traceResults trace; + + /*point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25;*/ + point = pm->ps.origin; + point.z -= 0.25f; + + //pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + //tracefunc(&trace, *(const D3DXVECTOR3* const)&(pm->ps.origin), *(const D3DXVECTOR3* const)&point, D3DXVECTOR3(0.0f, -0.25f, 0.0f), 0, pml.traceObj); + newtrace(&trace, pm->ps.origin, point, halfExtents, Ogre::Math::DegreesToRadians(pm->ps.viewangles.y), pm->isInterior, pm->mEngine); + pml.groundTrace = trace; + + // do something corrective if the trace starts in a solid... + if ( trace.allsolid ) { + //std::cout << "ALL SOLID\n"; + if ( !PM_CorrectAllSolid(&trace) ){ + //std::cout << "Returning after correct all solid\n"; + return; + } + } + // if the trace didn't hit anything, we are in free fall + if ( trace.fraction == 1.0) + { + if(pm->ps.snappingImplemented){ + if(pm->ps.bSnap && pm->ps.counter <= 0) + PM_GroundTraceMissed(); + } + + + return; + } + else + { + //It hit something, so we are on the ground + pm->ps.bSnap = true; + + } + // check if getting thrown off the ground + //if ( pm->ps->velocity[2] > 0 && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) + if (pm->ps.velocity.z > 0 && pm->ps.velocity.dotProduct(trace.planenormal) > 10.0f ) + { + //if ( pm->debugLevel ) + //Com_Printf("%i:kickoff\n", c_pmove); + + // go into jump animation + /*if ( pm->cmd.forwardmove >= 0 ) + { + PM_ForceLegsAnim( LEGS_JUMP ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + PM_ForceLegsAnim( LEGS_JUMPB ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + }*/ + if(!pm->ps.bSnap){ + pm->ps.groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = false; + pml.walking = false; + } + else + { + pml.groundPlane = true; + pml.walking = true; + } + return; + } + + + + + // slopes that are too steep will not be considered onground + //if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) + if (trace.planenormal.z < MIN_WALK_NORMAL) + { + //if ( pm->debugLevel ) + //Com_Printf("%i:steep\n", c_pmove); + + // FIXME: if they can't slide down the slope, let them + // walk (sharp crevices) + pm->ps.groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = true; + pml.walking = false; + return; + } + + pml.groundPlane = true; + pml.walking = true; + + // hitting solid ground will end a waterjump + /*if (pm->ps.pm_flags & PMF_TIME_WATERJUMP) + { + pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); + pm->ps->pm_time = 0; + }*/ + + if ( pm->ps.groundEntityNum == ENTITYNUM_NONE ) + { + // just hit the ground + /*if ( pm->debugLevel ) + Com_Printf("%i:Land\n", c_pmove);*/ + //bprintf("Land\n"); + + PM_CrashLand(); + + // don't do landing time if we were just going down a slope + //if ( pml.previous_velocity[2] < -200 ) + if (pml.previous_velocity.z < -200) + { + // don't allow another jump for a little while + //pm->ps->pm_flags |= PMF_TIME_LAND; + pm->ps.pm_time = 250; + } + } + + pm->ps.groundEntityNum = trace.entityNum; + + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + + //PM_AddTouchEnt( trace.entityNum ); +} + +void PM_AirMove() +{ + //int i; + Ogre::Vector3 wishvel; + float fmove, smove; + Ogre::Vector3 wishdir; + float wishspeed; + float scale; + playerMove::playercmd cmd; + //pm->ps.gravity = 800; + PM_Friction(); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + // set the movementDir so clients can rotate the legs for strafing + //PM_SetMovementDir(); + + // project moves down to flat plane + //pml.forward[2] = 0; + pml.forward.z = 0; //Z or Y? + //pml.right[2] = 0; + pml.right.z = 0; + //VectorNormalize (pml.forward); + VectorNormalize(pml.forward); + VectorNormalize(pml.right); + //VectorNormalize (pml.right); + + //for ( i = 0 ; i < 2 ; i++ ) + //wishvel[i] = pml.forward[i] * fmove + pml.right[i] * smove; + wishvel = pml.forward * fmove + pml.right * smove; + + //wishvel[2] = 0; + wishvel.z = 0; + + //VectorCopy (wishvel, wishdir); + wishdir = wishvel; + //wishspeed = VectorNormalize(wishdir); + wishspeed = VectorNormalize(wishdir); + + wishspeed *= scale; + + // not on ground, so little effect on velocity + PM_Accelerate (wishdir, wishspeed, pm_airaccelerate); + + // we may have a ground plane that is very steep, even + // though we don't have a groundentity + // slide along the steep plane + if ( pml.groundPlane ) + PM_ClipVelocity (pm->ps.velocity, pml.groundTrace.planenormal, pm->ps.velocity, OVERCLIP ); + +/*#if 0 + //ZOID: If we are on the grapple, try stair-stepping + //this allows a player to use the grapple to pull himself + //over a ledge + if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) + PM_StepSlideMove ( qtrue ); + else + PM_SlideMove ( qtrue ); +#endif*/ + + /*bprintf("%i ", */PM_StepSlideMove ( true )/* )*/; +} + +static void PM_NoclipMove( void ) +{ + float speed, drop, friction, control, newspeed; +// int i; + Ogre::Vector3 wishvel; + float fmove, smove; + Ogre::Vector3 wishdir; + float wishspeed; + float scale; + + //pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + + // friction + + //speed = VectorLength (pm->ps->velocity); + speed = pm->ps.velocity.length(); + if (speed < 1) + //VectorCopy (vec3_origin, pm->ps->velocity); + pm->ps.velocity = Ogre::Vector3(0.0f, 0.0f, 0.0f); + else + { + drop = 0; + + friction = pm_friction * 1.5f; // extra friction + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control * friction * pml.frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + //VectorScale (pm->ps->velocity, newspeed, pm->ps->velocity); + pm->ps.velocity = pm->ps.velocity * newspeed; + } + + // accelerate + scale = PM_CmdScale( &pm->cmd ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + //for (i=0 ; i<3 ; i++) + //wishvel[i] = pml.forward[i] * fmove + pml.right[i] * smove; + + wishvel = pml.forward * fmove + pml.right * smove; + //wishvel[2] += pm->cmd.upmove; + wishvel.z += pm->cmd.upmove; + + //VectorCopy (wishvel, wishdir); + wishdir = wishvel; + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + + PM_Accelerate( wishdir, wishspeed, pm_accelerate ); + + // move + //VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin); + pm->ps.origin = pm->ps.origin + pm->ps.velocity * pml.frametime; +} + +static void PM_DropTimers( void ) +{ + // drop misc timing counter + if ( pm->ps.pm_time ) + { + if ( pml.msec >= pm->ps.pm_time ) + { + //pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps.pm_time = 0; + } + else + pm->ps.pm_time -= pml.msec; + } + + //bprintf("Time: %i\n", pm->ps.pm_time); + + // drop animation counter + /*if ( pm->ps->legsTimer > 0 ) + { + pm->ps->legsTimer -= pml.msec; + if ( pm->ps->legsTimer < 0 ) + pm->ps->legsTimer = 0; + } + + if ( pm->ps->torsoTimer > 0 ) + { + pm->ps->torsoTimer -= pml.msec; + if ( pm->ps->torsoTimer < 0 ) + pm->ps->torsoTimer = 0; + }*/ +} + +static void PM_FlyMove( void ) +{ + //int i; + Ogre::Vector3 wishvel; + float wishspeed; + Ogre::Vector3 wishdir; + float scale; + + // normal slowdown + PM_Friction (); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if ( !scale ) + { + /*wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = 0;*/ + wishvel = Ogre::Vector3(0,0,0); + } + else + { + //for (i=0 ; i<3 ; i++) + //wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; + wishvel = pml.forward * scale * pm->cmd.forwardmove + pml.right * scale * pm->cmd.rightmove; + + //wishvel[2] += scale * pm->cmd.upmove; + wishvel.z += /*6.35f * */pm->cmd.upmove * scale; + } + + //VectorCopy (wishvel, wishdir); + wishdir = wishvel; + + //wishspeed = VectorNormalize(wishdir); + wishspeed = VectorNormalize(wishdir); + + PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate); + + PM_StepSlideMove( false ); +} + + +void PM_SetWaterLevel( playerMove* const pm ) +{ + Ogre::Vector3 point; + //int cont; + int sample1; + int sample2; + + // + // get waterlevel, accounting for ducking + // + + pm->ps.waterlevel = WL_DRYLAND; + pm->ps.watertype = 0; + + /*point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] + MINS_Z + 1; */ + point.x = pm->ps.origin.x; + point.y = pm->ps.origin.y; + point.z = pm->ps.origin.z + MINS_Z + 1; + + //cont = pm->pointcontents( point, pm->ps->clientNum ); + bool checkWater = (pml.hasWater && pml.waterHeight > point.z); + //if ( cont & MASK_WATER ) + if ( checkWater) + { + sample2 = /*pm->ps.viewheight*/DEFAULT_VIEWHEIGHT - MINS_Z; + sample1 = sample2 / 2; + + pm->ps.watertype = CONTENTS_WATER;//cont; + pm->ps.waterlevel = WL_ANKLE; + //point[2] = pm->ps->origin[2] + MINS_Z + sample1; + point.z = pm->ps.origin.z + MINS_Z + sample1; + checkWater = (pml.hasWater && pml.waterHeight > point.z); + //cont = pm->pointcontents (point, pm->ps->clientNum ); + //if ( cont & MASK_WATER ) + if (checkWater) + { + pm->ps.waterlevel = WL_WAIST; + //point[2] = pm->ps->origin[2] + MINS_Z + sample2; + point.z = pm->ps.origin.z + MINS_Z + sample2; + //cont = pm->pointcontents (point, pm->ps->clientNum ); + //if ( cont & MASK_WATER ) + checkWater = (pml.hasWater && pml.waterHeight > point.z); + if (checkWater ) + pm->ps.waterlevel = WL_UNDERWATER; + } + } +} + +void PmoveSingle (playerMove* const pmove) +{ + pmove->ps.counter--; + //pm = pmove; + + // Aedra doesn't support Q3-style VM traps D: //while(1); + + // this counter lets us debug movement problems with a journal + // by setting a conditional breakpoint fot the previous frame + //c_pmove++; + + // clear results + //pm->numtouch = 0; + pm->ps.watertype = 0; + pm->ps.waterlevel = WL_DRYLAND; + + //if ( pm->ps->stats[STAT_HEALTH] <= 0 ) + //pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies + + + // make sure walking button is clear if they are running, to avoid + // proxy no-footsteps cheats + //if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) + //pm->cmd.buttons &= ~BUTTON_WALKING; + + + // set the talk balloon flag + //if ( pm->cmd.buttons & BUTTON_TALK ) + //pm->ps->eFlags |= EF_TALK; + //else + //pm->ps->eFlags &= ~EF_TALK; + + // set the firing flag for continuous beam weapons + /*if ( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION + && ( pm->cmd.buttons & BUTTON_ATTACK ) && pm->ps->ammo[ pm->ps->weapon ] ) + pm->ps->eFlags |= EF_FIRING; + else + pm->ps->eFlags &= ~EF_FIRING;*/ + + // clear the respawned flag if attack and use are cleared + /*if ( pm->ps->stats[STAT_HEALTH] > 0 && + !( pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE) ) ) + pm->ps->pm_flags &= ~PMF_RESPAWNED;*/ + + // if talk button is down, dissallow all other input + // this is to prevent any possible intercept proxy from + // adding fake talk balloons + /*if ( pmove->cmd.buttons & BUTTON_TALK ) + { + // keep the talk button set tho for when the cmd.serverTime > 66 msec + // and the same cmd is used multiple times in Pmove + pmove->cmd.buttons = BUTTON_TALK; + pmove->cmd.forwardmove = 0; + pmove->cmd.rightmove = 0; + pmove->cmd.upmove = 0; + }*/ + + // clear all pmove local vars + memset (&pml, 0, sizeof(pml) ); + + // Aedra-specific code: + //pml.scene = global_lastscene; + + + // End Aedra-specific code + pml.hasWater = pmove->hasWater; + pml.isInterior = pmove->isInterior; + pml.waterHeight = pmove->waterHeight; +#ifdef _DEBUG + if (!pml.traceObj) + __debugbreak(); + + if (!pml.traceObj->incellptr) + __debugbreak(); +#endif + + // determine the time + pml.msec = pmove->cmd.serverTime - pm->ps.commandTime; + if ( pml.msec < 1 ) + pml.msec = 1; + else if ( pml.msec > 200 ) + pml.msec = 200; + + //pm->ps->commandTime = pmove->cmd.serverTime; + + // Commented out as a hack + pm->ps.commandTime = pmove->cmd.serverTime; + + // Handle state change procs: + if (pm->cmd.activating != pm->cmd.lastActivatingState) + { + if (!pm->cmd.lastActivatingState && pm->cmd.activating) + pm->cmd.procActivating = playerMove::playercmd::KEYDOWN; + else + pm->cmd.procActivating = playerMove::playercmd::KEYUP; + } + else + { + pm->cmd.procActivating = playerMove::playercmd::NO_CHANGE; + } + pm->cmd.lastActivatingState = pm->cmd.activating; + + if (pm->cmd.dropping != pm->cmd.lastDroppingState) + { + if (!pm->cmd.lastDroppingState && pm->cmd.dropping) + pm->cmd.procDropping = playerMove::playercmd::KEYDOWN; + else + pm->cmd.procDropping = playerMove::playercmd::KEYUP; + } + else + { + pm->cmd.procDropping = playerMove::playercmd::NO_CHANGE; + } + pm->cmd.lastDroppingState = pm->cmd.dropping; + + // save old org in case we get stuck + //VectorCopy (pm->ps->origin, pml.previous_origin); + pml.previous_origin = pm->ps.origin; + + // Copy over the lastframe origin + pmove->ps.lastframe_origin = pmove->ps.origin; + + //pmove->ps.lastframe_origin = pmove->ps.origin; + + // save old velocity for crashlanding + //VectorCopy (pm->ps->velocity, pml.previous_velocity); + pml.previous_velocity = pm->ps.velocity; + + pml.frametime = pml.msec * 0.001f; + + // update the viewangles + //PM_UpdateViewAngles( &(pm->ps), &(pm->cmd) ); + + AngleVectors (pm->ps.viewangles, &(pml.forward), &(pml.right), &(pml.up) ); + + //if ( pm->cmd.upmove < 10 ) + // not holding jump + //pm->ps->pm_flags &= ~PMF_JUMP_HELD; + + // decide if backpedaling animations should be used + /*if ( pm->cmd.forwardmove < 0 ) + pm->ps->pm_flags |= PMF_BACKWARDS_RUN; + else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) + pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN;*/ + + /*if ( pm->ps->pm_type >= PM_DEAD ) + { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + }*/ + + if ( pm->ps.move_type == PM_SPECTATOR ) + { + + //PM_CheckDuck (); + PM_FlyMove (); + PM_DropTimers (); + return; + } + + if ( pm->ps.move_type == PM_NOCLIP ) + { + + PM_NoclipMove (); + PM_DropTimers (); + return; + } + + if (pm->ps.move_type == PM_FREEZE){ + + return; // no movement at all + + } + + if ( pm->ps.move_type == PM_INTERMISSION || pm->ps.move_type == PM_SPINTERMISSION){ + return; // no movement at all + } + + // set watertype, and waterlevel + PM_SetWaterLevel(pmove); + pml.previous_waterlevel = pmove->ps.waterlevel; + + // set mins, maxs, and viewheight + //PM_CheckDuck (); + + // set groundentity + PM_GroundTrace(); + + /*if ( pm->ps->pm_type == PM_DEAD ) + PM_DeadMove (); + + PM_DropTimers();*/ + + PM_DropTimers(); + +/*#ifdef MISSIONPACK + if ( pm->ps->powerups[PW_INVULNERABILITY] ) { + PM_InvulnerabilityMove(); + } else +#endif*/ + /*if ( pm->ps->powerups[PW_FLIGHT] ) + // flight powerup doesn't allow jump and has different friction + PM_FlyMove(); + else if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) + { + PM_GrappleMove(); + // We can wiggle a bit + PM_AirMove(); + } + else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) + PM_WaterJumpMove();*/ + if ( pmove->ps.waterlevel > 1 ) + // swimming + PM_WaterMove(pmove); + else if ( pml.walking ) + { + + // walking on ground + PM_WalkMove(pmove); + //bprintf("WalkMove\n"); + } + else + { + // airborne + //std::cout << "AIRMOVE\n"; + PM_AirMove(); + //bprintf("AirMove\n"); + } + + //PM_Animate(); + + // set groundentity, watertype, and waterlevel + PM_GroundTrace(); + PM_SetWaterLevel(pmove); + + // weapons + /*PM_Weapon(); + + // torso animation + PM_TorsoAnimation(); + + // footstep events / legs animations + PM_Footsteps(); + + // entering / leaving water splashes + PM_WaterEvents(); + + // snap some parts of playerstate to save network bandwidth + trap_SnapVector( pm->ps->velocity );*/ +} + +void Ext_UpdateViewAngles(playerMove* const pm) +{ + playerMove::playerStruct* const ps = &(pm->ps); + playerMove::playercmd* const cmd = &(pm->cmd); + PM_UpdateViewAngles(ps, cmd); +} + +void Pmove (playerMove* const pmove) +{ + // warning: unused variable ‘fmove’ + //int fmove = pmove->cmd.forwardmove; + + pm = pmove; + + int finalTime; + + finalTime = pmove->cmd.serverTime; + + pmove->ps.commandTime = 40; + + if ( finalTime < pmove->ps.commandTime ) + return; // should not happen + + if ( finalTime > pmove->ps.commandTime + 1000 ) + pmove->ps.commandTime = finalTime - 1000; + + pmove->ps.pmove_framecount = (pmove->ps.pmove_framecount + 1) & ( (1 << PS_PMOVEFRAMECOUNTBITS) - 1); + + // chop the move up if it is too long, to prevent framerate + // dependent behavior + while ( pmove->ps.commandTime != finalTime ) + { + int msec; + + msec = finalTime - pmove->ps.commandTime; + + if ( pmove->pmove_fixed ) + { + if ( msec > pmove->pmove_msec ) + msec = pmove->pmove_msec; + } + else + { + if ( msec > 66 ) + msec = 66; + } + + pmove->cmd.serverTime = pmove->ps.commandTime + msec; + + if (pmove->isInterior) + { + PmoveSingle( pmove ); + } + else + { + PmoveSingle( pmove ); + /* + std::map::const_iterator it = ExtCellLookup.find(PositionToCell(pmove->ps.origin) ); + if (it != ExtCellLookup.end() ) + { + pmove->traceObj->incellptr = it->second; + }*/ + } + + //if ( pmove->ps->pm_flags & PMF_JUMP_HELD ) + //pmove->cmd.upmove = 20; + } + + //pmove->ps.last_compute_time = GetTimeQPC(); + //pmove->ps.lerp_multiplier = (pmove->ps.origin - pmove->ps.lastframe_origin);// * (1.000 / 31.0); + + //PM_CheckStuck(); + +} + + diff --git a/libs/openengine/bullet/pmove.h b/libs/openengine/bullet/pmove.h new file mode 100644 index 0000000000..e46eb9d2e7 --- /dev/null +++ b/libs/openengine/bullet/pmove.h @@ -0,0 +1,200 @@ +#ifndef OENGINE_BULLET_PMOVE_H +#define OENGINE_BULLET_PMOVE_H +/* +This source file is a *modified* version of various header files from the Quake 3 Arena source code, +which was released under the GNU GPL (v2) in 2005. +Quake 3 Arena is copyright (C) 1999-2005 Id Software, Inc. +*/ + +#include +#include +#include +#include "trace.h" +#include "physic.hpp" + + +//#include "GameMath.h" +//#include "GameTime.h" + +// Forwards-declare it! + +/*#ifndef COMPILING_PMOVE +#include "Scene.h" +extern SceneInstance* global_lastscene; +#endif*/ + +static const Ogre::Vector3 halfExtents(14.64f * 2, 14.24f * 2, 33.25f * 2); + +#define MAX_CLIP_PLANES 5 +#define OVERCLIP 1.001f +//#define STEPSIZE 18 // 18 is way too much +#define STEPSIZE (18 / 2) +#ifndef M_PI + #define M_PI 3.14159265358979323846f +#endif +#define YAW 0 +#define PITCH /*1*/2 +#define ROLL /*2*/1 +#define SHORT2ANGLE(x) ( (x) * (360.0f / 65536.0f) ) +#define ANGLE2SHORT(x) ( (const short)( (x) / (360.0f / 65536.0f) ) ) +#define GENTITYNUM_BITS 10 // don't need to send any more +#define MAX_GENTITIES (1 << GENTITYNUM_BITS) +#define ENTITYNUM_NONE (MAX_GENTITIES - 1) +#define ENTITYNUM_WORLD (MAX_GENTITIES - 2) +#define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes +#define JUMP_VELOCITY (270) +#define PS_PMOVEFRAMECOUNTBITS 6 +#define MINS_Z -24 +#define DEFAULT_VIEWHEIGHT 26 +#define CROUCH_VIEWHEIGHT 12 +#define DEAD_VIEWHEIGHT (-16) +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_FOG 64 +static const float pm_accelerate = 10.0f; +static const float pm_stopspeed = 100.0f; +static const float pm_friction = 12.0f; +static const float pm_flightfriction = 3.0f; +static const float pm_waterfriction = 1.0f; +static const float pm_airaccelerate = 1.0f; +static const float pm_swimScale = 0.50f; +static const float pm_duckScale = 0.25f; +static const float pm_flyaccelerate = 8.0f; +static const float pm_wateraccelerate = 4.0f; + +enum pmtype_t +{ + PM_NORMAL, // can accelerate and turn + PM_NOCLIP, // noclip movement + PM_SPECTATOR, // still run into walls + PM_DEAD, // no acceleration or turning, but free falling + PM_FREEZE, // stuck in place with no control + PM_INTERMISSION, // no movement or status bar + PM_SPINTERMISSION // no movement or status bar +}; + +enum waterlevel_t +{ + WL_DRYLAND = 0, + WL_ANKLE, + WL_WAIST, + WL_UNDERWATER +}; + + +//#include "bprintf.h" + +struct playerMove +{ + struct playerStruct + { + playerStruct() : gravity(800.0f), speed(480.0f), pmove_framecount(20), groundEntityNum(ENTITYNUM_NONE), commandTime(40), move_type(PM_NOCLIP), pm_time(0), snappingImplemented(true), bSnap(false), counter(-1) + { + origin = Ogre::Vector3(733.164f,900.0f, 839.432f); + velocity = Ogre::Vector3(0.0f, 0.0f, 0.0f); + + viewangles = Ogre::Vector3(0.0f, 0.0f, 0.0f); + + delta_angles[0] = delta_angles[1] = delta_angles[2] = 0; + + lastframe_origin.x = lastframe_origin.y = lastframe_origin.z = 0; + lerp_multiplier.x = lerp_multiplier.y = lerp_multiplier.z = 0; + } + + inline void SpeedUp(void) + { + //printf("speed up to: %f\n", speed); + speed *= 1.25f; + } + + inline void SpeedDown(void) + { + //printf("speed down to %f\n", speed); + speed /= 1.25f; + } + + Ogre::Vector3 velocity; + Ogre::Vector3 origin; + bool bSnap; + bool snappingImplemented; + int counter; + float gravity; // default = 800 + float speed; // default = 320 + + int commandTime; // the time at which this command was issued (in milliseconds) + + int pm_time; + + Ogre::Vector3 viewangles; + + int groundEntityNum; + + int pmove_framecount; + + int watertype; + waterlevel_t waterlevel; + + signed short delta_angles[3]; + + pmtype_t move_type; + + float last_compute_time; + Ogre::Vector3 lastframe_origin; + Ogre::Vector3 lerp_multiplier; + } ps; + + struct playercmd + { + enum CMDstateChange + { + NO_CHANGE, + KEYDOWN, + KEYUP + }; + + playercmd() : forwardmove(0), rightmove(0), upmove(0), serverTime(50), ducking(false), + activating(false), lastActivatingState(false), procActivating(NO_CHANGE), + dropping(false), lastDroppingState(false), procDropping(NO_CHANGE) + { + angles[0] = angles[1] = angles[2] = 0; + } + + int serverTime; + + short angles[3]; + + signed char forwardmove; + signed char rightmove; + signed char upmove; + + bool ducking; + bool activating; // if the user is holding down the activate button + bool dropping; // if the user is dropping an item + + bool lastActivatingState; + bool lastDroppingState; + + CMDstateChange procActivating; + CMDstateChange procDropping; + } cmd; + + playerMove() : msec(50), pmove_fixed(false), pmove_msec(50), waterHeight(0), isInterior(true), hasWater(false) + { + } + + int msec; + int pmove_msec; + bool pmove_fixed; + int waterHeight; + bool hasWater; + bool isInterior; + //Object* traceObj; + OEngine::Physic::PhysicEngine* mEngine; +}; + +void Pmove (playerMove* const pmove); +void Ext_UpdateViewAngles(playerMove* const pm); +void AngleVectors( const Ogre::Vector3& angles, Ogre::Vector3* const forward, Ogre::Vector3* const right, Ogre::Vector3* const up) ; +#endif diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp new file mode 100644 index 0000000000..2d18aaa4da --- /dev/null +++ b/libs/openengine/bullet/trace.cpp @@ -0,0 +1,190 @@ + +#include "trace.h" + + + +#include + + + + + + + +void newtrace(traceResults* const results, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBHalfExtents, const float rotation, bool isInterior, OEngine::Physic::PhysicEngine* enginePass) //Traceobj was a Aedra Object +{ + + //if (!traceobj) + // return; + + //if (!traceobj->incellptr) + // return; + + const Ogre::Vector3 rayDir = end - start; + + // Nudge starting point backwards + //const Position3D nudgestart = start + (rayDir * -0.1f); // by 10% (isn't that too much?) + //const Position3D nudgestart = start; + + NewPhysTraceResults out; + //std::cout << "Starting trace\n"; + //Ogre::Vector3 startReplace = Ogre::Vector3(650,950, 45); + //Ogre::Vector3 endReplace = startReplace; + //endReplace.z -= .25; + + const bool hasHit = NewPhysicsTrace(&out, start, end, BBHalfExtents, Ogre::Vector3(0.0f, 0.0, rotation), isInterior, enginePass); + + if (out.fraction < 0.001f) + results->startsolid = true; + else + results->startsolid = false; + + + //results->allsolid = out.startSolid; + + // If outside and underground, we're solid + /*if (isInterior) + { + const Ogre::Vector3 height = GetGroundPosition(start, CellCoords(traceCell->data->gridX, traceCell->data->gridY) ); + if (start.yPos - height.yPos < (-2.0f * BBHalfExtents.yPos) ) + { + results->allsolid = true; + } + else + results->allsolid = false; + }*/ + + // If inside and out of the tree, we're solid + //else + //{ + results->allsolid = out.startSolid; + //std::cout << "allsolid" << results->allsolid << "\n"; + //} + + if (!hasHit) + { + results->endpos = end; + results->planenormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); + results->entityNum = ENTITYNUM_NONE; + results->fraction = 1.0f; + } + else + { + results->fraction = out.fraction; + results->planenormal = out.hitNormal; + results->endpos = rayDir * results->fraction + start; + results->entityNum = ENTITYNUM_WORLD; + /*bprintf("Start: (%f, %f, %f) End: (%f, %f, %f) TraceDir: (%f, %f, %f) HitNormal: (%f, %f, %f) Fraction: %f Hitpos: (%f, %f, %f) CompensatedHitpos: (%f, %f, %f)\n", + start.xPos, start.yPos, start.zPos, + end.xPos, end.yPos, end.zPos, + rayDir.xPos, rayDir.yPos, rayDir.zPos, + results->planenormal.xPos, results->planenormal.yPos, results->planenormal.zPos, results->fraction, + out.endPos.xPos, out.endPos.yPos, out.endPos.zPos, + results->endpos.xPos, results->endpos.yPos, results->endpos.zPos);*/ + } +} + + + +template +const bool NewPhysicsTrace(NewPhysTraceResults* const out, const Ogre::Vector3& start, const Ogre::Vector3& end, + const Ogre::Vector3& BBHalfExtents, const Ogre::Vector3& rotation, bool isInterior, OEngine::Physic::PhysicEngine* enginePass) +{ + //if (!traceobj->incellptr) + // return false; + //if(enginePass->dynamicsWorld->getCollisionObjectArray().at(60)->getCollisionShape()->isConvex()) + // std::cout << "It's convex\n"; + + + + const btVector3 btstart(start.x, start.y, start.z); + const btVector3 btend(end.x, end.y, end.z); + const btQuaternion btrot(rotation.y, rotation.x, rotation.z); //y, x, z + + const btBoxShape newshape(btVector3(BBHalfExtents.x, BBHalfExtents.y, BBHalfExtents.z)); + const btTransform from(btrot, btstart); + const btTransform to(btrot, btend); + + // warning: unused variable ... + /* + float x = from.getOrigin().getX(); + float y = from.getOrigin().getY(); + float z = from.getOrigin().getZ(); + float x2 = to.getOrigin().getX(); + float y2 = to.getOrigin().getY(); + float z2 = to.getOrigin().getZ(); + */ + + //std::cout << "BtFrom: " << x << "," << y << "," << z << "\n"; + //std::cout << "BtTo: " << x2 << "," << y2 << "," << z2 << "\n"; + //std::cout << "BtTo: " << to.getOrigin().getX() << "," << to.getOrigin().getY() << "," << to.getOrigin().getZ() << "\n"; + + + btCollisionWorld::ClosestConvexResultCallback + newTraceCallback(btstart, btend); + + newTraceCallback.m_collisionFilterMask = (traceType == collisionWorldTrace) ? Only_Collision : Only_Pickup; + + + enginePass->dynamicsWorld->convexSweepTest(&newshape, from, to, newTraceCallback); + //newTraceCallback. + + + //std::cout << "NUM: " << enginePass->dynamicsWorld->getNumCollisionObjects() << "\n"; + + // Copy the hit data over to our trace results struct: + out->fraction = newTraceCallback.m_closestHitFraction; + + Ogre::Vector3& outhitnormal = out->hitNormal; + const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld; + + outhitnormal.x = tracehitnormal.x(); + outhitnormal.y = tracehitnormal.y(); + outhitnormal.z = tracehitnormal.z(); + + Ogre::Vector3& outhitpos = out->endPos; + const btVector3& tracehitpos = newTraceCallback.m_hitPointWorld; + + outhitpos.x = tracehitpos.x(); + outhitpos.y = tracehitpos.y(); + outhitpos.z= tracehitpos.z(); + + // StartSolid test: + { + out->startSolid = false; + //btCollisionObject collision; + //collision.setCollisionShape(const_cast(&newshape) ); + + //CustomContactCallback crb; + + //world.world->contactTest(&collision, crb); + //out->startSolid = crb.hit; + + // If outside and underground, we're solid + if (!isInterior) //Check if we are interior + { + } + + // If inside and out of the tree, we're solid + else + { + btVector3 aabbMin, aabbMax; + enginePass->broadphase->getBroadphaseAabb(aabbMin, aabbMax); + //std::cout << "AABBMIN" << aabbMin.getX() <<"," <startSolid = true; + } + } + } + + const bool hasHit = newTraceCallback.hasHit(); + + + + + return hasHit; +} diff --git a/libs/openengine/bullet/trace.h b/libs/openengine/bullet/trace.h index 025673ab7a..d446b6854c 100644 --- a/libs/openengine/bullet/trace.h +++ b/libs/openengine/bullet/trace.h @@ -18,7 +18,7 @@ enum traceWorldType bothWorldTrace = collisionWorldTrace | pickWorldTrace }; -enum collaborativePhysicsType : unsigned +enum collaborativePhysicsType { No_Physics = 0, // Both are empty (example: statics you can walk through, like tall grass) Only_Collision = 1, // This object only has collision physics but no pickup physics (example: statics) @@ -53,10 +53,10 @@ struct traceResults template const bool NewPhysicsTrace(NewPhysTraceResults* const out, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBExtents, const Ogre::Vector3& rotation, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); -template const bool NewPhysicsTrace(NewPhysTraceResults* const out, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBExtents, const Ogre::Vector3& rotation, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); -template const bool NewPhysicsTrace(NewPhysTraceResults* const out, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBExtents, const Ogre::Vector3& rotation, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); +//template const bool NewPhysicsTrace(NewPhysTraceResults* const out, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBExtents, const Ogre::Vector3& rotation, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); +//template const bool NewPhysicsTrace(NewPhysTraceResults* const out, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBExtents, const Ogre::Vector3& rotation, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); void newtrace(traceResults* const results, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBExtents, const float rotation, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); -#endif \ No newline at end of file +#endif diff --git a/libs/openengine/gui/events.cpp b/libs/openengine/gui/events.cpp index 3e52c6435d..bce70704b0 100644 --- a/libs/openengine/gui/events.cpp +++ b/libs/openengine/gui/events.cpp @@ -8,18 +8,14 @@ using namespace OIS; using namespace OEngine::GUI; EventInjector::EventInjector(MyGUI::Gui *g) - : gui(g), mouseX(0), mouseY(0), enabled(true) + : gui(g), enabled(true) + , mMouseX(0) + , mMouseY(0) { assert(gui); - maxX = MyGUI::RenderManager::getInstance().getViewSize().width; - maxY = MyGUI::RenderManager::getInstance().getViewSize().height; -} - -template -void setRange(X &x, X min, X max) -{ - if(x < min) x = min; - else if(x > max) x = max; + const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + mMouseX = viewSize.width/2; + mMouseY = viewSize.height/2; } void EventInjector::event(Type type, int index, const void *p) @@ -63,18 +59,19 @@ void EventInjector::event(Type type, int index, const void *p) MouseEvent *mouse = (MouseEvent*)p; MyGUI::MouseButton id = MyGUI::MouseButton::Enum(index); - // Update mouse position - mouseX += mouse->state.X.rel; - mouseY += mouse->state.Y.rel; + const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); - setRange(mouseX,0,maxX); - setRange(mouseY,0,maxY); + // Update mouse position + mMouseX += mouse->state.X.rel; + mMouseY += mouse->state.Y.rel; + mMouseX = std::max(0, std::min(mMouseX, viewSize.width)); + mMouseY = std::max(0, std::min(mMouseY, viewSize.height)); if(type == EV_MouseDown) - MyGUI::InputManager::getInstance().injectMousePress(mouseX, mouseY, id); + MyGUI::InputManager::getInstance().injectMousePress(mMouseX, mMouseY, id); else if(type == EV_MouseUp) - MyGUI::InputManager::getInstance().injectMouseRelease(mouseX, mouseY, id); + MyGUI::InputManager::getInstance().injectMouseRelease(mMouseX, mMouseY, id); else - MyGUI::InputManager::getInstance().injectMouseMove(mouseX, mouseY, mouse->state.Z.abs); + MyGUI::InputManager::getInstance().injectMouseMove(mMouseX, mMouseY, mouse->state.Z.abs); } } diff --git a/libs/openengine/gui/events.hpp b/libs/openengine/gui/events.hpp index 6ca83cf753..10c5309bc3 100644 --- a/libs/openengine/gui/events.hpp +++ b/libs/openengine/gui/events.hpp @@ -16,8 +16,9 @@ namespace GUI class EventInjector : public Mangle::Input::Event { MyGUI::Gui *gui; - int mouseX, mouseY; - int maxX, maxY; + + int mMouseX; + int mMouseY; public: bool enabled; diff --git a/libs/openengine/gui/layout.hpp b/libs/openengine/gui/layout.hpp index 05a23e8aea..e6feb3d0ed 100644 --- a/libs/openengine/gui/layout.hpp +++ b/libs/openengine/gui/layout.hpp @@ -71,7 +71,7 @@ namespace GUI void shutdown() { - MyGUI::LayoutManager::getInstance().unloadLayout(mListWindowRoot); + MyGUI::Gui::getInstance().destroyWidget(mMainWidget); mListWindowRoot.clear(); } @@ -85,7 +85,7 @@ namespace GUI // adjust the size of the window caption so that all text is visible // NOTE: this assumes that mMainWidget is of type Window. MyGUI::TextBox* box = static_cast(mMainWidget)->getCaptionWidget(); - box->setSize(box->getTextSize().width + 48, box->getSize().height); + box->setSize(box->getTextSize().width + 24, box->getSize().height); // in order to trigger alignment updates, we need to update the parent // mygui doesn't provide a proper way of doing this, so we are just changing size @@ -115,6 +115,20 @@ namespace GUI static_cast(pt)->setCaption(caption); } + void setTitle(const std::string& title) + { + // NOTE: this assume that mMainWidget is of type Window. + static_cast(mMainWidget)->setCaptionWithReplacing(title); + adjustWindowCaption(); + } + + void setState(const std::string& widget, const std::string& state) + { + MyGUI::Widget* pt; + getWidget(pt, widget); + pt->_setWidgetState(state); + } + void setTextColor(const std::string& name, float r, float g, float b) { MyGUI::Widget* pt; @@ -131,6 +145,13 @@ namespace GUI pt->setImageTexture(imgName); } + void adjustButtonSize(MyGUI::Button* button) + { + // adjust size of button to fit its text + MyGUI::IntSize size = button->getTextSize(); + button->setSize(size.width + 24, button->getSize().height); + } + protected: MyGUI::Widget* mMainWidget; diff --git a/libs/openengine/gui/manager.cpp b/libs/openengine/gui/manager.cpp index 1bf8ec3257..022c5efb53 100644 --- a/libs/openengine/gui/manager.cpp +++ b/libs/openengine/gui/manager.cpp @@ -36,7 +36,7 @@ void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool void MyGUIManager::shutdown() { - if(mGui) delete mGui; + delete mGui; if(mPlatform) { mPlatform->shutdown(); diff --git a/libs/openengine/ogre/atlas.cpp b/libs/openengine/ogre/atlas.cpp new file mode 100644 index 0000000000..01b84afabc --- /dev/null +++ b/libs/openengine/ogre/atlas.cpp @@ -0,0 +1,113 @@ +#include "atlas.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Ogre; +using namespace OEngine::Render; + +void Atlas::createFromFile (const std::string& filename, const std::string& textureName, const std::string& texturePrefix) +{ + ConfigFile file; + file.load(filename, ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME, "\t:=", true); + + Root* root = Ogre::Root::getSingletonPtr(); + + SceneManager* sceneMgr = root->createSceneManager(ST_GENERIC); + Camera* camera = sceneMgr->createCamera("AtlasCamera"); + + int width = StringConverter::parseInt(file.getSetting("size_x", "settings")); + int height = StringConverter::parseInt(file.getSetting("size_y", "settings")); + + std::vector rectangles; + int i = 0; + + ConfigFile::SectionIterator seci = file.getSectionIterator(); + while (seci.hasMoreElements()) + { + Ogre::String sectionName = seci.peekNextKey(); + seci.getNext(); + + if (sectionName == "settings" || sectionName == "") + continue; + + MaterialPtr material = MaterialManager::getSingleton().create("AtlasMaterial" + StringConverter::toString(i), ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + material->getTechnique(0)->getPass(0)->setLightingEnabled(false); + material->getTechnique(0)->getPass(0)->setDepthCheckEnabled(false); + TextureUnitState* tus = material->getTechnique(0)->getPass(0)->createTextureUnitState(texturePrefix + sectionName); + tus->setTextureBorderColour(ColourValue(0, 0, 0, 0)); + + Rectangle2D* rect = new Rectangle2D(true); + rect->setMaterial("AtlasMaterial" + StringConverter::toString(i)); + rect->setRenderQueueGroup(RENDER_QUEUE_BACKGROUND); + + int x = StringConverter::parseInt(file.getSetting("x", sectionName)); + int y = StringConverter::parseInt(file.getSetting("y", sectionName)); + + TexturePtr texture = TextureManager::getSingleton().getByName(texturePrefix + sectionName); + if (texture.isNull()) + { + std::cerr << "OEngine::Render::Atlas: Can't find texture " << texturePrefix + sectionName << ", skipping..." << std::endl; + continue; + } + int textureWidth = texture->getWidth(); + int textureHeight = texture->getHeight(); + + float left = x/float(width) * 2 - 1; + float top = (1-(y/float(height))) * 2 - 1; + float right = ((x+textureWidth))/float(width) * 2 - 1; + float bottom = (1-((y+textureHeight)/float(height))) * 2 - 1; + rect->setCorners(left, top, right, bottom); + + // Use infinite AAB to always stay visible + AxisAlignedBox aabInf; + aabInf.setInfinite(); + rect->setBoundingBox(aabInf); + + // Attach background to the scene + SceneNode* node = sceneMgr->getRootSceneNode()->createChildSceneNode(); + node->attachObject(rect); + + rectangles.push_back(rect); + ++i; + } + + TexturePtr destTexture = TextureManager::getSingleton().createManual( + textureName, + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + TEX_TYPE_2D, + width, height, + 0, + PF_FLOAT16_RGBA, + TU_RENDERTARGET); + + RenderTarget* rtt = destTexture->getBuffer()->getRenderTarget(); + rtt->setAutoUpdated(false); + Viewport* vp = rtt->addViewport(camera); + vp->setOverlaysEnabled(false); + vp->setShadowsEnabled(false); + vp->setBackgroundColour(ColourValue(0,0,0,0)); + + rtt->update(); + + // remove all the junk we've created + for (std::vector::iterator it=rectangles.begin(); + it!=rectangles.end(); ++it) + { + delete (*it); + } + while (i > 0) + { + MaterialManager::getSingleton().remove("AtlasMaterial" + StringConverter::toString(i-1)); + --i; + } + root->destroySceneManager(sceneMgr); +} diff --git a/libs/openengine/ogre/atlas.hpp b/libs/openengine/ogre/atlas.hpp new file mode 100644 index 0000000000..5dcd409ca5 --- /dev/null +++ b/libs/openengine/ogre/atlas.hpp @@ -0,0 +1,27 @@ +#ifndef OENGINE_OGRE_ATLAS_HPP +#define OENGINE_OGRE_ATLAS_HPP + +#include + +namespace OEngine +{ +namespace Render +{ + + /// \brief Creates a texture atlas at runtime + class Atlas + { + public: + /** + * @param absolute path to file that specifies layout of the texture (positions of the textures it contains) + * @param name of the destination texture to save to (in memory) + * @param texture directory prefix + */ + static void createFromFile (const std::string& filename, const std::string& textureName, const std::string& texturePrefix="textures\\"); + }; + +} +} + +#endif + diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 4ded3343f5..7c0f88bd70 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -5,28 +5,31 @@ #include "OgreRenderWindow.h" #include "OgreLogManager.h" #include "OgreLog.h" +#include "OgreTextureManager.h" +#include "OgreTexture.h" +#include "OgreHardwarePixelBuffer.h" #include +#include using namespace Ogre; using namespace OEngine::Render; void OgreRenderer::cleanup() { - if (mFader) delete mFader; - - if(mRoot) - delete mRoot; - mRoot = NULL; + mFader = NULL; + + OGRE_DELETE mRoot; + mRoot = NULL; } void OgreRenderer::start() { - mRoot->startRendering(); + mRoot->startRendering(); } -bool OgreRenderer::loadPlugins() +bool OgreRenderer::loadPlugins() const { #ifdef ENABLE_PLUGIN_GL mGLPlugin = new Ogre::GLPlugin(); @@ -53,96 +56,112 @@ bool OgreRenderer::loadPlugins() void OgreRenderer::update(float dt) { - mFader->update(dt); + mFader->update(dt); } void OgreRenderer::screenshot(const std::string &file) { - mWindow->writeContentsToFile(file); + mWindow->writeContentsToFile(file); } float OgreRenderer::getFPS() { - return mWindow->getLastFPS(); + return mWindow->getLastFPS(); } -bool OgreRenderer::configure(bool showConfig, - const std::string &cfgPath, - const std::string &logPath, - const std::string &pluginCfg, - bool _logging) +void OgreRenderer::configure(const std::string &logPath, + const std::string &pluginCfg, + const std::string& renderSystem, + bool _logging) { - // Set up logging first - new LogManager; - Log *log = LogManager::getSingleton().createLog(logPath + std::string("Ogre.log")); - logging = _logging; + // Set up logging first + new LogManager; + Log *log = LogManager::getSingleton().createLog(logPath + std::string("Ogre.log")); + logging = _logging; - if(logging) - // Full log detail - log->setLogDetail(LL_BOREME); - else - // Disable logging - log->setDebugOutputEnabled(false); + if(logging) + // Full log detail + log->setLogDetail(LL_BOREME); + else + // Disable logging + log->setDebugOutputEnabled(false); -#if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX) - mRoot = new Root("", cfgPath, ""); - loadPlugins(); -#else - mRoot = new Root(pluginCfg, cfgPath, ""); -#endif + #if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX) + mRoot = new Root("", "", ""); + loadPlugins(); + #else + mRoot = new Root(pluginCfg, "", ""); + #endif - // Show the configuration dialog and initialise the system, if the - // showConfig parameter is specified. The settings are stored in - // ogre.cfg. If showConfig is false, the settings are assumed to - // already exist in ogre.cfg. - int result; - if(showConfig) - result = mRoot->showConfigDialog(); - else - result = mRoot->restoreConfig(); - - return !result; + RenderSystem* rs = mRoot->getRenderSystemByName(renderSystem); + if (rs == 0) + throw std::runtime_error ("RenderSystem with name " + renderSystem + " not found, make sure the plugins are loaded"); + mRoot->setRenderSystem(rs); } -bool OgreRenderer::configure(bool showConfig, - const std::string &cfgPath, - const std::string &pluginCfg, - bool _logging) +void OgreRenderer::createWindow(const std::string &title, const WindowSettings& settings) { - return configure(showConfig, cfgPath, cfgPath, pluginCfg, _logging); + assert(mRoot); + mRoot->initialise(false); + + NameValuePairList params; + params.insert(std::make_pair("title", title)); + params.insert(std::make_pair("FSAA", settings.fsaa)); + params.insert(std::make_pair("vsync", settings.vsync ? "true" : "false")); + + mWindow = mRoot->createRenderWindow(title, settings.window_x, settings.window_y, settings.fullscreen, ¶ms); + + // create the semi-transparent black background texture used by the GUI. + // has to be created in code with TU_DYNAMIC_WRITE_ONLY_DISCARDABLE param + // so that it can be modified at runtime. + mTransparentBGTexture = Ogre::TextureManager::getSingleton().createManual( + "transparent.png", + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + 1, 1, + 0, + Ogre::PF_A8R8G8B8, + Ogre::TU_DYNAMIC_WRITE_ONLY_DISCARDABLE); } -bool OgreRenderer::configure(bool showConfig, - const std::string &pluginCfg, - bool _logging) +void OgreRenderer::createScene(const std::string& camName, float fov, float nearClip) { - return configure(showConfig, "", pluginCfg, _logging); + assert(mRoot); + assert(mWindow); + // Get the SceneManager, in this case a generic one + mScene = mRoot->createSceneManager(ST_GENERIC); + + // Create the camera + mCamera = mScene->createCamera(camName); + mCamera->setNearClipDistance(nearClip); + mCamera->setFOVy(Degree(fov)); + + // Create one viewport, entire window + mView = mWindow->addViewport(mCamera); + + // Alter the camera aspect ratio to match the viewport + mCamera->setAspectRatio(Real(mView->getActualWidth()) / Real(mView->getActualHeight())); + + mFader = new Fader(); } -void OgreRenderer::createWindow(const std::string &title) +void OgreRenderer::adjustViewport() { - assert(mRoot); - // Initialize OGRE window - mWindow = mRoot->initialise(true, title, ""); + // Alter the camera aspect ratio to match the viewport + mCamera->setAspectRatio(Real(mView->getActualWidth()) / Real(mView->getActualHeight())); } -void OgreRenderer::createScene(const std::string camName, float fov, float nearClip) +void OgreRenderer::setWindowEventListener(Ogre::WindowEventListener* listener) { - assert(mRoot); - assert(mWindow); - // Get the SceneManager, in this case a generic one - mScene = mRoot->createSceneManager(ST_GENERIC); - - // Create the camera - mCamera = mScene->createCamera(camName); - mCamera->setNearClipDistance(nearClip); - mCamera->setFOVy(Degree(fov)); - - // Create one viewport, entire window - mView = mWindow->addViewport(mCamera); - - // Alter the camera aspect ratio to match the viewport - mCamera->setAspectRatio(Real(mView->getActualWidth()) / Real(mView->getActualHeight())); - - mFader = new Fader(); + Ogre::WindowEventUtilities::addWindowEventListener(mWindow, listener); +} + +void OgreRenderer::removeWindowEventListener(Ogre::WindowEventListener* listener) +{ + Ogre::WindowEventUtilities::removeWindowEventListener(mWindow, listener); +} + +void OgreRenderer::setFov(float fov) +{ + mCamera->setFOVy(Degree(fov)); } diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp index 179515aa92..f1520a3db4 100644 --- a/libs/openengine/ogre/renderer.hpp +++ b/libs/openengine/ogre/renderer.hpp @@ -24,6 +24,9 @@ # include "OgreD3D9Plugin.h" #endif +#include "OgreTexture.h" +#include + namespace Ogre { class Root; @@ -33,124 +36,132 @@ namespace Ogre class Viewport; } -namespace OEngine { -namespace Render +namespace OEngine { - class Fader; - class OgreRenderer - { - Ogre::Root *mRoot; - Ogre::RenderWindow *mWindow; - Ogre::SceneManager *mScene; - Ogre::Camera *mCamera; - Ogre::Viewport *mView; -#ifdef ENABLE_PLUGIN_CgProgramManager - Ogre::CgPlugin* mCgPlugin; -#endif -#ifdef ENABLE_PLUGIN_OctreeSceneManager - Ogre::OctreePlugin* mOctreePlugin; -#endif -#ifdef ENABLE_PLUGIN_ParticleFX - Ogre::ParticleFXPlugin* mParticleFXPlugin; -#endif -#ifdef ENABLE_PLUGIN_GL - Ogre::GLPlugin* mGLPlugin; -#endif -#ifdef ENABLE_PLUGIN_Direct3D9 - Ogre::D3D9Plugin* mD3D9Plugin; -#endif - Fader* mFader; - bool logging; - - public: - OgreRenderer() - : mRoot(NULL) - , mWindow(NULL) - , mScene(NULL) - , mCamera(NULL) - , mView(NULL) -#ifdef ENABLE_PLUGIN_CgProgramManager - , mCgPlugin(NULL) -#endif -#ifdef ENABLE_PLUGIN_OctreeSceneManager - , mOctreePlugin(NULL) -#endif -#ifdef ENABLE_PLUGIN_ParticleFX - , mParticleFXPlugin(NULL) -#endif -#ifdef ENABLE_PLUGIN_GL - , mGLPlugin(NULL) -#endif -#ifdef ENABLE_PLUGIN_Direct3D9 - , mD3D9Plugin(NULL) -#endif - , mFader(NULL) - , logging(false) + namespace Render { + struct WindowSettings + { + bool vsync; + bool fullscreen; + int window_x, window_y; + std::string fsaa; + }; + + class Fader; + class OgreRenderer + { + Ogre::Root *mRoot; + Ogre::RenderWindow *mWindow; + Ogre::SceneManager *mScene; + Ogre::Camera *mCamera; + Ogre::Viewport *mView; + #ifdef ENABLE_PLUGIN_CgProgramManager + Ogre::CgPlugin* mCgPlugin; + #endif + #ifdef ENABLE_PLUGIN_OctreeSceneManager + Ogre::OctreePlugin* mOctreePlugin; + #endif + #ifdef ENABLE_PLUGIN_ParticleFX + Ogre::ParticleFXPlugin* mParticleFXPlugin; + #endif + #ifdef ENABLE_PLUGIN_GL + Ogre::GLPlugin* mGLPlugin; + #endif + #ifdef ENABLE_PLUGIN_Direct3D9 + Ogre::D3D9Plugin* mD3D9Plugin; + #endif + Fader* mFader; + bool logging; + + Ogre::TexturePtr mTransparentBGTexture; + + public: + OgreRenderer() + : mRoot(NULL) + , mWindow(NULL) + , mScene(NULL) + , mCamera(NULL) + , mView(NULL) + #ifdef ENABLE_PLUGIN_CgProgramManager + , mCgPlugin(NULL) + #endif + #ifdef ENABLE_PLUGIN_OctreeSceneManager + , mOctreePlugin(NULL) + #endif + #ifdef ENABLE_PLUGIN_ParticleFX + , mParticleFXPlugin(NULL) + #endif + #ifdef ENABLE_PLUGIN_GL + , mGLPlugin(NULL) + #endif + #ifdef ENABLE_PLUGIN_Direct3D9 + , mD3D9Plugin(NULL) + #endif + , mFader(NULL) + , logging(false) + { + } + + ~OgreRenderer() { cleanup(); } + + void setWindowEventListener(Ogre::WindowEventListener* listener); + void removeWindowEventListener(Ogre::WindowEventListener* listener); + + /** Configure the renderer. This will load configuration files and + set up the Root and logging classes. */ + void configure( + const std::string &logPath, // Path to directory where to store log files + const std::string &pluginCfg, // plugin.cfg file + const std::string &renderSystem, + bool _logging); // Enable or disable logging + + /// Create a window with the given title + void createWindow(const std::string &title, const WindowSettings& settings); + + /// Set up the scene manager, camera and viewport + void createScene(const std::string& camName="Camera",// Camera name + float fov=55, // Field of view angle + float nearClip=5 // Near clip distance + ); + + void setFov(float fov); + + /// Kill the renderer. + void cleanup(); + + /// Start the main rendering loop + void start(); + + bool loadPlugins() const; + + void update(float dt); + + /// Write a screenshot to file + void screenshot(const std::string &file); + + float getFPS(); + + /// Get the Root + Ogre::Root *getRoot() { return mRoot; } + + /// Get the rendering window + Ogre::RenderWindow *getWindow() { return mWindow; } + + /// Get the scene manager + Ogre::SceneManager *getScene() { return mScene; } + + /// Get the screen colour fader + Fader *getFader() { return mFader; } + + /// Camera + Ogre::Camera *getCamera() { return mCamera; } + + /// Viewport + Ogre::Viewport *getViewport() { return mView; } + + void adjustViewport(); + }; } - - ~OgreRenderer() { cleanup(); } - - /** Configure the renderer. This will load configuration files and - set up the Root and logging classes. */ - bool configure(bool showConfig, // Show config dialog box? - const std::string &cfgPath, // Path to directory where to store config files - const std::string &logPath, // Path to directory where to store log files - const std::string &pluginCfg, // plugin.cfg file - bool _logging); // Enable or disable logging - - bool configure(bool showConfig, // Show config dialog box? - const std::string &cfgPath, // Path to directory where to store config files - const std::string &pluginCfg, // plugin.cfg file - bool _logging); // Enable or disable logging - - /** Configure the renderer. This will load configuration files and - set up the Root and logging classes. */ - bool configure(bool showConfig, // Show config dialog box? - const std::string &pluginCfg, // plugin.cfg file - bool _logging); // Enable or disable logging - - /// Create a window with the given title - void createWindow(const std::string &title); - - /// Set up the scene manager, camera and viewport - void createScene(const std::string camName="Camera",// Camera name - float fov=55, // Field of view angle - float nearClip=5 // Near clip distance - ); - - /// Kill the renderer. - void cleanup(); - - /// Start the main rendering loop - void start(); - - bool loadPlugins(); - - void update(float dt); - - /// Write a screenshot to file - void screenshot(const std::string &file); - - float getFPS(); - - /// Get the Root - Ogre::Root *getRoot() { return mRoot; } - - /// Get the rendering window - Ogre::RenderWindow *getWindow() { return mWindow; } - - /// Get the scene manager - Ogre::SceneManager *getScene() { return mScene; } - - /// Get the screen colour fader - Fader *getFader() { return mFader; } - - /// Camera - Ogre::Camera *getCamera() { return mCamera; } - - /// Viewport - Ogre::Viewport *getViewport() { return mView; } - }; -}} +} #endif diff --git a/readme.txt b/readme.txt index 52c4e11a24..513474dd64 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind OpenMW is an attempt at recreating the engine for the popular role-playing game Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. -Version: 0.14.0 +Version: 0.15.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org @@ -87,7 +87,6 @@ Allowed options: win1252 - Western European (Latin) alphabet, used by default - --report-focus [=arg(=1)] (=0) write name of focussed object to cout --fallback arg fallback values @@ -133,6 +132,32 @@ Thanks to Kevin Ryan for kindly providing us with the icon used for the Data Fil CHANGELOG +0.15.0 + +Bug #5: Physics reimplementation (fixes various issues) +Bug #258: Resizing arrow's background is not transparent +Bug #268: Widening the stats window in X direction causes layout problems +Bug #269: Topic pane in dialgoue window is too small for some longer topics +Bug #271: Dialog choices are sorted incorrectly +Bug #281: The single quote character is not rendered on dialog windows +Bug #285: Terrain not handled properly in cells that are not predefined +Bug #289: Dialogue filter isn't doing case smashing/folding for item IDs +Feature #15: Collision with Terrain +Feature #17: Inventory-, Container- and Trade-Windows +Feature #44: Floating Labels above Focussed Objects +Feature #80: Tooltips +Feature #83: Barter Dialogue +Feature #90: Book and Scroll Windows +Feature #156: Item Stacking in Containers +Feature #213: Pulsating lights +Feature #218: Feather & Burden +Feature #256: Implement magic effect bookkeeping +Feature #259: Add missing information to Stats window +Feature #260: Correct case for dialogue topics +Feature #280: GUI texture atlasing +Feature #291: Ability to use GMST strings from GUI layout files +Task #255: Make MWWorld::Environment into a singleton + 0.14.0 Bug #1: Meshes rendered with wrong orientation