Merge pull request #469 from davidcernat/master while resolving conflicts

# Conflicts:
#	apps/openmw/main.cpp
#	apps/openmw/mwbase/world.hpp
#	apps/openmw/mwdialogue/dialoguemanagerimp.cpp
#	apps/openmw/mwmechanics/actors.cpp
#	apps/openmw/mwscript/dialogueextensions.cpp
#	apps/openmw/mwworld/worldimp.hpp
pull/471/head
David Cernat 6 years ago
commit a083439139

@ -7,10 +7,12 @@
Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped
Bug #2455: Creatures attacks degrade armor Bug #2455: Creatures attacks degrade armor
Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash
Bug #2626: Resurrecting the player does not resume the game
Bug #2772: Non-existing class or faction freezes the game Bug #2772: Non-existing class or faction freezes the game
Bug #2835: Player able to slowly move when overencumbered Bug #2835: Player able to slowly move when overencumbered
Bug #2852: No murder bounty when a player follower commits murder Bug #2852: No murder bounty when a player follower commits murder
Bug #2862: [macOS] Can't quit launcher using Command-Q or OpenMW->Quit Bug #2862: [macOS] Can't quit launcher using Command-Q or OpenMW->Quit
Bug #2872: Tab completion in console doesn't work with explicit reference
Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y
Bug #3249: Fixed revert function not updating views properly Bug #3249: Fixed revert function not updating views properly
Bug #3374: Touch spells not hitting kwama foragers Bug #3374: Touch spells not hitting kwama foragers
@ -20,6 +22,7 @@
Bug #3876: Landscape texture painting is misaligned Bug #3876: Landscape texture painting is misaligned
Bug #3897: Have Goodbye give all choices the effects of Goodbye Bug #3897: Have Goodbye give all choices the effects of Goodbye
Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters
Bug #3950: FLATTEN_STATIC_TRANSFORMS optimization breaks animated collision shapes
Bug #3993: Terrain texture blending map is not upscaled Bug #3993: Terrain texture blending map is not upscaled
Bug #3997: Almalexia doesn't pace Bug #3997: Almalexia doesn't pace
Bug #4036: Weird behaviour of AI packages if package target has non-unique ID Bug #4036: Weird behaviour of AI packages if package target has non-unique ID
@ -28,15 +31,18 @@
Bug #4125: OpenMW logo cropped on bugtracker Bug #4125: OpenMW logo cropped on bugtracker
Bug #4215: OpenMW shows book text after last EOL tag Bug #4215: OpenMW shows book text after last EOL tag
Bug #4221: Characters get stuck in V-shaped terrain Bug #4221: Characters get stuck in V-shaped terrain
Bug #4230: AiTravel package issues break some Tribunal quests
Bug #4251: Stationary NPCs do not return to their position after combat Bug #4251: Stationary NPCs do not return to their position after combat
Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+ Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+
Bug #4286: Scripted animations can be interrupted Bug #4286: Scripted animations can be interrupted
Bug #4291: Non-persistent actors that started the game as dead do not play death animations Bug #4291: Non-persistent actors that started the game as dead do not play death animations
Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4293: Faction members are not aware of faction ownerships in barter
Bug #4307: World cleanup should remove dead bodies only if death animation is finished Bug #4307: World cleanup should remove dead bodies only if death animation is finished
Bug #4311: OpenMW does not handle RootCollisionNode correctly
Bug #4327: Missing animations during spell/weapon stance switching Bug #4327: Missing animations during spell/weapon stance switching
Bug #4358: Running animation is interrupted when magic mode is toggled Bug #4358: Running animation is interrupted when magic mode is toggled
Bug #4368: Settings window ok button doesn't have key focus by default Bug #4368: Settings window ok button doesn't have key focus by default
Bug #4378: On-self absorb spells restore stats
Bug #4393: NPCs walk back to where they were after using ResetActors Bug #4393: NPCs walk back to where they were after using ResetActors
Bug #4416: Handle exception if we try to play non-music file Bug #4416: Handle exception if we try to play non-music file
Bug #4419: MRK NiStringExtraData is handled incorrectly Bug #4419: MRK NiStringExtraData is handled incorrectly
@ -62,17 +68,32 @@
Bug #4480: Segfault in QuickKeysMenu when item no longer in inventory Bug #4480: Segfault in QuickKeysMenu when item no longer in inventory
Bug #4489: Goodbye doesn't block dialogue hyperlinks Bug #4489: Goodbye doesn't block dialogue hyperlinks
Bug #4490: PositionCell on player gives "Error: tried to add local script twice" Bug #4490: PositionCell on player gives "Error: tried to add local script twice"
Bug #4491: Training cap based off Base Skill instead of Modified Skill Bug #4494: Training cap based off Base Skill instead of Modified Skill
Bug #4495: Crossbow animations blending is buggy Bug #4495: Crossbow animations blending is buggy
Bug #4496: SpellTurnLeft and SpellTurnRight animation groups are unused Bug #4496: SpellTurnLeft and SpellTurnRight animation groups are unused
Bug #4497: File names starting with x or X are not classified as animation Bug #4497: File names starting with x or X are not classified as animation
Bug #4503: Cast and ExplodeSpell commands increase alteration skill Bug #4503: Cast and ExplodeSpell commands increase alteration skill
Bug #4510: Division by zero in MWMechanics::CreatureStats::setAttribute Bug #4510: Division by zero in MWMechanics::CreatureStats::setAttribute
Bug #4519: Knockdown does not discard movement in the 1st-person mode Bug #4519: Knockdown does not discard movement in the 1st-person mode
Bug #4539: Paper Doll is affected by GUI scaling
Bug #4545: Creatures flee from werewolves
Bug #4551: Replace 0 sound range with default range separately
Bug #4553: Forcegreeting on non-actor opens a dialogue window which cannot be closed
Bug #4557: Topics with reserved names are handled differently from vanilla
Bug #4558: Mesh optimizer: check for reserved node name is case-sensitive
Bug #4563: Fast travel price logic checks destination cell instead of service actor cell
Bug #4565: Underwater view distance should be limited
Bug #4573: Player uses headtracking in the 1st-person mode
Bug #4574: Player turning animations are twitchy
Bug #4575: Weird result of attack animation blending with movement animations
Bug #4576: Reset of idle animations when attack can not be started
Feature #2606: Editor: Implemented (optional) case sensitive global search Feature #2606: Editor: Implemented (optional) case sensitive global search
Feature #3083: Play animation when NPC is casting spell via script Feature #3083: Play animation when NPC is casting spell via script
Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results Feature #3103: Provide option for disposition to get increased by successful trade
Feature #3276: Editor: Search - Show number of (remaining) search results and indicate a search without any results
Feature #3641: Editor: Limit FPS in 3d preview window Feature #3641: Editor: Limit FPS in 3d preview window
Feature #3703: Ranged sneak attack criticals
Feature #4012: Editor: Write a log file if OpenCS crashes
Feature #4222: 360° screenshots Feature #4222: 360° screenshots
Feature #4256: Implement ToggleBorders (TB) console command Feature #4256: Implement ToggleBorders (TB) console command
Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts
@ -81,10 +102,13 @@
Feature #4444: Per-group KF-animation files support Feature #4444: Per-group KF-animation files support
Feature #4466: Editor: Add option to ignore "Base" records when running verifier Feature #4466: Editor: Add option to ignore "Base" records when running verifier
Feature #4488: Make water shader rougher during rain Feature #4488: Make water shader rougher during rain
Feature #4012: Editor: Write a log file if OpenCS crashes
Feature #4509: Show count of enchanted items in stack in the spells list Feature #4509: Show count of enchanted items in stack in the spells list
Feature #4512: Editor: Use markers for lights and creatures levelled lists Feature #4512: Editor: Use markers for lights and creatures levelled lists
Feature #4548: Weapon priority: use the actual chance to hit the target instead of weapon skill
Feature #4549: Weapon priority: use the actual damage in weapon rating calculations
Feature #4550: Weapon priority: make ranged weapon bonus more sensible
Task #2490: Don't open command prompt window on Release-mode builds automatically Task #2490: Don't open command prompt window on Release-mode builds automatically
Task #4545: Enable is_pod string test
0.44.0 0.44.0
------ ------

@ -20,6 +20,7 @@ namespace ESSImport
item.mId = contItem.mItem.toString(); item.mId = contItem.mItem.toString();
item.mCount = contItem.mCount; item.mCount = contItem.mCount;
item.mRelativeEquipmentSlot = -1; item.mRelativeEquipmentSlot = -1;
item.mLockLevel = 0;
unsigned int itemCount = std::abs(item.mCount); unsigned int itemCount = std::abs(item.mCount);
bool separateStacks = false; bool separateStacks = false;

@ -43,7 +43,6 @@ void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked()
QDir::currentPath(), QDir::currentPath(),
QString(tr("Text file (*.txt)"))); QString(tr("Text file (*.txt)")));
if (scriptFile.isEmpty()) if (scriptFile.isEmpty())
return; return;
@ -53,7 +52,7 @@ void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked()
return; return;
const QString path(QDir::toNativeSeparators(info.absoluteFilePath())); const QString path(QDir::toNativeSeparators(info.absoluteFilePath()));
runScriptAfterStartupField->setText(path);
} }
bool Launcher::AdvancedPage::loadSettings() bool Launcher::AdvancedPage::loadSettings()
@ -74,6 +73,9 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
loadSettingBool(chargeForEveryFollowerCheckBox, "charge for every follower travelling", "Game");
loadSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game");
loadSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game");
// Input Settings // Input Settings
loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input");
@ -126,6 +128,9 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game"); saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "Game");
saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
saveSettingBool(chargeForEveryFollowerCheckBox, "charge for every follower travelling", "Game");
saveSettingBool(enchantedWeaponsMagicalCheckBox, "enchanted weapons are magical", "Game");
saveSettingBool(permanentBarterDispositionChangeCheckBox, "barter disposition change is permanent", "Game");
// Input Settings // Input Settings
saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input"); saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input");

@ -90,19 +90,19 @@ void Launcher::MainDialog::createIcons()
dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); dataFilesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget); QListWidgetItem *graphicsButton = new QListWidgetItem(iconWidget);
graphicsButton->setIcon(QIcon::fromTheme("video-display")); graphicsButton->setIcon(QIcon(":/images/preferences-video.png"));
graphicsButton->setText(tr("Graphics")); graphicsButton->setText(tr("Graphics"));
graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute); graphicsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom | Qt::AlignAbsolute);
graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); graphicsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *settingsButton = new QListWidgetItem(iconWidget); QListWidgetItem *settingsButton = new QListWidgetItem(iconWidget);
settingsButton->setIcon(QIcon::fromTheme("preferences-system")); settingsButton->setIcon(QIcon(":/images/preferences.png"));
settingsButton->setText(tr("Settings")); settingsButton->setText(tr("Settings"));
settingsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); settingsButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
settingsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); settingsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QListWidgetItem *advancedButton = new QListWidgetItem(iconWidget); QListWidgetItem *advancedButton = new QListWidgetItem(iconWidget);
advancedButton->setIcon(QIcon::fromTheme("emblem-system")); advancedButton->setIcon(QIcon(":/images/preferences-advanced.png"));
advancedButton->setText(tr("Advanced")); advancedButton->setText(tr("Advanced"));
advancedButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); advancedButton->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom);
advancedButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); advancedButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);

@ -8,8 +8,9 @@
#include <QIcon> #include <QIcon>
#include <QMetaType> #include <QMetaType>
#include "model/doc/messages.hpp" #include <components/misc/debugging.hpp>
#include "model/doc/messages.hpp"
#include "model/world/universalid.hpp" #include "model/world/universalid.hpp"
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
@ -41,14 +42,12 @@ class Application : public QApplication
Application (int& argc, char *argv[]) : QApplication (argc, argv) {} Application (int& argc, char *argv[]) : QApplication (argc, argv) {}
}; };
int main(int argc, char *argv[]) int runApplication(int argc, char *argv[])
{ {
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0);
#endif #endif
try
{
// To allow background thread drawing in OSG // To allow background thread drawing in OSG
QApplication::setAttribute(Qt::AA_X11InitThreads, true); QApplication::setAttribute(Qt::AA_X11InitThreads, true);
@ -60,10 +59,10 @@ int main(int argc, char *argv[])
Application application (argc, argv); Application application (argc, argv);
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
QDir dir(QCoreApplication::applicationDirPath()); QDir dir(QCoreApplication::applicationDirPath());
QDir::setCurrent(dir.absolutePath()); QDir::setCurrent(dir.absolutePath());
#endif #endif
application.setWindowIcon (QIcon (":./openmw-cs.png")); application.setWindowIcon (QIcon (":./openmw-cs.png"));
@ -74,12 +73,12 @@ int main(int argc, char *argv[])
editor.connectToIPCServer(); editor.connectToIPCServer();
return 0; return 0;
} }
return editor.run(); return editor.run();
} }
catch (std::exception& e)
{
std::cerr << "ERROR: " << e.what() << std::endl;
return 0;
}
int main(int argc, char *argv[])
{
return wrapApplication(&runApplication, argc, argv, "/openmw-cs.log");
} }

@ -289,6 +289,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager)
: mWindow(NULL) : mWindow(NULL)
, mEncoding(ToUTF8::WINDOWS_1252) , mEncoding(ToUTF8::WINDOWS_1252)
, mEncoder(NULL) , mEncoder(NULL)
, mScreenCaptureOperation(nullptr)
, mSkipMenu (false) , mSkipMenu (false)
, mUseSound (true) , mUseSound (true)
, mCompileAll (false) , mCompileAll (false)

@ -5,12 +5,10 @@
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
#include <components/files/escape.hpp> #include <components/files/escape.hpp>
#include <components/fallback/validate.hpp> #include <components/fallback/validate.hpp>
#include <components/misc/debugging.hpp>
#include <SDL_messagebox.h>
#include "engine.hpp" #include "engine.hpp"
#include <boost/filesystem/fstream.hpp>
/* /*
Start of tes3mp addition Start of tes3mp addition
@ -331,43 +329,25 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
return true; return true;
} }
#if defined(_WIN32) && defined(_DEBUG) int runApplication(int argc, char *argv[])
class DebugOutput : public boost::iostreams::sink
{ {
public: #ifdef __APPLE__
std::streamsize write(const char *str, std::streamsize size) boost::filesystem::path binary_path = boost::filesystem::system_complete(boost::filesystem::path(argv[0]));
{ boost::filesystem::current_path(binary_path.parent_path());
// Make a copy for null termination setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0);
std::string tmp (str, static_cast<unsigned int>(size)); #endif
// Write string to Visual Studio Debug output
OutputDebugString (tmp.c_str ()); Files::ConfigurationManager cfgMgr;
return size; std::unique_ptr<OMW::Engine> engine;
} engine.reset(new OMW::Engine(cfgMgr));
};
#else
class Tee : public boost::iostreams::sink
{
public:
Tee(std::ostream &stream, std::ostream &stream2)
: out(stream), out2(stream2)
{
}
std::streamsize write(const char *str, std::streamsize size) if (parseOptions(argc, argv, *engine, cfgMgr))
{ {
out.write (str, size); engine->go();
out.flush();
out2.write (str, size);
out2.flush();
return size;
} }
private: return 0;
std::ostream &out; }
std::ostream &out2;
};
#endif
#ifdef ANDROID #ifdef ANDROID
extern "C" int SDL_main(int argc, char**argv) extern "C" int SDL_main(int argc, char**argv)
@ -375,99 +355,16 @@ extern "C" int SDL_main(int argc, char**argv)
int main(int argc, char**argv) int main(int argc, char**argv)
#endif #endif
{ {
#if defined(__APPLE__)
setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0);
#endif
// Some objects used to redirect cout and cerr
// Scope must be here, so this still works inside the catch block for logging exceptions
std::streambuf* cout_rdbuf = std::cout.rdbuf ();
std::streambuf* cerr_rdbuf = std::cerr.rdbuf ();
#if !(defined(_WIN32) && defined(_DEBUG))
boost::iostreams::stream_buffer<Tee> coutsb;
boost::iostreams::stream_buffer<Tee> cerrsb;
#endif
std::ostream oldcout(cout_rdbuf);
std::ostream oldcerr(cerr_rdbuf);
boost::filesystem::ofstream logfile;
std::unique_ptr<OMW::Engine> engine;
int ret = 0;
try
{
Files::ConfigurationManager cfgMgr;
#if defined(_WIN32) && defined(_DEBUG)
// Redirect cout and cerr to VS debug output when running in debug mode
boost::iostreams::stream_buffer<DebugOutput> sb;
sb.open(DebugOutput());
std::cout.rdbuf (&sb);
std::cerr.rdbuf (&sb);
#else
/* /*
Start of tes3mp change (major) Start of tes3mp change (major)
Instead of logging information in openmw.log, use a more descriptive filename Instead of logging information in openmw.log, use a more descriptive filename
that includes a timestamp that includes a timestamp
*/ */
// Redirect cout and cerr to tes3mp client log return wrapApplication(&runApplication, argc, argv, "/tes3mp-client-" + Log::getFilenameTimestamp() + ".log");
logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / "/tes3mp-client-" += Log::getFilenameTimestamp() += ".log"));
/* /*
End of tes3mp change (major) End of tes3mp change (major)
*/ */
coutsb.open (Tee(logfile, oldcout));
cerrsb.open (Tee(logfile, oldcerr));
std::cout.rdbuf (&coutsb);
std::cerr.rdbuf (&cerrsb);
#endif
/*
Start of tes3mp addition
Initialize the logger added for multiplayer
*/
LOG_INIT(Log::LOG_INFO);
/*
End of tes3mp addition
*/
crashCatcherInstall(argc, argv, (cfgMgr.getLogPath() / "crash.log").string());
#ifdef __APPLE__
boost::filesystem::path binary_path = boost::filesystem::system_complete(boost::filesystem::path(argv[0]));
boost::filesystem::current_path(binary_path.parent_path());
#endif
engine.reset(new OMW::Engine(cfgMgr));
if (parseOptions(argc, argv, *engine, cfgMgr))
{
engine->go();
}
}
catch (std::exception &e)
{
#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix))
if (!isatty(fileno(stdin)))
#endif
SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL);
std::cerr << "\nERROR: " << e.what() << std::endl;
ret = 1;
}
// Restore cout and cerr
std::cout.rdbuf(cout_rdbuf);
std::cerr.rdbuf(cerr_rdbuf);
return ret;
} }
// Platform specific for Windows when there is no console built into the executable. // Platform specific for Windows when there is no console built into the executable.

@ -86,8 +86,8 @@ namespace MWBase
virtual void persuade (int type, ResponseCallback* callback) = 0; virtual void persuade (int type, ResponseCallback* callback) = 0;
virtual int getTemporaryDispositionChange () const = 0; virtual int getTemporaryDispositionChange () const = 0;
/// @note This change is temporary and gets discarded when dialogue ends. /// @note Controlled by an option, gets discarded when dialogue ends by default
virtual void applyDispositionChange (int delta) = 0; virtual void applyBarterDispositionChange (int delta) = 0;
virtual int countSavedGameRecords() const = 0; virtual int countSavedGameRecords() const = 0;

@ -55,6 +55,8 @@ namespace MWBase
virtual void endGame() = 0; virtual void endGame() = 0;
virtual void resumeGame() = 0;
virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot) = 0; virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot) = 0;
virtual void saveGame (const std::string& description, const MWState::Slot *slot = 0) = 0; virtual void saveGame (const std::string& description, const MWState::Slot *slot = 0) = 0;

@ -445,7 +445,7 @@ namespace MWBase
End of tes3mp addition End of tes3mp addition
*/ */
virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0; virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors=false) = 0;
///< cast a Ray and return true if there is an object in the ray path. ///< cast a Ray and return true if there is an object in the ray path.
virtual bool toggleCollisionMode() = 0; virtual bool toggleCollisionMode() = 0;

@ -747,7 +747,7 @@ namespace MWClass
float Creature::getCapacity (const MWWorld::Ptr& ptr) const float Creature::getCapacity (const MWWorld::Ptr& ptr) const
{ {
const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
return static_cast<float>(stats.getAttribute(0).getModified() * 5); return static_cast<float>(stats.getAttribute(ESM::Attribute::Strength).getModified() * 5);
} }
int Creature::getServices(const MWWorld::ConstPtr &actor) const int Creature::getServices(const MWWorld::ConstPtr &actor) const

@ -1250,7 +1250,7 @@ namespace MWClass
{ {
const MWMechanics::CreatureStats& stats = getCreatureStats (ptr); const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
static const float fEncumbranceStrMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fEncumbranceStrMult")->getFloat(); static const float fEncumbranceStrMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fEncumbranceStrMult")->getFloat();
return stats.getAttribute(0).getModified()*fEncumbranceStrMult; return stats.getAttribute(ESM::Attribute::Strength).getModified()*fEncumbranceStrMult;
} }
float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const float Npc::getEncumbrance (const MWWorld::Ptr& ptr) const

@ -21,6 +21,7 @@
#include <components/interpreter/interpreter.hpp> #include <components/interpreter/interpreter.hpp>
#include <components/interpreter/defines.hpp> #include <components/interpreter/defines.hpp>
#include <components/settings/settings.hpp>
/* /*
Start of tes3mp addition Start of tes3mp addition
@ -559,9 +560,11 @@ namespace MWDialogue
return static_cast<int>(mTemporaryDispositionChange); return static_cast<int>(mTemporaryDispositionChange);
} }
void DialogueManager::applyDispositionChange(int delta) void DialogueManager::applyBarterDispositionChange(int delta)
{ {
mTemporaryDispositionChange += delta; mTemporaryDispositionChange += delta;
if (Settings::Manager::getBool("barter disposition change is permanent", "Game"))
mPermanentDispositionChange += delta;
} }
bool DialogueManager::checkServiceRefused(ResponseCallback* callback) bool DialogueManager::checkServiceRefused(ResponseCallback* callback)

@ -105,8 +105,8 @@ namespace MWDialogue
virtual void persuade (int type, ResponseCallback* callback); virtual void persuade (int type, ResponseCallback* callback);
virtual int getTemporaryDispositionChange () const; virtual int getTemporaryDispositionChange () const;
/// @note This change is temporary and gets discarded when dialogue ends. /// @note Controlled by an option, gets discarded when dialogue ends by default
virtual void applyDispositionChange (int delta); virtual void applyBarterDispositionChange (int delta);
virtual int countSavedGameRecords() const; virtual int countSavedGameRecords() const;

@ -316,6 +316,14 @@ namespace MWGui
bool has_front_quote = false; bool has_front_quote = false;
/* Does the input string contain things that don't have to be completed? If yes erase them. */ /* Does the input string contain things that don't have to be completed? If yes erase them. */
/* Erase a possible call to an explicit reference. */
size_t explicitPos = tmp.find("->");
if (explicitPos != std::string::npos)
{
tmp.erase(0, explicitPos+2);
}
/* Are there quotation marks? */ /* Are there quotation marks? */
if( tmp.find('"') != std::string::npos ) { if( tmp.find('"') != std::string::npos ) {
int numquotes=0; int numquotes=0;
@ -352,6 +360,7 @@ namespace MWGui
} }
} }
} }
/* Erase the input from the output string so we can easily append the completed form later. */ /* Erase the input from the output string so we can easily append the completed form later. */
output.erase(output.end()-tmp.length(), output.end()); output.erase(output.end()-tmp.length(), output.end());

@ -67,7 +67,7 @@ namespace MWGui
void ContainerWindow::onItemSelected(int index) void ContainerWindow::onItemSelected(int index)
{ {
if (mDragAndDrop->mIsOnDragAndDrop && mModel) if (mDragAndDrop->mIsOnDragAndDrop)
{ {
dropItem(); dropItem();
return; return;
@ -103,6 +103,9 @@ namespace MWGui
void ContainerWindow::dragItem(MyGUI::Widget* sender, int count) void ContainerWindow::dragItem(MyGUI::Widget* sender, int count)
{ {
if (!mModel)
return;
if (!onTakeItem(mModel->getItem(mSelectedItem), count)) if (!onTakeItem(mModel->getItem(mSelectedItem), count))
return; return;
@ -148,6 +151,9 @@ namespace MWGui
void ContainerWindow::dropItem() void ContainerWindow::dropItem()
{ {
if (!mModel)
return;
bool success = mModel->onDropItem(mDragAndDrop->mItem.mBase, mDragAndDrop->mDraggedCount); bool success = mModel->onDropItem(mDragAndDrop->mItem.mBase, mDragAndDrop->mDraggedCount);
/* /*
@ -206,7 +212,7 @@ namespace MWGui
void ContainerWindow::onBackgroundSelected() void ContainerWindow::onBackgroundSelected()
{ {
if (mDragAndDrop->mIsOnDragAndDrop && mModel) if (mDragAndDrop->mIsOnDragAndDrop)
dropItem(); dropItem();
} }

@ -362,52 +362,59 @@ namespace MWGui
if (mGoodbye || MWBase::Environment::get().getDialogueManager()->isInChoice()) if (mGoodbye || MWBase::Environment::get().getDialogueManager()->isInChoice())
return; return;
int separatorPos = 0; const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
for (unsigned int i=0; i<mTopicsList->getItemCount(); ++i)
{ const std::string sPersuasion = gmst.find("sPersuasion")->getString();
if (mTopicsList->getItemNameAt(i) == "") const std::string sCompanionShare = gmst.find("sCompanionShare")->getString();
separatorPos = i; const std::string sBarter = gmst.find("sBarter")->getString();
} const std::string sSpells = gmst.find("sSpells")->getString();
const std::string sTravel = gmst.find("sTravel")->getString();
if (id >= separatorPos) const std::string sSpellMakingMenuTitle = gmst.find("sSpellMakingMenuTitle")->getString();
const std::string sEnchanting = gmst.find("sEnchanting")->getString();
const std::string sServiceTrainingTitle = gmst.find("sServiceTrainingTitle")->getString();
const std::string sRepair = gmst.find("sRepair")->getString();
if (topic != sPersuasion && topic != sCompanionShare && topic != sBarter
&& topic != sSpells && topic != sTravel && topic != sSpellMakingMenuTitle
&& topic != sEnchanting && topic != sServiceTrainingTitle && topic != sRepair)
{ {
onTopicActivated(topic); onTopicActivated(topic);
if (mGoodbyeButton->getEnabled()) if (mGoodbyeButton->getEnabled())
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton);
} }
else else if (topic == sPersuasion)
{
const MWWorld::Store<ESM::GameSetting> &gmst =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
if (topic == gmst.find("sPersuasion")->getString())
mPersuasionDialog.setVisible(true); mPersuasionDialog.setVisible(true);
else if (topic == gmst.find("sCompanionShare")->getString()) else if (topic == sCompanionShare)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Companion, mPtr); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Companion, mPtr);
else if (!MWBase::Environment::get().getDialogueManager()->checkServiceRefused(mCallback.get())) else if (!MWBase::Environment::get().getDialogueManager()->checkServiceRefused(mCallback.get()))
{ {
if (topic == gmst.find("sBarter")->getString()) if (topic == sBarter)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Barter, mPtr); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Barter, mPtr);
else if (topic == gmst.find("sSpells")->getString()) else if (topic == sSpells)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellBuying, mPtr); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellBuying, mPtr);
else if (topic == gmst.find("sTravel")->getString()) else if (topic == sTravel)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Travel, mPtr); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Travel, mPtr);
else if (topic == gmst.find("sSpellMakingMenuTitle")->getString()) else if (topic == sSpellMakingMenuTitle)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellCreation, mPtr); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellCreation, mPtr);
else if (topic == gmst.find("sEnchanting")->getString()) else if (topic == sEnchanting)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting, mPtr); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting, mPtr);
else if (topic == gmst.find("sServiceTrainingTitle")->getString()) else if (topic == sServiceTrainingTitle)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Training, mPtr); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Training, mPtr);
else if (topic == gmst.find("sRepair")->getString()) else if (topic == sRepair)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_MerchantRepair, mPtr); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_MerchantRepair, mPtr);
} }
else else
updateTopics(); updateTopics();
} }
}
void DialogueWindow::setPtr(const MWWorld::Ptr& actor) void DialogueWindow::setPtr(const MWWorld::Ptr& actor)
{ {
if (!actor.getClass().isActor())
{
std::cerr << "Warning: can not talk with non-actor object." << std::endl;
return;
}
bool sameActor = (mPtr == actor); bool sameActor = (mPtr == actor);
if (!sameActor) if (!sameActor)
{ {

@ -81,7 +81,12 @@ namespace MWGui
, mLastYSize(0) , mLastYSize(0)
, mPreview(new MWRender::InventoryPreview(parent, resourceSystem, MWMechanics::getPlayer())) , mPreview(new MWRender::InventoryPreview(parent, resourceSystem, MWMechanics::getPlayer()))
, mTrading(false) , mTrading(false)
, mScaleFactor(1.0f)
{ {
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
if (uiScale > 1.0)
mScaleFactor = uiScale;
mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture())); mPreviewTexture.reset(new osgMyGUI::OSGTexture(mPreview->getTexture()));
mPreview->rebuild(); mPreview->rebuild();
@ -445,10 +450,10 @@ namespace MWGui
MyGUI::IntSize size = mAvatarImage->getSize(); MyGUI::IntSize size = mAvatarImage->getSize();
int width = std::min(mPreview->getTextureWidth(), size.width); int width = std::min(mPreview->getTextureWidth(), size.width);
int height = std::min(mPreview->getTextureHeight(), size.height); int height = std::min(mPreview->getTextureHeight(), size.height);
mPreview->setViewport(width, height); mPreview->setViewport(int(width*mScaleFactor), int(height*mScaleFactor));
mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f, mAvatarImage->getSubWidgetMain()->_setUVSet(MyGUI::FloatRect(0.f, 0.f,
width/float(mPreview->getTextureWidth()), height/float(mPreview->getTextureHeight()))); width*mScaleFactor/float(mPreview->getTextureWidth()), height*mScaleFactor/float(mPreview->getTextureHeight())));
} }
void InventoryWindow::onFilterChanged(MyGUI::Widget* _sender) void InventoryWindow::onFilterChanged(MyGUI::Widget* _sender)
@ -615,6 +620,11 @@ namespace MWGui
{ {
// convert to OpenGL lower-left origin // convert to OpenGL lower-left origin
y = (mAvatarImage->getHeight()-1) - y; y = (mAvatarImage->getHeight()-1) - y;
// Scale coordinates
x = int(x*mScaleFactor);
y = int(y*mScaleFactor);
int slot = mPreview->getSlotSelected (x, y); int slot = mPreview->getSlotSelected (x, y);
if (slot == -1) if (slot == -1)

@ -101,6 +101,7 @@ namespace MWGui
std::unique_ptr<MWRender::InventoryPreview> mPreview; std::unique_ptr<MWRender::InventoryPreview> mPreview;
bool mTrading; bool mTrading;
float mScaleFactor;
void onItemSelected(int index); void onItemSelected(int index);
void onItemSelectedFromSourceModel(int index); void onItemSelectedFromSourceModel(int index);

@ -37,6 +37,7 @@ namespace MWGui
, mLastRenderTime(0.0) , mLastRenderTime(0.0)
, mLoadingOnTime(0.0) , mLoadingOnTime(0.0)
, mImportantLabel(false) , mImportantLabel(false)
, mVisible(false)
, mProgress(0) , mProgress(0)
, mShowWallpaper(true) , mShowWallpaper(true)
{ {
@ -169,12 +170,18 @@ namespace MWGui
// We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound() // We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound()
mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback); mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback);
mShowWallpaper = visible && (MWBase::Environment::get().getStateManager()->getState()
== MWBase::StateManager::State_NoGame);
if (!visible)
{
draw();
return;
}
mVisible = visible; mVisible = visible;
mLoadingBox->setVisible(mVisible); mLoadingBox->setVisible(mVisible);
mShowWallpaper = mVisible && (MWBase::Environment::get().getStateManager()->getState()
== MWBase::StateManager::State_NoGame);
setVisible(true); setVisible(true);
if (mShowWallpaper) if (mShowWallpaper)
@ -183,9 +190,6 @@ namespace MWGui
} }
MWBase::Environment::get().getWindowManager()->pushGuiMode(mShowWallpaper ? GM_LoadingWallpaper : GM_Loading); MWBase::Environment::get().getWindowManager()->pushGuiMode(mShowWallpaper ? GM_LoadingWallpaper : GM_Loading);
if (!mVisible)
draw();
} }
void LoadingScreen::loadingOff() void LoadingScreen::loadingOff()

@ -201,6 +201,12 @@ namespace MWGui
} }
} }
assert(index != -1); assert(index != -1);
if (index < 0)
{
mSelected = nullptr;
return;
}
mSelected = &mKey[index]; mSelected = &mKey[index];
// prevent reallocation of zero key from Type_HandToHand // prevent reallocation of zero key from Type_HandToHand
@ -398,15 +404,19 @@ namespace MWGui
bool isReturnNeeded = playerStats.isParalyzed() || playerStats.isDead(); bool isReturnNeeded = playerStats.isParalyzed() || playerStats.isDead();
if (isReturnNeeded) if (isReturnNeeded && key->type != Type_Item)
{
return; return;
}
else if (isDelayNeeded) else if (isDelayNeeded && key->type != Type_Item)
{
mActivated = key; mActivated = key;
return;
}
else else
{
mActivated = nullptr; mActivated = nullptr;
}
if (key->type == Type_Item || key->type == Type_MagicItem) if (key->type == Type_Item || key->type == Type_MagicItem)
{ {
@ -444,6 +454,11 @@ namespace MWGui
// delay weapon switching if player is busy // delay weapon switching if player is busy
if (isDelayNeeded && (isWeapon || isTool)) if (isDelayNeeded && (isWeapon || isTool))
{
mActivated = key;
return;
}
else if (isReturnNeeded && (isWeapon || isTool))
{ {
return; return;
} }

@ -26,6 +26,7 @@ namespace MWGui
Spell() Spell()
: mType(Type_Spell) : mType(Type_Spell)
, mCount(0)
, mSelected(false) , mSelected(false)
, mActive(false) , mActive(false)
{ {

@ -345,7 +345,7 @@ namespace MWGui
? gmst.find("iBarterSuccessDisposition")->getInt() ? gmst.find("iBarterSuccessDisposition")->getInt()
: gmst.find("iBarterFailDisposition")->getInt(); : gmst.find("iBarterFailDisposition")->getInt();
MWBase::Environment::get().getDialogueManager()->applyDispositionChange(dispositionDelta); MWBase::Environment::get().getDialogueManager()->applyBarterDispositionChange(dispositionDelta);
} }
// display message on haggle failure // display message on haggle failure

@ -154,7 +154,7 @@ namespace MWGui
return; return;
MWMechanics::NpcStats& npcStats = mPtr.getClass().getNpcStats (mPtr); MWMechanics::NpcStats& npcStats = mPtr.getClass().getNpcStats (mPtr);
if (npcStats.getSkill (skillId).getBase () <= pcStats.getSkill (skillId).getBase ()) if (npcStats.getSkill (skillId).getModified () <= pcStats.getSkill (skillId).getBase ())
{ {
MWBase::Environment::get().getWindowManager()->messageBox ("#{sServiceTrainingWords}"); MWBase::Environment::get().getWindowManager()->messageBox ("#{sServiceTrainingWords}");
return; return;

@ -46,7 +46,7 @@ namespace MWGui
mSelect->getHeight()); mSelect->getHeight());
} }
void TravelWindow::addDestination(const std::string& name,ESM::Position pos,bool interior) void TravelWindow::addDestination(const std::string& name, ESM::Position pos, bool interior)
{ {
int price; int price;
@ -56,7 +56,7 @@ namespace MWGui
MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
if(interior) if (!mPtr.getCell()->isExterior())
{ {
price = gmst.find("fMagesGuildTravel")->getInt(); price = gmst.find("fMagesGuildTravel")->getInt();
} }
@ -168,7 +168,7 @@ namespace MWGui
ESM::Position pos = *_sender->getUserData<ESM::Position>(); ESM::Position pos = *_sender->getUserData<ESM::Position>();
std::string cellname = _sender->getUserString("Destination"); std::string cellname = _sender->getUserString("Destination");
bool interior = _sender->getUserString("interior") == "y"; bool interior = _sender->getUserString("interior") == "y";
if (!interior) if (mPtr.getCell()->isExterior())
{ {
ESM::Position playerPos = player.getRefData().getPosition(); ESM::Position playerPos = player.getRefData().getPosition();
float d = (osg::Vec3f(pos.pos[0], pos.pos[1], 0) - osg::Vec3f(playerPos.pos[0], playerPos.pos[1], 0)).length(); float d = (osg::Vec3f(pos.pos[0], pos.pos[1], 0) - osg::Vec3f(playerPos.pos[0], playerPos.pos[1], 0)).length();

@ -941,6 +941,7 @@ namespace MWGui
mKeyboardNavigation->onFrame(); mKeyboardNavigation->onFrame();
if (mMessageBoxManager)
mMessageBoxManager->onFrame(frameDuration); mMessageBoxManager->onFrame(frameDuration);
mToolTips->onFrame(frameDuration); mToolTips->onFrame(frameDuration);

@ -805,6 +805,8 @@ namespace MWMechanics
for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it)
{ {
bool actorKilled = false;
const ActiveSpells::ActiveSpellParams& spell = it->second; const ActiveSpells::ActiveSpellParams& spell = it->second;
MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId); MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId);
for (std::vector<ActiveSpells::ActiveEffect>::const_iterator effectIt = spell.mEffects.begin(); for (std::vector<ActiveSpells::ActiveEffect>::const_iterator effectIt = spell.mEffects.begin();
@ -832,10 +834,14 @@ namespace MWMechanics
caster.getClass().getNpcStats(caster).addWerewolfKill(); caster.getClass().getNpcStats(caster).addWerewolfKill();
MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, player); MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, player);
actorKilled = true;
break; break;
} }
} }
} }
if (actorKilled)
break;
} }
} }
@ -1377,6 +1383,8 @@ namespace MWMechanics
// AI and magic effects update // AI and magic effects update
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
{ {
bool isPlayer = iter->first == player;
float distSqr = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2(); float distSqr = (player.getRefData().getPosition().asVec3() - iter->first.getRefData().getPosition().asVec3()).length2();
// AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this // AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this
// (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not) // (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not)
@ -1390,7 +1398,7 @@ namespace MWMechanics
Instead of merely updating the player character's mAttackingOrSpell here, Instead of merely updating the player character's mAttackingOrSpell here,
prepare an Attack packet for the LocalPlayer prepare an Attack packet for the LocalPlayer
*/ */
if (iter->first == player) if (isPlayer)
{ {
bool state = MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell(); bool state = MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell();
DrawState_ dstate = player.getClass().getNpcStats(player).getDrawState(); DrawState_ dstate = player.getClass().getNpcStats(player).getDrawState();
@ -1478,12 +1486,12 @@ namespace MWMechanics
{ {
if (timerUpdateAITargets == 0 && (isLocalActor || isAIActive)) if (timerUpdateAITargets == 0 && (isLocalActor || isAIActive))
{ {
if (iter->first != player) if (!isPlayer)
adjustCommandedActor(iter->first); adjustCommandedActor(iter->first);
for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it)
{ {
if (it->first == iter->first || iter->first == player) // player is not AI-controlled if (it->first == iter->first || isPlayer) // player is not AI-controlled
continue; continue;
engageCombat(iter->first, it->first, cachedAllies, it->first == player); engageCombat(iter->first, it->first, cachedAllies, it->first == player);
} }
@ -1494,12 +1502,15 @@ namespace MWMechanics
MWWorld::Ptr headTrackTarget; MWWorld::Ptr headTrackTarget;
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first); MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
bool firstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson();
// Unconsious actor can not track target // 1. Unconsious actor can not track target
// Also actors in combat and pursue mode do not bother to headtrack // 2. Actors in combat and pursue mode do not bother to headtrack
// 3. Player character does not use headtracking in the 1st-person view
if (!stats.getKnockedDown() && if (!stats.getKnockedDown() &&
!stats.getAiSequence().isInCombat() && !stats.getAiSequence().isInCombat() &&
!stats.getAiSequence().hasPackage(AiPackage::TypeIdPursue)) !stats.getAiSequence().hasPackage(AiPackage::TypeIdPursue) &&
!firstPersonPlayer)
{ {
for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it)
{ {

@ -494,11 +494,14 @@ namespace MWMechanics
static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->getInt(); static const int iWereWolfLevelToAttack = gmst.find("iWereWolfLevelToAttack")->getInt();
if (enemy.getClass().isNpc() && enemy.getClass().getNpcStats(enemy).isWerewolf() && stats.getLevel() < iWereWolfLevelToAttack) if (actor.getClass().isNpc() && enemy.getClass().isNpc())
{
if (enemy.getClass().getNpcStats(enemy).isWerewolf() && stats.getLevel() < iWereWolfLevelToAttack)
{ {
static const int iWereWolfFleeMod = gmst.find("iWereWolfFleeMod")->getInt(); static const int iWereWolfFleeMod = gmst.find("iWereWolfFleeMod")->getInt();
rating = iWereWolfFleeMod; rating = iWereWolfFleeMod;
} }
}
if (rating != 0.0f) if (rating != 0.0f)
rating += getFightDistanceBias(actor, enemy); rating += getFightDistanceBias(actor, enemy);

@ -53,7 +53,23 @@ namespace MWMechanics
if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), pos.asVec3())) if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), pos.asVec3()))
return false; return false;
if (pathTo(actor, ESM::Pathgrid::Point(static_cast<int>(mX), static_cast<int>(mY), static_cast<int>(mZ)), duration)) // Unfortunately, with vanilla assets destination is sometimes blocked by other actor.
// If we got close to target, check for actors nearby. If they are, finish AI package.
int destinationTolerance = 64;
ESM::Pathgrid::Point dest(static_cast<int>(mX), static_cast<int>(mY), static_cast<int>(mZ));
if (distance(pos.pos, dest) <= destinationTolerance)
{
std::vector<MWWorld::Ptr> targetActors;
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors);
if (!result.first.isEmpty())
{
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
return true;
}
}
if (pathTo(actor, dest, duration))
{ {
actor.getClass().getMovementSettings(actor).mPosition[1] = 0; actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
return true; return true;

@ -1272,7 +1272,9 @@ bool CharacterController::updateWeaponState()
mWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr(); mWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr();
} }
// Apply 1st-person weapon animations only for upper body
MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon);
if (mPtr != MWMechanics::getPlayer() || !MWBase::Environment::get().getWorld()->isFirstPerson())
priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody;
bool forcestateupdate = false; bool forcestateupdate = false;
@ -1281,11 +1283,11 @@ bool CharacterController::updateWeaponState()
bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell && bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell &&
mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell; mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell;
if(weaptype != mWeaponType && !isKnockedOut() && if(!isKnockedOut() && !isKnockedDown() && !isRecovery())
!isKnockedDown() && !isRecovery())
{ {
std::string weapgroup; std::string weapgroup;
if ((!isWerewolf || mWeaponType != WeapType_Spell) if ((!isWerewolf || mWeaponType != WeapType_Spell)
&& weaptype != mWeaponType
&& mUpperBodyState != UpperCharState_UnEquipingWeap && mUpperBodyState != UpperCharState_UnEquipingWeap
&& !isStillWeapon) && !isStillWeapon)
{ {
@ -1310,7 +1312,12 @@ bool CharacterController::updateWeaponState()
float complete; float complete;
bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
if (!animPlaying || complete >= 1.0f) if (!animPlaying || complete >= 1.0f)
{
// Weapon is changed, no current animation (e.g. unequipping or attack).
// Start equipping animation now.
if (weaptype != mWeaponType)
{ {
forcestateupdate = true; forcestateupdate = true;
mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype));
@ -1320,12 +1327,8 @@ bool CharacterController::updateWeaponState()
if (!isStillWeapon) if (!isStillWeapon)
{ {
if (weaptype == WeapType_None)
{
// Disable current weapon animation manually
mAnimation->disable(mCurrentWeapon); mAnimation->disable(mCurrentWeapon);
} if (weaptype != WeapType_None)
else
{ {
mAnimation->showWeapons(false); mAnimation->showWeapons(false);
mAnimation->play(weapgroup, priorityWeapon, mAnimation->play(weapgroup, priorityWeapon,
@ -1355,6 +1358,16 @@ bool CharacterController::updateWeaponState()
sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f); sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f);
} }
} }
// Make sure that we disabled unequipping animation
if (mUpperBodyState == UpperCharState_UnEquipingWeap)
{
mUpperBodyState = UpperCharState_Nothing;
mAnimation->disable(mCurrentWeapon);
mWeaponType = WeapType_None;
getWeaponGroup(mWeaponType, mCurrentWeapon);
}
}
} }
if(isWerewolf) if(isWerewolf)
@ -1407,14 +1420,7 @@ bool CharacterController::updateWeaponState()
{ {
MWWorld::Ptr player = getPlayer(); MWWorld::Ptr player = getPlayer();
// We should reset player's idle animation in the first-person mode. bool resetIdle = ammunition;
if (mPtr == player && MWBase::Environment::get().getWorld()->isFirstPerson())
mIdleState = CharState_None;
// In other cases we should not break swim and sneak animations
if (mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim)
mIdleState = CharState_None;
if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block))
{ {
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
@ -1498,6 +1504,11 @@ bool CharacterController::updateWeaponState()
0.0f, 0); 0.0f, 0);
mUpperBodyState = UpperCharState_CastingSpell; mUpperBodyState = UpperCharState_CastingSpell;
} }
else
{
resetIdle = false;
}
if (mPtr.getClass().hasInventoryStore(mPtr)) if (mPtr.getClass().hasInventoryStore(mPtr))
{ {
MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
@ -1578,6 +1589,14 @@ bool CharacterController::updateWeaponState()
} }
} }
// We should reset player's idle animation in the first-person mode.
if (resetIdle && mPtr == player && MWBase::Environment::get().getWorld()->isFirstPerson())
mIdleState = CharState_None;
// In other cases we should not break swim and sneak animations
if (resetIdle && mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim)
mIdleState = CharState_None;
animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown())
mAttackStrength = complete; mAttackStrength = complete;
@ -1741,18 +1760,22 @@ bool CharacterController::updateWeaponState()
break; break;
} }
// Note: apply reload animations only for upper body since blending with movement animations can give weird result. // Note: apply crossbow reload animation only for upper body
// Especially noticable with crossbow reload animation. // since blending with movement animations can give weird result.
if(!start.empty()) if(!start.empty())
{ {
int mask = MWRender::Animation::BlendMask_All;
if (mWeaponType == WeapType_Crossbow)
mask = MWRender::Animation::BlendMask_UpperBody;
mAnimation->disable(mCurrentWeapon); mAnimation->disable(mCurrentWeapon);
if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) if (mUpperBodyState == UpperCharState_FollowStartToFollowStop)
mAnimation->play(mCurrentWeapon, priorityWeapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_UpperBody, true, mask, true,
weapSpeed, start, stop, 0.0f, 0); weapSpeed, start, stop, 0.0f, 0);
else else
mAnimation->play(mCurrentWeapon, priorityWeapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_UpperBody, false, mask, false,
weapSpeed, start, stop, 0.0f, 0); weapSpeed, start, stop, 0.0f, 0);
} }
} }
@ -2091,26 +2114,38 @@ void CharacterController::update(float duration)
else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson()))
{ {
if(rot.z() > 0.0f) if(rot.z() > 0.0f)
{
movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight;
mAnimation->disable(mCurrentJump);
}
else if(rot.z() < 0.0f) else if(rot.z() < 0.0f)
{
movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft;
mAnimation->disable(mCurrentJump);
}
} }
} }
// Player can not use smooth turning as NPCs, so we play turning animation a bit to avoid jittering
if (mPtr == getPlayer())
{
float threshold = mCurrentMovement.find("swim") == std::string::npos ? 0.4f : 0.8f;
float complete;
bool animPlaying = mAnimation->getInfo(mCurrentMovement, &complete);
if (movestate == CharState_None && isTurning())
{
if (animPlaying && complete < threshold)
movestate = mMovementState;
}
}
else
{
mTurnAnimationThreshold -= duration; mTurnAnimationThreshold -= duration;
if (isTurning()) if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft ||
movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft)
{
mTurnAnimationThreshold = 0.05f; mTurnAnimationThreshold = 0.05f;
}
else if (movestate == CharState_None && isTurning() else if (movestate == CharState_None && isTurning()
&& mTurnAnimationThreshold > 0) && mTurnAnimationThreshold > 0)
{ {
movestate = mMovementState; movestate = mMovementState;
} }
}
if(movestate != CharState_None && !isTurning()) if(movestate != CharState_None && !isTurning())
clearAnimQueue(); clearAnimQueue();
@ -2140,8 +2175,10 @@ void CharacterController::update(float duration)
if (isTurning()) if (isTurning())
{ {
// Adjust animation speed from 1.0 to 1.5 multiplier
float turnSpeed = std::min(1.5f, std::abs(rot.z()) / duration / static_cast<float>(osg::PI));
if (duration > 0) if (duration > 0)
mAnimation->adjustSpeedMult(mCurrentMovement, std::min(1.5f, std::abs(rot.z()) / duration / static_cast<float>(osg::PI))); mAnimation->adjustSpeedMult(mCurrentMovement, std::max(turnSpeed, 1.0f));
} }
else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed) else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed)
{ {

@ -31,8 +31,10 @@ enum Priority {
Priority_WeaponLowerBody, Priority_WeaponLowerBody,
Priority_SneakIdleLowerBody, Priority_SneakIdleLowerBody,
Priority_SwimIdle, Priority_SwimIdle,
Priority_Jump,
Priority_Movement, Priority_Movement,
// Note: in vanilla movement anims have higher priority than jump ones.
// It causes issues with landing animations during movement.
Priority_Jump,
Priority_Hit, Priority_Hit,
Priority_Weapon, Priority_Weapon,
Priority_Block, Priority_Block,

@ -282,8 +282,21 @@ namespace MWMechanics
adjustWeaponDamage(damage, weapon, attacker); adjustWeaponDamage(damage, weapon, attacker);
if(attacker == getPlayer()) if (attacker == getPlayer())
{
attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0); attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0);
const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence();
bool unaware = !sequence.isInCombat()
&& !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim);
if (unaware)
{
damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat();
MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}");
MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
}
}
if (victim.getClass().getCreatureStats(victim).getKnockedDown()) if (victim.getClass().getCreatureStats(victim).getKnockedDown())
damage *= gmst.find("fCombatKODamageMult")->getFloat(); damage *= gmst.find("fCombatKODamageMult")->getFloat();

@ -23,8 +23,10 @@ float scaleDamage(float damage, const MWWorld::Ptr& attacker, const MWWorld::Ptr
{ {
const MWWorld::Ptr& player = MWMechanics::getPlayer(); const MWWorld::Ptr& player = MWMechanics::getPlayer();
// [-100, 100] // [-500, 500]
int difficultySetting = Settings::Manager::getInt("difficulty", "Game"); int difficultySetting = Settings::Manager::getInt("difficulty", "Game");
difficultySetting = std::min(difficultySetting, 500);
difficultySetting = std::max(difficultySetting, -500);
/* /*
Start of tes3mp change (major) Start of tes3mp change (major)

@ -231,6 +231,28 @@ namespace MWMechanics
{ {
mPath = pathgridGraph.aStarSearch(startNode, endNode.first); mPath = pathgridGraph.aStarSearch(startNode, endNode.first);
// If nearest path node is in opposite direction from second, remove it from path.
// Especially useful for wandering actors, if the nearest node is blocked for some reason.
if (mPath.size() > 1)
{
ESM::Pathgrid::Point secondNode = *(++mPath.begin());
osg::Vec3f firstNodeVec3f = MakeOsgVec3(mPathgrid->mPoints[startNode]);
osg::Vec3f secondNodeVec3f = MakeOsgVec3(secondNode);
osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f;
osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f;
if (toSecondNodeVec3f * toStartPointVec3f > 0)
{
ESM::Pathgrid::Point temp(secondNode);
converter.toWorld(temp);
// Add Z offset since path node can overlap with other objects.
// Also ignore doors in raytesting.
bool isPathClear = !MWBase::Environment::get().getWorld()->castRay(
startPoint.mX, startPoint.mY, startPoint.mZ+16, temp.mX, temp.mY, temp.mZ+16, true);
if (isPathClear)
mPath.pop_front();
}
}
// convert supplied path to world coordinates // convert supplied path to world coordinates
for (std::list<ESM::Pathgrid::Point>::iterator iter(mPath.begin()); iter != mPath.end(); ++iter) for (std::list<ESM::Pathgrid::Point>::iterator iter(mPath.begin()); iter != mPath.end(); ++iter)
{ {

@ -332,14 +332,14 @@ namespace MWMechanics
return true; return true;
} }
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool isScripted) CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell)
: mCaster(caster) : mCaster(caster)
, mTarget(target) , mTarget(target)
, mStack(false) , mStack(false)
, mHitPosition(0,0,0) , mHitPosition(0,0,0)
, mAlwaysSucceed(false) , mAlwaysSucceed(false)
, mFromProjectile(fromProjectile) , mFromProjectile(fromProjectile)
, mIsScripted(isScripted) , mManualSpell(manualSpell)
{ {
} }
@ -437,9 +437,9 @@ namespace MWMechanics
if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer)) if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer))
continue; continue;
// caster needs to be an actor for linked effects (e.g. Absorb) // caster needs to be an actor that's not the target for linked effects (e.g. Absorb)
if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked
&& (caster.isEmpty() || !caster.getClass().isActor())) && (caster.isEmpty() || !caster.getClass().isActor() || caster == target))
continue; continue;
// If player is healing someone, show the target's HP bar // If player is healing someone, show the target's HP bar
@ -595,7 +595,7 @@ namespace MWMechanics
// For absorb effects, also apply the effect to the caster - but with a negative // For absorb effects, also apply the effect to the caster - but with a negative
// magnitude, since we're transferring stats from the target to the caster // magnitude, since we're transferring stats from the target to the caster
if (!caster.isEmpty() && caster.getClass().isActor()) if (!caster.isEmpty() && caster != target && caster.getClass().isActor())
{ {
for (int i=0; i<5; ++i) for (int i=0; i<5; ++i)
{ {
@ -983,7 +983,7 @@ namespace MWMechanics
bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mIsScripted) if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mManualSpell)
{ {
school = getSpellSchool(spell, mCaster); school = getSpellSchool(spell, mCaster);
@ -1173,7 +1173,7 @@ namespace MWMechanics
bool CastSpell::spellIncreasesSkill() bool CastSpell::spellIncreasesSkill()
{ {
if (mIsScripted) if (mManualSpell)
return false; return false;
return MWMechanics::spellIncreasesSkill(mId); return MWMechanics::spellIncreasesSkill(mId);

@ -88,10 +88,10 @@ namespace MWMechanics
osg::Vec3f mHitPosition; // Used for spawning area orb osg::Vec3f mHitPosition; // Used for spawning area orb
bool mAlwaysSucceed; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false) bool mAlwaysSucceed; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false)
bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon) bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon)
bool mIsScripted; // True if spell is casted from script and ignores some checks (mana level, success chance, etc.) bool mManualSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, etc.)
public: public:
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false, const bool isScripted=false); CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false, const bool manualSpell=false);
bool cast (const ESM::Spell* spell); bool cast (const ESM::Spell* spell);

@ -30,7 +30,7 @@ namespace MWMechanics
return 0.f; return 0.f;
float rating=0.f; float rating=0.f;
float bonus=0.f; float rangedMult=1.f;
if (weapon->mData.mType >= ESM::Weapon::MarksmanBow && weapon->mData.mType <= ESM::Weapon::MarksmanThrown) if (weapon->mData.mType >= ESM::Weapon::MarksmanBow && weapon->mData.mType <= ESM::Weapon::MarksmanThrown)
{ {
@ -44,25 +44,33 @@ namespace MWMechanics
if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(enemy), 0.75f)) if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(enemy), 0.75f))
return 0.f; return 0.f;
bonus+=1.5f; if (getDistanceMinusHalfExtents(actor, enemy) >= getMaxAttackDistance(enemy))
rangedMult = 1.5f;
} }
if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) if (weapon->mData.mType >= ESM::Weapon::MarksmanBow)
{ {
rating = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f; float rangedDamage = weapon->mData.mChop[0] + weapon->mData.mChop[1];
MWMechanics::adjustWeaponDamage(rangedDamage, item, actor);
rating = rangedDamage / 2.f;
if (weapon->mData.mType >= ESM::Weapon::MarksmanThrown) if (weapon->mData.mType >= ESM::Weapon::MarksmanThrown)
MWMechanics::resistNormalWeapon(enemy, actor, item, rating); MWMechanics::resistNormalWeapon(enemy, actor, item, rating);
} }
else else
{ {
float meleeDamage = 0.f;
for (int i=0; i<2; ++i) for (int i=0; i<2; ++i)
{ {
rating += weapon->mData.mSlash[i]; meleeDamage += weapon->mData.mSlash[i];
rating += weapon->mData.mThrust[i]; meleeDamage += weapon->mData.mThrust[i];
rating += weapon->mData.mChop[i]; meleeDamage += weapon->mData.mChop[i];
} }
rating /= 6.f;
MWMechanics::adjustWeaponDamage(meleeDamage, item, actor);
rating = meleeDamage / 6.f;
MWMechanics::resistNormalWeapon(enemy, actor, item, rating); MWMechanics::resistNormalWeapon(enemy, actor, item, rating);
} }
@ -71,7 +79,6 @@ namespace MWMechanics
{ {
if (item.getClass().getItemHealth(item) == 0) if (item.getClass().getItemHealth(item) == 0)
return 0.f; return 0.f;
rating *= item.getClass().getItemHealth(item) / float(item.getClass().getItemMaxHealth(item));
} }
if (weapon->mData.mType == ESM::Weapon::MarksmanBow) if (weapon->mData.mType == ESM::Weapon::MarksmanBow)
@ -103,13 +110,12 @@ namespace MWMechanics
int skill = item.getClass().getEquipmentSkill(item); int skill = item.getClass().getEquipmentSkill(item);
if (skill != -1) if (skill != -1)
rating *= actor.getClass().getSkill(actor, skill) / 100.f; {
int value = actor.getClass().getSkill(actor, skill);
// There is no need to apply bonus if weapon rating == 0 rating *= MWMechanics::getHitChance(actor, enemy, value) / 100.f;
if (rating == 0.f) }
return 0.f;
return rating + bonus; return rating * rangedMult;
} }
float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, ESM::Weapon::Type ammoType) float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, ESM::Weapon::Type ammoType)

@ -626,7 +626,6 @@ namespace MWPhysics
assert (mShapeInstance->getCollisionShape()->isCompound()); assert (mShapeInstance->getCollisionShape()->isCompound());
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->getCollisionShape()); btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->getCollisionShape());
for (std::map<int, int>::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it) for (std::map<int, int>::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it)
{ {
int recIndex = it->first; int recIndex = it->first;
@ -640,6 +639,9 @@ namespace MWPhysics
if (!visitor.mFound) if (!visitor.mFound)
{ {
std::cerr << "Error: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId() << std::endl; std::cerr << "Error: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId() << std::endl;
// Remove nonexistent nodes from animated shapes map and early out
mShapeInstance->mAnimatedShapes.erase(recIndex);
return; return;
} }
osg::NodePath nodePath = visitor.mFoundPath; osg::NodePath nodePath = visitor.mFoundPath;

@ -203,6 +203,7 @@ namespace MWRender
, mNightEyeFactor(0.f) , mNightEyeFactor(0.f)
, mDistantFog(false) , mDistantFog(false)
, mDistantTerrain(false) , mDistantTerrain(false)
, mFieldOfViewOverridden(false)
, mFieldOfViewOverride(0.f) , mFieldOfViewOverride(0.f)
, mBorders(false) , mBorders(false)
{ {
@ -553,8 +554,8 @@ namespace MWRender
mLandFogStart = mViewDistance * (1 - fogDepth); mLandFogStart = mViewDistance * (1 - fogDepth);
mLandFogEnd = mViewDistance; mLandFogEnd = mViewDistance;
} }
mUnderwaterFogStart = mViewDistance * (1 - underwaterFog); mUnderwaterFogStart = std::min(mViewDistance, 6666.f) * (1 - underwaterFog);
mUnderwaterFogEnd = mViewDistance; mUnderwaterFogEnd = std::min(mViewDistance, 6666.f);
} }
mFogColor = color; mFogColor = color;
} }
@ -584,8 +585,6 @@ namespace MWRender
mCurrentCameraPos = cameraPos; mCurrentCameraPos = cameraPos;
if (mWater->isUnderwater(cameraPos)) if (mWater->isUnderwater(cameraPos))
{ {
float viewDistance = mViewDistance;
viewDistance = std::min(viewDistance, 6666.f);
setFogColor(mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight)); setFogColor(mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight));
mStateUpdater->setFogStart(mUnderwaterFogStart); mStateUpdater->setFogStart(mUnderwaterFogStart);
mStateUpdater->setFogEnd(mUnderwaterFogEnd); mStateUpdater->setFogEnd(mUnderwaterFogEnd);
@ -691,9 +690,6 @@ namespace MWRender
int screenshotW = mViewer->getCamera()->getViewport()->width(); int screenshotW = mViewer->getCamera()->getViewport()->width();
int screenshotH = mViewer->getCamera()->getViewport()->height(); int screenshotH = mViewer->getCamera()->getViewport()->height();
int screenshotMapping = 0; int screenshotMapping = 0;
int cubeSize = screenshotMapping == 2 ?
screenshotW: // planet mapping needs higher resolution
screenshotW / 2;
std::vector<std::string> settingArgs; std::vector<std::string> settingArgs;
boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" ")); boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" "));
@ -718,6 +714,9 @@ namespace MWRender
} }
} }
// planet mapping needs higher resolution
int cubeSize = screenshotMapping == 2 ? screenshotW : screenshotW / 2;
if (settingArgs.size() > 1) if (settingArgs.size() > 1)
screenshotW = std::min(10000,std::atoi(settingArgs[1].c_str())); screenshotW = std::min(10000,std::atoi(settingArgs[1].c_str()));

@ -1117,6 +1117,7 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana
, mWindSpeed(0.f) , mWindSpeed(0.f)
, mEnabled(true) , mEnabled(true)
, mSunEnabled(true) , mSunEnabled(true)
, mWeatherAlpha(0.f)
{ {
osg::ref_ptr<CameraRelativeTransform> skyroot (new CameraRelativeTransform); osg::ref_ptr<CameraRelativeTransform> skyroot (new CameraRelativeTransform);
skyroot->setName("Sky Root"); skyroot->setName("Sky Root");

@ -1,3 +1,5 @@
#include <iostream>
#include "dialogueextensions.hpp" #include "dialogueextensions.hpp"
/* /*
@ -181,6 +183,14 @@ namespace MWScript
if (!ptr.getRefData().isEnabled()) if (!ptr.getRefData().isEnabled())
return; return;
if (!ptr.getClass().isActor())
{
const std::string error = "Warning: \"forcegreeting\" command works only for actors.";
runtime.getContext().report(error);
std::cerr << error << std::endl;
return;
}
/* /*
Start of tes3mp change (major) Start of tes3mp change (major)

@ -28,6 +28,7 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -1260,7 +1261,11 @@ namespace MWScript
MWWorld::Ptr ptr = R()(runtime); MWWorld::Ptr ptr = R()(runtime);
if (ptr == MWMechanics::getPlayer()) if (ptr == MWMechanics::getPlayer())
{
ptr.getClass().getCreatureStats(ptr).resurrect(); ptr.getClass().getCreatureStats(ptr).resurrect();
if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_Ended)
MWBase::Environment::get().getStateManager()->resumeGame();
}
else if (ptr.getClass().getCreatureStats(ptr).isDead()) else if (ptr.getClass().getCreatureStats(ptr).isDead())
{ {
bool wasEnabled = ptr.getRefData().isEnabled(); bool wasEnabled = ptr.getRefData().isEnabled();

@ -142,16 +142,12 @@ namespace MWSound
float volume, min, max; float volume, min, max;
volume = static_cast<float>(pow(10.0, (sound->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0)); volume = static_cast<float>(pow(10.0, (sound->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0));
if(sound->mData.mMinRange == 0 && sound->mData.mMaxRange == 0)
{
min = fAudioDefaultMinDistance;
max = fAudioDefaultMaxDistance;
}
else
{
min = sound->mData.mMinRange; min = sound->mData.mMinRange;
max = sound->mData.mMaxRange; max = sound->mData.mMaxRange;
} if (min == 0)
min = fAudioDefaultMinDistance;
if (max == 0)
max = fAudioDefaultMaxDistance;
min *= fAudioMinDistanceMult; min *= fAudioMinDistanceMult;
max *= fAudioMaxDistanceMult; max *= fAudioMaxDistanceMult;

@ -173,6 +173,11 @@ void MWState::StateManager::endGame()
mState = State_Ended; mState = State_Ended;
} }
void MWState::StateManager::resumeGame()
{
mState = State_Running;
}
void MWState::StateManager::saveGame (const std::string& description, const Slot *slot) void MWState::StateManager::saveGame (const std::string& description, const Slot *slot)
{ {
MWState::Character* character = getCurrentCharacter(); MWState::Character* character = getCurrentCharacter();

@ -48,6 +48,8 @@ namespace MWState
virtual void endGame(); virtual void endGame();
virtual void resumeGame();
virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot); virtual void deleteGame (const MWState::Character *character, const MWState::Slot *slot);
///< Delete a saved game slot from this character. If all save slots are deleted, the character will be deleted too. ///< Delete a saved game slot from this character. If all save slots are deleted, the character will be deleted too.

@ -1373,9 +1373,9 @@ namespace MWWorld
addContainerScripts (getPlayerPtr(), newCell); addContainerScripts (getPlayerPtr(), newCell);
newPtr = getPlayerPtr(); newPtr = getPlayerPtr();
} }
else else if (currCell)
{ {
bool currCellActive = currCell && mWorldScene->isCellActive(*currCell); bool currCellActive = mWorldScene->isCellActive(*currCell);
bool newCellActive = newCell && mWorldScene->isCellActive(*newCell); bool newCellActive = newCell && mWorldScene->isCellActive(*newCell);
if (!currCellActive && newCellActive) if (!currCellActive && newCellActive)
{ {
@ -1708,12 +1708,16 @@ namespace MWWorld
moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false); moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false);
} }
bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2) bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors)
{ {
osg::Vec3f a(x1,y1,z1); osg::Vec3f a(x1,y1,z1);
osg::Vec3f b(x2,y2,z2); osg::Vec3f b(x2,y2,z2);
MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), std::vector<MWWorld::Ptr>(), MWPhysics::CollisionType_World|MWPhysics::CollisionType_Door); int mask = MWPhysics::CollisionType_World;
if (!ignoreDoors)
mask |= MWPhysics::CollisionType_Door;
MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), std::vector<MWWorld::Ptr>(), mask);
return result.mHit; return result.mHit;
} }
@ -3164,7 +3168,7 @@ namespace MWWorld
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
std::vector<MWWorld::Ptr> targetActors; std::vector<MWWorld::Ptr> targetActors;
if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell) if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell)
actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); stats.getAiSequence().getCombatTargets(targetActors);
const float fCombatDistance = getStore().get<ESM::GameSetting>().find("fCombatDistance")->getFloat(); const float fCombatDistance = getStore().get<ESM::GameSetting>().find("fCombatDistance")->getFloat();
@ -3187,7 +3191,6 @@ namespace MWWorld
// Actors that are targeted by this actor's Follow or Escort packages also side with them // Actors that are targeted by this actor's Follow or Escort packages also side with them
if (actor != MWMechanics::getPlayer()) if (actor != MWMechanics::getPlayer())
{ {
const MWMechanics::CreatureStats &stats = actor.getClass().getCreatureStats(actor);
for (std::list<MWMechanics::AiPackage*>::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) for (std::list<MWMechanics::AiPackage*>::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it)
{ {
if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCast) if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCast)

@ -535,7 +535,7 @@ namespace MWWorld
End of tes3mp addition End of tes3mp addition
*/ */
bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) override; bool castRay (float x1, float y1, float z1, float x2, float y2, float z2, bool ignoreDoors=false) override;
///< cast a Ray and return true if there is an object in the ray path. ///< cast a Ray and return true if there is an object in the ray path.
bool toggleCollisionMode() override; bool toggleCollisionMode() override;

@ -64,7 +64,7 @@ TEST(EsmFixedString, struct_size)
ASSERT_EQ(256, sizeof(ESM::NAME256)); ASSERT_EQ(256, sizeof(ESM::NAME256));
} }
TEST(EsmFixedString, DISABLED_is_pod) TEST(EsmFixedString, is_pod)
{ {
ASSERT_TRUE(std::is_pod<ESM::NAME>::value); ASSERT_TRUE(std::is_pod<ESM::NAME>::value);
ASSERT_TRUE(std::is_pod<ESM::NAME32>::value); ASSERT_TRUE(std::is_pod<ESM::NAME32>::value);

@ -7,7 +7,7 @@ struct PartialBinarySearchTest : public ::testing::Test
std::vector<std::string> mDataVec; std::vector<std::string> mDataVec;
virtual void SetUp() virtual void SetUp()
{ {
const char* data[] = { "Head", "Chest", "Tri Head", "Tri Chest", "Bip01" }; const char* data[] = { "Head", "Chest", "Tri Head", "Tri Chest", "Bip01", "Tri Bip01" };
mDataVec = std::vector<std::string>(data, data+sizeof(data)/sizeof(data[0])); mDataVec = std::vector<std::string>(data, data+sizeof(data)/sizeof(data[0]));
std::sort(mDataVec.begin(), mDataVec.end(), Misc::StringUtils::ciLess); std::sort(mDataVec.begin(), mDataVec.end(), Misc::StringUtils::ciLess);
} }
@ -29,7 +29,15 @@ TEST_F(PartialBinarySearchTest, partial_binary_search_test)
EXPECT_TRUE( matches("Tri Head 01") ); EXPECT_TRUE( matches("Tri Head 01") );
EXPECT_TRUE( matches("Tri Head") ); EXPECT_TRUE( matches("Tri Head") );
EXPECT_TRUE( matches("tri head") ); EXPECT_TRUE( matches("tri head") );
EXPECT_TRUE( matches("Tri bip01") );
EXPECT_TRUE( matches("bip01") );
EXPECT_TRUE( matches("bip01 head") );
EXPECT_TRUE( matches("Bip01 L Hand") );
EXPECT_TRUE( matches("BIp01 r Clavicle") );
EXPECT_TRUE( matches("Bip01 SpiNe1") );
EXPECT_FALSE( matches("") );
EXPECT_FALSE( matches("Node Bip01") );
EXPECT_FALSE( matches("Hea") ); EXPECT_FALSE( matches("Hea") );
EXPECT_FALSE( matches(" Head") ); EXPECT_FALSE( matches(" Head") );
EXPECT_FALSE( matches("Tri Head") ); EXPECT_FALSE( matches("Tri Head") );

@ -12,6 +12,9 @@ environment:
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
- msvc: 2017 - msvc: 2017
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
matrix:
allow_failures:
- msvc: 2015
platform: platform:
# - Win32 # - Win32

@ -87,7 +87,7 @@ add_component_dir (esmterrain
) )
add_component_dir (misc add_component_dir (misc
utf8stream stringops resourcehelpers rng messageformatparser utf8stream stringops resourcehelpers rng debugging messageformatparser
) )
IF(NOT WIN32 AND NOT APPLE) IF(NOT WIN32 AND NOT APPLE)

@ -182,8 +182,10 @@ static void gdb_info(pid_t pid)
/* Error creating temp file */ /* Error creating temp file */
if(fd >= 0) if(fd >= 0)
{ {
close(fd); if (close(fd) == 0)
remove(respfile); remove(respfile);
else
std::cerr << "Warning: can not close and remove file '" << respfile << "': " << std::strerror(errno) << std::endl;
} }
printf("!!! Could not create gdb command file\n"); printf("!!! Could not create gdb command file\n");
} }

@ -37,6 +37,8 @@ namespace ESMTerrain
} }
LandObject::LandObject(const LandObject &copy, const osg::CopyOp &copyop) LandObject::LandObject(const LandObject &copy, const osg::CopyOp &copyop)
: mLand(nullptr)
, mLoadFlags(0)
{ {
} }

@ -0,0 +1,128 @@
#ifndef MISC_DEBUGGING_H
#define MISC_DEBUGGING_H
#include <boost/filesystem/fstream.hpp>
#include <boost/iostreams/stream.hpp>
/*
Start of tes3mp addition
Include additional headers for multiplayer purposes
*/
#include <components/openmw-mp/Log.hpp>
/*
End of tes3mp addition
*/
#include <SDL_messagebox.h>
namespace Misc
{
#if defined(_WIN32) && defined(_DEBUG)
class DebugOutput : public boost::iostreams::sink
{
public:
std::streamsize write(const char *str, std::streamsize size)
{
// Make a copy for null termination
std::string tmp (str, static_cast<unsigned int>(size));
// Write string to Visual Studio Debug output
OutputDebugString (tmp.c_str ());
return size;
}
};
#else
class Tee : public boost::iostreams::sink
{
public:
Tee(std::ostream &stream, std::ostream &stream2)
: out(stream), out2(stream2)
{
}
std::streamsize write(const char *str, std::streamsize size)
{
out.write (str, size);
out.flush();
out2.write (str, size);
out2.flush();
return size;
}
private:
std::ostream &out;
std::ostream &out2;
};
#endif
}
int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& logName)
{
// Some objects used to redirect cout and cerr
// Scope must be here, so this still works inside the catch block for logging exceptions
std::streambuf* cout_rdbuf = std::cout.rdbuf ();
std::streambuf* cerr_rdbuf = std::cerr.rdbuf ();
#if !(defined(_WIN32) && defined(_DEBUG))
boost::iostreams::stream_buffer<Misc::Tee> coutsb;
boost::iostreams::stream_buffer<Misc::Tee> cerrsb;
#endif
std::ostream oldcout(cout_rdbuf);
std::ostream oldcerr(cerr_rdbuf);
boost::filesystem::ofstream logfile;
int ret = 0;
try
{
Files::ConfigurationManager cfgMgr;
#if defined(_WIN32) && defined(_DEBUG)
// Redirect cout and cerr to VS debug output when running in debug mode
boost::iostreams::stream_buffer<Misc::DebugOutput> sb;
sb.open(Misc::DebugOutput());
std::cout.rdbuf (&sb);
std::cerr.rdbuf (&sb);
#else
// Redirect cout and cerr to the log file
logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / logName));
coutsb.open (Misc::Tee(logfile, oldcout));
cerrsb.open (Misc::Tee(logfile, oldcerr));
std::cout.rdbuf (&coutsb);
std::cerr.rdbuf (&cerrsb);
/*
Start of tes3mp addition
Initialize the logger added for multiplayer
*/
LOG_INIT(Log::LOG_INFO);
/*
End of tes3mp addition
*/
#endif
ret = innerApplication(argc, argv);
}
catch (std::exception& e)
{
#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix))
if (!isatty(fileno(stdin)))
#endif
SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL);
std::cerr << "\nERROR: " << e.what() << std::endl;
ret = 1;
}
// Restore cout and cerr
std::cout.rdbuf(cout_rdbuf);
std::cerr.rdbuf(cerr_rdbuf);
return ret;
}
#endif

@ -146,8 +146,15 @@ public:
std::string::const_iterator yit = y.begin(); std::string::const_iterator yit = y.begin();
for(;xit != x.end() && yit != y.end() && len > 0;++xit,++yit,--len) for(;xit != x.end() && yit != y.end() && len > 0;++xit,++yit,--len)
{ {
int res = *xit - *yit; char left = *xit;
if(res != 0 && toLower(*xit) != toLower(*yit)) char right = *yit;
if (left == right)
continue;
left = toLower(left);
right = toLower(right);
int res = left - right;
if(res != 0)
return (res > 0) ? 1 : -1; return (res > 0) ? 1 : -1;
} }
if(len > 0) if(len > 0)

@ -94,13 +94,16 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::NIFFilePtr&
} }
else else
{ {
bool autogenerated = hasAutoGeneratedCollision(node);
// files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see Animation::addAnimSource). // files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see Animation::addAnimSource).
// assume all nodes in the file will be animated // assume all nodes in the file will be animated
const bool isAnimated = pathFileNameStartsWithX(nif->getFilename()); const bool isAnimated = pathFileNameStartsWithX(nif->getFilename());
handleNode(node, 0, autogenerated, isAnimated, autogenerated); // If the mesh has RootCollisionNode, attached to actual root node, use it as collision mesh
const Nif::Node* rootCollisionNode = getCollisionNode(node);
if (rootCollisionNode)
handleNode(nif->getFilename(), rootCollisionNode, 0, false, isAnimated, false);
else
handleNode(nif->getFilename(), node, 0, true, isAnimated, true);
if (mCompoundShape) if (mCompoundShape)
{ {
@ -153,25 +156,34 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node, int flags)
return false; return false;
} }
bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node* rootNode) const Nif::Node* BulletNifLoader::getCollisionNode(const Nif::Node* rootNode)
{ {
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(rootNode); const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(rootNode);
if(ninode) if(ninode)
{ {
// If root NiNode has only other NiNode as child, consider it as a wrapper, not as actual root node
const Nif::NodeList &list = ninode->children; const Nif::NodeList &list = ninode->children;
for(size_t i = 0;i < list.length();i++) if (list.length() == 1 &&
rootNode->recType == Nif::RC_NiNode &&
list[0].getPtr()->recType == Nif::RC_NiNode)
{ {
if(!list[i].empty()) return getCollisionNode(list[0].getPtr());
{
if(list[i].getPtr()->recType == Nif::RC_RootCollisionNode)
return false;
} }
for(size_t i = 0; i < list.length(); i++)
{
if(list[i].empty())
continue;
const Nif::Node* childNode = list[i].getPtr();
if(childNode->recType == Nif::RC_RootCollisionNode)
return childNode;
} }
} }
return true; return nullptr;
} }
void BulletNifLoader::handleNode(const Nif::Node *node, int flags, void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *node, int flags,
bool isCollisionNode, bool isAnimated, bool autogenerated) bool isCollisionNode, bool isAnimated, bool autogenerated)
{ {
// Accumulate the flags from all the child nodes. This works for all // Accumulate the flags from all the child nodes. This works for all
@ -184,6 +196,9 @@ void BulletNifLoader::handleNode(const Nif::Node *node, int flags,
isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode); isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode);
if (node->recType == Nif::RC_RootCollisionNode && autogenerated)
std::cerr << "Found RootCollisionNode attached to non-root node in " << fileName << ". Treat it as a common NiTriShape." << std::endl;
// Don't collide with AvoidNode shapes // Don't collide with AvoidNode shapes
if(node->recType == Nif::RC_AvoidNode) if(node->recType == Nif::RC_AvoidNode)
flags |= 0x800; flags |= 0x800;
@ -212,7 +227,6 @@ void BulletNifLoader::handleNode(const Nif::Node *node, int flags,
// Marker can still have collision if the model explicitely specifies it via a RootCollisionNode. // Marker can still have collision if the model explicitely specifies it via a RootCollisionNode.
return; return;
} }
} }
} }
@ -235,7 +249,7 @@ void BulletNifLoader::handleNode(const Nif::Node *node, int flags,
for(size_t i = 0;i < list.length();i++) for(size_t i = 0;i < list.length();i++)
{ {
if(!list[i].empty()) if(!list[i].empty())
handleNode(list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated); handleNode(fileName, list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated);
} }
} }
} }

@ -55,9 +55,9 @@ public:
private: private:
bool findBoundingBox(const Nif::Node* node, int flags = 0); bool findBoundingBox(const Nif::Node* node, int flags = 0);
void handleNode(Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false); void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false);
bool hasAutoGeneratedCollision(const Nif::Node *rootNode); const Nif::Node* getCollisionNode(const Nif::Node* rootNode);
void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf& transform, bool isAnimated); void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf& transform, bool isAnimated);

@ -547,6 +547,11 @@ namespace NifOsg
node->setDataVariance(osg::Object::DYNAMIC); node->setDataVariance(osg::Object::DYNAMIC);
} }
if (nifNode->recType == Nif::RC_NiTriShape && isAnimated) // the same thing for animated NiTriShapes
{
node->setDataVariance(osg::Object::DYNAMIC);
}
osg::ref_ptr<SceneUtil::CompositeStateSetUpdater> composite = new SceneUtil::CompositeStateSetUpdater; osg::ref_ptr<SceneUtil::CompositeStateSetUpdater> composite = new SceneUtil::CompositeStateSetUpdater;
applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags); applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags);
@ -574,13 +579,11 @@ namespace NifOsg
if (composite->getNumControllers() > 0) if (composite->getNumControllers() > 0)
node->addUpdateCallback(composite); node->addUpdateCallback(composite);
// Note: NiTriShapes are not allowed to have KeyframeControllers (the vanilla engine just crashes when there is one). // Note: NiTriShapes are not allowed to have KeyframeControllers (the vanilla engine just crashes when there is one).
// We can take advantage of this constraint for optimizations later. // We can take advantage of this constraint for optimizations later.
if (!nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC) if (nifNode->recType != Nif::RC_NiTriShape && !nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC)
handleNodeControllers(nifNode, static_cast<osg::MatrixTransform*>(node.get()), animflags); handleNodeControllers(nifNode, static_cast<osg::MatrixTransform*>(node.get()), animflags);
if (nifNode->recType == Nif::RC_NiLODNode) if (nifNode->recType == Nif::RC_NiLODNode)
{ {
const Nif::NiLODNode* niLodNode = static_cast<const Nif::NiLODNode*>(nifNode); const Nif::NiLODNode* niLodNode = static_cast<const Nif::NiLODNode*>(nifNode);

@ -393,9 +393,9 @@ namespace Resource
static std::vector<std::string> reservedNames; static std::vector<std::string> reservedNames;
if (reservedNames.empty()) if (reservedNames.empty())
{ {
const char* reserved[] = {"Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm", "Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle", "Left Clavicle", "Weapon Bone", "Tail", const char* reserved[] = {"Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm",
"Bip01 L Hand", "Bip01 R Hand", "Bip01 Head", "Bip01 Spine1", "Bip01 Spine2", "Bip01 L Clavicle", "Bip01 R Clavicle", "bip01", "Root Bone", "Bip01 Neck", "Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle",
"BoneOffset", "AttachLight", "ArrowBone", "Camera"}; "Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "ArrowBone", "Camera"};
reservedNames = std::vector<std::string>(reserved, reserved + sizeof(reserved)/sizeof(reserved[0])); reservedNames = std::vector<std::string>(reserved, reserved + sizeof(reserved)/sizeof(reserved[0]));
for (unsigned int i=0; i<sizeof(reserved)/sizeof(reserved[0]); ++i) for (unsigned int i=0; i<sizeof(reserved)/sizeof(reserved[0]); ++i)

@ -130,7 +130,7 @@ enchanted weapons are magical
:Range: True/False :Range: True/False
:Default: True :Default: True
Makes enchanted weapons without Magical flag bypass normal weapons resistance (and weakness) certain creatures have. Make enchanted weapons without Magical flag bypass normal weapons resistance (and weakness) certain creatures have.
This is how original Morrowind behaves. This is how original Morrowind behaves.
This setting can only be configured by editing the settings configuration file. This setting can only be configured by editing the settings configuration file.
@ -142,7 +142,7 @@ prevent merchant equipping
:Range: True/False :Range: True/False
:Default: False :Default: False
Prevents merchants from equipping items that are sold to them. Prevent merchants from equipping items that are sold to them.
This setting can only be configured by editing the settings configuration file. This setting can only be configured by editing the settings configuration file.
@ -153,7 +153,7 @@ followers attack on sight
:Range: True/False :Range: True/False
:Default: False :Default: False
Makes player followers and escorters start combat with enemies who have started combat with them or the player. Make player followers and escorters start combat with enemies who have started combat with them or the player.
Otherwise they wait for the enemies or the player to do an attack first. Otherwise they wait for the enemies or the player to do an attack first.
Please note this setting has not been extensively tested and could have side effects with certain quests. Please note this setting has not been extensively tested and could have side effects with certain quests.
@ -171,3 +171,15 @@ For example, if the main animation mesh has name Meshes/x.nif, an engine will lo
Can be useful if you want to use several animation replacers without merging them. Can be useful if you want to use several animation replacers without merging them.
Attention: animations from AnimKit have own format and are not supposed to be directly loaded in-game! Attention: animations from AnimKit have own format and are not supposed to be directly loaded in-game!
This setting can only be configured by editing the settings configuration file. This setting can only be configured by editing the settings configuration file.
barter disposition change is permanent
--------------------------------------
:Type: boolean
:Range: True/False
:Default: False
If this setting is true, disposition change of merchants caused by trading will be permanent and won't be discarded upon exiting dialogue with them.
This imitates the option Morrowind Code Patch offers.
This setting can be toggled with a checkbox in Advanced tab of the launcher.

@ -104,25 +104,17 @@ void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* leng
output += *length; output += *length;
// Scary scary fall throughs. int lengthLeft = *length;
switch (*length) while (lengthLeft > 1)
{ {
case 4:
--output; --output;
*output = (char)((input | BYTE_MARK) & BYTE_MASK); *output = (char)((input | BYTE_MARK) & BYTE_MASK);
input >>= 6; input >>= 6;
case 3: --lengthLeft;
--output; }
*output = (char)((input | BYTE_MARK) & BYTE_MASK);
input >>= 6;
case 2:
--output;
*output = (char)((input | BYTE_MARK) & BYTE_MASK);
input >>= 6;
case 1:
--output; --output;
*output = (char)(input | FIRST_BYTE_MARK[*length]); *output = (char)(input | FIRST_BYTE_MARK[*length]);
}
} }
@ -1295,9 +1287,10 @@ const char* TiXmlUnknown::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc
++p; ++p;
} }
if ( !p ) if ( !p || !*p )
{ {
if ( document ) document->SetError( TIXML_ERROR_PARSING_UNKNOWN, 0, 0, encoding ); if ( document ) document->SetError( TIXML_ERROR_PARSING_UNKNOWN, 0, 0, encoding );
return 0;
} }
if ( *p == '>' ) if ( *p == '>' )
return p+1; return p+1;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 590 B

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

@ -5,6 +5,9 @@
<file alias="openmw.png">images/openmw.png</file> <file alias="openmw.png">images/openmw.png</file>
<file alias="openmw-plugin.png">images/openmw-plugin.png</file> <file alias="openmw-plugin.png">images/openmw-plugin.png</file>
<file alias="openmw-header.png">images/openmw-header.png</file> <file alias="openmw-header.png">images/openmw-header.png</file>
<file alias="preferences.png">images/preferences.png</file>
<file alias="preferences-advanced.png">images/preferences-advanced.png</file>
<file alias="preferences-video.png">images/preferences-video.png</file>
<file alias="playpage-background.png">images/playpage-background.png</file> <file alias="playpage-background.png">images/playpage-background.png</file>
</qresource> </qresource>
<qresource prefix="icons/tango"> <qresource prefix="icons/tango">

@ -203,25 +203,28 @@ show effect duration = false
# Account for the first follower in fast travel cost calculations. # Account for the first follower in fast travel cost calculations.
charge for every follower travelling = false charge for every follower travelling = false
# Prevents merchants from equipping items that are sold to them. # Prevent merchants from equipping items that are sold to them.
prevent merchant equipping = false prevent merchant equipping = false
# Make enchanted weaponry without Magical flag bypass normal weapons resistance # Make enchanted weaponry without Magical flag bypass normal weapons resistance
enchanted weapons are magical = true enchanted weapons are magical = true
# Makes player followers and escorters start combat with enemies who have started combat with them # Make player followers and escorters start combat with enemies who have started combat with them
# or the player. Otherwise they wait for the enemies or the player to do an attack first. # or the player. Otherwise they wait for the enemies or the player to do an attack first.
followers attack on sight = false followers attack on sight = false
# Can loot non-fighting actors during death animation # Can loot non-fighting actors during death animation
can loot during death animation = true can loot during death animation = true
# Makes the value of filled soul gems dependent only on soul magnitude (with formula from the Morrowind Code Patch) # Make the value of filled soul gems dependent only on soul magnitude (with formula from the Morrowind Code Patch)
rebalance soul gem values = false rebalance soul gem values = false
# Allow to load per-group KF-files from Animations folder # Allow to load per-group KF-files from Animations folder
use additional anim sources = false use additional anim sources = false
# Make the disposition change of merchants caused by barter dealings permanent
barter disposition change is permanent = false
[General] [General]
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).

@ -21,8 +21,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>630</width> <width>641</width>
<height>791</height> <height>998</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="scrollAreaVerticalLayout"> <layout class="QVBoxLayout" name="scrollAreaVerticalLayout">
@ -45,7 +45,7 @@
<item> <item>
<widget class="QCheckBox" name="followersAttackOnSightCheckBox"> <widget class="QCheckBox" name="followersAttackOnSightCheckBox">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Makes player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Followers attack on sight</string> <string>Followers attack on sight</string>
@ -65,13 +65,43 @@
<item> <item>
<widget class="QCheckBox" name="rebalanceSoulGemValuesCheckBox"> <widget class="QCheckBox" name="rebalanceSoulGemValuesCheckBox">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this setting is true, the value of filled soul gems is dependent only on soul magnitude.&lt;/p&gt;&lt;p&gt;The default value is false.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make the value of filled soul gems dependent only on soul magnitude.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Rebalance soul gem values</string> <string>Rebalance soul gem values</string>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="chargeForEveryFollowerCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Account for the first follower in fast travel cost calculations.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Charge for every follower travelling</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="enchantedWeaponsMagicalCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make enchanted weaponry without Magical flag bypass normal weapons resistance, like in Morrowind.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enchanted weapons are magical</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="permanentBarterDispositionChangeCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Make disposition change of merchants caused by trading permanent.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Barter disposition change is permanent</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -137,7 +167,7 @@
</property> </property>
<layout class="QHBoxLayout" name="maximumQuicksavesLayout"> <layout class="QHBoxLayout" name="maximumQuicksavesLayout">
<property name="spacing"> <property name="spacing">
<number>-1</number> <number>6</number>
</property> </property>
<property name="leftMargin"> <property name="leftMargin">
<number>0</number> <number>0</number>
@ -312,7 +342,7 @@
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing"> <property name="spacing">
<number>-1</number> <number>6</number>
</property> </property>
<property name="leftMargin"> <property name="leftMargin">
<number>0</number> <number>0</number>
@ -379,7 +409,7 @@
</property> </property>
<layout class="QHBoxLayout" name="screenshotFormatLayout"> <layout class="QHBoxLayout" name="screenshotFormatLayout">
<property name="spacing"> <property name="spacing">
<number>-1</number> <number>6</number>
</property> </property>
<property name="leftMargin"> <property name="leftMargin">
<number>0</number> <number>0</number>

Loading…
Cancel
Save