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
fix/skillcap
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 #2455: Creatures attacks degrade armor
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 #2835: Player able to slowly move when overencumbered
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 #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 #3249: Fixed revert function not updating views properly
Bug #3374: Touch spells not hitting kwama foragers
@ -20,6 +22,7 @@
Bug #3876: Landscape texture painting is misaligned
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 #3950: FLATTEN_STATIC_TRANSFORMS optimization breaks animated collision shapes
Bug #3993: Terrain texture blending map is not upscaled
Bug #3997: Almalexia doesn't pace
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 #4215: OpenMW shows book text after last EOL tag
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 #4274: Pre-0.43 death animations are not forward-compatible with 0.43+
Bug #4286: Scripted animations can be interrupted
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 #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 #4358: Running animation is interrupted when magic mode is toggled
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 #4416: Handle exception if we try to play non-music file
Bug #4419: MRK NiStringExtraData is handled incorrectly
@ -62,17 +68,32 @@
Bug #4480: Segfault in QuickKeysMenu when item no longer in inventory
Bug #4489: Goodbye doesn't block dialogue hyperlinks
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 #4496: SpellTurnLeft and SpellTurnRight animation groups are unused
Bug #4497: File names starting with x or X are not classified as animation
Bug #4503: Cast and ExplodeSpell commands increase alteration skill
Bug #4510: Division by zero in MWMechanics::CreatureStats::setAttribute
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 #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 #3703: Ranged sneak attack criticals
Feature #4012: Editor: Write a log file if OpenCS crashes
Feature #4222: 360° screenshots
Feature #4256: Implement ToggleBorders (TB) console command
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 #4466: Editor: Add option to ignore "Base" records when running verifier
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 #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 #4545: Enable is_pod string test
0.44.0
------

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

@ -43,7 +43,6 @@ void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked()
QDir::currentPath(),
QString(tr("Text file (*.txt)")));
if (scriptFile.isEmpty())
return;
@ -53,7 +52,7 @@ void Launcher::AdvancedPage::on_runScriptAfterStartupBrowseButton_clicked()
return;
const QString path(QDir::toNativeSeparators(info.absoluteFilePath()));
runScriptAfterStartupField->setText(path);
}
bool Launcher::AdvancedPage::loadSettings()
@ -74,6 +73,9 @@ bool Launcher::AdvancedPage::loadSettings()
loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
loadSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "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
loadSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input");
@ -126,6 +128,9 @@ void Launcher::AdvancedPage::saveSettings()
saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
saveSettingBool(preventMerchantEquippingCheckBox, "prevent merchant equipping", "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
saveSettingBool(allowThirdPersonZoomCheckBox, "allow third person zoom", "Input");
@ -168,4 +173,4 @@ void Launcher::AdvancedPage::saveSettingBool(QCheckBox *checkbox, const std::str
void Launcher::AdvancedPage::slotLoadedCellsChanged(QStringList cellNames)
{
loadCellsForAutocomplete(cellNames);
}
}

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

@ -8,8 +8,9 @@
#include <QIcon>
#include <QMetaType>
#include "model/doc/messages.hpp"
#include <components/misc/debugging.hpp>
#include "model/doc/messages.hpp"
#include "model/world/universalid.hpp"
#ifdef Q_OS_MAC
@ -41,45 +42,43 @@ class Application : public QApplication
Application (int& argc, char *argv[]) : QApplication (argc, argv) {}
};
int main(int argc, char *argv[])
int runApplication(int argc, char *argv[])
{
#ifdef Q_OS_MAC
setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0);
#endif
#ifdef Q_OS_MAC
setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0);
#endif
try
{
// To allow background thread drawing in OSG
QApplication::setAttribute(Qt::AA_X11InitThreads, true);
// To allow background thread drawing in OSG
QApplication::setAttribute(Qt::AA_X11InitThreads, true);
Q_INIT_RESOURCE (resources);
Q_INIT_RESOURCE (resources);
qRegisterMetaType<std::string> ("std::string");
qRegisterMetaType<CSMWorld::UniversalId> ("CSMWorld::UniversalId");
qRegisterMetaType<CSMDoc::Message> ("CSMDoc::Message");
qRegisterMetaType<std::string> ("std::string");
qRegisterMetaType<CSMWorld::UniversalId> ("CSMWorld::UniversalId");
qRegisterMetaType<CSMDoc::Message> ("CSMDoc::Message");
Application application (argc, argv);
Application application (argc, argv);
#ifdef Q_OS_MAC
QDir dir(QCoreApplication::applicationDirPath());
QDir::setCurrent(dir.absolutePath());
#endif
#ifdef Q_OS_MAC
QDir dir(QCoreApplication::applicationDirPath());
QDir::setCurrent(dir.absolutePath());
#endif
application.setWindowIcon (QIcon (":./openmw-cs.png"));
application.setWindowIcon (QIcon (":./openmw-cs.png"));
CS::Editor editor(argc, argv);
CS::Editor editor(argc, argv);
if(!editor.makeIPCServer())
{
editor.connectToIPCServer();
return 0;
}
return editor.run();
}
catch (std::exception& e)
if(!editor.makeIPCServer())
{
std::cerr << "ERROR: " << e.what() << std::endl;
editor.connectToIPCServer();
return 0;
}
return editor.run();
}
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)
, mEncoding(ToUTF8::WINDOWS_1252)
, mEncoder(NULL)
, mScreenCaptureOperation(nullptr)
, mSkipMenu (false)
, mUseSound (true)
, mCompileAll (false)

@ -5,12 +5,10 @@
#include <components/files/configurationmanager.hpp>
#include <components/files/escape.hpp>
#include <components/fallback/validate.hpp>
#include <components/misc/debugging.hpp>
#include <SDL_messagebox.h>
#include "engine.hpp"
#include <boost/filesystem/fstream.hpp>
/*
Start of tes3mp addition
@ -331,43 +329,25 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
return true;
}
#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
int runApplication(int argc, char *argv[])
{
public:
Tee(std::ostream &stream, std::ostream &stream2)
: out(stream), out2(stream2)
{
}
#ifdef __APPLE__
boost::filesystem::path binary_path = boost::filesystem::system_complete(boost::filesystem::path(argv[0]));
boost::filesystem::current_path(binary_path.parent_path());
setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0);
#endif
Files::ConfigurationManager cfgMgr;
std::unique_ptr<OMW::Engine> engine;
engine.reset(new OMW::Engine(cfgMgr));
std::streamsize write(const char *str, std::streamsize size)
if (parseOptions(argc, argv, *engine, cfgMgr))
{
out.write (str, size);
out.flush();
out2.write (str, size);
out2.flush();
return size;
engine->go();
}
private:
std::ostream &out;
std::ostream &out2;
};
#endif
return 0;
}
#ifdef ANDROID
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)
#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)
Instead of logging information in openmw.log, use a more descriptive filename
that includes a timestamp
*/
// Redirect cout and cerr to tes3mp client log
logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / "/tes3mp-client-" += Log::getFilenameTimestamp() += ".log"));
/*
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);
/*
Start of tes3mp change (major)
return ret;
Instead of logging information in openmw.log, use a more descriptive filename
that includes a timestamp
*/
return wrapApplication(&runApplication, argc, argv, "/tes3mp-client-" + Log::getFilenameTimestamp() + ".log");
/*
End of tes3mp change (major)
*/
}
// 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 int getTemporaryDispositionChange () const = 0;
/// @note This change is temporary and gets discarded when dialogue ends.
virtual void applyDispositionChange (int delta) = 0;
/// @note Controlled by an option, gets discarded when dialogue ends by default
virtual void applyBarterDispositionChange (int delta) = 0;
virtual int countSavedGameRecords() const = 0;

@ -55,6 +55,8 @@ namespace MWBase
virtual void endGame() = 0;
virtual void resumeGame() = 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;

@ -445,7 +445,7 @@ namespace MWBase
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.
virtual bool toggleCollisionMode() = 0;

@ -747,7 +747,7 @@ namespace MWClass
float Creature::getCapacity (const MWWorld::Ptr& ptr) const
{
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

@ -1250,7 +1250,7 @@ namespace MWClass
{
const MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
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

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

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

@ -316,6 +316,14 @@ namespace MWGui
bool has_front_quote = false;
/* 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? */
if( tmp.find('"') != std::string::npos ) {
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. */
output.erase(output.end()-tmp.length(), output.end());

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

@ -362,52 +362,59 @@ namespace MWGui
if (mGoodbye || MWBase::Environment::get().getDialogueManager()->isInChoice())
return;
int separatorPos = 0;
for (unsigned int i=0; i<mTopicsList->getItemCount(); ++i)
{
if (mTopicsList->getItemNameAt(i) == "")
separatorPos = i;
}
if (id >= separatorPos)
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
const std::string sPersuasion = gmst.find("sPersuasion")->getString();
const std::string sCompanionShare = gmst.find("sCompanionShare")->getString();
const std::string sBarter = gmst.find("sBarter")->getString();
const std::string sSpells = gmst.find("sSpells")->getString();
const std::string sTravel = gmst.find("sTravel")->getString();
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);
if (mGoodbyeButton->getEnabled())
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mGoodbyeButton);
}
else
else if (topic == sPersuasion)
mPersuasionDialog.setVisible(true);
else if (topic == sCompanionShare)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Companion, mPtr);
else if (!MWBase::Environment::get().getDialogueManager()->checkServiceRefused(mCallback.get()))
{
const MWWorld::Store<ESM::GameSetting> &gmst =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
if (topic == gmst.find("sPersuasion")->getString())
mPersuasionDialog.setVisible(true);
else if (topic == gmst.find("sCompanionShare")->getString())
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Companion, mPtr);
else if (!MWBase::Environment::get().getDialogueManager()->checkServiceRefused(mCallback.get()))
{
if (topic == gmst.find("sBarter")->getString())
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Barter, mPtr);
else if (topic == gmst.find("sSpells")->getString())
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellBuying, mPtr);
else if (topic == gmst.find("sTravel")->getString())
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Travel, mPtr);
else if (topic == gmst.find("sSpellMakingMenuTitle")->getString())
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellCreation, mPtr);
else if (topic == gmst.find("sEnchanting")->getString())
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting, mPtr);
else if (topic == gmst.find("sServiceTrainingTitle")->getString())
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Training, mPtr);
else if (topic == gmst.find("sRepair")->getString())
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_MerchantRepair, mPtr);
}
else
updateTopics();
if (topic == sBarter)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Barter, mPtr);
else if (topic == sSpells)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellBuying, mPtr);
else if (topic == sTravel)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Travel, mPtr);
else if (topic == sSpellMakingMenuTitle)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_SpellCreation, mPtr);
else if (topic == sEnchanting)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Enchanting, mPtr);
else if (topic == sServiceTrainingTitle)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Training, mPtr);
else if (topic == sRepair)
MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_MerchantRepair, mPtr);
}
else
updateTopics();
}
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);
if (!sameActor)
{

@ -81,7 +81,12 @@ namespace MWGui
, mLastYSize(0)
, mPreview(new MWRender::InventoryPreview(parent, resourceSystem, MWMechanics::getPlayer()))
, 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()));
mPreview->rebuild();
@ -445,10 +450,10 @@ namespace MWGui
MyGUI::IntSize size = mAvatarImage->getSize();
int width = std::min(mPreview->getTextureWidth(), size.width);
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,
width/float(mPreview->getTextureWidth()), height/float(mPreview->getTextureHeight())));
width*mScaleFactor/float(mPreview->getTextureWidth()), height*mScaleFactor/float(mPreview->getTextureHeight())));
}
void InventoryWindow::onFilterChanged(MyGUI::Widget* _sender)
@ -615,6 +620,11 @@ namespace MWGui
{
// convert to OpenGL lower-left origin
y = (mAvatarImage->getHeight()-1) - y;
// Scale coordinates
x = int(x*mScaleFactor);
y = int(y*mScaleFactor);
int slot = mPreview->getSlotSelected (x, y);
if (slot == -1)

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

@ -37,6 +37,7 @@ namespace MWGui
, mLastRenderTime(0.0)
, mLoadingOnTime(0.0)
, mImportantLabel(false)
, mVisible(false)
, mProgress(0)
, 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()
mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback);
mShowWallpaper = visible && (MWBase::Environment::get().getStateManager()->getState()
== MWBase::StateManager::State_NoGame);
if (!visible)
{
draw();
return;
}
mVisible = visible;
mLoadingBox->setVisible(mVisible);
mShowWallpaper = mVisible && (MWBase::Environment::get().getStateManager()->getState()
== MWBase::StateManager::State_NoGame);
setVisible(true);
if (mShowWallpaper)
@ -183,9 +190,6 @@ namespace MWGui
}
MWBase::Environment::get().getWindowManager()->pushGuiMode(mShowWallpaper ? GM_LoadingWallpaper : GM_Loading);
if (!mVisible)
draw();
}
void LoadingScreen::loadingOff()

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

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

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

@ -154,7 +154,7 @@ namespace MWGui
return;
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}");
return;

@ -46,7 +46,7 @@ namespace MWGui
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;
@ -56,7 +56,7 @@ namespace MWGui
MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);
if(interior)
if (!mPtr.getCell()->isExterior())
{
price = gmst.find("fMagesGuildTravel")->getInt();
}
@ -168,7 +168,7 @@ namespace MWGui
ESM::Position pos = *_sender->getUserData<ESM::Position>();
std::string cellname = _sender->getUserString("Destination");
bool interior = _sender->getUserString("interior") == "y";
if (!interior)
if (mPtr.getCell()->isExterior())
{
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();

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

@ -805,6 +805,8 @@ namespace MWMechanics
for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it)
{
bool actorKilled = false;
const ActiveSpells::ActiveSpellParams& spell = it->second;
MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId);
for (std::vector<ActiveSpells::ActiveEffect>::const_iterator effectIt = spell.mEffects.begin();
@ -832,10 +834,14 @@ namespace MWMechanics
caster.getClass().getNpcStats(caster).addWerewolfKill();
MWBase::Environment::get().getMechanicsManager()->actorKilled(ptr, player);
actorKilled = true;
break;
}
}
}
if (actorKilled)
break;
}
}
@ -1377,6 +1383,8 @@ namespace MWMechanics
// AI and magic effects update
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();
// 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)
@ -1390,7 +1398,7 @@ namespace MWMechanics
Instead of merely updating the player character's mAttackingOrSpell here,
prepare an Attack packet for the LocalPlayer
*/
if (iter->first == player)
if (isPlayer)
{
bool state = MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell();
DrawState_ dstate = player.getClass().getNpcStats(player).getDrawState();
@ -1478,12 +1486,12 @@ namespace MWMechanics
{
if (timerUpdateAITargets == 0 && (isLocalActor || isAIActive))
{
if (iter->first != player)
if (!isPlayer)
adjustCommandedActor(iter->first);
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;
engageCombat(iter->first, it->first, cachedAllies, it->first == player);
}
@ -1494,12 +1502,15 @@ namespace MWMechanics
MWWorld::Ptr headTrackTarget;
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
bool firstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson();
// Unconsious actor can not track target
// Also actors in combat and pursue mode do not bother to headtrack
// 1. Unconsious actor can not track target
// 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() &&
!stats.getAiSequence().isInCombat() &&
!stats.getAiSequence().hasPackage(AiPackage::TypeIdPursue))
!stats.getAiSequence().hasPackage(AiPackage::TypeIdPursue) &&
!firstPersonPlayer)
{
for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it)
{

@ -494,10 +494,13 @@ namespace MWMechanics
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())
{
static const int iWereWolfFleeMod = gmst.find("iWereWolfFleeMod")->getInt();
rating = iWereWolfFleeMod;
if (enemy.getClass().getNpcStats(enemy).isWerewolf() && stats.getLevel() < iWereWolfLevelToAttack)
{
static const int iWereWolfFleeMod = gmst.find("iWereWolfFleeMod")->getInt();
rating = iWereWolfFleeMod;
}
}
if (rating != 0.0f)

@ -53,7 +53,23 @@ namespace MWMechanics
if (!isWithinMaxRange(osg::Vec3f(mX, mY, mZ), pos.asVec3()))
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;
return true;

@ -1272,8 +1272,10 @@ bool CharacterController::updateWeaponState()
mWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr();
}
// Apply 1st-person weapon animations only for upper body
MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon);
priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody;
if (mPtr != MWMechanics::getPlayer() || !MWBase::Environment::get().getWorld()->isFirstPerson())
priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody;
bool forcestateupdate = false;
@ -1281,11 +1283,11 @@ bool CharacterController::updateWeaponState()
bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell &&
mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell;
if(weaptype != mWeaponType && !isKnockedOut() &&
!isKnockedDown() && !isRecovery())
if(!isKnockedOut() && !isKnockedDown() && !isRecovery())
{
std::string weapgroup;
if ((!isWerewolf || mWeaponType != WeapType_Spell)
&& weaptype != mWeaponType
&& mUpperBodyState != UpperCharState_UnEquipingWeap
&& !isStillWeapon)
{
@ -1310,49 +1312,60 @@ bool CharacterController::updateWeaponState()
float complete;
bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
if (!animPlaying || complete >= 1.0f)
{
forcestateupdate = true;
mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype));
// Weapon is changed, no current animation (e.g. unequipping or attack).
// Start equipping animation now.
if (weaptype != mWeaponType)
{
forcestateupdate = true;
mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype));
getWeaponGroup(weaptype, weapgroup);
mAnimation->setWeaponGroup(weapgroup);
getWeaponGroup(weaptype, weapgroup);
mAnimation->setWeaponGroup(weapgroup);
if (!isStillWeapon)
{
if (weaptype == WeapType_None)
if (!isStillWeapon)
{
// Disable current weapon animation manually
mAnimation->disable(mCurrentWeapon);
if (weaptype != WeapType_None)
{
mAnimation->showWeapons(false);
mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
1.0f, "equip start", "equip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_EquipingWeap;
}
}
else
if(isWerewolf)
{
mAnimation->showWeapons(false);
mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
1.0f, "equip start", "equip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_EquipingWeap;
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfEquip");
if(sound)
{
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f);
}
}
}
if(isWerewolf)
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfEquip");
if(sound)
mWeaponType = weaptype;
getWeaponGroup(mWeaponType, mCurrentWeapon);
if(!upSoundId.empty() && !isStillWeapon)
{
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f);
sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f);
}
}
mWeaponType = weaptype;
getWeaponGroup(mWeaponType, mCurrentWeapon);
if(!upSoundId.empty() && !isStillWeapon)
// Make sure that we disabled unequipping animation
if (mUpperBodyState == UpperCharState_UnEquipingWeap)
{
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f);
mUpperBodyState = UpperCharState_Nothing;
mAnimation->disable(mCurrentWeapon);
mWeaponType = WeapType_None;
getWeaponGroup(mWeaponType, mCurrentWeapon);
}
}
}
@ -1407,14 +1420,7 @@ bool CharacterController::updateWeaponState()
{
MWWorld::Ptr player = getPlayer();
// We should reset player's idle animation in the first-person mode.
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;
bool resetIdle = ammunition;
if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block))
{
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
@ -1498,6 +1504,11 @@ bool CharacterController::updateWeaponState()
0.0f, 0);
mUpperBodyState = UpperCharState_CastingSpell;
}
else
{
resetIdle = false;
}
if (mPtr.getClass().hasInventoryStore(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);
if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown())
mAttackStrength = complete;
@ -1741,18 +1760,22 @@ bool CharacterController::updateWeaponState()
break;
}
// Note: apply reload animations only for upper body since blending with movement animations can give weird result.
// Especially noticable with crossbow reload animation.
// Note: apply crossbow reload animation only for upper body
// since blending with movement animations can give weird result.
if(!start.empty())
{
int mask = MWRender::Animation::BlendMask_All;
if (mWeaponType == WeapType_Crossbow)
mask = MWRender::Animation::BlendMask_UpperBody;
mAnimation->disable(mCurrentWeapon);
if (mUpperBodyState == UpperCharState_FollowStartToFollowStop)
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_UpperBody, true,
mask, true,
weapSpeed, start, stop, 0.0f, 0);
else
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_UpperBody, false,
mask, false,
weapSpeed, start, stop, 0.0f, 0);
}
}
@ -2091,25 +2114,37 @@ void CharacterController::update(float duration)
else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson()))
{
if(rot.z() > 0.0f)
{
movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight;
mAnimation->disable(mCurrentJump);
}
else if(rot.z() < 0.0f)
{
movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft;
mAnimation->disable(mCurrentJump);
}
}
}
mTurnAnimationThreshold -= duration;
if (isTurning())
mTurnAnimationThreshold = 0.05f;
else if (movestate == CharState_None && isTurning()
&& mTurnAnimationThreshold > 0)
// 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
{
movestate = mMovementState;
mTurnAnimationThreshold -= duration;
if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft ||
movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft)
{
mTurnAnimationThreshold = 0.05f;
}
else if (movestate == CharState_None && isTurning()
&& mTurnAnimationThreshold > 0)
{
movestate = mMovementState;
}
}
if(movestate != CharState_None && !isTurning())
@ -2140,8 +2175,10 @@ void CharacterController::update(float duration)
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)
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)
{

@ -31,8 +31,10 @@ enum Priority {
Priority_WeaponLowerBody,
Priority_SneakIdleLowerBody,
Priority_SwimIdle,
Priority_Jump,
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_Weapon,
Priority_Block,

@ -282,8 +282,21 @@ namespace MWMechanics
adjustWeaponDamage(damage, weapon, attacker);
if(attacker == getPlayer())
if (attacker == getPlayer())
{
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())
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();
// [-100, 100]
// [-500, 500]
int difficultySetting = Settings::Manager::getInt("difficulty", "Game");
difficultySetting = std::min(difficultySetting, 500);
difficultySetting = std::max(difficultySetting, -500);
/*
Start of tes3mp change (major)

@ -231,6 +231,28 @@ namespace MWMechanics
{
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
for (std::list<ESM::Pathgrid::Point>::iterator iter(mPath.begin()); iter != mPath.end(); ++iter)
{

@ -332,14 +332,14 @@ namespace MWMechanics
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)
, mTarget(target)
, mStack(false)
, mHitPosition(0,0,0)
, mAlwaysSucceed(false)
, mFromProjectile(fromProjectile)
, mIsScripted(isScripted)
, mManualSpell(manualSpell)
{
}
@ -437,9 +437,9 @@ namespace MWMechanics
if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer))
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
&& (caster.isEmpty() || !caster.getClass().isActor()))
&& (caster.isEmpty() || !caster.getClass().isActor() || caster == target))
continue;
// 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
// 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)
{
@ -983,7 +983,7 @@ namespace MWMechanics
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);
@ -1173,7 +1173,7 @@ namespace MWMechanics
bool CastSpell::spellIncreasesSkill()
{
if (mIsScripted)
if (mManualSpell)
return false;
return MWMechanics::spellIncreasesSkill(mId);

@ -88,10 +88,10 @@ namespace MWMechanics
osg::Vec3f mHitPosition; // Used for spawning area orb
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 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:
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);

@ -30,7 +30,7 @@ namespace MWMechanics
return 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)
{
@ -44,25 +44,33 @@ namespace MWMechanics
if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(enemy), 0.75f))
return 0.f;
bonus+=1.5f;
if (getDistanceMinusHalfExtents(actor, enemy) >= getMaxAttackDistance(enemy))
rangedMult = 1.5f;
}
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)
MWMechanics::resistNormalWeapon(enemy, actor, item, rating);
}
else
{
float meleeDamage = 0.f;
for (int i=0; i<2; ++i)
{
rating += weapon->mData.mSlash[i];
rating += weapon->mData.mThrust[i];
rating += weapon->mData.mChop[i];
meleeDamage += weapon->mData.mSlash[i];
meleeDamage += weapon->mData.mThrust[i];
meleeDamage += weapon->mData.mChop[i];
}
rating /= 6.f;
MWMechanics::adjustWeaponDamage(meleeDamage, item, actor);
rating = meleeDamage / 6.f;
MWMechanics::resistNormalWeapon(enemy, actor, item, rating);
}
@ -71,7 +79,6 @@ namespace MWMechanics
{
if (item.getClass().getItemHealth(item) == 0)
return 0.f;
rating *= item.getClass().getItemHealth(item) / float(item.getClass().getItemMaxHealth(item));
}
if (weapon->mData.mType == ESM::Weapon::MarksmanBow)
@ -103,13 +110,12 @@ namespace MWMechanics
int skill = item.getClass().getEquipmentSkill(item);
if (skill != -1)
rating *= actor.getClass().getSkill(actor, skill) / 100.f;
// There is no need to apply bonus if weapon rating == 0
if (rating == 0.f)
return 0.f;
{
int value = actor.getClass().getSkill(actor, skill);
rating *= MWMechanics::getHitChance(actor, enemy, value) / 100.f;
}
return rating + bonus;
return rating * rangedMult;
}
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());
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->getCollisionShape());
for (std::map<int, int>::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it)
{
int recIndex = it->first;
@ -640,6 +639,9 @@ namespace MWPhysics
if (!visitor.mFound)
{
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;
}
osg::NodePath nodePath = visitor.mFoundPath;

@ -203,6 +203,7 @@ namespace MWRender
, mNightEyeFactor(0.f)
, mDistantFog(false)
, mDistantTerrain(false)
, mFieldOfViewOverridden(false)
, mFieldOfViewOverride(0.f)
, mBorders(false)
{
@ -553,8 +554,8 @@ namespace MWRender
mLandFogStart = mViewDistance * (1 - fogDepth);
mLandFogEnd = mViewDistance;
}
mUnderwaterFogStart = mViewDistance * (1 - underwaterFog);
mUnderwaterFogEnd = mViewDistance;
mUnderwaterFogStart = std::min(mViewDistance, 6666.f) * (1 - underwaterFog);
mUnderwaterFogEnd = std::min(mViewDistance, 6666.f);
}
mFogColor = color;
}
@ -584,8 +585,6 @@ namespace MWRender
mCurrentCameraPos = cameraPos;
if (mWater->isUnderwater(cameraPos))
{
float viewDistance = mViewDistance;
viewDistance = std::min(viewDistance, 6666.f);
setFogColor(mUnderwaterColor * mUnderwaterWeight + mFogColor * (1.f-mUnderwaterWeight));
mStateUpdater->setFogStart(mUnderwaterFogStart);
mStateUpdater->setFogEnd(mUnderwaterFogEnd);
@ -691,9 +690,6 @@ namespace MWRender
int screenshotW = mViewer->getCamera()->getViewport()->width();
int screenshotH = mViewer->getCamera()->getViewport()->height();
int screenshotMapping = 0;
int cubeSize = screenshotMapping == 2 ?
screenshotW: // planet mapping needs higher resolution
screenshotW / 2;
std::vector<std::string> settingArgs;
boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" "));
@ -717,7 +713,10 @@ namespace MWRender
return false;
}
}
// planet mapping needs higher resolution
int cubeSize = screenshotMapping == 2 ? screenshotW : screenshotW / 2;
if (settingArgs.size() > 1)
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)
, mEnabled(true)
, mSunEnabled(true)
, mWeatherAlpha(0.f)
{
osg::ref_ptr<CameraRelativeTransform> skyroot (new CameraRelativeTransform);
skyroot->setName("Sky Root");

@ -1,3 +1,5 @@
#include <iostream>
#include "dialogueextensions.hpp"
/*
@ -181,6 +183,14 @@ namespace MWScript
if (!ptr.getRefData().isEnabled())
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)

@ -28,6 +28,7 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
@ -1260,7 +1261,11 @@ namespace MWScript
MWWorld::Ptr ptr = R()(runtime);
if (ptr == MWMechanics::getPlayer())
{
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())
{
bool wasEnabled = ptr.getRefData().isEnabled();

@ -142,16 +142,12 @@ namespace MWSound
float volume, min, max;
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 = sound->mData.mMinRange;
max = sound->mData.mMaxRange;
if (min == 0)
min = fAudioDefaultMinDistance;
if (max == 0)
max = fAudioDefaultMaxDistance;
}
else
{
min = sound->mData.mMinRange;
max = sound->mData.mMaxRange;
}
min *= fAudioMinDistanceMult;
max *= fAudioMaxDistanceMult;

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

@ -48,6 +48,8 @@ namespace MWState
virtual void endGame();
virtual void resumeGame();
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.

@ -1373,9 +1373,9 @@ namespace MWWorld
addContainerScripts (getPlayerPtr(), newCell);
newPtr = getPlayerPtr();
}
else
else if (currCell)
{
bool currCellActive = currCell && mWorldScene->isCellActive(*currCell);
bool currCellActive = mWorldScene->isCellActive(*currCell);
bool newCellActive = newCell && mWorldScene->isCellActive(*newCell);
if (!currCellActive && newCellActive)
{
@ -1708,12 +1708,16 @@ namespace MWWorld
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 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;
}
@ -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.
std::vector<MWWorld::Ptr> targetActors;
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();
@ -3187,7 +3191,6 @@ namespace MWWorld
// Actors that are targeted by this actor's Follow or Escort packages also side with them
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)
{
if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCast)

@ -535,7 +535,7 @@ namespace MWWorld
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.
bool toggleCollisionMode() override;

@ -64,7 +64,7 @@ TEST(EsmFixedString, struct_size)
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::NAME32>::value);

@ -7,7 +7,7 @@ struct PartialBinarySearchTest : public ::testing::Test
std::vector<std::string> mDataVec;
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]));
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") );
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(" Head") );
EXPECT_FALSE( matches("Tri Head") );

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

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

@ -182,8 +182,10 @@ static void gdb_info(pid_t pid)
/* Error creating temp file */
if(fd >= 0)
{
close(fd);
remove(respfile);
if (close(fd) == 0)
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");
}

@ -37,6 +37,8 @@ namespace ESMTerrain
}
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();
for(;xit != x.end() && yit != y.end() && len > 0;++xit,++yit,--len)
{
int res = *xit - *yit;
if(res != 0 && toLower(*xit) != toLower(*yit))
char left = *xit;
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;
}
if(len > 0)

@ -94,13 +94,16 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::NIFFilePtr&
}
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).
// assume all nodes in the file will be animated
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)
{
@ -153,25 +156,34 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node, int flags)
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);
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;
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())
{
if(list[i].getPtr()->recType == Nif::RC_RootCollisionNode)
return false;
}
return getCollisionNode(list[0].getPtr());
}
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)
{
// 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);
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
if(node->recType == Nif::RC_AvoidNode)
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.
return;
}
}
}
@ -235,7 +249,7 @@ void BulletNifLoader::handleNode(const Nif::Node *node, int flags,
for(size_t i = 0;i < list.length();i++)
{
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:
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);

@ -547,6 +547,11 @@ namespace NifOsg
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;
applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags);
@ -574,13 +579,11 @@ namespace NifOsg
if (composite->getNumControllers() > 0)
node->addUpdateCallback(composite);
// 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.
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);
if (nifNode->recType == Nif::RC_NiLODNode)
{
const Nif::NiLODNode* niLodNode = static_cast<const Nif::NiLODNode*>(nifNode);

@ -393,9 +393,9 @@ namespace Resource
static std::vector<std::string> reservedNames;
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",
"Bip01 L Hand", "Bip01 R Hand", "Bip01 Head", "Bip01 Spine1", "Bip01 Spine2", "Bip01 L Clavicle", "Bip01 R Clavicle", "bip01", "Root Bone", "Bip01 Neck",
"BoneOffset", "AttachLight", "ArrowBone", "Camera"};
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", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "ArrowBone", "Camera"};
reservedNames = std::vector<std::string>(reserved, reserved + sizeof(reserved)/sizeof(reserved[0]));
for (unsigned int i=0; i<sizeof(reserved)/sizeof(reserved[0]); ++i)

@ -130,7 +130,7 @@ enchanted weapons are magical
:Range: True/False
: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 setting can only be configured by editing the settings configuration file.
@ -142,7 +142,7 @@ prevent merchant equipping
:Range: True/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.
@ -153,7 +153,7 @@ followers attack on sight
:Range: True/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.
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.
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.
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;
// Scary scary fall throughs.
switch (*length)
{
case 4:
--output;
*output = (char)((input | BYTE_MARK) & BYTE_MASK);
input >>= 6;
case 3:
--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 = (char)(input | FIRST_BYTE_MARK[*length]);
}
int lengthLeft = *length;
while (lengthLeft > 1)
{
--output;
*output = (char)((input | BYTE_MARK) & BYTE_MASK);
input >>= 6;
--lengthLeft;
}
--output;
*output = (char)(input | FIRST_BYTE_MARK[*length]);
}
@ -1295,9 +1287,10 @@ const char* TiXmlUnknown::Parse( const char* p, TiXmlParsingData* data, TiXmlEnc
++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 == '>' )
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-plugin.png">images/openmw-plugin.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>
</qresource>
<qresource prefix="icons/tango">

@ -203,25 +203,28 @@ show effect duration = false
# Account for the first follower in fast travel cost calculations.
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
# Make enchanted weaponry without Magical flag bypass normal weapons resistance
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.
followers attack on sight = false
# Can loot non-fighting actors during death animation
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
# Allow to load per-group KF-files from Animations folder
use additional anim sources = false
# Make the disposition change of merchants caused by barter dealings permanent
barter disposition change is permanent = false
[General]
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).

@ -194,9 +194,9 @@ void main(void)
float bump = mix(BUMP,BUMP_RAIN,rainIntensity);
vec3 normal = (normal0 * bigWaves.x + normal1 * bigWaves.y +
normal2 * midWaves.x + normal3 * midWaves.y +
normal4 * smallWaves.x + normal5 * smallWaves.y +
rippleAdd);
normal2 * midWaves.x + normal3 * midWaves.y +
normal4 * smallWaves.x + normal5 * smallWaves.y +
rippleAdd);
normal = normalize(vec3(normal.x * bump, normal.y * bump, normal.z));
@ -204,9 +204,9 @@ void main(void)
// normal for sunlight scattering
vec3 lNormal = (normal0 * bigWaves.x * 0.5 + normal1 * bigWaves.y * 0.5 +
normal2 * midWaves.x * 0.2 + normal3 * midWaves.y * 0.2 +
normal4 * smallWaves.x * 0.1 + normal5 * smallWaves.y * 0.1 +
rippleAdd).xyz;
normal2 * midWaves.x * 0.2 + normal3 * midWaves.y * 0.2 +
normal4 * smallWaves.x * 0.1 + normal5 * smallWaves.y * 0.1 +
rippleAdd).xyz;
lNormal = normalize(vec3(lNormal.x * bump, lNormal.y * bump, lNormal.z));
lNormal = vec3(-lNormal.x, -lNormal.y, lNormal.z);

@ -21,8 +21,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>630</width>
<height>791</height>
<width>641</width>
<height>998</height>
</rect>
</property>
<layout class="QVBoxLayout" name="scrollAreaVerticalLayout">
@ -45,7 +45,7 @@
<item>
<widget class="QCheckBox" name="followersAttackOnSightCheckBox">
<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 name="text">
<string>Followers attack on sight</string>
@ -65,13 +65,43 @@
<item>
<widget class="QCheckBox" name="rebalanceSoulGemValuesCheckBox">
<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 name="text">
<string>Rebalance soul gem values</string>
</property>
</widget>
</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>
</widget>
</item>
@ -137,7 +167,7 @@
</property>
<layout class="QHBoxLayout" name="maximumQuicksavesLayout">
<property name="spacing">
<number>-1</number>
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
@ -312,7 +342,7 @@
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>-1</number>
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
@ -379,7 +409,7 @@
</property>
<layout class="QHBoxLayout" name="screenshotFormatLayout">
<property name="spacing">
<number>-1</number>
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>

Loading…
Cancel
Save