forked from mirror/openmw-tes3mp
Merge branch 'master' into savedgame
Conflicts: apps/openmw/mwmechanics/actors.cpp apps/openmw/mwworld/worldimp.cpp files/settings-default.cfg
This commit is contained in:
commit
030c733e2d
172 changed files with 4247 additions and 1455 deletions
|
@ -9,13 +9,13 @@ before_install:
|
|||
- pwd
|
||||
- git submodule update --init --recursive
|
||||
- echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse"
|
||||
- echo "yes" | sudo apt-add-repository ppa:openmw/deps
|
||||
- echo "yes" | sudo apt-add-repository ppa:openmw/openmw
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev
|
||||
- sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev uuid-dev
|
||||
- sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev
|
||||
- sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev
|
||||
- sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev
|
||||
- sudo apt-get install -qq libbullet-dev libogre-static-dev libmygui-static-dev libsdl2-static-dev libunshield-dev
|
||||
- sudo apt-get install -qq libbullet-dev libogre-1.8-dev libmygui-dev libsdl2-dev libunshield-dev
|
||||
- sudo mkdir /usr/src/gtest/build
|
||||
- cd /usr/src/gtest/build
|
||||
- sudo cmake .. -DBUILD_SHARED_LIBS=1
|
||||
|
@ -26,7 +26,7 @@ before_script:
|
|||
- cd -
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake .. -DOGRE_STATIC=1 -DMYGUI_STATIC=1 -DBOOST_STATIC=1 -DSDL2_STATIC=1 -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1
|
||||
- cmake .. -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 -DBUILD_WITH_DPKG=1
|
||||
script:
|
||||
- make -j4
|
||||
after_script:
|
||||
|
|
|
@ -50,7 +50,12 @@ option(USE_MPG123 "use mpg123 + libsndfile for sound" ON)
|
|||
# OS X deployment
|
||||
option(OPENMW_OSX_DEPLOYMENT OFF)
|
||||
|
||||
find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems")
|
||||
if(UNIX AND NOT APPLE)
|
||||
option(BUILD_WITH_DPKG "enable dpkg-based install for debian and debian derivatives" OFF)
|
||||
if(BUILD_WITH_DPKG)
|
||||
find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems")
|
||||
endif(BUILD_WITH_DPKG)
|
||||
endif(UNIX AND NOT APPLE)
|
||||
|
||||
# Location of morrowind data files
|
||||
if (APPLE)
|
||||
|
@ -684,7 +689,10 @@ if (APPLE)
|
|||
set(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINO})
|
||||
set(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE})
|
||||
|
||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}")
|
||||
set(OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}")
|
||||
|
||||
set(OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/OpenCS.app")
|
||||
|
||||
set(PLUGINS "")
|
||||
set(ABSOLUTE_PLUGINS "")
|
||||
|
||||
|
@ -745,7 +753,8 @@ if (APPLE)
|
|||
cmake_policy(SET CMP0009 OLD)
|
||||
set(BU_CHMOD_BUNDLE_ITEMS ON)
|
||||
include(BundleUtilities)
|
||||
fixup_bundle(\"${APPS}\" \"${PLUGINS}\" \"${DIRS}\")
|
||||
fixup_bundle(\"${OPENMW_APP}\" \"${PLUGINS}\" \"${DIRS}\")
|
||||
fixup_bundle(\"${OPENCS_APP}\" \"\" \"${DIRS}\")
|
||||
" COMPONENT Runtime)
|
||||
include(CPack)
|
||||
endif (APPLE)
|
||||
|
|
|
@ -184,7 +184,7 @@ int list(Bsa::BSAFile& bsa, Arguments& info)
|
|||
{
|
||||
// List all files
|
||||
const Bsa::BSAFile::FileList &files = bsa.getList();
|
||||
for(int i=0; i<files.size(); i++)
|
||||
for(unsigned int i=0; i<files.size(); i++)
|
||||
{
|
||||
if(info.longformat)
|
||||
{
|
||||
|
|
|
@ -24,7 +24,12 @@ namespace EsmTool
|
|||
bool mPrintPlain;
|
||||
|
||||
public:
|
||||
RecordBase () { mPrintPlain = false; }
|
||||
RecordBase ()
|
||||
: mFlags(0)
|
||||
, mPrintPlain(false)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~RecordBase() {}
|
||||
|
||||
const std::string &getId() const {
|
||||
|
|
|
@ -75,14 +75,8 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile)
|
|||
mLauncherSettings.setValue(QString("Profiles/currentprofile"), ui.profilesComboBox->currentText());
|
||||
|
||||
foreach(const ContentSelectorModel::EsmFile *item, items) {
|
||||
|
||||
if (item->gameFiles().size() == 0) {
|
||||
mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName());
|
||||
mGameSettings.setMultiValue(QString("content"), item->fileName());
|
||||
} else {
|
||||
mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName());
|
||||
mGameSettings.setMultiValue(QString("content"), item->fileName());
|
||||
}
|
||||
mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName());
|
||||
mGameSettings.setMultiValue(QString("content"), item->fileName());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -116,8 +110,7 @@ void Launcher::DataFilesPage::buildView()
|
|||
|
||||
void Launcher::DataFilesPage::removeProfile(const QString &profile)
|
||||
{
|
||||
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/game"));
|
||||
mLauncherSettings.remove(QString("Profiles/") + profile + QString("/addon"));
|
||||
mLauncherSettings.remove(QString("Profiles/") + profile);
|
||||
}
|
||||
|
||||
QAbstractItemModel *Launcher::DataFilesPage::profilesModel() const
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
#include <QMessageBox>
|
||||
#include <QDir>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#ifdef MAC_OS_X_VERSION_MIN_REQUIRED
|
||||
#undef MAC_OS_X_VERSION_MIN_REQUIRED
|
||||
// We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154
|
||||
#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
|
||||
#endif
|
||||
#endif // MAC_OS_X_VERSION_MIN_REQUIRED
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <boost/math/common_factor.hpp>
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
#include <QDir>
|
||||
#include <QDebug>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#ifdef MAC_OS_X_VERSION_MIN_REQUIRED
|
||||
#undef MAC_OS_X_VERSION_MIN_REQUIRED
|
||||
// We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154
|
||||
#define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
|
||||
#endif
|
||||
#endif // MAC_OS_X_VERSION_MIN_REQUIRED
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "maindialog.hpp"
|
||||
|
|
|
@ -152,13 +152,36 @@ qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES})
|
|||
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
if(APPLE)
|
||||
set (OPENCS_MAC_ICON ${CMAKE_SOURCE_DIR}/files/mac/opencs.icns)
|
||||
else()
|
||||
set (OPENCS_MAC_ICON "")
|
||||
endif(APPLE)
|
||||
|
||||
add_executable(opencs
|
||||
MACOSX_BUNDLE
|
||||
${OPENCS_SRC}
|
||||
${OPENCS_UI_HDR}
|
||||
${OPENCS_MOC_SRC}
|
||||
${OPENCS_RES_SRC}
|
||||
${OPENCS_MAC_ICON}
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
set_target_properties(opencs PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}"
|
||||
OUTPUT_NAME "OpenCS"
|
||||
MACOSX_BUNDLE_ICON_FILE "opencs.icns"
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "OpenCS"
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs"
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION}
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION}
|
||||
)
|
||||
|
||||
set_source_files_properties(${OPENCS_MAC_ICON} PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION Resources)
|
||||
endif(APPLE)
|
||||
|
||||
target_link_libraries(opencs
|
||||
${Boost_LIBRARIES}
|
||||
${QT_LIBRARIES}
|
||||
|
@ -169,3 +192,6 @@ if(DPKG_PROGRAM)
|
|||
INSTALL(TARGETS opencs RUNTIME DESTINATION games COMPONENT opencs)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
INSTALL(TARGETS opencs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE)
|
||||
endif()
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
|
||||
#include <components/ogreinit/ogreinit.hpp>
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include <QDir>
|
||||
#endif
|
||||
|
||||
class Application : public QApplication
|
||||
{
|
||||
private:
|
||||
|
@ -43,6 +47,25 @@ int main(int argc, char *argv[])
|
|||
|
||||
Application mApplication (argc, argv);
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
QDir dir(QCoreApplication::applicationDirPath());
|
||||
if (dir.dirName() == "MacOS") {
|
||||
dir.cdUp();
|
||||
dir.cdUp();
|
||||
dir.cdUp();
|
||||
}
|
||||
QDir::setCurrent(dir.absolutePath());
|
||||
|
||||
// force Qt to load only LOCAL plugins, don't touch system Qt installation
|
||||
QDir pluginsPath(QCoreApplication::applicationDirPath());
|
||||
pluginsPath.cdUp();
|
||||
pluginsPath.cd("Plugins");
|
||||
|
||||
QStringList libraryPaths;
|
||||
libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath();
|
||||
mApplication.setLibraryPaths(libraryPaths);
|
||||
#endif
|
||||
|
||||
mApplication.setWindowIcon (QIcon (":./opencs.png"));
|
||||
|
||||
CS::Editor editor;
|
||||
|
|
|
@ -2245,29 +2245,39 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, co
|
|||
mData.setDescription ("");
|
||||
mData.setAuthor ("");
|
||||
}
|
||||
/// \todo un-outcomment the else, once loading an existing content file works properly again.
|
||||
|
||||
bool filtersFound = false;
|
||||
|
||||
if (boost::filesystem::exists (mProjectPath))
|
||||
{
|
||||
filtersFound = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (boost::filesystem::exists (mProjectPath))
|
||||
boost::filesystem::path locCustomFiltersPath (configuration.getUserPath());
|
||||
locCustomFiltersPath /= "defaultfilters";
|
||||
|
||||
if (boost::filesystem::exists(locCustomFiltersPath))
|
||||
{
|
||||
getData().loadFile (mProjectPath, false, true);
|
||||
boost::filesystem::copy_file (locCustomFiltersPath, mProjectPath);
|
||||
filtersFound = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
boost::filesystem::path locCustomFiltersPath (configuration.getUserPath());
|
||||
locCustomFiltersPath /= "defaultfilters";
|
||||
if (boost::filesystem::exists(locCustomFiltersPath))
|
||||
boost::filesystem::path filters(mResDir);
|
||||
filters /= "defaultfilters";
|
||||
|
||||
if (boost::filesystem::exists(filters))
|
||||
{
|
||||
boost::filesystem::copy_file (locCustomFiltersPath, mProjectPath);
|
||||
} else {
|
||||
boost::filesystem::path filters(mResDir);
|
||||
filters /= "defaultfilters";
|
||||
boost::filesystem::copy_file(filters, mProjectPath);
|
||||
filtersFound = true;
|
||||
}
|
||||
getData().loadFile (mProjectPath, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (filtersFound)
|
||||
getData().loadFile (mProjectPath, false, true);
|
||||
|
||||
addOptionalGmsts();
|
||||
addOptionalGlobals();
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@ namespace CSMFilter
|
|||
{
|
||||
class AndNode : public NAryNode
|
||||
{
|
||||
bool mAnd;
|
||||
|
||||
public:
|
||||
|
||||
AndNode (const std::vector<boost::shared_ptr<Node> >& nodes);
|
||||
|
|
|
@ -7,8 +7,6 @@ namespace CSMFilter
|
|||
{
|
||||
class OrNode : public NAryNode
|
||||
{
|
||||
bool mAnd;
|
||||
|
||||
public:
|
||||
|
||||
OrNode (const std::vector<boost::shared_ptr<Node> >& nodes);
|
||||
|
|
|
@ -61,11 +61,11 @@ namespace CSMFilter
|
|||
bool isString() const;
|
||||
};
|
||||
|
||||
Token::Token (Type type) : mType (type) {}
|
||||
Token::Token (Type type) : mType (type), mNumber(0.0) {}
|
||||
|
||||
Token::Token (Type type, const std::string& string) : mType (type), mString (string) {}
|
||||
Token::Token (Type type, const std::string& string) : mType (type), mString (string), mNumber(0.0) {}
|
||||
|
||||
Token::Token (const std::string& string) : mType (Type_String), mString (string) {}
|
||||
Token::Token (const std::string& string) : mType (Type_String), mString (string), mNumber(0.0) {}
|
||||
|
||||
Token::Token (double number) : mType (Type_Number), mNumber (number) {}
|
||||
|
||||
|
@ -614,4 +614,4 @@ boost::shared_ptr<CSMFilter::Node> CSMFilter::Parser::getFilter() const
|
|||
throw std::logic_error ("No filter available");
|
||||
|
||||
return mFilter;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,11 @@ namespace CSMSettings
|
|||
inline QStringPair *getValuePair() { return mValuePair; }
|
||||
|
||||
/// set value range (spinbox / integer use)
|
||||
inline void setValuePair (QStringPair valuePair) { mValuePair = new QStringPair(valuePair); }
|
||||
inline void setValuePair (QStringPair valuePair)
|
||||
{
|
||||
delete mValuePair;
|
||||
mValuePair = new QStringPair(valuePair);
|
||||
}
|
||||
|
||||
inline bool isMultivalue () { return mIsMultiValue; }
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager)
|
|||
manager.add (CSMWorld::UniversalId::Type_Topics,
|
||||
new CSVDoc::SubViewFactoryWithCreator<TableSubView, TopicCreatorFactory>);
|
||||
|
||||
manager.add (CSMWorld::UniversalId::Type_Journal,
|
||||
manager.add (CSMWorld::UniversalId::Type_Journals,
|
||||
new CSVDoc::SubViewFactoryWithCreator<TableSubView, JournalCreatorFactory>);
|
||||
|
||||
manager.add (CSMWorld::UniversalId::Type_TopicInfos,
|
||||
|
|
|
@ -49,10 +49,10 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event)
|
|||
{
|
||||
int row =selectedRows.begin()->row();
|
||||
|
||||
int column = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Topic);
|
||||
int column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Topic);
|
||||
|
||||
if (column==-1)
|
||||
column = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Journal);
|
||||
column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Journal);
|
||||
|
||||
if (column!=-1)
|
||||
{
|
||||
|
@ -410,4 +410,4 @@ void CSVWorld::Table::requestFocus (const std::string& id)
|
|||
void CSVWorld::Table::recordFilterChanged (boost::shared_ptr<CSMFilter::Node> filter)
|
||||
{
|
||||
mProxyModel->setFilter (filter);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@ set(GAME
|
|||
main.cpp
|
||||
engine.cpp
|
||||
)
|
||||
if(NOT WIN32)
|
||||
set(GAME ${GAME} crashcatcher.cpp)
|
||||
endif()
|
||||
set(GAME_HEADER
|
||||
engine.hpp
|
||||
config.hpp
|
||||
|
@ -35,6 +38,7 @@ add_openmw_dir (mwgui
|
|||
merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks
|
||||
keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview
|
||||
tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog
|
||||
recharge
|
||||
)
|
||||
|
||||
add_openmw_dir (mwdialogue
|
||||
|
@ -69,7 +73,7 @@ add_openmw_dir (mwclass
|
|||
add_openmw_dir (mwmechanics
|
||||
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects
|
||||
drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow
|
||||
aiescort aiactivate repair enchanting pathfinding security spellsuccess
|
||||
aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting
|
||||
)
|
||||
|
||||
add_openmw_dir (mwstate
|
||||
|
|
449
apps/openmw/crashcatcher.cpp
Normal file
449
apps/openmw/crashcatcher.cpp
Normal file
|
@ -0,0 +1,449 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/ucontext.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/ptrace.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <SDL_messagebox.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/prctl.h>
|
||||
#ifndef PR_SET_PTRACER
|
||||
#define PR_SET_PTRACER 0x59616d61
|
||||
#endif
|
||||
#elif defined (__APPLE__)
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
|
||||
static const char crash_switch[] = "--cc-handle-crash";
|
||||
|
||||
static const char fatal_err[] = "\n\n*** Fatal Error ***\n";
|
||||
static const char pipe_err[] = "!!! Failed to create pipe\n";
|
||||
static const char fork_err[] = "!!! Failed to fork debug process\n";
|
||||
static const char exec_err[] = "!!! Failed to exec debug process\n";
|
||||
|
||||
static char argv0[PATH_MAX];
|
||||
|
||||
static char altstack[SIGSTKSZ];
|
||||
|
||||
|
||||
static struct {
|
||||
int signum;
|
||||
pid_t pid;
|
||||
int has_siginfo;
|
||||
siginfo_t siginfo;
|
||||
char buf[1024];
|
||||
} crash_info;
|
||||
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
int signum;
|
||||
} signals[] = {
|
||||
{ "Segmentation fault", SIGSEGV },
|
||||
{ "Illegal instruction", SIGILL },
|
||||
{ "FPU exception", SIGFPE },
|
||||
{ "System BUS error", SIGBUS },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
static const struct {
|
||||
int code;
|
||||
const char *name;
|
||||
} sigill_codes[] = {
|
||||
#ifndef __FreeBSD__
|
||||
{ ILL_ILLOPC, "Illegal opcode" },
|
||||
{ ILL_ILLOPN, "Illegal operand" },
|
||||
{ ILL_ILLADR, "Illegal addressing mode" },
|
||||
{ ILL_ILLTRP, "Illegal trap" },
|
||||
{ ILL_PRVOPC, "Privileged opcode" },
|
||||
{ ILL_PRVREG, "Privileged register" },
|
||||
{ ILL_COPROC, "Coprocessor error" },
|
||||
{ ILL_BADSTK, "Internal stack error" },
|
||||
#endif
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static const struct {
|
||||
int code;
|
||||
const char *name;
|
||||
} sigfpe_codes[] = {
|
||||
{ FPE_INTDIV, "Integer divide by zero" },
|
||||
{ FPE_INTOVF, "Integer overflow" },
|
||||
{ FPE_FLTDIV, "Floating point divide by zero" },
|
||||
{ FPE_FLTOVF, "Floating point overflow" },
|
||||
{ FPE_FLTUND, "Floating point underflow" },
|
||||
{ FPE_FLTRES, "Floating point inexact result" },
|
||||
{ FPE_FLTINV, "Floating point invalid operation" },
|
||||
{ FPE_FLTSUB, "Subscript out of range" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static const struct {
|
||||
int code;
|
||||
const char *name;
|
||||
} sigsegv_codes[] = {
|
||||
#ifndef __FreeBSD__
|
||||
{ SEGV_MAPERR, "Address not mapped to object" },
|
||||
{ SEGV_ACCERR, "Invalid permissions for mapped object" },
|
||||
#endif
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static const struct {
|
||||
int code;
|
||||
const char *name;
|
||||
} sigbus_codes[] = {
|
||||
#ifndef __FreeBSD__
|
||||
{ BUS_ADRALN, "Invalid address alignment" },
|
||||
{ BUS_ADRERR, "Non-existent physical address" },
|
||||
{ BUS_OBJERR, "Object specific hardware error" },
|
||||
#endif
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static int (*cc_user_info)(char*, char*);
|
||||
|
||||
|
||||
static void gdb_info(pid_t pid)
|
||||
{
|
||||
char respfile[64];
|
||||
char cmd_buf[128];
|
||||
FILE *f;
|
||||
int fd;
|
||||
|
||||
/* Create a temp file to put gdb commands into */
|
||||
strcpy(respfile, "gdb-respfile-XXXXXX");
|
||||
if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != NULL)
|
||||
{
|
||||
fprintf(f, "attach %d\n"
|
||||
"shell echo \"\"\n"
|
||||
"shell echo \"* Loaded Libraries\"\n"
|
||||
"info sharedlibrary\n"
|
||||
"shell echo \"\"\n"
|
||||
"shell echo \"* Threads\"\n"
|
||||
"info threads\n"
|
||||
"shell echo \"\"\n"
|
||||
"shell echo \"* FPU Status\"\n"
|
||||
"info float\n"
|
||||
"shell echo \"\"\n"
|
||||
"shell echo \"* Registers\"\n"
|
||||
"info registers\n"
|
||||
"shell echo \"\"\n"
|
||||
"shell echo \"* Backtrace\"\n"
|
||||
"thread apply all backtrace full\n"
|
||||
"detach\n"
|
||||
"quit\n", pid);
|
||||
fclose(f);
|
||||
|
||||
/* Run gdb and print process info. */
|
||||
snprintf(cmd_buf, sizeof(cmd_buf), "gdb --quiet --batch --command=%s", respfile);
|
||||
printf("Executing: %s\n", cmd_buf);
|
||||
fflush(stdout);
|
||||
|
||||
system(cmd_buf);
|
||||
/* Clean up */
|
||||
remove(respfile);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Error creating temp file */
|
||||
if(fd >= 0)
|
||||
{
|
||||
close(fd);
|
||||
remove(respfile);
|
||||
}
|
||||
printf("!!! Could not create gdb command file\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void sys_info(void)
|
||||
{
|
||||
#ifdef __unix__
|
||||
system("echo \"System: `uname -a`\"");
|
||||
putchar('\n');
|
||||
fflush(stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static size_t safe_write(int fd, const void *buf, size_t len)
|
||||
{
|
||||
size_t ret = 0;
|
||||
while(ret < len)
|
||||
{
|
||||
ssize_t rem;
|
||||
if((rem=write(fd, (const char*)buf+ret, len-ret)) == -1)
|
||||
{
|
||||
if(errno == EINTR)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
ret += rem;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void crash_catcher(int signum, siginfo_t *siginfo, void *context)
|
||||
{
|
||||
//ucontext_t *ucontext = (ucontext_t*)context;
|
||||
pid_t dbg_pid;
|
||||
int fd[2];
|
||||
|
||||
/* Make sure the effective uid is the real uid */
|
||||
if(getuid() != geteuid())
|
||||
{
|
||||
raise(signum);
|
||||
return;
|
||||
}
|
||||
|
||||
safe_write(STDERR_FILENO, fatal_err, sizeof(fatal_err)-1);
|
||||
if(pipe(fd) == -1)
|
||||
{
|
||||
safe_write(STDERR_FILENO, pipe_err, sizeof(pipe_err)-1);
|
||||
raise(signum);
|
||||
return;
|
||||
}
|
||||
|
||||
crash_info.signum = signum;
|
||||
crash_info.pid = getpid();
|
||||
crash_info.has_siginfo = !!siginfo;
|
||||
if(siginfo)
|
||||
crash_info.siginfo = *siginfo;
|
||||
if(cc_user_info)
|
||||
cc_user_info(crash_info.buf, crash_info.buf+sizeof(crash_info.buf));
|
||||
|
||||
/* Fork off to start a crash handler */
|
||||
switch((dbg_pid=fork()))
|
||||
{
|
||||
/* Error */
|
||||
case -1:
|
||||
safe_write(STDERR_FILENO, fork_err, sizeof(fork_err)-1);
|
||||
raise(signum);
|
||||
return;
|
||||
|
||||
case 0:
|
||||
dup2(fd[0], STDIN_FILENO);
|
||||
close(fd[0]);
|
||||
close(fd[1]);
|
||||
|
||||
execl(argv0, argv0, crash_switch, NULL);
|
||||
|
||||
safe_write(STDERR_FILENO, exec_err, sizeof(exec_err)-1);
|
||||
_exit(1);
|
||||
|
||||
default:
|
||||
#ifdef __linux__
|
||||
prctl(PR_SET_PTRACER, dbg_pid, 0, 0, 0);
|
||||
#endif
|
||||
safe_write(fd[1], &crash_info, sizeof(crash_info));
|
||||
close(fd[0]);
|
||||
close(fd[1]);
|
||||
|
||||
/* Wait; we'll be killed when gdb is done */
|
||||
do {
|
||||
int status;
|
||||
if(waitpid(dbg_pid, &status, 0) == dbg_pid &&
|
||||
(WIFEXITED(status) || WIFSIGNALED(status)))
|
||||
{
|
||||
/* The debug process died before it could kill us */
|
||||
raise(signum);
|
||||
break;
|
||||
}
|
||||
} while(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void crash_handler(const char *logfile)
|
||||
{
|
||||
const char *sigdesc = "";
|
||||
int i;
|
||||
|
||||
if(fread(&crash_info, sizeof(crash_info), 1, stdin) != 1)
|
||||
{
|
||||
fprintf(stderr, "!!! Failed to retrieve info from crashed process\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Get the signal description */
|
||||
for(i = 0;signals[i].name;++i)
|
||||
{
|
||||
if(signals[i].signum == crash_info.signum)
|
||||
{
|
||||
sigdesc = signals[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(crash_info.has_siginfo)
|
||||
{
|
||||
switch(crash_info.signum)
|
||||
{
|
||||
case SIGSEGV:
|
||||
for(i = 0;sigsegv_codes[i].name;++i)
|
||||
{
|
||||
if(sigsegv_codes[i].code == crash_info.siginfo.si_code)
|
||||
{
|
||||
sigdesc = sigsegv_codes[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SIGFPE:
|
||||
for(i = 0;sigfpe_codes[i].name;++i)
|
||||
{
|
||||
if(sigfpe_codes[i].code == crash_info.siginfo.si_code)
|
||||
{
|
||||
sigdesc = sigfpe_codes[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SIGILL:
|
||||
for(i = 0;sigill_codes[i].name;++i)
|
||||
{
|
||||
if(sigill_codes[i].code == crash_info.siginfo.si_code)
|
||||
{
|
||||
sigdesc = sigill_codes[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SIGBUS:
|
||||
for(i = 0;sigbus_codes[i].name;++i)
|
||||
{
|
||||
if(sigbus_codes[i].code == crash_info.siginfo.si_code)
|
||||
{
|
||||
sigdesc = sigbus_codes[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "%s (signal %i)\n", sigdesc, crash_info.signum);
|
||||
if(crash_info.has_siginfo)
|
||||
fprintf(stderr, "Address: %p\n", crash_info.siginfo.si_addr);
|
||||
fputc('\n', stderr);
|
||||
|
||||
if(logfile)
|
||||
{
|
||||
/* Create crash log file and redirect shell output to it */
|
||||
if(freopen(logfile, "wa", stdout) != stdout)
|
||||
{
|
||||
fprintf(stderr, "!!! Could not create %s following signal\n", logfile);
|
||||
exit(1);
|
||||
}
|
||||
fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid);
|
||||
|
||||
printf("*** Fatal Error ***\n"
|
||||
"%s (signal %i)\n", sigdesc, crash_info.signum);
|
||||
if(crash_info.has_siginfo)
|
||||
printf("Address: %p\n", crash_info.siginfo.si_addr);
|
||||
fputc('\n', stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
sys_info();
|
||||
|
||||
crash_info.buf[sizeof(crash_info.buf)-1] = '\0';
|
||||
printf("%s\n", crash_info.buf);
|
||||
fflush(stdout);
|
||||
|
||||
if(crash_info.pid > 0)
|
||||
{
|
||||
gdb_info(crash_info.pid);
|
||||
kill(crash_info.pid, SIGKILL);
|
||||
}
|
||||
|
||||
if(logfile)
|
||||
{
|
||||
char cwd[MAXPATHLEN];
|
||||
getcwd(cwd, MAXPATHLEN);
|
||||
|
||||
std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(cwd) + "/" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !";
|
||||
SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), NULL);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*))
|
||||
{
|
||||
struct sigaction sa;
|
||||
stack_t altss;
|
||||
int retval;
|
||||
|
||||
if(argc == 2 && strcmp(argv[1], crash_switch) == 0)
|
||||
crash_handler(logfile);
|
||||
|
||||
cc_user_info = user_info;
|
||||
|
||||
if(argv[0][0] == '/')
|
||||
snprintf(argv0, sizeof(argv0), "%s", argv[0]);
|
||||
else
|
||||
{
|
||||
getcwd(argv0, sizeof(argv0));
|
||||
retval = strlen(argv0);
|
||||
snprintf(argv0+retval, sizeof(argv0)-retval, "/%s", argv[0]);
|
||||
}
|
||||
|
||||
/* Set an alternate signal stack so SIGSEGVs caused by stack overflows
|
||||
* still run */
|
||||
altss.ss_sp = altstack;
|
||||
altss.ss_flags = 0;
|
||||
altss.ss_size = sizeof(altstack);
|
||||
sigaltstack(&altss, NULL);
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_sigaction = crash_catcher;
|
||||
sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
|
||||
retval = 0;
|
||||
while(num_signals--)
|
||||
{
|
||||
if((*signals != SIGSEGV && *signals != SIGILL && *signals != SIGFPE && *signals != SIGABRT &&
|
||||
*signals != SIGBUS) || sigaction(*signals, &sa, NULL) == -1)
|
||||
{
|
||||
*signals = 0;
|
||||
retval = -1;
|
||||
}
|
||||
++signals;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
// gdb apparently opens FD(s) 3,4,5 (whereas a typical prog uses only stdin=0, stdout=1,stderr=2)
|
||||
bool
|
||||
is_debugger_attached(void)
|
||||
{
|
||||
bool rc = false;
|
||||
FILE *fd = fopen("/tmp", "r");
|
||||
|
||||
if (fileno(fd) > 5)
|
||||
{
|
||||
rc = true;
|
||||
}
|
||||
|
||||
fclose(fd);
|
||||
return rc;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
#include "engine.hpp"
|
||||
|
||||
#include "components/esm/loadcell.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
#include <OgreRoot.h>
|
||||
#include <OgreRenderWindow.h>
|
||||
|
@ -20,6 +20,8 @@
|
|||
#include <components/nifbullet/bulletnifloader.hpp>
|
||||
#include <components/nifogre/ogrenifloader.hpp>
|
||||
|
||||
#include <components/esm/loadcell.hpp>
|
||||
|
||||
#include "mwinput/inputmanagerimp.hpp"
|
||||
|
||||
#include "mwgui/windowmanagerimp.hpp"
|
||||
|
@ -153,6 +155,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
|
|||
, mEncoding(ToUTF8::WINDOWS_1252)
|
||||
, mEncoder(NULL)
|
||||
, mActivationDistanceOverride(-1)
|
||||
, mGrab(true)
|
||||
|
||||
{
|
||||
std::srand ( std::time(NULL) );
|
||||
|
@ -216,7 +219,9 @@ void OMW::Engine::loadBSA()
|
|||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Archive " << *archive << " not found" << std::endl;
|
||||
std::stringstream message;
|
||||
message << "Archive '" << *archive << "' not found";
|
||||
throw std::runtime_error(message.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -379,7 +384,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
|||
|
||||
std::string keybinderUser = (mCfgMgr.getUserPath() / "input.xml").string();
|
||||
bool keybinderUserExists = boost::filesystem::exists(keybinderUser);
|
||||
MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists);
|
||||
MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists, mGrab);
|
||||
mEnvironment.setInputManager (input);
|
||||
|
||||
MWGui::WindowManager* window = new MWGui::WindowManager(
|
||||
|
|
|
@ -79,6 +79,8 @@ namespace OMW
|
|||
bool mScriptConsoleMode;
|
||||
std::string mStartupScript;
|
||||
int mActivationDistanceOverride;
|
||||
// Grab mouse?
|
||||
bool mGrab;
|
||||
|
||||
Compiler::Extensions mExtensions;
|
||||
Compiler::Context *mScriptContext;
|
||||
|
@ -151,6 +153,8 @@ namespace OMW
|
|||
|
||||
void setSkipMenu (bool skipMenu);
|
||||
|
||||
void setGrabMouse(bool grab) { mGrab = grab; }
|
||||
|
||||
/// Initialise and enter main loop.
|
||||
void go();
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
#include <iostream>
|
||||
#include <cstdio>
|
||||
|
||||
#include <components/files/configurationmanager.hpp>
|
||||
|
||||
#include <SDL_messagebox.h>
|
||||
#include <SDL_main.h>
|
||||
#include "engine.hpp"
|
||||
|
||||
|
@ -16,6 +18,13 @@
|
|||
|
||||
#endif
|
||||
|
||||
|
||||
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE
|
||||
#include <csignal>
|
||||
extern int cc_install_handlers(int argc, char **argv, int num_signals, int *sigs, const char *logfile, int (*user_info)(char*, char*));
|
||||
extern int is_debugger_attached(void);
|
||||
#endif
|
||||
|
||||
// for Ogre::macBundlePath
|
||||
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
|
||||
#include <OSX/macUtils.h>
|
||||
|
@ -147,6 +156,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
|||
("fallback", bpo::value<FallbackMap>()->default_value(FallbackMap(), "")
|
||||
->multitoken()->composing(), "fallback values")
|
||||
|
||||
("no-grab", "Don't grab mouse cursor")
|
||||
|
||||
("activate-dist", bpo::value <int> ()->default_value (-1), "activation distance override");
|
||||
|
||||
bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv)
|
||||
|
@ -177,6 +188,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
|||
if (!run)
|
||||
return false;
|
||||
|
||||
engine.setGrabMouse(!variables.count("no-grab"));
|
||||
|
||||
// Font encoding settings
|
||||
std::string encoding(variables["encoding"].as<std::string>());
|
||||
std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl;
|
||||
|
@ -239,6 +252,18 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
|||
|
||||
int main(int argc, char**argv)
|
||||
{
|
||||
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE
|
||||
// Unix crash catcher
|
||||
if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached())
|
||||
{
|
||||
int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT };
|
||||
cc_install_handlers(argc, argv, 5, s, "crash.log", NULL);
|
||||
std::cout << "Installing crash catcher" << std::endl;
|
||||
}
|
||||
else
|
||||
std::cout << "Running in a debugger, not installing crash catcher" << std::endl;
|
||||
#endif
|
||||
|
||||
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
|
||||
// set current dir to bundle path
|
||||
boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path();
|
||||
|
@ -257,7 +282,13 @@ int main(int argc, char**argv)
|
|||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
std::cout << "\nERROR: " << e.what() << std::endl;
|
||||
#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE
|
||||
if (isatty(fileno(stdin)))
|
||||
std::cerr << "\nERROR: " << e.what() << std::endl;
|
||||
else
|
||||
#endif
|
||||
SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -59,6 +59,8 @@ namespace MWBase
|
|||
/// \param paused In game type does not currently advance (this usually means some GUI
|
||||
/// component is up).
|
||||
|
||||
virtual void advanceTime (float duration) = 0;
|
||||
|
||||
virtual void setPlayerName (const std::string& name) = 0;
|
||||
///< Set player name.
|
||||
|
||||
|
@ -114,6 +116,13 @@ namespace MWBase
|
|||
/// references that are currently not in the scene should be ignored.
|
||||
|
||||
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0;
|
||||
|
||||
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
|
||||
/// paused we may want to do it manually (after equipping permanent enchantment)
|
||||
virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0;
|
||||
|
||||
virtual void toggleAI() = 0;
|
||||
virtual bool isAIActive() = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -265,6 +265,7 @@ namespace MWBase
|
|||
virtual void showCompanionWindow(MWWorld::Ptr actor) = 0;
|
||||
virtual void startSpellMaking(MWWorld::Ptr actor) = 0;
|
||||
virtual void startEnchanting(MWWorld::Ptr actor) = 0;
|
||||
virtual void startRecharge(MWWorld::Ptr soulgem) = 0;
|
||||
virtual void startSelfEnchanting(MWWorld::Ptr soulgem) = 0;
|
||||
virtual void startTraining(MWWorld::Ptr actor) = 0;
|
||||
virtual void startRepair(MWWorld::Ptr actor) = 0;
|
||||
|
@ -283,6 +284,9 @@ namespace MWBase
|
|||
virtual void setKeyFocusWidget (MyGUI::Widget* widget) = 0;
|
||||
|
||||
virtual Loading::Listener* getLoadingScreen() = 0;
|
||||
|
||||
/// Should the cursor be visible?
|
||||
virtual bool getCursorVisible() = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -259,7 +259,7 @@ namespace MWBase
|
|||
|
||||
virtual void localRotateObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0;
|
||||
|
||||
virtual void safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) = 0;
|
||||
virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) = 0;
|
||||
///< place an object in a "safe" location (ie not in the void, etc).
|
||||
|
||||
virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false)
|
||||
|
@ -380,6 +380,9 @@ namespace MWBase
|
|||
virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector<MWWorld::Ptr>& out) = 0;
|
||||
///< get all items in active cells owned by this Npc
|
||||
|
||||
virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) = 0;
|
||||
///< get Line of Sight (morrowind stupid implementation)
|
||||
|
||||
virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0;
|
||||
|
||||
virtual void setupExternalRendering (MWRender::ExternalRendering& rendering) = 0;
|
||||
|
@ -432,7 +435,7 @@ namespace MWBase
|
|||
|
||||
virtual void castSpell (const MWWorld::Ptr& actor) = 0;
|
||||
|
||||
virtual void launchProjectile (const std::string& id, const ESM::EffectList& effects,
|
||||
virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects,
|
||||
const MWWorld::Ptr& actor, const std::string& sourceName) = 0;
|
||||
|
||||
virtual const std::vector<std::string>& getContentFiles() const = 0;
|
||||
|
|
|
@ -169,7 +169,10 @@ namespace MWClass
|
|||
MWWorld::LiveCellRef<ESM::Armor> *ref =
|
||||
ptr.get<ESM::Armor>();
|
||||
|
||||
return ref->mBase->mData.mValue;
|
||||
if (ptr.getCellRef().mCharge == -1)
|
||||
return ref->mBase->mData.mValue;
|
||||
else
|
||||
return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr));
|
||||
}
|
||||
|
||||
void Armor::registerSelf()
|
||||
|
@ -281,6 +284,7 @@ namespace MWClass
|
|||
newItem.mEnchant=enchId;
|
||||
const ESM::Armor *record = MWBase::Environment::get().getWorld()->createRecord (newItem);
|
||||
ref->mBase = record;
|
||||
ref->mRef.mRefID = record->mId;
|
||||
}
|
||||
|
||||
std::pair<int, std::string> Armor::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const
|
||||
|
|
|
@ -171,6 +171,7 @@ namespace MWClass
|
|||
newItem.mEnchant=enchId;
|
||||
const ESM::Book *record = MWBase::Environment::get().getWorld()->createRecord (newItem);
|
||||
ref->mBase = record;
|
||||
ref->mRef.mRefID = record->mId;
|
||||
}
|
||||
|
||||
boost::shared_ptr<MWWorld::Action> Book::use (const MWWorld::Ptr& ptr) const
|
||||
|
|
|
@ -227,6 +227,7 @@ namespace MWClass
|
|||
newItem.mEnchant=enchId;
|
||||
const ESM::Clothing *record = MWBase::Environment::get().getWorld()->createRecord (newItem);
|
||||
ref->mBase = record;
|
||||
ref->mRef.mRefID = record->mId;
|
||||
}
|
||||
|
||||
std::pair<int, std::string> Clothing::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const
|
||||
|
|
|
@ -98,6 +98,8 @@ namespace MWClass
|
|||
data->mContainerStore.fill(ref->mBase->mInventory, getId(ptr),
|
||||
MWBase::Environment::get().getWorld()->getStore());
|
||||
|
||||
data->mContainerStore.add("gold_001", ref->mBase->mData.mGold, ptr);
|
||||
|
||||
// store
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
}
|
||||
|
|
|
@ -156,6 +156,9 @@ namespace MWClass
|
|||
MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player);
|
||||
int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase();
|
||||
|
||||
static const float fWortChanceValue =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fWortChanceValue")->getFloat();
|
||||
|
||||
MWGui::Widgets::SpellEffectList list;
|
||||
for (int i=0; i<4; ++i)
|
||||
{
|
||||
|
@ -166,10 +169,10 @@ namespace MWClass
|
|||
params.mAttribute = ref->mBase->mData.mAttributes[i];
|
||||
params.mSkill = ref->mBase->mData.mSkills[i];
|
||||
|
||||
params.mKnown = ( (i == 0 && alchemySkill >= 15)
|
||||
|| (i == 1 && alchemySkill >= 30)
|
||||
|| (i == 2 && alchemySkill >= 45)
|
||||
|| (i == 3 && alchemySkill >= 60));
|
||||
params.mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue)
|
||||
|| (i == 1 && alchemySkill >= fWortChanceValue*2)
|
||||
|| (i == 2 && alchemySkill >= fWortChanceValue*3)
|
||||
|| (i == 3 && alchemySkill >= fWortChanceValue*4));
|
||||
|
||||
list.push_back(params);
|
||||
}
|
||||
|
|
|
@ -86,7 +86,10 @@ namespace MWClass
|
|||
MWWorld::LiveCellRef<ESM::Lockpick> *ref =
|
||||
ptr.get<ESM::Lockpick>();
|
||||
|
||||
return ref->mBase->mData.mValue;
|
||||
if (ptr.getCellRef().mCharge == -1)
|
||||
return ref->mBase->mData.mValue;
|
||||
else
|
||||
return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr));
|
||||
}
|
||||
|
||||
void Lockpick::registerSelf()
|
||||
|
|
|
@ -241,7 +241,8 @@ namespace MWClass
|
|||
MWWorld::LiveCellRef<ESM::Miscellaneous> *ref =
|
||||
item.get<ESM::Miscellaneous>();
|
||||
|
||||
return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc);
|
||||
return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc)
|
||||
&& !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_001");
|
||||
}
|
||||
|
||||
float Miscellaneous::getWeight(const MWWorld::Ptr &ptr) const
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
#include "../mwmechanics/movement.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/actiontalk.hpp"
|
||||
|
@ -183,8 +184,11 @@ namespace MWClass
|
|||
}
|
||||
|
||||
// creature stats
|
||||
int gold=0;
|
||||
if(ref->mBase->mNpdt52.mGold != -10)
|
||||
{
|
||||
gold = ref->mBase->mNpdt52.mGold;
|
||||
|
||||
for (int i=0; i<27; ++i)
|
||||
data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt52.mSkills[i]);
|
||||
|
||||
|
@ -206,6 +210,8 @@ namespace MWClass
|
|||
}
|
||||
else
|
||||
{
|
||||
gold = ref->mBase->mNpdt12.mGold;
|
||||
|
||||
for (int i=0; i<3; ++i)
|
||||
data->mNpcStats.setDynamic (i, 10);
|
||||
|
||||
|
@ -235,6 +241,8 @@ namespace MWClass
|
|||
// store
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
|
||||
getContainerStore(ptr).add("gold_001", gold, ptr);
|
||||
|
||||
getInventoryStore(ptr).autoEquip(ptr);
|
||||
}
|
||||
}
|
||||
|
@ -455,7 +463,7 @@ namespace MWClass
|
|||
// Check if we have enough charges
|
||||
const float enchantCost = enchantment->mData.mCost;
|
||||
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
|
||||
const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10);
|
||||
const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
|
||||
|
||||
if (weapon.getCellRef().mEnchantmentCharge == -1)
|
||||
weapon.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge;
|
||||
|
@ -467,12 +475,12 @@ namespace MWClass
|
|||
else
|
||||
{
|
||||
weapon.getCellRef().mEnchantmentCharge -= castCost;
|
||||
// Touch
|
||||
othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ESM::RT_Touch, weapon.getClass().getName(weapon));
|
||||
// Self
|
||||
getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ESM::RT_Self, weapon.getClass().getName(weapon));
|
||||
// Target
|
||||
MWBase::Environment::get().getWorld()->launchProjectile(enchantmentName, enchantment->mEffects, ptr, weapon.getClass().getName(weapon));
|
||||
|
||||
MWMechanics::CastSpell cast(ptr, victim);
|
||||
cast.cast(weapon);
|
||||
|
||||
if (ptr.getRefData().getHandle() == "player")
|
||||
skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -932,11 +940,8 @@ namespace MWClass
|
|||
bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id,
|
||||
const MWWorld::Ptr& actor) const
|
||||
{
|
||||
MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
|
||||
|
||||
/// \todo consider instant effects
|
||||
|
||||
return stats.getActiveSpells().addSpell (id, actor);
|
||||
MWMechanics::CastSpell cast(ptr, ptr);
|
||||
return cast.cast(id);
|
||||
}
|
||||
|
||||
void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const
|
||||
|
|
|
@ -137,13 +137,14 @@ namespace MWClass
|
|||
MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player);
|
||||
int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase();
|
||||
int i=0;
|
||||
static const float fWortChanceValue =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fWortChanceValue")->getFloat();
|
||||
for (MWGui::Widgets::SpellEffectList::iterator it = info.effects.begin(); it != info.effects.end(); ++it)
|
||||
{
|
||||
/// \todo this code is duplicated from mwclass/ingredient, put it in a helper function
|
||||
it->mKnown = ( (i == 0 && alchemySkill >= 15)
|
||||
|| (i == 1 && alchemySkill >= 30)
|
||||
|| (i == 2 && alchemySkill >= 45)
|
||||
|| (i == 3 && alchemySkill >= 60));
|
||||
it->mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue)
|
||||
|| (i == 1 && alchemySkill >= fWortChanceValue*2)
|
||||
|| (i == 2 && alchemySkill >= fWortChanceValue*3)
|
||||
|| (i == 3 && alchemySkill >= fWortChanceValue*4));
|
||||
|
||||
++i;
|
||||
}
|
||||
|
|
|
@ -85,7 +85,10 @@ namespace MWClass
|
|||
MWWorld::LiveCellRef<ESM::Probe> *ref =
|
||||
ptr.get<ESM::Probe>();
|
||||
|
||||
return ref->mBase->mData.mValue;
|
||||
if (ptr.getCellRef().mCharge == -1)
|
||||
return ref->mBase->mData.mValue;
|
||||
else
|
||||
return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr));
|
||||
}
|
||||
|
||||
void Probe::registerSelf()
|
||||
|
|
|
@ -76,7 +76,10 @@ namespace MWClass
|
|||
MWWorld::LiveCellRef<ESM::Repair> *ref =
|
||||
ptr.get<ESM::Repair>();
|
||||
|
||||
return ref->mBase->mData.mValue;
|
||||
if (ptr.getCellRef().mCharge == -1)
|
||||
return ref->mBase->mData.mValue;
|
||||
else
|
||||
return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr));
|
||||
}
|
||||
|
||||
void Repair::registerSelf()
|
||||
|
|
|
@ -154,7 +154,10 @@ namespace MWClass
|
|||
MWWorld::LiveCellRef<ESM::Weapon> *ref =
|
||||
ptr.get<ESM::Weapon>();
|
||||
|
||||
return ref->mBase->mData.mValue;
|
||||
if (ptr.getCellRef().mCharge == -1)
|
||||
return ref->mBase->mData.mValue;
|
||||
else
|
||||
return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr));
|
||||
}
|
||||
|
||||
void Weapon::registerSelf()
|
||||
|
@ -370,16 +373,17 @@ namespace MWClass
|
|||
|
||||
void Weapon::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const
|
||||
{
|
||||
MWWorld::LiveCellRef<ESM::Weapon> *ref =
|
||||
ptr.get<ESM::Weapon>();
|
||||
MWWorld::LiveCellRef<ESM::Weapon> *ref =
|
||||
ptr.get<ESM::Weapon>();
|
||||
|
||||
ESM::Weapon newItem = *ref->mBase;
|
||||
newItem.mId="";
|
||||
newItem.mName=newName;
|
||||
newItem.mData.mEnchant=enchCharge;
|
||||
newItem.mEnchant=enchId;
|
||||
const ESM::Weapon *record = MWBase::Environment::get().getWorld()->createRecord (newItem);
|
||||
ref->mBase = record;
|
||||
ESM::Weapon newItem = *ref->mBase;
|
||||
newItem.mId="";
|
||||
newItem.mName=newName;
|
||||
newItem.mData.mEnchant=enchCharge;
|
||||
newItem.mEnchant=enchId;
|
||||
const ESM::Weapon *record = MWBase::Environment::get().getWorld()->createRecord (newItem);
|
||||
ref->mBase = record;
|
||||
ref->mRef.mRefID = record->mId;
|
||||
}
|
||||
|
||||
std::pair<int, std::string> Weapon::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const
|
||||
|
|
|
@ -86,12 +86,13 @@ void CompanionWindow::onBackgroundSelected()
|
|||
void CompanionWindow::open(const MWWorld::Ptr& npc)
|
||||
{
|
||||
mPtr = npc;
|
||||
setTitle(MWWorld::Class::get(npc).getName(npc));
|
||||
updateEncumbranceBar();
|
||||
|
||||
mModel = new CompanionItemModel(npc);
|
||||
mSortModel = new SortFilterItemModel(mModel);
|
||||
mItemView->setModel(mSortModel);
|
||||
|
||||
setTitle(MWWorld::Class::get(npc).getName(npc));
|
||||
}
|
||||
|
||||
void CompanionWindow::onFrame()
|
||||
|
|
|
@ -246,7 +246,7 @@ namespace MWGui
|
|||
{
|
||||
if(mCurrent != mCommandHistory.end())
|
||||
{
|
||||
--mCurrent;
|
||||
++mCurrent;
|
||||
|
||||
if(mCurrent != mCommandHistory.end())
|
||||
mCommandLine->setCaption(*mCurrent);
|
||||
|
|
|
@ -220,11 +220,13 @@ namespace MWGui
|
|||
|
||||
mDisposeCorpseButton->setVisible(loot);
|
||||
|
||||
setTitle(MWWorld::Class::get(container).getName(container));
|
||||
|
||||
mSortModel = new SortFilterItemModel(mModel);
|
||||
|
||||
mItemView->setModel (mSortModel);
|
||||
|
||||
// Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last
|
||||
// or we end up using a possibly invalid model.
|
||||
setTitle(MWWorld::Class::get(container).getName(container));
|
||||
}
|
||||
|
||||
void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender)
|
||||
|
|
|
@ -73,57 +73,4 @@ namespace MWGui
|
|||
return mSize;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
|
||||
Cursor::Cursor()
|
||||
{
|
||||
// hide mygui's pointer since we're rendering it ourselves (because mygui's pointer doesn't support rotation)
|
||||
MyGUI::PointerManager::getInstance().setVisible(false);
|
||||
|
||||
MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &Cursor::onCursorChange);
|
||||
|
||||
mWidget = MyGUI::Gui::getInstance().createWidget<MyGUI::ImageBox>("RotatingSkin",0,0,0,0,MyGUI::Align::Default,"Pointer","");
|
||||
|
||||
onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer());
|
||||
}
|
||||
|
||||
Cursor::~Cursor()
|
||||
{
|
||||
}
|
||||
|
||||
void Cursor::onCursorChange(const std::string &name)
|
||||
{
|
||||
ResourceImageSetPointerFix* imgSetPtr = dynamic_cast<ResourceImageSetPointerFix*>(
|
||||
MyGUI::PointerManager::getInstance().getByName(name));
|
||||
assert(imgSetPtr != NULL);
|
||||
|
||||
MyGUI::ResourceImageSet* imgSet = imgSetPtr->getImageSet();
|
||||
|
||||
std::string texture = imgSet->getIndexInfo(0,0).texture;
|
||||
|
||||
mSize = imgSetPtr->getSize();
|
||||
mHotSpot = imgSetPtr->getHotSpot();
|
||||
|
||||
int rotation = imgSetPtr->getRotation();
|
||||
|
||||
mWidget->setImageTexture(texture);
|
||||
MyGUI::ISubWidget* main = mWidget->getSubWidgetMain();
|
||||
MyGUI::RotatingSkin* rotatingSubskin = main->castType<MyGUI::RotatingSkin>();
|
||||
rotatingSubskin->setCenter(MyGUI::IntPoint(mSize.width/2,mSize.height/2));
|
||||
rotatingSubskin->setAngle(Ogre::Degree(rotation).valueRadians());
|
||||
}
|
||||
|
||||
void Cursor::update()
|
||||
{
|
||||
MyGUI::IntPoint position = MyGUI::InputManager::getInstance().getMousePosition();
|
||||
|
||||
mWidget->setPosition(position - mHotSpot);
|
||||
mWidget->setSize(mSize);
|
||||
}
|
||||
|
||||
void Cursor::setVisible(bool visible)
|
||||
{
|
||||
mWidget->setVisible(visible);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,23 +39,6 @@ namespace MWGui
|
|||
int mRotation; // rotation in degrees
|
||||
};
|
||||
|
||||
class Cursor
|
||||
{
|
||||
public:
|
||||
Cursor();
|
||||
~Cursor();
|
||||
void update ();
|
||||
|
||||
void setVisible (bool visible);
|
||||
|
||||
void onCursorChange (const std::string& name);
|
||||
|
||||
private:
|
||||
MyGUI::ImageBox* mWidget;
|
||||
|
||||
MyGUI::IntSize mSize;
|
||||
MyGUI::IntPoint mHotSpot;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "../mwmechanics/npcstats.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
|
||||
#include "../mwdialogue/dialoguemanagerimp.hpp"
|
||||
|
||||
|
@ -67,23 +69,24 @@ namespace MWGui
|
|||
|
||||
void PersuasionDialog::onPersuade(MyGUI::Widget *sender)
|
||||
{
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
MWBase::MechanicsManager::PersuasionType type;
|
||||
if (sender == mAdmireButton) type = MWBase::MechanicsManager::PT_Admire;
|
||||
else if (sender == mIntimidateButton) type = MWBase::MechanicsManager::PT_Intimidate;
|
||||
else if (sender == mTauntButton) type = MWBase::MechanicsManager::PT_Taunt;
|
||||
else if (sender == mBribe10Button)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-10);
|
||||
player.getClass().getContainerStore(player).remove("gold_001", 10, player);
|
||||
type = MWBase::MechanicsManager::PT_Bribe10;
|
||||
}
|
||||
else if (sender == mBribe100Button)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-100);
|
||||
player.getClass().getContainerStore(player).remove("gold_001", 100, player);
|
||||
type = MWBase::MechanicsManager::PT_Bribe100;
|
||||
}
|
||||
else /*if (sender == mBribe1000Button)*/
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-1000);
|
||||
player.getClass().getContainerStore(player).remove("gold_001", 1000, player);
|
||||
type = MWBase::MechanicsManager::PT_Bribe1000;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/manualref.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
@ -298,9 +299,15 @@ namespace MWGui
|
|||
int result = mEnchanting.create();
|
||||
|
||||
if(result==1)
|
||||
{
|
||||
MWBase::Environment::get().getSoundManager()->playSound("enchant success", 1.f, 1.f);
|
||||
MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu12}");
|
||||
}
|
||||
else
|
||||
{
|
||||
MWBase::Environment::get().getSoundManager()->playSound("enchant fail", 1.f, 1.f);
|
||||
MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage34}");
|
||||
}
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting);
|
||||
}
|
||||
|
|
|
@ -37,10 +37,6 @@ namespace MWGui
|
|||
, mPreviewDirty(true)
|
||||
, mDragAndDrop(dragAndDrop)
|
||||
, mSelectedItem(-1)
|
||||
, mPositionInventory(0, 342, 498, 258)
|
||||
, mPositionContainer(0, 342, 498, 258)
|
||||
, mPositionCompanion(0, 342, 498, 258)
|
||||
, mPositionBarter(0, 342, 498, 258)
|
||||
, mGuiMode(GM_Inventory)
|
||||
{
|
||||
static_cast<MyGUI::Window*>(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize);
|
||||
|
@ -71,8 +67,19 @@ namespace MWGui
|
|||
|
||||
mFilterAll->setStateSelected(true);
|
||||
|
||||
setCoord(mPositionInventory.left, mPositionInventory.top, mPositionInventory.width, mPositionInventory.height);
|
||||
onWindowResize(static_cast<MyGUI::Window*>(mMainWidget));
|
||||
setGuiMode(mGuiMode);
|
||||
|
||||
adjustPanes();
|
||||
}
|
||||
|
||||
void InventoryWindow::adjustPanes()
|
||||
{
|
||||
const float aspect = 0.5; // fixed aspect ratio for the left pane
|
||||
mLeftPane->setSize( (mMainWidget->getSize().height-44) * aspect, mMainWidget->getSize().height-44 );
|
||||
mRightPane->setCoord( mLeftPane->getPosition().left + (mMainWidget->getSize().height-44) * aspect + 4,
|
||||
mRightPane->getPosition().top,
|
||||
mMainWidget->getSize().width - 12 - (mMainWidget->getSize().height-44) * aspect - 15,
|
||||
mMainWidget->getSize().height-44 );
|
||||
}
|
||||
|
||||
void InventoryWindow::updatePlayer()
|
||||
|
@ -87,27 +94,36 @@ namespace MWGui
|
|||
|
||||
void InventoryWindow::setGuiMode(GuiMode mode)
|
||||
{
|
||||
std::string setting = "inventory";
|
||||
mGuiMode = mode;
|
||||
switch(mode) {
|
||||
case GM_Container:
|
||||
setPinButtonVisible(false);
|
||||
mMainWidget->setCoord(mPositionContainer);
|
||||
setting += " container";
|
||||
break;
|
||||
case GM_Companion:
|
||||
setPinButtonVisible(false);
|
||||
mMainWidget->setCoord(mPositionCompanion);
|
||||
setting += " companion";
|
||||
break;
|
||||
case GM_Barter:
|
||||
setPinButtonVisible(false);
|
||||
mMainWidget->setCoord(mPositionBarter);
|
||||
setting += " barter";
|
||||
break;
|
||||
case GM_Inventory:
|
||||
default:
|
||||
setPinButtonVisible(true);
|
||||
mMainWidget->setCoord(mPositionInventory);
|
||||
break;
|
||||
}
|
||||
onWindowResize(static_cast<MyGUI::Window*>(mMainWidget));
|
||||
|
||||
MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
||||
MyGUI::IntPoint pos (Settings::Manager::getFloat(setting + " x", "Windows") * viewSize.width,
|
||||
Settings::Manager::getFloat(setting + " y", "Windows") * viewSize.height);
|
||||
MyGUI::IntSize size (Settings::Manager::getFloat(setting + " w", "Windows") * viewSize.width,
|
||||
Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height);
|
||||
mMainWidget->setPosition(pos);
|
||||
mMainWidget->setSize(size);
|
||||
adjustPanes();
|
||||
mPreviewDirty = true;
|
||||
}
|
||||
|
||||
TradeItemModel* InventoryWindow::getTradeModel()
|
||||
|
@ -256,32 +272,37 @@ namespace MWGui
|
|||
mItemView->update();
|
||||
|
||||
notifyContentChanged();
|
||||
adjustPanes();
|
||||
}
|
||||
|
||||
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 );
|
||||
|
||||
adjustPanes();
|
||||
std::string setting = "inventory";
|
||||
switch(mGuiMode) {
|
||||
case GM_Container:
|
||||
mPositionContainer = _sender->getCoord();
|
||||
setting += " container";
|
||||
break;
|
||||
case GM_Companion:
|
||||
mPositionCompanion = _sender->getCoord();
|
||||
setting += " companion";
|
||||
break;
|
||||
case GM_Barter:
|
||||
mPositionBarter = _sender->getCoord();
|
||||
setting += " barter";
|
||||
break;
|
||||
case GM_Inventory:
|
||||
default:
|
||||
mPositionInventory = _sender->getCoord();
|
||||
break;
|
||||
}
|
||||
|
||||
MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
||||
float x = _sender->getPosition().left / float(viewSize.width);
|
||||
float y = _sender->getPosition().top / float(viewSize.height);
|
||||
float w = _sender->getSize().width / float(viewSize.width);
|
||||
float h = _sender->getSize().height / float(viewSize.height);
|
||||
Settings::Manager::setFloat(setting + " x", "Windows", x);
|
||||
Settings::Manager::setFloat(setting + " y", "Windows", y);
|
||||
Settings::Manager::setFloat(setting + " w", "Windows", w);
|
||||
Settings::Manager::setFloat(setting + " h", "Windows", h);
|
||||
|
||||
if (mMainWidget->getSize().width != mLastXSize || mMainWidget->getSize().height != mLastYSize)
|
||||
{
|
||||
mLastXSize = mMainWidget->getSize().width;
|
||||
|
@ -455,14 +476,6 @@ namespace MWGui
|
|||
if (MWBase::Environment::get().getWindowManager()->getSpellWindow())
|
||||
MWBase::Environment::get().getWindowManager()->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())
|
||||
MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon();
|
||||
else
|
||||
MWBase::Environment::get().getWindowManager()->setSelectedWeapon(*weaponSlot);
|
||||
|
||||
mPreviewDirty = true;
|
||||
|
||||
mArmorRating->setCaptionWithReplacing ("#{sArmor}: "
|
||||
|
|
|
@ -76,11 +76,6 @@ namespace MWGui
|
|||
MyGUI::Button* mFilterMagic;
|
||||
MyGUI::Button* mFilterMisc;
|
||||
|
||||
MyGUI::IntCoord mPositionInventory;
|
||||
MyGUI::IntCoord mPositionContainer;
|
||||
MyGUI::IntCoord mPositionCompanion;
|
||||
MyGUI::IntCoord mPositionBarter;
|
||||
|
||||
GuiMode mGuiMode;
|
||||
|
||||
int mLastXSize;
|
||||
|
@ -105,6 +100,8 @@ namespace MWGui
|
|||
|
||||
void updateEncumbranceBar();
|
||||
void notifyContentChanged();
|
||||
|
||||
void adjustPanes();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -261,6 +261,7 @@ namespace MWGui
|
|||
: MWGui::WindowPinnableBase("openmw_map_window.layout")
|
||||
, mGlobal(false)
|
||||
, mGlobalMap(0)
|
||||
, mGlobalMapRender(0)
|
||||
{
|
||||
setCoord(500,0,320,300);
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "../mwworld/containerstore.hpp"
|
||||
|
||||
#include "inventorywindow.hpp"
|
||||
#include "tradewindow.hpp"
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
|
@ -119,7 +118,9 @@ void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender)
|
|||
MWBase::Environment::get().getSoundManager()->playSound("Repair",1,1);
|
||||
|
||||
int price = boost::lexical_cast<int>(sender->getUserString("Price"));
|
||||
MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price);
|
||||
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
player.getClass().getContainerStore(player).remove("gold_001", price, player);
|
||||
|
||||
startRepair(mActor);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ namespace MWGui
|
|||
|
||||
MessageBoxManager::MessageBoxManager ()
|
||||
{
|
||||
// defines
|
||||
mMessageBoxSpeed = 0.1;
|
||||
mInterMessageBoxe = NULL;
|
||||
mStaticMessageBox = NULL;
|
||||
|
@ -19,47 +18,26 @@ namespace MWGui
|
|||
|
||||
void MessageBoxManager::onFrame (float frameDuration)
|
||||
{
|
||||
std::vector<MessageBoxManagerTimer>::iterator it;
|
||||
for(it = mTimers.begin(); it != mTimers.end();)
|
||||
std::vector<MessageBox*>::iterator it;
|
||||
for(it = mMessageBoxes.begin(); it != mMessageBoxes.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)->mCurrentTime += frameDuration;
|
||||
if((*it)->mCurrentTime >= (*it)->mMaxTime && *it != mStaticMessageBox)
|
||||
{
|
||||
it = mTimers.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
it->current += frameDuration;
|
||||
if(it->current >= it->max)
|
||||
{
|
||||
it->messageBox->mMarkedToDelete = true;
|
||||
|
||||
if(*mMessageBoxes.begin() == it->messageBox) // if this box is the last one
|
||||
{
|
||||
// collect all with mMarkedToDelete and delete them.
|
||||
// and place the other messageboxes on the right position
|
||||
int height = 0;
|
||||
std::vector<MessageBox*>::iterator it2 = mMessageBoxes.begin();
|
||||
while(it2 != mMessageBoxes.end())
|
||||
{
|
||||
if((*it2)->mMarkedToDelete)
|
||||
{
|
||||
delete (*it2);
|
||||
it2 = mMessageBoxes.erase(it2);
|
||||
}
|
||||
else {
|
||||
(*it2)->update(height);
|
||||
height += (*it2)->getHeight();
|
||||
++it2;
|
||||
}
|
||||
}
|
||||
}
|
||||
it = mTimers.erase(it);
|
||||
delete *it;
|
||||
it = mMessageBoxes.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
float height = 0;
|
||||
it = mMessageBoxes.begin();
|
||||
while(it != mMessageBoxes.end())
|
||||
{
|
||||
(*it)->update(height);
|
||||
height += (*it)->getHeight();
|
||||
++it;
|
||||
}
|
||||
|
||||
if(mInterMessageBoxe != NULL && mInterMessageBoxe->mMarkedToDelete) {
|
||||
|
@ -74,14 +52,13 @@ namespace MWGui
|
|||
void MessageBoxManager::createMessageBox (const std::string& message, bool stat)
|
||||
{
|
||||
MessageBox *box = new MessageBox(*this, message);
|
||||
box->mCurrentTime = 0;
|
||||
box->mMaxTime = message.length()*mMessageBoxSpeed;
|
||||
|
||||
if(stat)
|
||||
mStaticMessageBox = box;
|
||||
else
|
||||
removeMessageBox(message.length()*mMessageBoxSpeed, box);
|
||||
|
||||
mMessageBoxes.push_back(box);
|
||||
std::vector<MessageBox*>::iterator it;
|
||||
|
||||
if(mMessageBoxes.size() > 3) {
|
||||
delete *mMessageBoxes.begin();
|
||||
|
@ -89,7 +66,7 @@ namespace MWGui
|
|||
}
|
||||
|
||||
int height = 0;
|
||||
for(it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it)
|
||||
for(std::vector<MessageBox*>::iterator it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it)
|
||||
{
|
||||
(*it)->update(height);
|
||||
height += (*it)->getHeight();
|
||||
|
@ -119,15 +96,6 @@ namespace MWGui
|
|||
return mInterMessageBoxe != NULL;
|
||||
}
|
||||
|
||||
void MessageBoxManager::removeMessageBox (float time, MessageBox *msgbox)
|
||||
{
|
||||
MessageBoxManagerTimer timer;
|
||||
timer.current = 0;
|
||||
timer.max = time;
|
||||
timer.messageBox = msgbox;
|
||||
|
||||
mTimers.insert(mTimers.end(), timer);
|
||||
}
|
||||
|
||||
bool MessageBoxManager::removeMessageBox (MessageBox *msgbox)
|
||||
{
|
||||
|
@ -169,12 +137,13 @@ namespace MWGui
|
|||
: Layout("openmw_messagebox.layout")
|
||||
, mMessageBoxManager(parMessageBoxManager)
|
||||
, mMessage(message)
|
||||
, mCurrentTime(0)
|
||||
, mMaxTime(0)
|
||||
{
|
||||
// defines
|
||||
mFixedWidth = 300;
|
||||
mBottomPadding = 20;
|
||||
mNextBoxPadding = 20;
|
||||
mMarkedToDelete = false;
|
||||
|
||||
getWidget(mMessageWidget, "message");
|
||||
|
||||
|
@ -185,10 +154,6 @@ namespace MWGui
|
|||
size.width = mFixedWidth;
|
||||
size.height = 100; // dummy
|
||||
|
||||
MyGUI::IntCoord coord;
|
||||
coord.left = 10; // dummy
|
||||
coord.top = 10; // dummy
|
||||
|
||||
mMessageWidget->setSize(size);
|
||||
|
||||
MyGUI::IntSize textSize = mMessageWidget->getTextSize();
|
||||
|
|
|
@ -19,13 +19,6 @@ namespace MWGui
|
|||
class InteractiveMessageBox;
|
||||
class MessageBoxManager;
|
||||
class MessageBox;
|
||||
|
||||
struct MessageBoxManagerTimer {
|
||||
float current;
|
||||
float max;
|
||||
MessageBox *messageBox;
|
||||
};
|
||||
|
||||
class MessageBoxManager
|
||||
{
|
||||
public:
|
||||
|
@ -36,7 +29,6 @@ namespace MWGui
|
|||
bool createInteractiveMessageBox (const std::string& message, const std::vector<std::string>& buttons);
|
||||
bool isInteractiveMessageBox ();
|
||||
|
||||
void removeMessageBox (float time, MessageBox *msgbox);
|
||||
bool removeMessageBox (MessageBox *msgbox);
|
||||
void setMessageBoxSpeed (int speed);
|
||||
|
||||
|
@ -54,7 +46,6 @@ namespace MWGui
|
|||
std::vector<MessageBox*> mMessageBoxes;
|
||||
InteractiveMessageBox* mInterMessageBoxe;
|
||||
MessageBox* mStaticMessageBox;
|
||||
std::vector<MessageBoxManagerTimer> mTimers;
|
||||
float mMessageBoxSpeed;
|
||||
int mLastButtonPressed;
|
||||
};
|
||||
|
@ -67,7 +58,8 @@ namespace MWGui
|
|||
int getHeight ();
|
||||
void update (int height);
|
||||
|
||||
bool mMarkedToDelete;
|
||||
float mCurrentTime;
|
||||
float mMaxTime;
|
||||
|
||||
protected:
|
||||
MessageBoxManager& mMessageBoxManager;
|
||||
|
|
|
@ -28,6 +28,7 @@ namespace MWGui
|
|||
GM_Travel,
|
||||
GM_SpellCreation,
|
||||
GM_Enchanting,
|
||||
GM_Recharge,
|
||||
GM_Training,
|
||||
GM_MerchantRepair,
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/actionequip.hpp"
|
||||
#include "../mwmechanics/spellsuccess.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwgui/inventorywindow.hpp"
|
||||
#include "../mwgui/bookwindow.hpp"
|
||||
#include "../mwgui/scrollwindow.hpp"
|
||||
|
@ -284,8 +284,9 @@ namespace MWGui
|
|||
MWWorld::Ptr item = *button->getChildAt (0)->getUserData<MWWorld::Ptr>();
|
||||
|
||||
// make sure the item is available
|
||||
if (item.getRefData ().getCount() == 0)
|
||||
if (item.getRefData ().getCount() < 1)
|
||||
{
|
||||
// TODO: Try to find a replacement with the same ID?
|
||||
MWBase::Environment::get().getWindowManager ()->messageBox (
|
||||
"#{sQuickMenu5} " + MWWorld::Class::get(item).getName(item));
|
||||
return;
|
||||
|
|
196
apps/openmw/mwgui/recharge.cpp
Normal file
196
apps/openmw/mwgui/recharge.cpp
Normal file
|
@ -0,0 +1,196 @@
|
|||
#include "recharge.hpp"
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
|
||||
#include "widgets.hpp"
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
|
||||
Recharge::Recharge()
|
||||
: WindowBase("openmw_recharge_dialog.layout")
|
||||
{
|
||||
getWidget(mBox, "Box");
|
||||
getWidget(mView, "View");
|
||||
getWidget(mGemBox, "GemBox");
|
||||
getWidget(mGemIcon, "GemIcon");
|
||||
getWidget(mChargeLabel, "ChargeLabel");
|
||||
getWidget(mCancelButton, "CancelButton");
|
||||
|
||||
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onCancel);
|
||||
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
void Recharge::open()
|
||||
{
|
||||
center();
|
||||
}
|
||||
|
||||
void Recharge::start (const MWWorld::Ptr &item)
|
||||
{
|
||||
std::string path = std::string("icons\\");
|
||||
path += MWWorld::Class::get(item).getInventoryIcon(item);
|
||||
int pos = path.rfind(".");
|
||||
path.erase(pos);
|
||||
path.append(".dds");
|
||||
mGemIcon->setImageTexture (path);
|
||||
mGemIcon->setUserString("ToolTipType", "ItemPtr");
|
||||
mGemIcon->setUserData(item);
|
||||
|
||||
updateView();
|
||||
}
|
||||
|
||||
void Recharge::updateView()
|
||||
{
|
||||
MWWorld::Ptr gem = *mGemIcon->getUserData<MWWorld::Ptr>();
|
||||
|
||||
std::string soul = gem.getCellRef().mSoul;
|
||||
const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().find(soul);
|
||||
|
||||
mChargeLabel->setCaptionWithReplacing("#{sCharges} " + boost::lexical_cast<std::string>(creature->mData.mSoul));
|
||||
|
||||
bool toolBoxVisible = (gem.getRefData().getCount() != 0);
|
||||
mGemBox->setVisible(toolBoxVisible);
|
||||
|
||||
bool toolBoxWasVisible = (mBox->getPosition().top != mGemBox->getPosition().top);
|
||||
|
||||
if (toolBoxVisible && !toolBoxWasVisible)
|
||||
{
|
||||
// shrink
|
||||
mBox->setPosition(mBox->getPosition() + MyGUI::IntPoint(0, mGemBox->getSize().height));
|
||||
mBox->setSize(mBox->getSize() - MyGUI::IntSize(0,mGemBox->getSize().height));
|
||||
}
|
||||
else if (!toolBoxVisible && toolBoxWasVisible)
|
||||
{
|
||||
// expand
|
||||
mBox->setPosition(MyGUI::IntPoint (mBox->getPosition().left, mGemBox->getPosition().top));
|
||||
mBox->setSize(mBox->getSize() + MyGUI::IntSize(0,mGemBox->getSize().height));
|
||||
}
|
||||
|
||||
while (mView->getChildCount())
|
||||
MyGUI::Gui::getInstance().destroyWidget(mView->getChildAt(0));
|
||||
|
||||
int currentY = 0;
|
||||
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player);
|
||||
for (MWWorld::ContainerStoreIterator iter (store.begin());
|
||||
iter!=store.end(); ++iter)
|
||||
{
|
||||
std::string enchantmentName = iter->getClass().getEnchantment(*iter);
|
||||
if (enchantmentName.empty())
|
||||
continue;
|
||||
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantmentName);
|
||||
if (iter->getCellRef().mEnchantmentCharge >= enchantment->mData.mCharge
|
||||
|| iter->getCellRef().mEnchantmentCharge == -1)
|
||||
continue;
|
||||
|
||||
MyGUI::TextBox* text = mView->createWidget<MyGUI::TextBox> (
|
||||
"SandText", MyGUI::IntCoord(8, currentY, mView->getWidth()-8, 18), MyGUI::Align::Default);
|
||||
text->setCaption(MWWorld::Class::get(*iter).getName(*iter));
|
||||
text->setNeedMouseFocus(false);
|
||||
currentY += 19;
|
||||
|
||||
MyGUI::ImageBox* icon = mView->createWidget<MyGUI::ImageBox> (
|
||||
"ImageBox", MyGUI::IntCoord(16, currentY, 32, 32), MyGUI::Align::Default);
|
||||
std::string path = std::string("icons\\");
|
||||
path += MWWorld::Class::get(*iter).getInventoryIcon(*iter);
|
||||
int pos = path.rfind(".");
|
||||
path.erase(pos);
|
||||
path.append(".dds");
|
||||
icon->setImageTexture (path);
|
||||
icon->setUserString("ToolTipType", "ItemPtr");
|
||||
icon->setUserData(*iter);
|
||||
icon->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onItemClicked);
|
||||
icon->eventMouseWheel += MyGUI::newDelegate(this, &Recharge::onMouseWheel);
|
||||
|
||||
Widgets::MWDynamicStatPtr chargeWidget = mView->createWidget<Widgets::MWDynamicStat>
|
||||
("MW_ChargeBar", MyGUI::IntCoord(72, currentY+2, 199, 20), MyGUI::Align::Default);
|
||||
chargeWidget->setValue(iter->getCellRef().mEnchantmentCharge, enchantment->mData.mCharge);
|
||||
chargeWidget->setNeedMouseFocus(false);
|
||||
|
||||
currentY += 32 + 4;
|
||||
}
|
||||
mView->setCanvasSize (MyGUI::IntSize(mView->getWidth(), std::max(mView->getHeight(), currentY)));
|
||||
}
|
||||
|
||||
void Recharge::onCancel(MyGUI::Widget *sender)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Recharge);
|
||||
}
|
||||
|
||||
void Recharge::onItemClicked(MyGUI::Widget *sender)
|
||||
{
|
||||
MWWorld::Ptr gem = *mGemIcon->getUserData<MWWorld::Ptr>();
|
||||
|
||||
if (!gem.getRefData().getCount())
|
||||
return;
|
||||
|
||||
MWWorld::Ptr item = *sender->getUserData<MWWorld::Ptr>();
|
||||
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
|
||||
MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player);
|
||||
|
||||
float luckTerm = 0.1 * stats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||
if (luckTerm < 1|| luckTerm > 10)
|
||||
luckTerm = 1;
|
||||
|
||||
float intelligenceTerm = 0.2 * stats.getAttribute(ESM::Attribute::Intelligence).getModified();
|
||||
|
||||
if (intelligenceTerm > 20)
|
||||
intelligenceTerm = 20;
|
||||
if (intelligenceTerm < 1)
|
||||
intelligenceTerm = 1;
|
||||
|
||||
float x = (npcStats.getSkill(ESM::Skill::Enchant).getModified() + intelligenceTerm + luckTerm) * stats.getFatigueTerm();
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
if (roll < x)
|
||||
{
|
||||
std::string soul = gem.getCellRef().mSoul;
|
||||
const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get<ESM::Creature>().find(soul);
|
||||
|
||||
float restored = creature->mData.mSoul * (roll / x);
|
||||
|
||||
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
|
||||
item.getClass().getEnchantment(item));
|
||||
item.getCellRef().mEnchantmentCharge =
|
||||
std::min(item.getCellRef().mEnchantmentCharge + restored, static_cast<float>(enchantment->mData.mCharge));
|
||||
|
||||
player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0);
|
||||
}
|
||||
|
||||
gem.getContainerStore()->remove(gem, 1, player);
|
||||
|
||||
if (gem.getRefData().getCount() == 0)
|
||||
{
|
||||
std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage51")->getString();
|
||||
message = boost::str(boost::format(message) % gem.getClass().getName(gem));
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(message);
|
||||
}
|
||||
|
||||
updateView();
|
||||
}
|
||||
|
||||
void Recharge::onMouseWheel(MyGUI::Widget* _sender, int _rel)
|
||||
{
|
||||
if (mView->getViewOffset().top + _rel*0.3 > 0)
|
||||
mView->setViewOffset(MyGUI::IntPoint(0, 0));
|
||||
else
|
||||
mView->setViewOffset(MyGUI::IntPoint(0, mView->getViewOffset().top + _rel*0.3));
|
||||
}
|
||||
|
||||
}
|
42
apps/openmw/mwgui/recharge.hpp
Normal file
42
apps/openmw/mwgui/recharge.hpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
#ifndef OPENMW_MWGUI_RECHARGE_H
|
||||
#define OPENMW_MWGUI_RECHARGE_H
|
||||
|
||||
#include "windowbase.hpp"
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
|
||||
class Recharge : public WindowBase
|
||||
{
|
||||
public:
|
||||
Recharge();
|
||||
|
||||
virtual void open();
|
||||
|
||||
void start (const MWWorld::Ptr& gem);
|
||||
|
||||
protected:
|
||||
MyGUI::Widget* mBox;
|
||||
MyGUI::ScrollView* mView;
|
||||
|
||||
MyGUI::Widget* mGemBox;
|
||||
|
||||
MyGUI::ImageBox* mGemIcon;
|
||||
|
||||
MyGUI::TextBox* mChargeLabel;
|
||||
|
||||
MyGUI::Button* mCancelButton;
|
||||
|
||||
void updateView();
|
||||
|
||||
void onItemClicked (MyGUI::Widget* sender);
|
||||
void onCancel (MyGUI::Widget* sender);
|
||||
void onMouseWheel(MyGUI::Widget* _sender, int _rel);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -21,7 +21,8 @@ namespace MWGui
|
|||
{
|
||||
if (button == 0)
|
||||
{
|
||||
/// \todo show recharge enchanted item dialog here
|
||||
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Recharge);
|
||||
MWBase::Environment::get().getWindowManager()->startRecharge(mSoulgem);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
|
||||
#include "inventorywindow.hpp"
|
||||
#include "tradewindow.hpp"
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
|
@ -123,7 +123,7 @@ namespace MWGui
|
|||
MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
|
||||
MWMechanics::Spells& spells = stats.getSpells();
|
||||
spells.add (mSpellsWidgetMap.find(_sender)->second);
|
||||
MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price);
|
||||
player.getClass().getContainerStore(player).remove("gold_001", price, player);
|
||||
startSpellBuying(mPtr);
|
||||
|
||||
MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0);
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
|
||||
#include "../mwmechanics/spellsuccess.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
|
||||
#include "tooltips.hpp"
|
||||
#include "class.hpp"
|
||||
#include "inventorywindow.hpp"
|
||||
#include "tradewindow.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -89,6 +89,8 @@ namespace MWGui
|
|||
mEffect.mMagnMax = 1;
|
||||
mEffect.mDuration = 1;
|
||||
mEffect.mArea = 0;
|
||||
mEffect.mSkill = -1;
|
||||
mEffect.mAttribute = -1;
|
||||
eventEffectAdded(mEffect);
|
||||
|
||||
onRangeButtonClicked(mRangeButton);
|
||||
|
@ -340,13 +342,14 @@ namespace MWGui
|
|||
|
||||
mSpell.mName = mNameEdit->getCaption();
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-boost::lexical_cast<int>(mPriceLabel->getCaption()));
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
|
||||
player.getClass().getContainerStore(player).remove("gold_001", boost::lexical_cast<int>(mPriceLabel->getCaption()), player);
|
||||
|
||||
MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0);
|
||||
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->createRecord(mSpell);
|
||||
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
|
||||
MWMechanics::Spells& spells = stats.getSpells();
|
||||
spells.add (spell->mId);
|
||||
|
|
|
@ -21,6 +21,20 @@
|
|||
namespace MWGui
|
||||
{
|
||||
|
||||
void EffectSourceVisitor::visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, float magnitude, float remainingTime)
|
||||
{
|
||||
MagicEffectInfo newEffectSource;
|
||||
newEffectSource.mKey = key;
|
||||
newEffectSource.mMagnitude = magnitude;
|
||||
newEffectSource.mPermanent = mIsPermanent;
|
||||
newEffectSource.mRemainingTime = remainingTime;
|
||||
newEffectSource.mSource = sourceName;
|
||||
|
||||
mEffectSources[key.mId].push_back(newEffectSource);
|
||||
}
|
||||
|
||||
|
||||
void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize)
|
||||
{
|
||||
// TODO: Tracking add/remove/expire would be better than force updating every frame
|
||||
|
@ -28,125 +42,20 @@ namespace MWGui
|
|||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player);
|
||||
|
||||
std::map <int, std::vector<MagicEffectInfo> > effects;
|
||||
|
||||
// add permanent item enchantments
|
||||
EffectSourceVisitor visitor;
|
||||
|
||||
// permanent item enchantments & permanent spells
|
||||
visitor.mIsPermanent = true;
|
||||
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
|
||||
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
||||
{
|
||||
MWWorld::ContainerStoreIterator it = store.getSlot(slot);
|
||||
if (it == store.end())
|
||||
continue;
|
||||
std::string enchantment = MWWorld::Class::get(*it).getEnchantment(*it);
|
||||
if (enchantment.empty())
|
||||
continue;
|
||||
const ESM::Enchantment* enchant = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantment);
|
||||
if (enchant->mData.mType != ESM::Enchantment::ConstantEffect)
|
||||
continue;
|
||||
store.visitEffectSources(visitor);
|
||||
stats.getSpells().visitEffectSources(visitor);
|
||||
|
||||
const ESM::EffectList& list = enchant->mEffects;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = list.mList.begin();
|
||||
effectIt != list.mList.end(); ++effectIt)
|
||||
{
|
||||
const ESM::MagicEffect* magicEffect =
|
||||
MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(effectIt->mEffectID);
|
||||
// now add lasting effects
|
||||
visitor.mIsPermanent = false;
|
||||
stats.getActiveSpells().visitEffectSources(visitor);
|
||||
|
||||
MagicEffectInfo effectInfo;
|
||||
effectInfo.mSource = MWWorld::Class::get(*it).getName(*it);
|
||||
effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID);
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)
|
||||
effectInfo.mKey.mArg = effectIt->mSkill;
|
||||
else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)
|
||||
effectInfo.mKey.mArg = effectIt->mAttribute;
|
||||
// just using the min magnitude here, permanent enchantments with a random magnitude just wouldn't make any sense
|
||||
effectInfo.mMagnitude = effectIt->mMagnMin;
|
||||
effectInfo.mPermanent = true;
|
||||
effects[effectIt->mEffectID].push_back (effectInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// add permanent spells
|
||||
const MWMechanics::Spells& spells = stats.getSpells();
|
||||
for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
|
||||
{
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(it->first);
|
||||
|
||||
// these are the spell types that are permanently in effect
|
||||
if (!(spell->mData.mType == ESM::Spell::ST_Ability)
|
||||
&& !(spell->mData.mType == ESM::Spell::ST_Disease)
|
||||
&& !(spell->mData.mType == ESM::Spell::ST_Curse)
|
||||
&& !(spell->mData.mType == ESM::Spell::ST_Blight))
|
||||
continue;
|
||||
const ESM::EffectList& list = spell->mEffects;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = list.mList.begin();
|
||||
effectIt != list.mList.end(); ++effectIt)
|
||||
{
|
||||
const ESM::MagicEffect* magicEffect =
|
||||
MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(effectIt->mEffectID);
|
||||
MagicEffectInfo effectInfo;
|
||||
effectInfo.mSource = getSpellDisplayName (it->first);
|
||||
effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID);
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)
|
||||
effectInfo.mKey.mArg = effectIt->mSkill;
|
||||
else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)
|
||||
effectInfo.mKey.mArg = effectIt->mAttribute;
|
||||
// just using the min magnitude here, permanent spells with a random magnitude just wouldn't make any sense
|
||||
effectInfo.mMagnitude = effectIt->mMagnMin;
|
||||
effectInfo.mPermanent = true;
|
||||
|
||||
effects[effectIt->mEffectID].push_back (effectInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// add lasting effect spells/potions etc
|
||||
|
||||
// TODO: Move this to ActiveSpells
|
||||
const MWMechanics::ActiveSpells::TContainer& activeSpells = stats.getActiveSpells().getActiveSpells();
|
||||
for (MWMechanics::ActiveSpells::TContainer::const_iterator it = activeSpells.begin();
|
||||
it != activeSpells.end(); ++it)
|
||||
{
|
||||
const ESM::EffectList& list = getSpellEffectList(it->first);
|
||||
|
||||
float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor();
|
||||
|
||||
int i=0;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = list.mList.begin();
|
||||
effectIt != list.mList.end(); ++effectIt, ++i)
|
||||
{
|
||||
if (effectIt->mRange != it->second.mRange)
|
||||
continue;
|
||||
|
||||
float randomFactor = it->second.mRandom[i];
|
||||
|
||||
const ESM::MagicEffect* magicEffect =
|
||||
MWBase::Environment::get().getWorld ()->getStore ().get<ESM::MagicEffect>().find(effectIt->mEffectID);
|
||||
|
||||
MagicEffectInfo effectInfo;
|
||||
if (!it->second.mName.empty())
|
||||
effectInfo.mSource = it->second.mName;
|
||||
else
|
||||
effectInfo.mSource = getSpellDisplayName(it->first);
|
||||
effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID);
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)
|
||||
effectInfo.mKey.mArg = effectIt->mSkill;
|
||||
else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)
|
||||
effectInfo.mKey.mArg = effectIt->mAttribute;
|
||||
effectInfo.mMagnitude = effectIt->mMagnMin + (effectIt->mMagnMax-effectIt->mMagnMin) * randomFactor;
|
||||
effectInfo.mRemainingTime = effectIt->mDuration +
|
||||
(it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale;
|
||||
|
||||
// ingredients need special casing for their magnitude / duration
|
||||
if (MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (it->first))
|
||||
{
|
||||
effectInfo.mRemainingTime = effectIt->mDuration * randomFactor +
|
||||
(it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale;
|
||||
|
||||
effectInfo.mMagnitude = static_cast<int> (0.05*randomFactor / (0.1 * magicEffect->mData.mBaseCost));
|
||||
}
|
||||
|
||||
effects[effectIt->mEffectID].push_back (effectInfo);
|
||||
}
|
||||
}
|
||||
std::map <int, std::vector<MagicEffectInfo> >& effects = visitor.mEffectSources;
|
||||
|
||||
int w=2;
|
||||
|
||||
|
@ -280,59 +189,4 @@ namespace MWGui
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
std::string SpellIcons::getSpellDisplayName (const std::string& id)
|
||||
{
|
||||
if (const ESM::Spell *spell =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
|
||||
return spell->mName;
|
||||
|
||||
if (const ESM::Potion *potion =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
|
||||
return potion->mName;
|
||||
|
||||
if (const ESM::Ingredient *ingredient =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
|
||||
return ingredient->mName;
|
||||
|
||||
throw std::runtime_error ("ID " + id + " has no display name");
|
||||
}
|
||||
|
||||
ESM::EffectList SpellIcons::getSpellEffectList (const std::string& id)
|
||||
{
|
||||
if (const ESM::Enchantment* enchantment =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().search (id))
|
||||
return enchantment->mEffects;
|
||||
|
||||
if (const ESM::Spell *spell =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
|
||||
return spell->mEffects;
|
||||
|
||||
if (const ESM::Potion *potion =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
|
||||
return potion->mEffects;
|
||||
|
||||
if (const ESM::Ingredient *ingredient =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
|
||||
{
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
ingredient->mData.mEffectID[0]);
|
||||
|
||||
ESM::ENAMstruct effect;
|
||||
effect.mEffectID = ingredient->mData.mEffectID[0];
|
||||
effect.mSkill = ingredient->mData.mSkills[0];
|
||||
effect.mAttribute = ingredient->mData.mAttributes[0];
|
||||
effect.mRange = 0;
|
||||
effect.mArea = 0;
|
||||
effect.mDuration = magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration ? 0 : 1;
|
||||
effect.mMagnMin = 1;
|
||||
effect.mMagnMax = 1;
|
||||
ESM::EffectList result;
|
||||
result.mList.push_back (effect);
|
||||
return result;
|
||||
}
|
||||
throw std::runtime_error("ID " + id + " does not have effects");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define MWGUI_SPELLICONS_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../mwmechanics/magiceffects.hpp"
|
||||
|
||||
|
@ -34,14 +35,23 @@ namespace MWGui
|
|||
bool mPermanent; // the effect is permanent
|
||||
};
|
||||
|
||||
class EffectSourceVisitor : public MWMechanics::EffectSourceVisitor
|
||||
{
|
||||
public:
|
||||
bool mIsPermanent;
|
||||
|
||||
std::map <int, std::vector<MagicEffectInfo> > mEffectSources;
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, float magnitude, float remainingTime = -1);
|
||||
};
|
||||
|
||||
class SpellIcons
|
||||
{
|
||||
public:
|
||||
void updateWidgets(MyGUI::Widget* parent, bool adjustSize);
|
||||
|
||||
private:
|
||||
std::string getSpellDisplayName (const std::string& id);
|
||||
ESM::EffectList getSpellEffectList (const std::string& id);
|
||||
|
||||
std::map<int, MyGUI::ImageBox*> mWidgetMap;
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/actionequip.hpp"
|
||||
|
||||
#include "../mwmechanics/spellsuccess.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
|
||||
#include "spellicons.hpp"
|
||||
#include "inventorywindow.hpp"
|
||||
|
@ -282,8 +282,16 @@ namespace MWGui
|
|||
MyGUI::Button* costCharge = mSpellView->createWidget<MyGUI::Button>(equipped ? "SpellText" : "SpellTextUnequipped",
|
||||
MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top);
|
||||
|
||||
std::string cost = boost::lexical_cast<std::string>(enchant->mData.mCost);
|
||||
std::string charge = boost::lexical_cast<std::string>(enchant->mData.mCharge); /// \todo track current charge
|
||||
float enchantCost = enchant->mData.mCost;
|
||||
MWMechanics::NpcStats &stats = player.getClass().getNpcStats(player);
|
||||
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
|
||||
int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
|
||||
|
||||
std::string cost = boost::lexical_cast<std::string>(castCost);
|
||||
int currentCharge = int(item.getCellRef().mEnchantmentCharge);
|
||||
if (currentCharge == -1)
|
||||
currentCharge = enchant->mData.mCharge;
|
||||
std::string charge = boost::lexical_cast<std::string>(currentCharge);
|
||||
if (enchant->mData.mType == ESM::Enchantment::CastOnce)
|
||||
{
|
||||
// this is Morrowind behaviour
|
||||
|
|
|
@ -420,8 +420,6 @@ namespace MWGui
|
|||
}
|
||||
mSkillWidgets.clear();
|
||||
|
||||
mSkillView->setViewOffset (MyGUI::IntPoint(0,0));
|
||||
|
||||
const int valueSize = 40;
|
||||
MyGUI::IntCoord coord1(10, 0, mSkillView->getWidth() - (10 + valueSize) - 24, 18);
|
||||
MyGUI::IntCoord coord2(coord1.left + coord1.width, coord1.top, valueSize, coord1.height);
|
||||
|
|
|
@ -37,6 +37,8 @@ namespace MWGui
|
|||
void setBounty (int bounty) { if (bounty != mBounty) mChanged = true; this->mBounty = bounty; }
|
||||
void updateSkillArea();
|
||||
|
||||
virtual void open() { onWindowResize(static_cast<MyGUI::Window*>(mMainWidget)); }
|
||||
|
||||
private:
|
||||
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);
|
||||
|
|
|
@ -110,11 +110,6 @@ namespace MWGui
|
|||
|
||||
else
|
||||
{
|
||||
const MyGUI::IntPoint& lastPressed = MyGUI::InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left);
|
||||
|
||||
if (mousePos == lastPressed) // mouseclick makes tooltip disappear
|
||||
return;
|
||||
|
||||
if (mousePos.left == mLastMouseX && mousePos.top == mLastMouseY)
|
||||
{
|
||||
mRemainingDelay -= frameDuration;
|
||||
|
|
|
@ -80,9 +80,8 @@ namespace MWGui
|
|||
}
|
||||
|
||||
void TradeWindow::startTrade(const MWWorld::Ptr& actor)
|
||||
{
|
||||
{
|
||||
mPtr = actor;
|
||||
setTitle(MWWorld::Class::get(actor).getName(actor));
|
||||
|
||||
mCurrentBalance = 0;
|
||||
mCurrentMerchantOffer = 0;
|
||||
|
@ -99,6 +98,12 @@ namespace MWGui
|
|||
mItemView->setModel (mSortModel);
|
||||
|
||||
updateLabels();
|
||||
|
||||
// Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last
|
||||
// or we end up using a possibly invalid model.
|
||||
setTitle(MWWorld::Class::get(actor).getName(actor));
|
||||
|
||||
onFilterChanged(mFilterAll);
|
||||
}
|
||||
|
||||
void TradeWindow::onFilterChanged(MyGUI::Widget* _sender)
|
||||
|
@ -200,19 +205,17 @@ namespace MWGui
|
|||
sellToNpc(item.mBase, count, true);
|
||||
}
|
||||
|
||||
void TradeWindow::addOrRemoveGold(int amount)
|
||||
void TradeWindow::addOrRemoveGold(int amount, const MWWorld::Ptr& actor)
|
||||
{
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
MWWorld::ContainerStore& playerStore = MWWorld::Class::get(player).getContainerStore(player);
|
||||
MWWorld::ContainerStore& store = MWWorld::Class::get(actor).getContainerStore(actor);
|
||||
|
||||
if (amount > 0)
|
||||
{
|
||||
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), "Gold_001", amount);
|
||||
playerStore.add(ref.getPtr(), player);
|
||||
store.add("gold_001", amount, actor);
|
||||
}
|
||||
else
|
||||
{
|
||||
playerStore.remove("gold_001", - amount, player);
|
||||
store.remove("gold_001", - amount, actor);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,6 +270,8 @@ namespace MWGui
|
|||
return;
|
||||
}
|
||||
|
||||
MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
|
||||
if(mCurrentBalance > mCurrentMerchantOffer)
|
||||
{
|
||||
//if npc is a creature: reject (no haggle)
|
||||
|
@ -289,7 +294,6 @@ namespace MWGui
|
|||
+ MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange()),100));
|
||||
|
||||
const MWMechanics::NpcStats &sellerStats = MWWorld::Class::get(mPtr).getNpcStats(mPtr);
|
||||
MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr);
|
||||
|
||||
float a1 = std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f);
|
||||
|
@ -329,9 +333,12 @@ namespace MWGui
|
|||
mTradeModel->transferItems();
|
||||
playerItemModel->transferItems();
|
||||
|
||||
// add or remove gold from the player.
|
||||
// transfer the gold
|
||||
if (mCurrentBalance != 0)
|
||||
addOrRemoveGold(mCurrentBalance);
|
||||
{
|
||||
addOrRemoveGold(mCurrentBalance, playerPtr);
|
||||
addOrRemoveGold(-mCurrentBalance, mPtr);
|
||||
}
|
||||
|
||||
std::string sound = "Item Gold Up";
|
||||
MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0);
|
||||
|
@ -432,22 +439,13 @@ namespace MWGui
|
|||
|
||||
int TradeWindow::getMerchantGold()
|
||||
{
|
||||
int merchantGold;
|
||||
|
||||
if (mPtr.getTypeName() == typeid(ESM::NPC).name())
|
||||
int merchantGold = 0;
|
||||
MWWorld::ContainerStore store = mPtr.getClass().getContainerStore(mPtr);
|
||||
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
|
||||
{
|
||||
MWWorld::LiveCellRef<ESM::NPC>* ref = mPtr.get<ESM::NPC>();
|
||||
if (ref->mBase->mNpdt52.mGold == -10)
|
||||
merchantGold = ref->mBase->mNpdt12.mGold;
|
||||
else
|
||||
merchantGold = ref->mBase->mNpdt52.mGold;
|
||||
if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001"))
|
||||
merchantGold += it->getRefData().getCount();
|
||||
}
|
||||
else // ESM::Creature
|
||||
{
|
||||
MWWorld::LiveCellRef<ESM::Creature>* ref = mPtr.get<ESM::Creature>();
|
||||
merchantGold = ref->mBase->mData.mGold;
|
||||
}
|
||||
|
||||
return merchantGold;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace MWGui
|
|||
|
||||
void startTrade(const MWWorld::Ptr& actor);
|
||||
|
||||
void addOrRemoveGold(int gold);
|
||||
void addOrRemoveGold(int gold, const MWWorld::Ptr& actor);
|
||||
|
||||
void onFrame(float frameDuration);
|
||||
|
||||
|
|
|
@ -11,11 +11,11 @@
|
|||
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
|
||||
#include "inventorywindow.hpp"
|
||||
#include "tradewindow.hpp"
|
||||
#include "tooltips.hpp"
|
||||
|
||||
namespace MWGui
|
||||
|
@ -142,7 +142,7 @@ namespace MWGui
|
|||
pcStats.increaseSkill (skillId, *class_, true);
|
||||
|
||||
// remove gold
|
||||
MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price);
|
||||
player.getClass().getContainerStore(player).remove("gold_001", price, player);
|
||||
|
||||
// go back to game mode
|
||||
MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training);
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
|
||||
#include "inventorywindow.hpp"
|
||||
#include "tradewindow.hpp"
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
|
@ -121,13 +121,15 @@ namespace MWGui
|
|||
int price;
|
||||
iss >> price;
|
||||
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
|
||||
if (MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()<price)
|
||||
return;
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->getTradeWindow ()->addOrRemoveGold (-price);
|
||||
|
||||
player.getClass().getContainerStore(player).remove("gold_001", price, player);
|
||||
|
||||
MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(1);
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
ESM::Position pos = *_sender->getUserData<ESM::Position>();
|
||||
std::string cellname = _sender->getUserString("Destination");
|
||||
int x,y;
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
#include "waitdialog.hpp"
|
||||
#include "enchantingdialog.hpp"
|
||||
#include "trainingwindow.hpp"
|
||||
#include "recharge.hpp"
|
||||
#include "exposedwindow.hpp"
|
||||
#include "cursor.hpp"
|
||||
#include "merchantrepair.hpp"
|
||||
|
@ -95,10 +96,10 @@ namespace MWGui
|
|||
, mTrainingWindow(NULL)
|
||||
, mMerchantRepair(NULL)
|
||||
, mSoulgemDialog(NULL)
|
||||
, mRecharge(NULL)
|
||||
, mRepair(NULL)
|
||||
, mCompanionWindow(NULL)
|
||||
, mTranslationDataStorage (translationDataStorage)
|
||||
, mSoftwareCursor(NULL)
|
||||
, mCharGen(NULL)
|
||||
, mInputBlocker(NULL)
|
||||
, mCrosshairEnabled(Settings::Manager::getBool ("crosshair", "HUD"))
|
||||
|
@ -126,7 +127,6 @@ namespace MWGui
|
|||
, mFPS(0.0f)
|
||||
, mTriangleCount(0)
|
||||
, mBatchCount(0)
|
||||
, mUseHardwareCursors(Settings::Manager::getBool("hardware cursors", "GUI"))
|
||||
{
|
||||
// Set up the GUI system
|
||||
mGuiManager = new OEngine::GUI::MyGUIManager(mRendering->getWindow(), mRendering->getScene(), false, logpath);
|
||||
|
@ -171,16 +171,19 @@ namespace MWGui
|
|||
mLoadingScreen->onResChange (w,h);
|
||||
|
||||
//set up the hardware cursor manager
|
||||
mSoftwareCursor = new Cursor();
|
||||
mCursorManager = new SFO::SDLCursorManager();
|
||||
|
||||
MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &WindowManager::onCursorChange);
|
||||
|
||||
MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged);
|
||||
|
||||
setUseHardwareCursors(mUseHardwareCursors);
|
||||
mCursorManager->setEnabled(true);
|
||||
|
||||
onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer());
|
||||
mCursorManager->cursorVisibilityChange(false);
|
||||
SDL_ShowCursor(false);
|
||||
|
||||
// hide mygui's pointer
|
||||
MyGUI::PointerManager::getInstance().setVisible(false);
|
||||
}
|
||||
|
||||
void WindowManager::initUI()
|
||||
|
@ -197,18 +200,25 @@ namespace MWGui
|
|||
mDragAndDrop->mDraggedWidget = 0;
|
||||
mDragAndDrop->mDragAndDropWidget = dragAndDropWidget;
|
||||
|
||||
mRecharge = new Recharge();
|
||||
mMenu = new MainMenu(w,h);
|
||||
mMap = new MapWindow("");
|
||||
trackWindow(mMap, "map");
|
||||
mStatsWindow = new StatsWindow();
|
||||
trackWindow(mStatsWindow, "stats");
|
||||
mConsole = new Console(w,h, mConsoleOnlyScripts);
|
||||
trackWindow(mConsole, "console");
|
||||
mJournal = JournalWindow::create(JournalViewModel::create ());
|
||||
mMessageBoxManager = new MessageBoxManager();
|
||||
mInventoryWindow = new InventoryWindow(mDragAndDrop);
|
||||
mTradeWindow = new TradeWindow();
|
||||
trackWindow(mTradeWindow, "barter");
|
||||
mSpellBuyingWindow = new SpellBuyingWindow();
|
||||
mTravelWindow = new TravelWindow();
|
||||
mDialogueWindow = new DialogueWindow();
|
||||
trackWindow(mDialogueWindow, "dialogue");
|
||||
mContainerWindow = new ContainerWindow(mDragAndDrop);
|
||||
trackWindow(mContainerWindow, "container");
|
||||
mHud = new HUD(w,h, mShowFPSLevel, mDragAndDrop);
|
||||
mToolTips = new ToolTips();
|
||||
mScrollWindow = new ScrollWindow();
|
||||
|
@ -217,7 +227,9 @@ namespace MWGui
|
|||
mSettingsWindow = new SettingsWindow();
|
||||
mConfirmationDialog = new ConfirmationDialog();
|
||||
mAlchemyWindow = new AlchemyWindow();
|
||||
trackWindow(mAlchemyWindow, "alchemy");
|
||||
mSpellWindow = new SpellWindow();
|
||||
trackWindow(mSpellWindow, "spells");
|
||||
mQuickKeysMenu = new QuickKeysMenu();
|
||||
mLevelupDialog = new LevelupDialog();
|
||||
mWaitDialog = new WaitDialog();
|
||||
|
@ -228,6 +240,7 @@ namespace MWGui
|
|||
mRepair = new Repair();
|
||||
mSoulgemDialog = new SoulgemDialog(mMessageBoxManager);
|
||||
mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager);
|
||||
trackWindow(mCompanionWindow, "companion");
|
||||
|
||||
mInputBlocker = mGui->createWidget<MyGUI::Widget>("",0,0,w,h,MyGUI::Align::Default,"Windows","");
|
||||
|
||||
|
@ -310,8 +323,8 @@ namespace MWGui
|
|||
delete mMerchantRepair;
|
||||
delete mRepair;
|
||||
delete mSoulgemDialog;
|
||||
delete mSoftwareCursor;
|
||||
delete mCursorManager;
|
||||
delete mRecharge;
|
||||
|
||||
cleanupGarbage();
|
||||
|
||||
|
@ -340,8 +353,6 @@ namespace MWGui
|
|||
mHud->setBatchCount(mBatchCount);
|
||||
|
||||
mHud->update();
|
||||
|
||||
mSoftwareCursor->update();
|
||||
}
|
||||
|
||||
void WindowManager::updateVisible()
|
||||
|
@ -375,6 +386,7 @@ namespace MWGui
|
|||
mRepair->setVisible(false);
|
||||
mCompanionWindow->setVisible(false);
|
||||
mInventoryWindow->setTrading(false);
|
||||
mRecharge->setVisible(false);
|
||||
|
||||
mHud->setVisible(mHudEnabled);
|
||||
|
||||
|
@ -492,6 +504,9 @@ namespace MWGui
|
|||
case GM_SpellCreation:
|
||||
mSpellCreationDialog->setVisible(true);
|
||||
break;
|
||||
case GM_Recharge:
|
||||
mRecharge->setVisible(true);
|
||||
break;
|
||||
case GM_Enchanting:
|
||||
mEnchantingDialog->setVisible(true);
|
||||
break;
|
||||
|
@ -857,21 +872,9 @@ namespace MWGui
|
|||
MWBase::Environment::get().getInputManager()->setDragDrop(dragDrop);
|
||||
}
|
||||
|
||||
void WindowManager::setUseHardwareCursors(bool use)
|
||||
{
|
||||
mCursorManager->setEnabled(use);
|
||||
mSoftwareCursor->setVisible(!use && mCursorVisible);
|
||||
}
|
||||
|
||||
void WindowManager::setCursorVisible(bool visible)
|
||||
{
|
||||
if(mCursorVisible == visible)
|
||||
return;
|
||||
|
||||
mCursorVisible = visible;
|
||||
mCursorManager->cursorVisibilityChange(visible);
|
||||
|
||||
mSoftwareCursor->setVisible(!mUseHardwareCursors && visible);
|
||||
}
|
||||
|
||||
void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result)
|
||||
|
@ -902,8 +905,6 @@ namespace MWGui
|
|||
mHud->setFpsLevel(Settings::Manager::getInt("fps", "HUD"));
|
||||
mToolTips->setDelay(Settings::Manager::getFloat("tooltip delay", "GUI"));
|
||||
|
||||
setUseHardwareCursors(Settings::Manager::getBool("hardware cursors", "GUI"));
|
||||
|
||||
for (Settings::CategorySettingVector::const_iterator it = changed.begin();
|
||||
it != changed.end(); ++it)
|
||||
{
|
||||
|
@ -920,6 +921,17 @@ namespace MWGui
|
|||
mLoadingScreen->onResChange (x,y);
|
||||
if (!mHud)
|
||||
return; // UI not initialized yet
|
||||
|
||||
for (std::map<MyGUI::Window*, std::string>::iterator it = mTrackedWindows.begin(); it != mTrackedWindows.end(); ++it)
|
||||
{
|
||||
MyGUI::IntPoint pos (Settings::Manager::getFloat(it->second + " x", "Windows") * x,
|
||||
Settings::Manager::getFloat(it->second+ " y", "Windows") * y);
|
||||
MyGUI::IntSize size (Settings::Manager::getFloat(it->second + " w", "Windows") * x,
|
||||
Settings::Manager::getFloat(it->second + " h", "Windows") * y);
|
||||
it->first->setPosition(pos);
|
||||
it->first->setSize(size);
|
||||
}
|
||||
|
||||
mHud->onResChange(x, y);
|
||||
mConsole->onResChange(x, y);
|
||||
mMenu->onResChange(x, y);
|
||||
|
@ -955,8 +967,6 @@ namespace MWGui
|
|||
|
||||
void WindowManager::onCursorChange(const std::string &name)
|
||||
{
|
||||
mSoftwareCursor->onCursorChange(name);
|
||||
|
||||
if(!mCursorManager->cursorChanged(name))
|
||||
return; //the cursor manager doesn't want any more info about this cursor
|
||||
//See if we can get the information we need out of the cursor resource
|
||||
|
@ -1300,6 +1310,7 @@ namespace MWGui
|
|||
|
||||
void WindowManager::changePointer(const std::string &name)
|
||||
{
|
||||
MyGUI::PointerManager::getInstance().setPointer(name);
|
||||
onCursorChange(name);
|
||||
}
|
||||
|
||||
|
@ -1349,4 +1360,45 @@ namespace MWGui
|
|||
return mLoadingScreen;
|
||||
}
|
||||
|
||||
void WindowManager::startRecharge(MWWorld::Ptr soulgem)
|
||||
{
|
||||
mRecharge->start(soulgem);
|
||||
}
|
||||
|
||||
bool WindowManager::getCursorVisible()
|
||||
{
|
||||
return mCursorVisible;
|
||||
}
|
||||
|
||||
void WindowManager::trackWindow(OEngine::GUI::Layout *layout, const std::string &name)
|
||||
{
|
||||
MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
||||
MyGUI::IntPoint pos (Settings::Manager::getFloat(name + " x", "Windows") * viewSize.width,
|
||||
Settings::Manager::getFloat(name + " y", "Windows") * viewSize.height);
|
||||
MyGUI::IntSize size (Settings::Manager::getFloat(name + " w", "Windows") * viewSize.width,
|
||||
Settings::Manager::getFloat(name + " h", "Windows") * viewSize.height);
|
||||
layout->mMainWidget->setPosition(pos);
|
||||
layout->mMainWidget->setSize(size);
|
||||
|
||||
MyGUI::Window* window = dynamic_cast<MyGUI::Window*>(layout->mMainWidget);
|
||||
if (!window)
|
||||
throw std::runtime_error("Attempting to track size of a non-resizable window");
|
||||
window->eventWindowChangeCoord += MyGUI::newDelegate(this, &WindowManager::onWindowChangeCoord);
|
||||
mTrackedWindows[window] = name;
|
||||
}
|
||||
|
||||
void WindowManager::onWindowChangeCoord(MyGUI::Window *_sender)
|
||||
{
|
||||
std::string setting = mTrackedWindows[_sender];
|
||||
MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize();
|
||||
float x = _sender->getPosition().left / float(viewSize.width);
|
||||
float y = _sender->getPosition().top / float(viewSize.height);
|
||||
float w = _sender->getSize().width / float(viewSize.width);
|
||||
float h = _sender->getSize().height / float(viewSize.height);
|
||||
Settings::Manager::setFloat(setting + " x", "Windows", x);
|
||||
Settings::Manager::setFloat(setting + " y", "Windows", y);
|
||||
Settings::Manager::setFloat(setting + " w", "Windows", w);
|
||||
Settings::Manager::setFloat(setting + " h", "Windows", h);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ namespace MyGUI
|
|||
{
|
||||
class Gui;
|
||||
class Widget;
|
||||
class Window;
|
||||
class UString;
|
||||
}
|
||||
|
||||
|
@ -77,6 +78,7 @@ namespace MWGui
|
|||
class MerchantRepair;
|
||||
class Repair;
|
||||
class SoulgemDialog;
|
||||
class Recharge;
|
||||
class CompanionWindow;
|
||||
|
||||
class WindowManager : public MWBase::WindowManager
|
||||
|
@ -263,6 +265,7 @@ namespace MWGui
|
|||
virtual void startTraining(MWWorld::Ptr actor);
|
||||
virtual void startRepair(MWWorld::Ptr actor);
|
||||
virtual void startRepairItem(MWWorld::Ptr item);
|
||||
virtual void startRecharge(MWWorld::Ptr soulgem);
|
||||
|
||||
virtual void frameStarted(float dt);
|
||||
|
||||
|
@ -276,9 +279,15 @@ namespace MWGui
|
|||
|
||||
void onSoulgemDialogButtonPressed (int button);
|
||||
|
||||
virtual bool getCursorVisible();
|
||||
|
||||
private:
|
||||
bool mConsoleOnlyScripts;
|
||||
|
||||
std::map<MyGUI::Window*, std::string> mTrackedWindows;
|
||||
void trackWindow(OEngine::GUI::Layout* layout, const std::string& name);
|
||||
void onWindowChangeCoord(MyGUI::Window* _sender);
|
||||
|
||||
OEngine::GUI::MyGUIManager *mGuiManager;
|
||||
OEngine::Render::OgreRenderer *mRendering;
|
||||
HUD *mHud;
|
||||
|
@ -313,6 +322,7 @@ namespace MWGui
|
|||
MerchantRepair* mMerchantRepair;
|
||||
SoulgemDialog* mSoulgemDialog;
|
||||
Repair* mRepair;
|
||||
Recharge* mRecharge;
|
||||
CompanionWindow* mCompanionWindow;
|
||||
|
||||
Translation::Storage& mTranslationDataStorage;
|
||||
|
@ -366,9 +376,6 @@ namespace MWGui
|
|||
unsigned int mTriangleCount;
|
||||
unsigned int mBatchCount;
|
||||
|
||||
bool mUseHardwareCursors;
|
||||
void setUseHardwareCursors(bool use);
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
|
@ -88,7 +88,7 @@ namespace MWInput
|
|||
{
|
||||
InputManager::InputManager(OEngine::Render::OgreRenderer &ogre,
|
||||
OMW::Engine& engine,
|
||||
const std::string& userFile, bool userFileExists)
|
||||
const std::string& userFile, bool userFileExists, bool grab)
|
||||
: mOgre(ogre)
|
||||
, mPlayer(NULL)
|
||||
, mEngine(engine)
|
||||
|
@ -112,7 +112,7 @@ namespace MWInput
|
|||
|
||||
Ogre::RenderWindow* window = ogre.getWindow ();
|
||||
|
||||
mInputManager = new SFO::InputWrapper(mOgre.getSDLWindow(), mOgre.getWindow());
|
||||
mInputManager = new SFO::InputWrapper(mOgre.getSDLWindow(), mOgre.getWindow(), grab);
|
||||
mInputManager->setMouseEventCallback (this);
|
||||
mInputManager->setKeyboardEventCallback (this);
|
||||
mInputManager->setWindowEventCallback(this);
|
||||
|
@ -265,6 +265,8 @@ namespace MWInput
|
|||
|
||||
void InputManager::update(float dt, bool loading)
|
||||
{
|
||||
mInputManager->setMouseVisible(MWBase::Environment::get().getWindowManager()->getCursorVisible());
|
||||
|
||||
mInputManager->capture(loading);
|
||||
// inject some fake mouse movement to force updating MyGUI's widget states
|
||||
// this shouldn't do any harm since we're moving back to the original position afterwards
|
||||
|
@ -504,7 +506,7 @@ namespace MWInput
|
|||
|
||||
mInputBinder->keyPressed (arg);
|
||||
|
||||
if(arg.keysym.sym == SDLK_RETURN
|
||||
if((arg.keysym.sym == SDLK_RETURN || arg.keysym.sym == SDLK_KP_ENTER)
|
||||
&& MWBase::Environment::get().getWindowManager()->isGuiMode())
|
||||
{
|
||||
// Pressing enter when a messagebox is prompting for "ok" will activate the ok button
|
||||
|
|
|
@ -61,7 +61,7 @@ namespace MWInput
|
|||
public:
|
||||
InputManager(OEngine::Render::OgreRenderer &_ogre,
|
||||
OMW::Engine& engine,
|
||||
const std::string& userFile, bool userFileExists);
|
||||
const std::string& userFile, bool userFileExists, bool grab);
|
||||
|
||||
virtual ~InputManager();
|
||||
|
||||
|
|
|
@ -1,28 +1,7 @@
|
|||
|
||||
#include "activespells.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <components/esm/loadalch.hpp>
|
||||
#include <components/esm/loadspel.hpp>
|
||||
#include <components/esm/loadingr.hpp>
|
||||
#include <components/esm/loadmgef.hpp>
|
||||
#include <components/esm/loadskil.hpp>
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
#include "npcstats.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
@ -32,6 +11,7 @@ namespace MWMechanics
|
|||
|
||||
MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp();
|
||||
|
||||
// Erase no longer active spells
|
||||
if (mLastUpdate!=now)
|
||||
{
|
||||
TContainer::iterator iter (mSpells.begin());
|
||||
|
@ -39,7 +19,6 @@ namespace MWMechanics
|
|||
if (!timeToExpire (iter))
|
||||
{
|
||||
mSpells.erase (iter++);
|
||||
//onSpellExpired
|
||||
rebuild = true;
|
||||
}
|
||||
else
|
||||
|
@ -66,214 +45,28 @@ namespace MWMechanics
|
|||
|
||||
for (TIterator iter (begin()); iter!=end(); ++iter)
|
||||
{
|
||||
std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (iter->first);
|
||||
|
||||
const MWWorld::TimeStamp& start = iter->second.mTimeStamp;
|
||||
|
||||
int i = 0;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIter (effects.first.mList.begin());
|
||||
effectIter!=effects.first.mList.end(); ++effectIter, ++i)
|
||||
const std::vector<Effect>& effects = iter->second.mEffects;
|
||||
|
||||
for (std::vector<Effect>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt)
|
||||
{
|
||||
float magnitude = iter->second.mRandom[i];
|
||||
if (effectIter->mRange != iter->second.mRange)
|
||||
continue;
|
||||
int duration = effectIt->mDuration;
|
||||
MWWorld::TimeStamp end = start;
|
||||
end += static_cast<double> (duration)*
|
||||
MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60);
|
||||
|
||||
if (effectIter->mDuration)
|
||||
{
|
||||
int duration = effectIter->mDuration;
|
||||
|
||||
if (effects.second.first)
|
||||
duration *= magnitude;
|
||||
|
||||
MWWorld::TimeStamp end = start;
|
||||
end += static_cast<double> (duration)*
|
||||
MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60);
|
||||
|
||||
if (end>now)
|
||||
{
|
||||
EffectParam param;
|
||||
|
||||
if (effects.second.first)
|
||||
{
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
effectIter->mEffectID);
|
||||
|
||||
if (effectIter->mDuration==0)
|
||||
{
|
||||
param.mMagnitude =
|
||||
static_cast<int> (magnitude / (0.1 * magicEffect->mData.mBaseCost));
|
||||
}
|
||||
else
|
||||
{
|
||||
param.mMagnitude =
|
||||
static_cast<int> (0.05*magnitude / (0.1 * magicEffect->mData.mBaseCost));
|
||||
}
|
||||
}
|
||||
else
|
||||
param.mMagnitude = static_cast<int> (
|
||||
(effectIter->mMagnMax-effectIter->mMagnMin)*magnitude + effectIter->mMagnMin);
|
||||
|
||||
mEffects.add (*effectIter, param);
|
||||
}
|
||||
}
|
||||
if (end>now)
|
||||
mEffects.add(effectIt->mKey, MWMechanics::EffectParam(effectIt->mMagnitude));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<ESM::EffectList, std::pair<bool, bool> > ActiveSpells::getEffectList (const std::string& id) const
|
||||
{
|
||||
if (const ESM::Enchantment* enchantment =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().search (id))
|
||||
return std::make_pair (enchantment->mEffects, std::make_pair(false, false));
|
||||
|
||||
if (const ESM::Spell *spell =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
|
||||
return std::make_pair (spell->mEffects, std::make_pair(false, false));
|
||||
|
||||
if (const ESM::Potion *potion =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
|
||||
return std::make_pair (potion->mEffects, std::make_pair(false, true));
|
||||
|
||||
if (const ESM::Ingredient *ingredient =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
|
||||
{
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
ingredient->mData.mEffectID[0]);
|
||||
|
||||
ESM::ENAMstruct effect;
|
||||
effect.mEffectID = ingredient->mData.mEffectID[0];
|
||||
effect.mSkill = ingredient->mData.mSkills[0];
|
||||
effect.mAttribute = ingredient->mData.mAttributes[0];
|
||||
effect.mRange = 0;
|
||||
effect.mArea = 0;
|
||||
effect.mDuration = magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration ? 0 : 1;
|
||||
effect.mMagnMin = 1;
|
||||
effect.mMagnMax = 1;
|
||||
|
||||
std::pair<ESM::EffectList, std::pair<bool, bool> > result;
|
||||
result.second.second = true;
|
||||
result.second.first = true;
|
||||
|
||||
result.first.mList.push_back (effect);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
throw std::runtime_error ("ID " + id + " can not produce lasting effects");
|
||||
}
|
||||
|
||||
ActiveSpells::ActiveSpells()
|
||||
: mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp())
|
||||
: mSpellsChanged (false)
|
||||
, mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp())
|
||||
{}
|
||||
|
||||
bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range, const std::string& name)
|
||||
{
|
||||
std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (id);
|
||||
bool stacks = effects.second.second;
|
||||
|
||||
bool found = false;
|
||||
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
|
||||
iter!=effects.first.mList.end(); ++iter)
|
||||
{
|
||||
if (iter->mDuration)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return false;
|
||||
|
||||
TContainer::iterator iter = mSpells.find (id);
|
||||
|
||||
float random = static_cast<float> (std::rand()) / RAND_MAX;
|
||||
|
||||
if (effects.second.first)
|
||||
{
|
||||
// ingredient -> special treatment required.
|
||||
const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor);
|
||||
const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor);
|
||||
|
||||
float x =
|
||||
(npcStats.getSkill (ESM::Skill::Alchemy).getModified() +
|
||||
0.2 * creatureStats.getAttribute (1).getModified()
|
||||
+ 0.1 * creatureStats.getAttribute (7).getModified())
|
||||
* creatureStats.getFatigueTerm();
|
||||
random *= 100;
|
||||
random = random / std::min (x, 100.0f);
|
||||
random *= 0.25 * x;
|
||||
}
|
||||
|
||||
ActiveSpellParams params;
|
||||
for (unsigned int i=0; i<effects.first.mList.size(); ++i)
|
||||
params.mRandom.push_back(static_cast<float> (std::rand()) / RAND_MAX);
|
||||
params.mRange = range;
|
||||
params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp();
|
||||
params.mName = name;
|
||||
|
||||
if (iter==mSpells.end() || stacks)
|
||||
mSpells.insert (std::make_pair (id, params));
|
||||
else
|
||||
iter->second = params;
|
||||
|
||||
// Play sounds & particles
|
||||
bool first=true;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
|
||||
iter!=effects.first.mList.end(); ++iter)
|
||||
{
|
||||
if (iter->mRange != range)
|
||||
continue;
|
||||
|
||||
// TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World.
|
||||
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
iter->mEffectID);
|
||||
|
||||
// Only the sound of the first effect plays
|
||||
if (first)
|
||||
{
|
||||
static const std::string schools[] = {
|
||||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
||||
};
|
||||
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
if(!magicEffect->mHitSound.empty())
|
||||
sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f);
|
||||
else
|
||||
sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
if (!magicEffect->mHit.empty())
|
||||
{
|
||||
const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
|
||||
bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
|
||||
MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
mSpellsChanged = true;
|
||||
|
||||
return 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();
|
||||
|
@ -282,37 +75,31 @@ namespace MWMechanics
|
|||
|
||||
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
|
||||
{
|
||||
std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (iterator->first);
|
||||
const std::vector<Effect>& effects = iterator->second.mEffects;
|
||||
|
||||
int duration = 0;
|
||||
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
|
||||
iter!=effects.first.mList.end(); ++iter)
|
||||
for (std::vector<Effect>::const_iterator iter (effects.begin());
|
||||
iter!=effects.end(); ++iter)
|
||||
{
|
||||
if (iter->mDuration > duration)
|
||||
duration = iter->mDuration;
|
||||
}
|
||||
|
||||
// Scale duration by magnitude if needed
|
||||
if (effects.second.first && iterator->second.mRandom.size())
|
||||
duration *= iterator->second.mRandom.front();
|
||||
|
||||
double scaledDuration = duration *
|
||||
MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60);
|
||||
|
||||
double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp()-iterator->second.mTimeStamp;
|
||||
double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp() - iterator->second.mTimeStamp;
|
||||
|
||||
if (usedUp>=scaledDuration)
|
||||
return 0;
|
||||
|
@ -338,4 +125,68 @@ namespace MWMechanics
|
|||
{
|
||||
return mSpells;
|
||||
}
|
||||
|
||||
void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector<Effect> effects, const std::string &displayName)
|
||||
{
|
||||
bool exists = false;
|
||||
for (TContainer::const_iterator it = begin(); it != end(); ++it)
|
||||
{
|
||||
if (id == it->first)
|
||||
exists = true;
|
||||
}
|
||||
|
||||
ActiveSpellParams params;
|
||||
params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp();
|
||||
params.mEffects = effects;
|
||||
params.mDisplayName = displayName;
|
||||
|
||||
if (!exists || stack)
|
||||
mSpells.insert (std::make_pair(id, params));
|
||||
else
|
||||
mSpells.find(id)->second = params;
|
||||
|
||||
mSpellsChanged = true;
|
||||
}
|
||||
|
||||
void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const
|
||||
{
|
||||
for (TContainer::const_iterator it = begin(); it != end(); ++it)
|
||||
{
|
||||
float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor();
|
||||
|
||||
for (std::vector<Effect>::const_iterator effectIt = it->second.mEffects.begin();
|
||||
effectIt != it->second.mEffects.end(); ++effectIt)
|
||||
{
|
||||
std::string name = it->second.mDisplayName;
|
||||
|
||||
float remainingTime = effectIt->mDuration +
|
||||
(it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale;
|
||||
float magnitude = effectIt->mMagnitude;
|
||||
|
||||
if (magnitude)
|
||||
visitor.visit(effectIt->mKey, name, magnitude, remainingTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActiveSpells::purgeAll()
|
||||
{
|
||||
mSpells.clear();
|
||||
}
|
||||
|
||||
void ActiveSpells::purgeEffect(short effectId)
|
||||
{
|
||||
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
|
||||
{
|
||||
for (std::vector<Effect>::iterator effectIt = it->second.mEffects.begin();
|
||||
effectIt != it->second.mEffects.end();)
|
||||
{
|
||||
if (effectIt->mKey.mId == effectId)
|
||||
effectIt = it->second.mEffects.erase(effectIt);
|
||||
else
|
||||
effectIt++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,35 +11,8 @@
|
|||
|
||||
#include <components/esm/defs.hpp>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct Spell;
|
||||
struct EffectList;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
struct ActiveSpellParams
|
||||
{
|
||||
// Only apply effects of this range type
|
||||
ESM::RangeType mRange;
|
||||
|
||||
// When the spell was added
|
||||
MWWorld::TimeStamp mTimeStamp;
|
||||
|
||||
// Random factor for each effect
|
||||
std::vector<float> mRandom;
|
||||
|
||||
// Display name, we need this for enchantments, which don't have a name - so you need to supply the
|
||||
// name of the item with the enchantment to addSpell
|
||||
std::string mName;
|
||||
};
|
||||
|
||||
/// \brief Lasting spell effects
|
||||
///
|
||||
/// \note The name of this class is slightly misleading, since it also handels lasting potion
|
||||
|
@ -48,12 +21,32 @@ namespace MWMechanics
|
|||
{
|
||||
public:
|
||||
|
||||
// Parameters of an effect concerning lasting effects.
|
||||
// Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc.
|
||||
// It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster.
|
||||
struct Effect
|
||||
{
|
||||
float mMagnitude;
|
||||
EffectKey mKey;
|
||||
float mDuration;
|
||||
};
|
||||
|
||||
struct ActiveSpellParams
|
||||
{
|
||||
std::vector<Effect> mEffects;
|
||||
MWWorld::TimeStamp mTimeStamp;
|
||||
std::string mDisplayName;
|
||||
|
||||
// TODO: To handle CASTER_LINKED flag (spell is purged when caster dies),
|
||||
// we should probably store a handle to the caster here.
|
||||
};
|
||||
|
||||
typedef std::multimap<std::string, ActiveSpellParams > TContainer;
|
||||
typedef TContainer::const_iterator TIterator;
|
||||
|
||||
private:
|
||||
|
||||
mutable TContainer mSpells; // spellId, (time of casting, relative magnitude)
|
||||
mutable TContainer mSpells;
|
||||
mutable MagicEffects mEffects;
|
||||
mutable bool mSpellsChanged;
|
||||
mutable MWWorld::TimeStamp mLastUpdate;
|
||||
|
@ -62,29 +55,9 @@ namespace MWMechanics
|
|||
|
||||
void rebuildEffects() const;
|
||||
|
||||
std::pair<ESM::EffectList, std::pair<bool, bool> > getEffectList (const std::string& id) const;
|
||||
///< @return (EffectList, (isIngredient, stacks))
|
||||
|
||||
public:
|
||||
|
||||
ActiveSpells();
|
||||
|
||||
bool addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range = ESM::RT_Self, const std::string& name = "");
|
||||
///< Overwrites an existing spell with the same ID. If the spell does not have any
|
||||
/// non-instant effects, it is ignored.
|
||||
/// @param id
|
||||
/// @param actor
|
||||
/// @param range Only effects with range type \a range will be applied
|
||||
/// @param name Display name for enchantments, since they don't have a name in their record
|
||||
///
|
||||
/// \return Has the spell been added?
|
||||
|
||||
void removeSpell (const std::string& id);
|
||||
|
||||
bool isSpellActive (std::string id) const;
|
||||
///< case insensitive
|
||||
|
||||
const MagicEffects& getMagicEffects() const;
|
||||
double timeToExpire (const TIterator& iterator) const;
|
||||
///< Returns time (in in-game hours) until the spell pointed to by \a iterator
|
||||
/// expires.
|
||||
|
||||
const TContainer& getActiveSpells() const;
|
||||
|
||||
|
@ -92,9 +65,33 @@ namespace MWMechanics
|
|||
|
||||
TIterator end() const;
|
||||
|
||||
double timeToExpire (const TIterator& iterator) const;
|
||||
///< Returns time (in in-game hours) until the spell pointed to by \a iterator
|
||||
/// expires.
|
||||
public:
|
||||
|
||||
ActiveSpells();
|
||||
|
||||
/// Add lasting effects
|
||||
///
|
||||
/// \brief addSpell
|
||||
/// \param id ID for stacking purposes.
|
||||
/// \param stack If false, the spell is not added if one with the same ID exists already.
|
||||
/// \param effects
|
||||
/// \param displayName Name for display in magic menu.
|
||||
///
|
||||
void addSpell (const std::string& id, bool stack, std::vector<Effect> effects, const std::string& displayName);
|
||||
|
||||
/// Remove all active effects with this id
|
||||
void purgeEffect (short effectId);
|
||||
|
||||
/// Remove all active effects
|
||||
void purgeAll ();
|
||||
|
||||
bool isSpellActive (std::string id) const;
|
||||
///< case insensitive
|
||||
|
||||
const MagicEffects& getMagicEffects() const;
|
||||
|
||||
void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/manualref.hpp"
|
||||
#include "../mwworld/actionequip.hpp"
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
@ -23,6 +25,30 @@
|
|||
#include "creaturestats.hpp"
|
||||
#include "movement.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
|
||||
#include "aicombat.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& actor)
|
||||
{
|
||||
if (bound)
|
||||
{
|
||||
MWWorld::Ptr newPtr = *actor.getClass().getContainerStore(actor).add(item, 1, actor);
|
||||
MWWorld::ActionEquip action(newPtr);
|
||||
action.execute(actor);
|
||||
}
|
||||
else
|
||||
{
|
||||
actor.getClass().getContainerStore(actor).remove(item, 1, actor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
void Actors::updateActor (const MWWorld::Ptr& ptr, float duration)
|
||||
|
@ -30,13 +56,48 @@ namespace MWMechanics
|
|||
// magic effects
|
||||
adjustMagicEffects (ptr);
|
||||
calculateDynamicStats (ptr);
|
||||
calculateCreatureStatModifiers (ptr);
|
||||
calculateCreatureStatModifiers (ptr, duration);
|
||||
|
||||
if(!MWBase::Environment::get().getWindowManager()->isGuiMode())
|
||||
{
|
||||
// AI
|
||||
CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
|
||||
creatureStats.getAiSequence().execute (ptr);
|
||||
if(MWBase::Environment::get().getMechanicsManager()->isAIActive())
|
||||
{
|
||||
CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
|
||||
//engage combat or not?
|
||||
if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !creatureStats.isHostile())
|
||||
{
|
||||
ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition();
|
||||
ESM::Position actorpos = ptr.getRefData().getPosition();
|
||||
float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0])
|
||||
+(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1])
|
||||
+(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2]));
|
||||
float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(1);
|
||||
float disp = 100; //creatures don't have disposition, so set it to 100 by default
|
||||
if(ptr.getTypeName() == typeid(ESM::NPC).name())
|
||||
{
|
||||
disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr);
|
||||
}
|
||||
bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer());
|
||||
if( ( (fight == 100 )
|
||||
|| (fight >= 95 && d <= 3000)
|
||||
|| (fight >= 90 && d <= 2000)
|
||||
|| (fight >= 80 && d <= 1000)
|
||||
|| (fight >= 80 && disp <= 40)
|
||||
|| (fight >= 70 && disp <= 35 && d <= 1000)
|
||||
|| (fight >= 60 && disp <= 30 && d <= 1000)
|
||||
|| (fight >= 50 && disp == 0)
|
||||
|| (fight >= 40 && disp <= 10 && d <= 500) )
|
||||
&& LOS
|
||||
)
|
||||
{
|
||||
creatureStats.getAiSequence().stack(AiCombat("player"));
|
||||
creatureStats.setHostile(true);
|
||||
}
|
||||
}
|
||||
|
||||
creatureStats.getAiSequence().execute (ptr,duration);
|
||||
}
|
||||
|
||||
// fatigue restoration
|
||||
calculateRestoration(ptr, duration);
|
||||
|
@ -100,6 +161,8 @@ namespace MWMechanics
|
|||
|
||||
void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration)
|
||||
{
|
||||
if (ptr.getClass().getCreatureStats(ptr).isDead())
|
||||
return;
|
||||
CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr);
|
||||
const MWWorld::Store<ESM::GameSetting>& settings = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||
|
||||
|
@ -146,7 +209,7 @@ namespace MWMechanics
|
|||
stats.setFatigue (fatigue);
|
||||
}
|
||||
|
||||
void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr)
|
||||
void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration)
|
||||
{
|
||||
CreatureStats &creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr);
|
||||
const MagicEffects &effects = creatureStats.getMagicEffects();
|
||||
|
@ -156,7 +219,8 @@ namespace MWMechanics
|
|||
{
|
||||
Stat<int> stat = creatureStats.getAttribute(i);
|
||||
stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).mMagnitude -
|
||||
effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude);
|
||||
effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude -
|
||||
effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).mMagnitude);
|
||||
|
||||
creatureStats.setAttribute(i, stat);
|
||||
}
|
||||
|
@ -168,10 +232,160 @@ namespace MWMechanics
|
|||
stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyHealth+i)).mMagnitude -
|
||||
effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude);
|
||||
|
||||
|
||||
float currentDiff = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::RestoreHealth+i)).mMagnitude
|
||||
- creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude
|
||||
- creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::AbsorbHealth+i)).mMagnitude;
|
||||
stat.setCurrent(stat.getCurrent() + currentDiff * duration);
|
||||
|
||||
creatureStats.setDynamic(i, stat);
|
||||
}
|
||||
|
||||
// Apply damage ticks
|
||||
int damageEffects[] = {
|
||||
ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison,
|
||||
ESM::MagicEffect::SunDamage
|
||||
};
|
||||
|
||||
DynamicStat<float> health = creatureStats.getHealth();
|
||||
for (unsigned int i=0; i<sizeof(damageEffects)/sizeof(int); ++i)
|
||||
{
|
||||
float magnitude = creatureStats.getMagicEffects().get(EffectKey(damageEffects[i])).mMagnitude;
|
||||
|
||||
if (damageEffects[i] == ESM::MagicEffect::SunDamage)
|
||||
{
|
||||
// isInCell shouldn't be needed, but updateActor called during game start
|
||||
if (!ptr.isInCell() || !ptr.getCell()->isExterior())
|
||||
continue;
|
||||
float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour();
|
||||
float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13)));
|
||||
float damageScale = 1.f - timeDiff / 7.f;
|
||||
// When cloudy, the sun damage effect is halved
|
||||
static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
"fMagicSunBlockedMult")->getFloat();
|
||||
|
||||
int weather = MWBase::Environment::get().getWorld()->getCurrentWeather();
|
||||
if (weather > 1)
|
||||
damageScale *= fMagicSunBlockedMult;
|
||||
health.setCurrent(health.getCurrent() - magnitude * duration * damageScale);
|
||||
}
|
||||
else
|
||||
health.setCurrent(health.getCurrent() - magnitude * duration);
|
||||
|
||||
}
|
||||
creatureStats.setHealth(health);
|
||||
|
||||
// TODO: dirty flag for magic effects to avoid some unnecessary work below?
|
||||
|
||||
// Update bound effects
|
||||
static std::map<int, std::string> boundItemsMap;
|
||||
if (boundItemsMap.empty())
|
||||
{
|
||||
boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "battle_axe";
|
||||
boundItemsMap[ESM::MagicEffect::BoundBoots] = "boots";
|
||||
boundItemsMap[ESM::MagicEffect::BoundCuirass] = "cuirass";
|
||||
boundItemsMap[ESM::MagicEffect::BoundDagger] = "dagger";
|
||||
boundItemsMap[ESM::MagicEffect::BoundGloves] = "gauntlet"; // Note: needs both _left and _right variants, see below
|
||||
boundItemsMap[ESM::MagicEffect::BoundHelm] = "helm";
|
||||
boundItemsMap[ESM::MagicEffect::BoundLongbow] = "longbow";
|
||||
boundItemsMap[ESM::MagicEffect::BoundLongsword] = "longsword";
|
||||
boundItemsMap[ESM::MagicEffect::BoundMace] = "mace";
|
||||
boundItemsMap[ESM::MagicEffect::BoundShield] = "shield";
|
||||
boundItemsMap[ESM::MagicEffect::BoundSpear] = "spear";
|
||||
}
|
||||
|
||||
for (std::map<int, std::string>::iterator it = boundItemsMap.begin(); it != boundItemsMap.end(); ++it)
|
||||
{
|
||||
bool found = creatureStats.mBoundItems.find(it->first) != creatureStats.mBoundItems.end();
|
||||
int magnitude = creatureStats.getMagicEffects().get(EffectKey(it->first)).mMagnitude;
|
||||
if (found != (magnitude > 0))
|
||||
{
|
||||
std::string item = "bound_" + it->second;
|
||||
if (it->first == ESM::MagicEffect::BoundGloves)
|
||||
{
|
||||
adjustBoundItem(item + "_left", magnitude > 0, ptr);
|
||||
adjustBoundItem(item + "_right", magnitude > 0, ptr);
|
||||
}
|
||||
else
|
||||
adjustBoundItem(item, magnitude > 0, ptr);
|
||||
|
||||
if (magnitude > 0)
|
||||
creatureStats.mBoundItems.insert(it->first);
|
||||
else
|
||||
creatureStats.mBoundItems.erase(it->first);
|
||||
}
|
||||
}
|
||||
|
||||
// Update summon effects
|
||||
static std::map<int, std::string> summonMap;
|
||||
if (summonMap.empty())
|
||||
{
|
||||
summonMap[ESM::MagicEffect::SummonAncestralGhost] = "ancestor_ghost_summon";
|
||||
summonMap[ESM::MagicEffect::SummonBear] = "BM_bear_black_summon";
|
||||
summonMap[ESM::MagicEffect::SummonBonelord] = "bonelord_summon";
|
||||
summonMap[ESM::MagicEffect::SummonBonewalker] = "bonewalker_summon";
|
||||
summonMap[ESM::MagicEffect::SummonBonewolf] = "BM_wolf_bone_summon";
|
||||
summonMap[ESM::MagicEffect::SummonCenturionSphere] = "centurion_sphere_summon";
|
||||
summonMap[ESM::MagicEffect::SummonClannfear] = "clannfear_summon";
|
||||
summonMap[ESM::MagicEffect::SummonDaedroth] = "daedroth_summon";
|
||||
summonMap[ESM::MagicEffect::SummonDremora] = "dremora_summon";
|
||||
summonMap[ESM::MagicEffect::SummonFabricant] = "fabricant_summon";
|
||||
summonMap[ESM::MagicEffect::SummonFlameAtronach] = "atronach_flame_summon";
|
||||
summonMap[ESM::MagicEffect::SummonFrostAtronach] = "atronach_frost_summon";
|
||||
summonMap[ESM::MagicEffect::SummonGoldenSaint] = "golden saint_summon";
|
||||
summonMap[ESM::MagicEffect::SummonGreaterBonewalker] = "bonewalker_greater_summ";
|
||||
summonMap[ESM::MagicEffect::SummonHunger] = "hunger_summon";
|
||||
summonMap[ESM::MagicEffect::SummonScamp] = "scamp_summon";
|
||||
summonMap[ESM::MagicEffect::SummonSkeletalMinion] = "skeleton_summon";
|
||||
summonMap[ESM::MagicEffect::SummonStormAtronach] = "atronach_storm_summon";
|
||||
summonMap[ESM::MagicEffect::SummonWingedTwilight] = "winged twilight_summon";
|
||||
summonMap[ESM::MagicEffect::SummonWolf] = "BM_wolf_grey_summon";
|
||||
}
|
||||
|
||||
for (std::map<int, std::string>::iterator it = summonMap.begin(); it != summonMap.end(); ++it)
|
||||
{
|
||||
bool found = creatureStats.mSummonedCreatures.find(it->first) != creatureStats.mSummonedCreatures.end();
|
||||
int magnitude = creatureStats.getMagicEffects().get(EffectKey(it->first)).mMagnitude;
|
||||
if (found != (magnitude > 0))
|
||||
{
|
||||
if (magnitude > 0)
|
||||
{
|
||||
ESM::Position ipos = ptr.getRefData().getPosition();
|
||||
Ogre::Vector3 pos(ipos.pos[0],ipos.pos[1],ipos.pos[2]);
|
||||
Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z);
|
||||
const float distance = 50;
|
||||
pos = pos + distance*rot.yAxis();
|
||||
ipos.pos[0] = pos.x;
|
||||
ipos.pos[1] = pos.y;
|
||||
ipos.pos[2] = pos.z;
|
||||
ipos.rot[0] = 0;
|
||||
ipos.rot[1] = 0;
|
||||
ipos.rot[2] = 0;
|
||||
|
||||
MWWorld::CellStore* store = ptr.getCell();
|
||||
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->second, 1);
|
||||
ref.getPtr().getCellRef().mPos = ipos;
|
||||
|
||||
// TODO: Add AI to follow player and fight for him
|
||||
|
||||
creatureStats.mSummonedCreatures.insert(std::make_pair(it->first,
|
||||
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos).getRefData().getHandle()));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string handle = creatureStats.mSummonedCreatures[it->first];
|
||||
// TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation
|
||||
// plays though, which is a rather lame exploit in vanilla.
|
||||
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaHandle(handle);
|
||||
if (!ptr.isEmpty())
|
||||
{
|
||||
MWBase::Environment::get().getWorld()->deleteObject(ptr);
|
||||
creatureStats.mSummonedCreatures.erase(it->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr)
|
||||
|
@ -184,7 +398,8 @@ namespace MWMechanics
|
|||
{
|
||||
Stat<float>& skill = npcStats.getSkill(i);
|
||||
skill.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).mMagnitude -
|
||||
effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).mMagnitude);
|
||||
effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).mMagnitude -
|
||||
effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).mMagnitude);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,7 +481,7 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
Actors::Actors() : mDuration (0) {}
|
||||
Actors::Actors() {}
|
||||
|
||||
void Actors::addActor (const MWWorld::Ptr& ptr)
|
||||
{
|
||||
|
@ -302,12 +517,12 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
void Actors::dropActors (const MWWorld::CellStore *cellStore)
|
||||
void Actors::dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore)
|
||||
{
|
||||
PtrControllerMap::iterator iter = mActors.begin();
|
||||
while(iter != mActors.end())
|
||||
{
|
||||
if(iter->first.getCell()==cellStore)
|
||||
if(iter->first.getCell()==cellStore && iter->first != ignore)
|
||||
{
|
||||
delete iter->second;
|
||||
mActors.erase(iter++);
|
||||
|
@ -319,13 +534,8 @@ namespace MWMechanics
|
|||
|
||||
void Actors::update (float duration, bool paused)
|
||||
{
|
||||
mDuration += duration;
|
||||
|
||||
//if (mDuration>=0.25)
|
||||
if (!paused)
|
||||
{
|
||||
float totalDuration = mDuration;
|
||||
mDuration = 0;
|
||||
|
||||
for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++)
|
||||
{
|
||||
const MWWorld::Class &cls = MWWorld::Class::get(iter->first);
|
||||
|
@ -337,9 +547,9 @@ namespace MWMechanics
|
|||
if(iter->second->isDead())
|
||||
iter->second->resurrect();
|
||||
|
||||
updateActor(iter->first, totalDuration);
|
||||
updateActor(iter->first, duration);
|
||||
if(iter->first.getTypeName() == typeid(ESM::NPC).name())
|
||||
updateNpc(iter->first, totalDuration, paused);
|
||||
updateNpc(iter->first, duration, paused);
|
||||
|
||||
if(!stats.isDead())
|
||||
continue;
|
||||
|
|
|
@ -30,8 +30,6 @@ namespace MWMechanics
|
|||
|
||||
std::map<std::string, int> mDeathCount;
|
||||
|
||||
float mDuration;
|
||||
|
||||
void updateNpc(const MWWorld::Ptr &ptr, float duration, bool paused);
|
||||
|
||||
|
||||
|
@ -40,7 +38,7 @@ namespace MWMechanics
|
|||
|
||||
void calculateDynamicStats (const MWWorld::Ptr& ptr);
|
||||
|
||||
void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr);
|
||||
void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration);
|
||||
void calculateNpcStatModifiers (const MWWorld::Ptr& ptr);
|
||||
|
||||
void calculateRestoration (const MWWorld::Ptr& ptr, float duration);
|
||||
|
@ -53,6 +51,10 @@ namespace MWMechanics
|
|||
|
||||
Actors();
|
||||
|
||||
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
|
||||
/// paused we may want to do it manually (after equipping permanent enchantment)
|
||||
void updateMagicEffects (const MWWorld::Ptr& ptr) { adjustMagicEffects(ptr); }
|
||||
|
||||
void addActor (const MWWorld::Ptr& ptr);
|
||||
///< Register an actor for stats management
|
||||
///
|
||||
|
@ -66,8 +68,8 @@ namespace MWMechanics
|
|||
void updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr);
|
||||
///< Updates an actor with a new Ptr
|
||||
|
||||
void dropActors (const MWWorld::CellStore *cellStore);
|
||||
///< Deregister all actors in the given cell.
|
||||
void dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore);
|
||||
///< Deregister all actors (except for \a ignore) in the given cell.
|
||||
|
||||
void update (float duration, bool paused);
|
||||
///< Update actor stats and store desired velocity vectors in \a movement
|
||||
|
|
|
@ -9,7 +9,7 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const
|
|||
{
|
||||
return new AiActivate(*this);
|
||||
}
|
||||
bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor)
|
||||
bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration)
|
||||
{
|
||||
std::cout << "AiActivate completed.\n";
|
||||
return true;
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace MWMechanics
|
|||
public:
|
||||
AiActivate(const std::string &objectId);
|
||||
virtual AiActivate *clone() const;
|
||||
virtual bool execute (const MWWorld::Ptr& actor);
|
||||
virtual bool execute (const MWWorld::Ptr& actor,float duration);
|
||||
///< \return Package completed?
|
||||
virtual int getTypeId() const;
|
||||
|
||||
|
|
148
apps/openmw/mwmechanics/aicombat.cpp
Normal file
148
apps/openmw/mwmechanics/aicombat.cpp
Normal file
|
@ -0,0 +1,148 @@
|
|||
#include "aicombat.hpp"
|
||||
|
||||
#include "movement.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
#include "../mwworld/timestamp.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
#include "npcstats.hpp"
|
||||
|
||||
#include "OgreMath.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
static float sgn(float a)
|
||||
{
|
||||
if(a > 0)
|
||||
return 1.0;
|
||||
return -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
AiCombat::AiCombat(const std::string &targetId)
|
||||
:mTargetId(targetId),mTimer(0),mTimer2(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool AiCombat::execute (const MWWorld::Ptr& actor,float duration)
|
||||
{
|
||||
if(!MWWorld::Class::get(actor).getCreatureStats(actor).isHostile()) return true;
|
||||
|
||||
const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mTargetId, false);
|
||||
|
||||
if(MWWorld::Class::get(actor).getCreatureStats(actor).getHealth().getCurrent() <= 0) return true;
|
||||
|
||||
if(actor.getTypeName() == typeid(ESM::NPC).name())
|
||||
{
|
||||
MWWorld::Class::get(actor).
|
||||
MWWorld::Class::get(actor).setStance(actor, MWWorld::Class::Run,true);
|
||||
MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState();
|
||||
if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing)
|
||||
MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon);
|
||||
//MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true);
|
||||
}
|
||||
ESM::Position pos = actor.getRefData().getPosition();
|
||||
const ESM::Pathgrid *pathgrid =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->mCell);
|
||||
|
||||
float xCell = 0;
|
||||
float yCell = 0;
|
||||
|
||||
if (actor.getCell()->mCell->isExterior())
|
||||
{
|
||||
xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE;
|
||||
yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE;
|
||||
}
|
||||
|
||||
ESM::Pathgrid::Point dest;
|
||||
dest.mX = target.getRefData().getPosition().pos[0];
|
||||
dest.mY = target.getRefData().getPosition().pos[1];
|
||||
dest.mZ = target.getRefData().getPosition().pos[2];
|
||||
|
||||
ESM::Pathgrid::Point start;
|
||||
start.mX = pos.pos[0];
|
||||
start.mY = pos.pos[1];
|
||||
start.mZ = pos.pos[2];
|
||||
|
||||
mTimer2 = mTimer2 + duration;
|
||||
|
||||
if(!mPathFinder.isPathConstructed())
|
||||
mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true);
|
||||
else
|
||||
{
|
||||
mPathFinder2.buildPath(start, dest, pathgrid, xCell, yCell, true);
|
||||
ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back();
|
||||
if((mTimer2 > 0.25)&&(mPathFinder2.getPathSize() < mPathFinder.getPathSize() ||
|
||||
(dest.mX - lastPt.mX)*(dest.mX - lastPt.mX)+(dest.mY - lastPt.mY)*(dest.mY - lastPt.mY)+(dest.mZ - lastPt.mZ)*(dest.mZ - lastPt.mZ) > 200*200))
|
||||
{
|
||||
mTimer2 = 0;
|
||||
mPathFinder = mPathFinder2;
|
||||
}
|
||||
}
|
||||
|
||||
mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]);
|
||||
|
||||
float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
|
||||
MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false);
|
||||
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
|
||||
|
||||
|
||||
float range = 100;
|
||||
MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false);
|
||||
if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ)
|
||||
< range*range)
|
||||
{
|
||||
float directionX = dest.mX - start.mX;
|
||||
float directionY = dest.mY - start.mY;
|
||||
float directionResult = sqrt(directionX * directionX + directionY * directionY);
|
||||
|
||||
zAngle = Ogre::Radian( acos(directionY / directionResult) * sgn(asin(directionX / directionResult)) ).valueDegrees();
|
||||
MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false);
|
||||
|
||||
mPathFinder.clearPath();
|
||||
|
||||
if(mTimer == 0)
|
||||
{
|
||||
MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false);
|
||||
//mTimer = mTimer + duration;
|
||||
}
|
||||
if( mTimer > 1)
|
||||
{
|
||||
MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true);
|
||||
mTimer = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
mTimer = mTimer + duration;
|
||||
}
|
||||
|
||||
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
|
||||
//MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(!MWWorld::Class::get(actor).getCreatureStats(actor).getAttackingOrSpell());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int AiCombat::getTypeId() const
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
unsigned int AiCombat::getPriority() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
AiCombat *MWMechanics::AiCombat::clone() const
|
||||
{
|
||||
return new AiCombat(*this);
|
||||
}
|
||||
}
|
||||
|
36
apps/openmw/mwmechanics/aicombat.hpp
Normal file
36
apps/openmw/mwmechanics/aicombat.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef GAME_MWMECHANICS_AICOMBAT_H
|
||||
#define GAME_MWMECHANICS_AICOMBAT_H
|
||||
|
||||
#include "aipackage.hpp"
|
||||
|
||||
#include "pathfinding.hpp"
|
||||
|
||||
#include "movement.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
class AiCombat : public AiPackage
|
||||
{
|
||||
public:
|
||||
AiCombat(const std::string &targetId);
|
||||
|
||||
virtual AiCombat *clone() const;
|
||||
|
||||
virtual bool execute (const MWWorld::Ptr& actor,float duration);
|
||||
///< \return Package completed?
|
||||
|
||||
virtual int getTypeId() const;
|
||||
|
||||
virtual unsigned int getPriority() const;
|
||||
|
||||
private:
|
||||
std::string mTargetId;
|
||||
|
||||
PathFinder mPathFinder;
|
||||
PathFinder mPathFinder2;
|
||||
float mTimer;
|
||||
float mTimer2;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -71,7 +71,7 @@ namespace MWMechanics
|
|||
return new AiEscort(*this);
|
||||
}
|
||||
|
||||
bool AiEscort::execute (const MWWorld::Ptr& actor)
|
||||
bool AiEscort::execute (const MWWorld::Ptr& actor,float duration)
|
||||
{
|
||||
// If AiEscort has ran for as long or longer then the duration specified
|
||||
// and the duration is not infinite, the package is complete.
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace MWMechanics
|
|||
|
||||
virtual AiEscort *clone() const;
|
||||
|
||||
virtual bool execute (const MWWorld::Ptr& actor);
|
||||
virtual bool execute (const MWWorld::Ptr& actor,float duration);
|
||||
///< \return Package completed?
|
||||
|
||||
virtual int getTypeId() const;
|
||||
|
|
|
@ -15,7 +15,7 @@ MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const
|
|||
return new AiFollow(*this);
|
||||
}
|
||||
|
||||
bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor)
|
||||
bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
|
||||
{
|
||||
std::cout << "AiFollow completed.\n";
|
||||
return true;
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace MWMechanics
|
|||
AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z);
|
||||
AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z);
|
||||
virtual AiFollow *clone() const;
|
||||
virtual bool execute (const MWWorld::Ptr& actor);
|
||||
virtual bool execute (const MWWorld::Ptr& actor,float duration);
|
||||
///< \return Package completed?
|
||||
virtual int getTypeId() const;
|
||||
|
||||
|
|
|
@ -17,11 +17,14 @@ namespace MWMechanics
|
|||
|
||||
virtual AiPackage *clone() const = 0;
|
||||
|
||||
virtual bool execute (const MWWorld::Ptr& actor) = 0;
|
||||
virtual bool execute (const MWWorld::Ptr& actor,float duration) = 0;
|
||||
///< \return Package completed?
|
||||
|
||||
virtual int getTypeId() const = 0;
|
||||
///< 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate
|
||||
|
||||
virtual unsigned int getPriority() const {return 0;}
|
||||
///< higher number is higher priority (0 beeing the lowest)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,14 @@
|
|||
#include "aitravel.hpp"
|
||||
#include "aifollow.hpp"
|
||||
#include "aiactivate.hpp"
|
||||
#include "aicombat.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "creaturestats.hpp"
|
||||
#include "npcstats.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
|
||||
void MWMechanics::AiSequence::copy (const AiSequence& sequence)
|
||||
{
|
||||
|
@ -29,6 +37,7 @@ MWMechanics::AiSequence& MWMechanics::AiSequence::operator= (const AiSequence& s
|
|||
{
|
||||
clear();
|
||||
copy (sequence);
|
||||
mDone = sequence.mDone;
|
||||
}
|
||||
|
||||
return *this;
|
||||
|
@ -52,17 +61,20 @@ bool MWMechanics::AiSequence::isPackageDone() const
|
|||
return mDone;
|
||||
}
|
||||
|
||||
void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor)
|
||||
void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration)
|
||||
{
|
||||
if (!mPackages.empty())
|
||||
if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer())
|
||||
{
|
||||
if (mPackages.front()->execute (actor))
|
||||
if (!mPackages.empty())
|
||||
{
|
||||
mPackages.erase (mPackages.begin());
|
||||
mDone = true;
|
||||
if (mPackages.front()->execute (actor,duration))
|
||||
{
|
||||
mPackages.erase (mPackages.begin());
|
||||
mDone = true;
|
||||
}
|
||||
else
|
||||
mDone = false;
|
||||
}
|
||||
else
|
||||
mDone = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +88,14 @@ void MWMechanics::AiSequence::clear()
|
|||
|
||||
void MWMechanics::AiSequence::stack (const AiPackage& package)
|
||||
{
|
||||
mPackages.push_front (package.clone());
|
||||
for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end(); it++)
|
||||
{
|
||||
if(mPackages.front()->getPriority() <= package.getPriority())
|
||||
mPackages.insert(it,package.clone());
|
||||
}
|
||||
|
||||
if(mPackages.empty())
|
||||
mPackages.push_front (package.clone());
|
||||
}
|
||||
|
||||
void MWMechanics::AiSequence::queue (const AiPackage& package)
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace MWMechanics
|
|||
class AiSequence
|
||||
{
|
||||
std::list<AiPackage *> mPackages;
|
||||
|
||||
bool mDone;
|
||||
|
||||
void copy (const AiSequence& sequence);
|
||||
|
@ -33,17 +34,17 @@ namespace MWMechanics
|
|||
virtual ~AiSequence();
|
||||
|
||||
int getTypeId() const;
|
||||
///< -1: None, 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate
|
||||
///< -1: None, 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate, 5 Combat
|
||||
|
||||
bool isPackageDone() const;
|
||||
///< Has a package been completed during the last update?
|
||||
|
||||
void execute (const MWWorld::Ptr& actor);
|
||||
void execute (const MWWorld::Ptr& actor,float duration);
|
||||
///< Execute package.
|
||||
|
||||
void clear();
|
||||
///< Remove all packages.
|
||||
|
||||
|
||||
void stack (const AiPackage& package);
|
||||
///< Add \a package to the front of the sequence (suspends current package)
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace MWMechanics
|
|||
return new AiTravel(*this);
|
||||
}
|
||||
|
||||
bool AiTravel::execute (const MWWorld::Ptr& actor)
|
||||
bool AiTravel::execute (const MWWorld::Ptr& actor,float duration)
|
||||
{
|
||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
ESM::Position pos = actor.getRefData().getPosition();
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace MWMechanics
|
|||
AiTravel(float x, float y, float z);
|
||||
virtual AiTravel *clone() const;
|
||||
|
||||
virtual bool execute (const MWWorld::Ptr& actor);
|
||||
virtual bool execute (const MWWorld::Ptr& actor,float duration);
|
||||
///< \return Package completed?
|
||||
|
||||
virtual int getTypeId() const;
|
||||
|
|
|
@ -63,7 +63,7 @@ namespace MWMechanics
|
|||
return new AiWander(*this);
|
||||
}
|
||||
|
||||
bool AiWander::execute (const MWWorld::Ptr& actor)
|
||||
bool AiWander::execute (const MWWorld::Ptr& actor,float duration)
|
||||
{
|
||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
if(mDuration)
|
||||
|
@ -144,12 +144,12 @@ namespace MWMechanics
|
|||
mCurrentNode = mAllowedNodes[index];
|
||||
mAllowedNodes.erase(mAllowedNodes.begin() + index);
|
||||
}
|
||||
|
||||
if(mAllowedNodes.empty())
|
||||
mDistance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(mAllowedNodes.empty())
|
||||
mDistance = 0;
|
||||
|
||||
// Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles.
|
||||
if(mDistance && (mCellX != actor.getCell()->mCell->mData.mX || mCellY != actor.getCell()->mCell->mData.mY))
|
||||
mDistance = 0;
|
||||
|
@ -200,6 +200,7 @@ namespace MWMechanics
|
|||
{
|
||||
if(!mPathFinder.isPathConstructed())
|
||||
{
|
||||
assert(mAllowedNodes.size());
|
||||
unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size());
|
||||
Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, mAllowedNodes[randNode].mY, mAllowedNodes[randNode].mZ);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace MWMechanics
|
|||
|
||||
AiWander(int distance, int duration, int timeOfDay, const std::vector<int>& idle, bool repeat);
|
||||
virtual AiPackage *clone() const;
|
||||
virtual bool execute (const MWWorld::Ptr& actor);
|
||||
virtual bool execute (const MWWorld::Ptr& actor,float duration);
|
||||
///< \return Package completed?
|
||||
virtual int getTypeId() const;
|
||||
///< 0: Wander
|
||||
|
|
|
@ -287,8 +287,7 @@ void MWMechanics::Alchemy::addPotion (const std::string& name)
|
|||
record = MWBase::Environment::get().getWorld()->createRecord (newRecord);
|
||||
}
|
||||
|
||||
MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), record->mId);
|
||||
MWWorld::Class::get (mAlchemist).getContainerStore (mAlchemist).add (ref.getPtr(), mAlchemist);
|
||||
mAlchemist.getClass().getContainerStore (mAlchemist).add (record->mId, 1, mAlchemist);
|
||||
}
|
||||
|
||||
void MWMechanics::Alchemy::increaseSkill()
|
||||
|
|
|
@ -350,6 +350,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
|
|||
, mSkipAnim(false)
|
||||
, mSecondsOfRunning(0)
|
||||
, mSecondsOfSwimming(0)
|
||||
, mFallHeight(0)
|
||||
{
|
||||
if(!mAnimation)
|
||||
return;
|
||||
|
@ -425,6 +426,11 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
|
|||
{
|
||||
forcestateupdate = true;
|
||||
|
||||
// Shields shouldn't be visible during spellcasting
|
||||
// There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop",
|
||||
// but they are also present in weapon drawing animation.
|
||||
mAnimation->showShield(weaptype != WeapType_Spell);
|
||||
|
||||
std::string weapgroup;
|
||||
if(weaptype == WeapType_None)
|
||||
{
|
||||
|
@ -442,6 +448,7 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
|
|||
MWRender::Animation::Group_UpperBody, true,
|
||||
1.0f, "equip start", "equip stop", 0.0f, 0);
|
||||
mUpperBodyState = UpperCharState_EquipingWeap;
|
||||
|
||||
if(isWerewolf)
|
||||
{
|
||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||
|
@ -515,6 +522,12 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
|
|||
const ESM::Static* castStatic = store.get<ESM::Static>().find (effect->mCasting);
|
||||
mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex);
|
||||
|
||||
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Hands");
|
||||
//mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle);
|
||||
//mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle);
|
||||
mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Left Hand", effect->mParticle);
|
||||
mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Right Hand", effect->mParticle);
|
||||
|
||||
switch(effectentry.mRange)
|
||||
{
|
||||
case 0: mAttackType = "self"; break;
|
||||
|
@ -563,10 +576,8 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
|
|||
if(!resultSound.empty())
|
||||
MWBase::Environment::get().getSoundManager()->playSound(resultSound, 1.0f, 1.0f);
|
||||
|
||||
// tool used up?
|
||||
if(!item.getRefData().getCount())
|
||||
MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon();
|
||||
else
|
||||
// Set again, just to update the charge bar
|
||||
if(item.getRefData().getCount())
|
||||
MWBase::Environment::get().getWindowManager()->setSelectedWeapon(item);
|
||||
}
|
||||
else
|
||||
|
@ -782,7 +793,7 @@ void CharacterController::update(float duration)
|
|||
|
||||
if(!onground && !flying && !inwater)
|
||||
{
|
||||
// The player is in the air (either getting up —ascending part of jump— or falling).
|
||||
// In the air (either getting up —ascending part of jump— or falling).
|
||||
|
||||
if (world->isSlowFalling(mPtr))
|
||||
{
|
||||
|
@ -817,8 +828,7 @@ void CharacterController::update(float duration)
|
|||
}
|
||||
else if(vec.z > 0.0f && mJumpState == JumpState_None)
|
||||
{
|
||||
// The player has started a jump.
|
||||
|
||||
// Started a jump.
|
||||
float z = cls.getJump(mPtr);
|
||||
if(vec.x == 0 && vec.y == 0)
|
||||
vec = Ogre::Vector3(0.0f, 0.0f, z);
|
||||
|
@ -829,7 +839,8 @@ void CharacterController::update(float duration)
|
|||
}
|
||||
|
||||
// advance acrobatics
|
||||
cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0);
|
||||
if (mPtr.getRefData().getHandle() == "player")
|
||||
cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0);
|
||||
|
||||
// decrease fatigue
|
||||
const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
|
||||
|
@ -843,8 +854,6 @@ void CharacterController::update(float duration)
|
|||
}
|
||||
else if(mJumpState == JumpState_Falling)
|
||||
{
|
||||
// The player is landing.
|
||||
|
||||
forcestateupdate = true;
|
||||
mJumpState = JumpState_Landing;
|
||||
vec.z = 0.0f;
|
||||
|
@ -861,7 +870,8 @@ void CharacterController::update(float duration)
|
|||
cls.getCreatureStats(mPtr).setHealth(health);
|
||||
|
||||
// report acrobatics progression
|
||||
cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1);
|
||||
if (mPtr.getRefData().getHandle() == "player")
|
||||
cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1);
|
||||
|
||||
const float acrobaticsSkill = cls.getNpcStats(mPtr).getSkill(ESM::Skill::Acrobatics).getModified();
|
||||
if (healthLost > (acrobaticsSkill * fatigueTerm))
|
||||
|
@ -1099,7 +1109,7 @@ void CharacterController::resurrect()
|
|||
|
||||
if(mAnimation)
|
||||
mAnimation->disable(mCurrentDeath);
|
||||
mCurrentDeath.empty();
|
||||
mCurrentDeath.clear();
|
||||
mDeathState = CharState_None;
|
||||
}
|
||||
|
||||
|
|
|
@ -171,6 +171,11 @@ namespace MWMechanics
|
|||
|
||||
void setLastHitObject(const std::string &objectid);
|
||||
const std::string &getLastHitObject() const;
|
||||
|
||||
// Note, this is just a cache to avoid checking the whole container store every frame TODO: Put it somewhere else?
|
||||
std::set<int> mBoundItems;
|
||||
// Same as above
|
||||
std::map<int, std::string> mSummonedCreatures;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -62,17 +62,14 @@ namespace MWMechanics
|
|||
|
||||
//Exception for Azura Star, new one will be added after enchanting
|
||||
if(boost::iequals(mSoulGemPtr.get<ESM::Miscellaneous>()->mBase->mId, "Misc_SoulGem_Azura"))
|
||||
{
|
||||
MWWorld::ManualRef azura (MWBase::Environment::get().getWorld()->getStore(), "Misc_SoulGem_Azura");
|
||||
store.add(azura.getPtr(), player);
|
||||
}
|
||||
store.add("Misc_SoulGem_Azura", 1, player);
|
||||
|
||||
if(mSelfEnchanting)
|
||||
{
|
||||
if(getEnchantChance()<std::rand()/static_cast<double> (RAND_MAX)*100)
|
||||
return false;
|
||||
|
||||
MWWorld::Class::get (mEnchanter).skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 1);
|
||||
MWWorld::Class::get (mEnchanter).skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2);
|
||||
}
|
||||
|
||||
if(mCastStyle==ESM::Enchantment::ConstantEffect)
|
||||
|
@ -92,8 +89,8 @@ namespace MWMechanics
|
|||
MWWorld::Class::get(newItemPtr).applyEnchantment(newItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName);
|
||||
|
||||
// Add the new item to player inventory and remove the old one
|
||||
store.add(newItemPtr, player);
|
||||
store.remove(mOldItemPtr, 1, player);
|
||||
store.add(newItemPtr, player);
|
||||
|
||||
if(!mSelfEnchanting)
|
||||
payForEnchantment();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define GAME_MWMECHANICS_MAGICEFFECTS_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
@ -16,8 +17,6 @@ namespace MWMechanics
|
|||
int mId;
|
||||
int mArg; // skill or ability
|
||||
|
||||
// TODO: Add caster here for Absorb effects?
|
||||
|
||||
EffectKey();
|
||||
|
||||
EffectKey (int id, int arg = -1) : mId (id), mArg (arg) {}
|
||||
|
@ -29,11 +28,12 @@ namespace MWMechanics
|
|||
|
||||
struct EffectParam
|
||||
{
|
||||
int mMagnitude;
|
||||
// Note usually this would be int, but applying partial resistance might introduce decimal point.
|
||||
float mMagnitude;
|
||||
|
||||
EffectParam();
|
||||
|
||||
EffectParam(int magnitude) : mMagnitude(magnitude) {}
|
||||
EffectParam(float magnitude) : mMagnitude(magnitude) {}
|
||||
|
||||
EffectParam& operator+= (const EffectParam& param);
|
||||
|
||||
|
@ -52,6 +52,13 @@ namespace MWMechanics
|
|||
return param -= right;
|
||||
}
|
||||
|
||||
// Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display
|
||||
struct EffectSourceVisitor
|
||||
{
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, float magnitude, float remainingTime = -1) = 0;
|
||||
};
|
||||
|
||||
/// \brief Effects currently affecting a NPC or creature
|
||||
class MagicEffects
|
||||
{
|
||||
|
|
|
@ -159,13 +159,13 @@ namespace MWMechanics
|
|||
// auto-equip again. we need this for when the race is changed to a beast race
|
||||
MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore(ptr);
|
||||
for (int i=0; i<MWWorld::InventoryStore::Slots; ++i)
|
||||
invStore.equip(i, invStore.end(), ptr);
|
||||
invStore.unequipAll(ptr);
|
||||
invStore.autoEquip(ptr);
|
||||
}
|
||||
|
||||
MechanicsManager::MechanicsManager()
|
||||
: mUpdatePlayer (true), mClassSelected (false),
|
||||
mRaceSelected (false)
|
||||
mRaceSelected (false), mAI(true)
|
||||
{
|
||||
//buildPlayer no longer here, needs to be done explicitely after all subsystems are up and running
|
||||
}
|
||||
|
@ -200,10 +200,7 @@ namespace MWMechanics
|
|||
|
||||
void MechanicsManager::drop(const MWWorld::CellStore *cellStore)
|
||||
{
|
||||
if(!mWatched.isEmpty() && mWatched.getCell() == cellStore)
|
||||
mWatched = MWWorld::Ptr();
|
||||
|
||||
mActors.dropActors(cellStore);
|
||||
mActors.dropActors(cellStore, mWatched);
|
||||
mObjects.dropObjects(cellStore);
|
||||
}
|
||||
|
||||
|
@ -213,6 +210,14 @@ namespace MWMechanics
|
|||
mWatched = ptr;
|
||||
}
|
||||
|
||||
void MechanicsManager::advanceTime (float duration)
|
||||
{
|
||||
// Uses ingame time, but scaled to real time
|
||||
duration /= MWBase::Environment::get().getWorld()->getTimeScaleFactor();
|
||||
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
||||
player.getClass().getInventoryStore(player).rechargeItems(duration);
|
||||
}
|
||||
|
||||
void MechanicsManager::update(float duration, bool paused)
|
||||
{
|
||||
if(!mWatched.isEmpty())
|
||||
|
@ -411,7 +416,7 @@ namespace MWMechanics
|
|||
MWWorld::LiveCellRef<ESM::NPC>* player = playerPtr.get<ESM::NPC>();
|
||||
const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr);
|
||||
|
||||
if (Misc::StringUtils::lowerCase(npc->mBase->mRace) == Misc::StringUtils::lowerCase(player->mBase->mRace))
|
||||
if (Misc::StringUtils::ciEqual(npc->mBase->mRace, player->mBase->mRace))
|
||||
x += MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDispRaceMod")->getFloat();
|
||||
|
||||
x += MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fDispPersonalityMult")->getFloat()
|
||||
|
@ -427,7 +432,9 @@ namespace MWMechanics
|
|||
for(std::vector<ESM::Faction::Reaction>::const_iterator it = MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().find(Misc::StringUtils::lowerCase(npcFaction))->mReactions.begin();
|
||||
it != MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>().find(Misc::StringUtils::lowerCase(npcFaction))->mReactions.end(); ++it)
|
||||
{
|
||||
if(Misc::StringUtils::lowerCase(it->mFaction) == Misc::StringUtils::lowerCase(npcFaction)) reaction = it->mReaction;
|
||||
if(Misc::StringUtils::lowerCase(it->mFaction) == Misc::StringUtils::lowerCase(npcFaction)
|
||||
&& playerStats.getExpelled().find(Misc::StringUtils::lowerCase(it->mFaction)) == playerStats.getExpelled().end())
|
||||
reaction = it->mReaction;
|
||||
}
|
||||
rank = playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(npcFaction))->second;
|
||||
}
|
||||
|
@ -438,7 +445,8 @@ namespace MWMechanics
|
|||
{
|
||||
if(playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(it->mFaction)) != playerStats.getFactionRanks().end() )
|
||||
{
|
||||
if(it->mReaction<reaction) reaction = it->mReaction;
|
||||
if(it->mReaction < reaction)
|
||||
reaction = it->mReaction;
|
||||
}
|
||||
}
|
||||
rank = 0;
|
||||
|
@ -679,4 +687,18 @@ namespace MWMechanics
|
|||
return false;
|
||||
}
|
||||
|
||||
void MechanicsManager::updateMagicEffects(const MWWorld::Ptr &ptr)
|
||||
{
|
||||
mActors.updateMagicEffects(ptr);
|
||||
}
|
||||
|
||||
void MechanicsManager::toggleAI()
|
||||
{
|
||||
mAI = !mAI;
|
||||
}
|
||||
|
||||
bool MechanicsManager::isAIActive()
|
||||
{
|
||||
return mAI;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ namespace MWMechanics
|
|||
bool mUpdatePlayer;
|
||||
bool mClassSelected;
|
||||
bool mRaceSelected;
|
||||
bool mAI;///< is AI active?
|
||||
|
||||
Objects mObjects;
|
||||
Actors mActors;
|
||||
|
@ -63,6 +64,8 @@ namespace MWMechanics
|
|||
/// \param paused In game type does not currently advance (this usually means some GUI
|
||||
/// component is up).
|
||||
|
||||
virtual void advanceTime (float duration);
|
||||
|
||||
virtual void setPlayerName (const std::string& name);
|
||||
///< Set player name.
|
||||
|
||||
|
@ -89,7 +92,7 @@ namespace MWMechanics
|
|||
|
||||
virtual int countDeaths (const std::string& id) const;
|
||||
///< Return the number of deaths for actors with the given ID.
|
||||
|
||||
|
||||
virtual void getPersuasionDispositionChange (const MWWorld::Ptr& npc, PersuasionType type,
|
||||
float currentTemporaryDispositionDelta, bool& success, float& tempChange, float& permChange);
|
||||
void toLower(std::string npcFaction);
|
||||
|
@ -100,6 +103,13 @@ namespace MWMechanics
|
|||
virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number);
|
||||
virtual void skipAnimation(const MWWorld::Ptr& ptr);
|
||||
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName);
|
||||
|
||||
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
|
||||
/// paused we may want to do it manually (after equipping permanent enchantment)
|
||||
virtual void updateMagicEffects (const MWWorld::Ptr& ptr);
|
||||
|
||||
virtual void toggleAI();
|
||||
virtual bool isAIActive();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -189,7 +189,7 @@ namespace MWMechanics
|
|||
// This should never happen (programmers should have an if statement checking mIsPathConstructed that prevents this call
|
||||
// if otherwise).
|
||||
if(mPath.empty())
|
||||
return 0;
|
||||
return 0.;
|
||||
|
||||
const ESM::Pathgrid::Point &nextPoint = *mPath.begin();
|
||||
float directionX = nextPoint.mX - x;
|
||||
|
@ -199,6 +199,21 @@ namespace MWMechanics
|
|||
return Ogre::Radian(acos(directionY / directionResult) * sgn(asin(directionX / directionResult))).valueDegrees();
|
||||
}
|
||||
|
||||
bool PathFinder::checkWaypoint(float x, float y, float z)
|
||||
{
|
||||
if(mPath.empty())
|
||||
return true;
|
||||
|
||||
ESM::Pathgrid::Point nextPoint = *mPath.begin();
|
||||
if(distanceZCorrected(nextPoint, x, y, z) < 64)
|
||||
{
|
||||
mPath.pop_front();
|
||||
if(mPath.empty()) mIsPathConstructed = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PathFinder::checkPathCompleted(float x, float y, float z)
|
||||
{
|
||||
if(mPath.empty())
|
||||
|
|
|
@ -18,6 +18,8 @@ namespace MWMechanics
|
|||
|
||||
bool checkPathCompleted(float x, float y, float z);
|
||||
///< \Returns true if the last point of the path has been reached.
|
||||
bool checkWaypoint(float x, float y, float z);
|
||||
///< \Returns true if a way point was reached
|
||||
float getZAngleToNext(float x, float y) const;
|
||||
|
||||
bool isPathConstructed() const
|
||||
|
@ -25,6 +27,16 @@ namespace MWMechanics
|
|||
return mIsPathConstructed;
|
||||
}
|
||||
|
||||
int getPathSize() const
|
||||
{
|
||||
return mPath.size();
|
||||
}
|
||||
|
||||
std::list<ESM::Pathgrid::Point> getPath() const
|
||||
{
|
||||
return mPath;
|
||||
}
|
||||
|
||||
private:
|
||||
std::list<ESM::Pathgrid::Point> mPath;
|
||||
bool mIsPathConstructed;
|
||||
|
|
505
apps/openmw/mwmechanics/spellcasting.cpp
Normal file
505
apps/openmw/mwmechanics/spellcasting.cpp
Normal file
|
@ -0,0 +1,505 @@
|
|||
#include "spellcasting.hpp"
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
|
||||
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target)
|
||||
: mCaster(caster)
|
||||
, mTarget(target)
|
||||
, mStack(false)
|
||||
{
|
||||
}
|
||||
|
||||
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
|
||||
const ESM::EffectList &effects, ESM::RangeType range, bool reflected)
|
||||
{
|
||||
// If none of the effects need to apply, we can early-out
|
||||
bool found = false;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
|
||||
iter!=effects.mList.end(); ++iter)
|
||||
{
|
||||
if (iter->mRange != range)
|
||||
continue;
|
||||
found = true;
|
||||
}
|
||||
if (!found)
|
||||
return;
|
||||
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (mId);
|
||||
if (spell && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight))
|
||||
{
|
||||
float x = (spell->mData.mType == ESM::Spell::ST_Disease) ?
|
||||
target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).mMagnitude
|
||||
: target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).mMagnitude;
|
||||
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
if (roll <= x)
|
||||
{
|
||||
// Fully resisted, show message
|
||||
if (target.getRefData().getHandle() == "player")
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ESM::EffectList reflectedEffects;
|
||||
std::vector<ActiveSpells::Effect> appliedLastingEffects;
|
||||
bool firstAppliedEffect = true;
|
||||
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
|
||||
effectIt!=effects.mList.end(); ++effectIt)
|
||||
{
|
||||
if (effectIt->mRange != range)
|
||||
continue;
|
||||
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
effectIt->mEffectID);
|
||||
|
||||
float magnitudeMult = 1;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && target.getClass().isActor())
|
||||
{
|
||||
// If player is attempting to cast a harmful spell, show the target's HP bar
|
||||
if (caster.getRefData().getHandle() == "player" && target != caster)
|
||||
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
||||
|
||||
// Try absorbing if it's a spell
|
||||
// NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure
|
||||
// if that is worth replicating.
|
||||
if (spell && caster != target)
|
||||
{
|
||||
int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude;
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
bool isAbsorbed = (roll < absorb);
|
||||
if (isAbsorbed)
|
||||
{
|
||||
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Absorb");
|
||||
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
|
||||
"meshes\\" + absorbStatic->mModel, ESM::MagicEffect::Reflect, false, "");
|
||||
// Magicka is increased by cost of spell
|
||||
DynamicStat<float> magicka = target.getClass().getCreatureStats(target).getMagicka();
|
||||
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
|
||||
target.getClass().getCreatureStats(target).setMagicka(magicka);
|
||||
magnitudeMult = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Try reflecting
|
||||
if (!reflected && magnitudeMult > 0 && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable))
|
||||
{
|
||||
int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude;
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
bool isReflected = (roll < reflect);
|
||||
if (isReflected)
|
||||
{
|
||||
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect");
|
||||
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
|
||||
"meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, "");
|
||||
reflectedEffects.mList.push_back(*effectIt);
|
||||
magnitudeMult = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Try resisting
|
||||
if (magnitudeMult > 0 && target.getClass().isActor())
|
||||
{
|
||||
|
||||
magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell);
|
||||
if (magnitudeMult == 0)
|
||||
{
|
||||
// Fully resisted, show message
|
||||
if (target.getRefData().getHandle() == "player")
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
||||
else
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (magnitudeMult > 0)
|
||||
{
|
||||
float random = std::rand() / static_cast<float>(RAND_MAX);
|
||||
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random;
|
||||
magnitude *= magnitudeMult;
|
||||
|
||||
if (target.getClass().isActor() && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
|
||||
{
|
||||
ActiveSpells::Effect effect;
|
||||
effect.mKey = MWMechanics::EffectKey(*effectIt);
|
||||
effect.mDuration = effectIt->mDuration;
|
||||
effect.mMagnitude = magnitude;
|
||||
|
||||
appliedLastingEffects.push_back(effect);
|
||||
|
||||
// For absorb effects, also apply the effect to the caster - but with a negative
|
||||
// magnitude, since we're transfering stats from the target to the caster
|
||||
for (int i=0; i<5; ++i)
|
||||
{
|
||||
if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i)
|
||||
{
|
||||
std::vector<ActiveSpells::Effect> effects;
|
||||
ActiveSpells::Effect effect_ = effect;
|
||||
effect_.mMagnitude *= -1;
|
||||
effects.push_back(effect_);
|
||||
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, effects, mSourceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
applyInstantEffect(target, effectIt->mEffectID, magnitude);
|
||||
|
||||
if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||
{
|
||||
// Play sound, only for the first effect
|
||||
if (firstAppliedEffect)
|
||||
{
|
||||
static const std::string schools[] = {
|
||||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
||||
};
|
||||
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
if(!magicEffect->mHitSound.empty())
|
||||
sndMgr->playSound3D(target, magicEffect->mHitSound, 1.0f, 1.0f);
|
||||
else
|
||||
sndMgr->playSound3D(target, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f);
|
||||
firstAppliedEffect = false;
|
||||
}
|
||||
|
||||
// Add VFX
|
||||
if (!magicEffect->mHit.empty())
|
||||
{
|
||||
const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
|
||||
bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
|
||||
// Note: in case of non actor, a free effect should be fine as well
|
||||
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target);
|
||||
if (anim)
|
||||
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World.
|
||||
}
|
||||
}
|
||||
|
||||
if (reflectedEffects.mList.size())
|
||||
inflict(caster, target, reflectedEffects, range, true);
|
||||
|
||||
if (appliedLastingEffects.size())
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, mSourceName);
|
||||
}
|
||||
|
||||
void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, short effectId, float magnitude)
|
||||
{
|
||||
if (!target.getClass().isActor())
|
||||
{
|
||||
if (effectId == ESM::MagicEffect::Lock)
|
||||
{
|
||||
if (target.getCellRef().mLockLevel < magnitude)
|
||||
target.getCellRef().mLockLevel = magnitude;
|
||||
}
|
||||
else if (effectId == ESM::MagicEffect::Open)
|
||||
{
|
||||
// TODO: This is a crime
|
||||
if (target.getCellRef().mLockLevel <= magnitude)
|
||||
{
|
||||
if (target.getCellRef().mLockLevel > 0)
|
||||
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f);
|
||||
target.getCellRef().mLockLevel = 0;
|
||||
}
|
||||
else
|
||||
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (effectId == ESM::MagicEffect::CurePoison)
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison);
|
||||
else if (effectId == ESM::MagicEffect::CureParalyzation)
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
|
||||
else if (effectId == ESM::MagicEffect::CureCommonDisease)
|
||||
target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease();
|
||||
else if (effectId == ESM::MagicEffect::CureBlightDisease)
|
||||
target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease();
|
||||
else if (effectId == ESM::MagicEffect::CureCorprusDisease)
|
||||
target.getClass().getCreatureStats(target).getSpells().purgeCorprusDisease();
|
||||
else if (effectId == ESM::MagicEffect::Dispel)
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().purgeAll();
|
||||
else if (effectId == ESM::MagicEffect::RemoveCurse)
|
||||
target.getClass().getCreatureStats(target).getSpells().purgeCurses();
|
||||
|
||||
else if (effectId == ESM::MagicEffect::DivineIntervention)
|
||||
{
|
||||
// We need to be able to get the world location of an interior cell before implementing this
|
||||
// or alternatively, the last known exterior location of the player, which is how vanilla does it.
|
||||
}
|
||||
else if (effectId == ESM::MagicEffect::AlmsiviIntervention)
|
||||
{
|
||||
// Same as above
|
||||
}
|
||||
|
||||
else if (effectId == ESM::MagicEffect::Mark)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
else if (effectId == ESM::MagicEffect::Recall)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CastSpell::cast(const std::string &id)
|
||||
{
|
||||
if (const ESM::Spell *spell =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
|
||||
return cast(spell);
|
||||
|
||||
if (const ESM::Potion *potion =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
|
||||
return cast(potion);
|
||||
|
||||
if (const ESM::Ingredient *ingredient =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
|
||||
return cast(ingredient);
|
||||
|
||||
throw std::runtime_error("ID type cannot be casted");
|
||||
}
|
||||
|
||||
bool CastSpell::cast(const MWWorld::Ptr &item)
|
||||
{
|
||||
std::string enchantmentName = item.getClass().getEnchantment(item);
|
||||
if (enchantmentName.empty())
|
||||
throw std::runtime_error("can't cast an item without an enchantment");
|
||||
|
||||
mSourceName = item.getClass().getName(item);
|
||||
mId = item.getCellRef().mRefID;
|
||||
|
||||
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantmentName);
|
||||
|
||||
mStack = (enchantment->mData.mType == ESM::Enchantment::CastOnce);
|
||||
|
||||
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed)
|
||||
{
|
||||
// Check if there's enough charge left
|
||||
const float enchantCost = enchantment->mData.mCost;
|
||||
MWMechanics::NpcStats &stats = MWWorld::Class::get(mCaster).getNpcStats(mCaster);
|
||||
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
|
||||
const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
|
||||
|
||||
if (item.getCellRef().mEnchantmentCharge == -1)
|
||||
item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge;
|
||||
|
||||
if (mCaster.getRefData().getHandle() == "player" && item.getCellRef().mEnchantmentCharge < castCost)
|
||||
{
|
||||
// TODO: Should there be a sound here?
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reduce charge
|
||||
item.getCellRef().mEnchantmentCharge -= castCost;
|
||||
}
|
||||
if (enchantment->mData.mType == ESM::Enchantment::CastOnce)
|
||||
item.getContainerStore()->remove(item, 1, mCaster);
|
||||
else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes)
|
||||
{
|
||||
if (mCaster.getRefData().getHandle() == "player")
|
||||
MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge
|
||||
}
|
||||
|
||||
if (mCaster.getRefData().getHandle() == "player")
|
||||
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1);
|
||||
|
||||
inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
|
||||
|
||||
if (!mTarget.isEmpty())
|
||||
{
|
||||
if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead())
|
||||
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch);
|
||||
}
|
||||
|
||||
MWBase::Environment::get().getWorld()->launchProjectile(mId, false, enchantment->mEffects, mCaster, mSourceName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CastSpell::cast(const ESM::Potion* potion)
|
||||
{
|
||||
mSourceName = potion->mName;
|
||||
mId = potion->mId;
|
||||
mStack = true;
|
||||
|
||||
inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CastSpell::cast(const ESM::Spell* spell)
|
||||
{
|
||||
mSourceName = spell->mName;
|
||||
mId = spell->mId;
|
||||
mStack = false;
|
||||
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
|
||||
int school = 0;
|
||||
|
||||
if (mCaster.getClass().isActor())
|
||||
{
|
||||
school = getSpellSchool(spell, mCaster);
|
||||
|
||||
CreatureStats& stats = mCaster.getClass().getCreatureStats(mCaster);
|
||||
|
||||
// Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss)
|
||||
static const float fFatigueSpellBase = store.get<ESM::GameSetting>().find("fFatigueSpellBase")->getFloat();
|
||||
static const float fFatigueSpellMult = store.get<ESM::GameSetting>().find("fFatigueSpellMult")->getFloat();
|
||||
DynamicStat<float> fatigue = stats.getFatigue();
|
||||
const float normalizedEncumbrance = mCaster.getClass().getEncumbrance(mCaster) / mCaster.getClass().getCapacity(mCaster);
|
||||
float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult);
|
||||
fatigue.setCurrent(std::max(0.f, fatigue.getCurrent() - fatigueLoss));
|
||||
stats.setFatigue(fatigue);
|
||||
|
||||
// Check mana
|
||||
bool fail = false;
|
||||
DynamicStat<float> magicka = stats.getMagicka();
|
||||
if (magicka.getCurrent() < spell->mData.mCost)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}");
|
||||
fail = true;
|
||||
}
|
||||
|
||||
// Reduce mana
|
||||
if (!fail)
|
||||
{
|
||||
magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost);
|
||||
stats.setMagicka(magicka);
|
||||
}
|
||||
|
||||
// If this is a power, check if it was already used in last 24h
|
||||
if (!fail && spell->mData.mType & ESM::Spell::ST_Power)
|
||||
{
|
||||
if (stats.canUsePower(spell->mId))
|
||||
stats.usePower(spell->mId);
|
||||
else
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sPowerAlreadyUsed}");
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check success
|
||||
int successChance = getSpellSuccessChance(spell, mCaster);
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
if (!fail && roll >= successChance)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}");
|
||||
fail = true;
|
||||
}
|
||||
|
||||
if (fail)
|
||||
{
|
||||
// Failure sound
|
||||
static const std::string schools[] = {
|
||||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
||||
};
|
||||
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mCaster.getRefData().getHandle() == "player" && spell->mData.mType == ESM::Spell::ST_Spell)
|
||||
mCaster.getClass().skillUsageSucceeded(mCaster,
|
||||
spellSchoolToSkill(school), 0);
|
||||
|
||||
inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self);
|
||||
|
||||
if (!mTarget.isEmpty())
|
||||
{
|
||||
if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead())
|
||||
{
|
||||
inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch);
|
||||
}
|
||||
}
|
||||
|
||||
MWBase::Environment::get().getWorld()->launchProjectile(mId, false, spell->mEffects, mCaster, mSourceName);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CastSpell::cast (const ESM::Ingredient* ingredient)
|
||||
{
|
||||
mId = ingredient->mId;
|
||||
mStack = true;
|
||||
mSourceName = ingredient->mName;
|
||||
|
||||
ESM::ENAMstruct effect;
|
||||
effect.mEffectID = ingredient->mData.mEffectID[0];
|
||||
effect.mSkill = ingredient->mData.mSkills[0];
|
||||
effect.mAttribute = ingredient->mData.mAttributes[0];
|
||||
effect.mRange = ESM::RT_Self;
|
||||
effect.mArea = 0;
|
||||
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
effect.mEffectID);
|
||||
|
||||
const MWMechanics::NpcStats& npcStats = mCaster.getClass().getNpcStats(mCaster);
|
||||
const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster);
|
||||
|
||||
float x = (npcStats.getSkill (ESM::Skill::Alchemy).getModified() +
|
||||
0.2 * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified()
|
||||
+ 0.1 * creatureStats.getAttribute (ESM::Attribute::Luck).getModified())
|
||||
* creatureStats.getFatigueTerm();
|
||||
|
||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||
if (roll > x)
|
||||
{
|
||||
// "X has no effect on you"
|
||||
std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage50")->getString();
|
||||
message = boost::str(boost::format(message) % ingredient->mName);
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
float magnitude = 0;
|
||||
float y = roll / std::min(x, 100.f);
|
||||
y *= 0.25 * x;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||
effect.mDuration = int(y);
|
||||
else
|
||||
effect.mDuration = 1;
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
|
||||
{
|
||||
if (!magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||
magnitude = int((0.05 * y) / (0.1 * magicEffect->mData.mBaseCost));
|
||||
else
|
||||
magnitude = int(y / (0.1 * magicEffect->mData.mBaseCost));
|
||||
magnitude = std::max(1.f, magnitude);
|
||||
}
|
||||
else
|
||||
magnitude = 1;
|
||||
|
||||
effect.mMagnMax = magnitude;
|
||||
effect.mMagnMin = magnitude;
|
||||
|
||||
ESM::EffectList effects;
|
||||
effects.mList.push_back(effect);
|
||||
|
||||
inflict(mCaster, mCaster, effects, ESM::RT_Self);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -44,12 +44,6 @@ namespace MWMechanics
|
|||
if (creatureStats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude)
|
||||
return 0;
|
||||
|
||||
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
||||
return 100;
|
||||
|
||||
if (spell->mData.mFlags & ESM::Spell::F_Always)
|
||||
return 100;
|
||||
|
||||
float y = FLT_MAX;
|
||||
float lowestSkill = 0;
|
||||
|
||||
|
@ -63,7 +57,7 @@ namespace MWMechanics
|
|||
x *= 0.1 * magicEffect->mData.mBaseCost;
|
||||
x *= 0.5 * (it->mMagnMin + it->mMagnMax);
|
||||
x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost;
|
||||
if (it->mRange == ESM::RT_Target)
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget)
|
||||
x *= 1.5;
|
||||
static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
"fEffectCostMult")->getFloat();
|
||||
|
@ -79,8 +73,14 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
||||
return 100;
|
||||
|
||||
if (spell->mData.mFlags & ESM::Spell::F_Always)
|
||||
return 100;
|
||||
|
||||
int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude;
|
||||
|
||||
int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
||||
int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||
|
||||
|
@ -98,7 +98,6 @@ namespace MWMechanics
|
|||
return getSpellSuccessChance(spell, actor, effectiveSchool);
|
||||
}
|
||||
|
||||
/// @note this only works for ST_Spell
|
||||
inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
|
||||
{
|
||||
int school = 0;
|
||||
|
@ -106,7 +105,6 @@ namespace MWMechanics
|
|||
return school;
|
||||
}
|
||||
|
||||
/// @note this only works for ST_Spell
|
||||
inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
|
||||
{
|
||||
int school = 0;
|
||||
|
@ -114,6 +112,100 @@ namespace MWMechanics
|
|||
return school;
|
||||
}
|
||||
|
||||
/// @return >=100 for fully resisted. can also return negative value for damage amplification.
|
||||
inline float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL)
|
||||
{
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
effectId);
|
||||
|
||||
const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||
|
||||
float resisted = 0;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
|
||||
{
|
||||
|
||||
short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
|
||||
short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
|
||||
|
||||
float resistance = 0;
|
||||
if (resistanceEffect != -1)
|
||||
resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude;
|
||||
if (weaknessEffect != -1)
|
||||
resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude;
|
||||
|
||||
|
||||
float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
||||
float luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||
float x = (willpower + 0.1 * luck) * stats.getFatigueTerm();
|
||||
|
||||
// This makes spells that are easy to cast harder to resist and vice versa
|
||||
if (spell != NULL && caster.getClass().isActor())
|
||||
{
|
||||
float castChance = getSpellSuccessChance(spell, caster);
|
||||
if (castChance > 0)
|
||||
x *= 50 / castChance;
|
||||
}
|
||||
|
||||
float roll = static_cast<float>(std::rand()) / RAND_MAX * 100;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
||||
roll -= resistance;
|
||||
|
||||
if (x <= roll)
|
||||
x = 0;
|
||||
else
|
||||
{
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
||||
x = 100;
|
||||
else
|
||||
x = roll / std::min(x, 100.f);
|
||||
}
|
||||
|
||||
x = std::min(x + resistance, 100.f);
|
||||
|
||||
resisted = x;
|
||||
}
|
||||
|
||||
return resisted;
|
||||
}
|
||||
|
||||
inline float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL)
|
||||
{
|
||||
float resistance = getEffectResistance(effectId, actor, caster, spell);
|
||||
if (resistance >= 0)
|
||||
return 1 - resistance / 100.f;
|
||||
else
|
||||
return -(resistance-100) / 100.f;
|
||||
}
|
||||
|
||||
|
||||
class CastSpell
|
||||
{
|
||||
private:
|
||||
MWWorld::Ptr mCaster;
|
||||
MWWorld::Ptr mTarget;
|
||||
public:
|
||||
bool mStack;
|
||||
std::string mId; // ID of spell, potion, item etc
|
||||
std::string mSourceName; // Display name for spell, potion, etc
|
||||
|
||||
public:
|
||||
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
|
||||
|
||||
bool cast (const ESM::Spell* spell);
|
||||
bool cast (const MWWorld::Ptr& item);
|
||||
bool cast (const ESM::Ingredient* ingredient);
|
||||
bool cast (const ESM::Potion* potion);
|
||||
|
||||
/// @note Auto detects if spell, ingredient or potion
|
||||
bool cast (const std::string& id);
|
||||
|
||||
void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
|
||||
const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false);
|
||||
|
||||
void applyInstantEffect (const MWWorld::Ptr& target, short effectId, float magnitude);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue