Merge branch 'master' into savedgame

Conflicts:
	apps/openmw/mwmechanics/actors.cpp
	apps/openmw/mwworld/worldimp.cpp
	files/settings-default.cfg
actorid
Marc Zinnschlag 11 years ago
commit 030c733e2d

@ -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

@ -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>();
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;
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;
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 = mTimers.erase(it);
continue;
}
it->current += frameDuration;
if(it->current >= it->max)
(*it)->mCurrentTime += frameDuration;
if((*it)->mCurrentTime >= (*it)->mMaxTime && *it != mStaticMessageBox)
{
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;

@ -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));
}
}

@ -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
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;
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);
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);
EffectSourceVisitor visitor;
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;
// permanent item enchantments & permanent spells
visitor.mIsPermanent = true;
MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player);
store.visitEffectSources(visitor);
stats.getSpells().visitEffectSources(visitor);
effectInfo.mMagnitude = static_cast<int> (0.05*randomFactor / (0.1 * magicEffect->mData.mBaseCost));
}
// now add lasting effects
visitor.mIsPermanent = false;
stats.getActiveSpells().visitEffectSources(visitor);
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())
{
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;
}
else // ESM::Creature
int merchantGold = 0;
MWWorld::ContainerStore store = mPtr.getClass().getContainerStore(mPtr);
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{
MWWorld::LiveCellRef<ESM::Creature>* ref = mPtr.get<ESM::Creature>();
merchantGold = ref->mBase->mData.mGold;
if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001"))
merchantGold += it->getRefData().getCount();
}
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,39 +55,43 @@ namespace MWMechanics
void rebuildEffects() const;
std::pair<ESM::EffectList, std::pair<bool, bool> > getEffectList (const std::string& id) const;
///< @return (EffectList, (isIngredient, stacks))
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;
TIterator begin() const;
TIterator end() const;
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
/// Add lasting effects
///
/// \return Has the spell been added?
/// \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);
void removeSpell (const std::string& id);
/// Remove all active effects
void purgeAll ();
bool isSpellActive (std::string id) const;
///< case insensitive
const MagicEffects& getMagicEffects() const;
const TContainer& getActiveSpells() const;
TIterator begin() const;
TIterator end() const;
void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const;
double timeToExpire (const TIterator& iterator) const;
///< Returns time (in in-game hours) until the spell pointed to by \a iterator
/// expires.
};
}

@ -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;

@ -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);
}
}

@ -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;

@ -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…
Cancel
Save