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
30
CHANGELOG.md
|
@ -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,143 +329,42 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
|
|||
return true;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && defined(_DEBUG)
|
||||
|
||||
class DebugOutput : public boost::iostreams::sink
|
||||
int runApplication(int argc, char *argv[])
|
||||
{
|
||||
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;
|
||||
};
|
||||
#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));
|
||||
|
||||
if (parseOptions(argc, argv, *engine, cfgMgr))
|
||||
{
|
||||
engine->go();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
extern "C" int SDL_main(int argc, char**argv)
|
||||
#else
|
||||
int main(int argc, char**argv)
|
||||
#endif
|
||||
{
|
||||
#if defined(__APPLE__)
|
||||
setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0);
|
||||
#endif
|
||||
/*
|
||||
Start of tes3mp change (major)
|
||||
|
||||
// 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);
|
||||
|
||||
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;
|
||||
}
|
||||
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||
|
||||
if (id >= separatorPos)
|
||||
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));
|
||||
|
||||
getWeaponGroup(weaptype, weapgroup);
|
||||
mAnimation->setWeaponGroup(weapgroup);
|
||||
|
||||
if (!isStillWeapon)
|
||||
// Weapon is changed, no current animation (e.g. unequipping or attack).
|
||||
// Start equipping animation now.
|
||||
if (weaptype != mWeaponType)
|
||||
{
|
||||
if (weaptype == WeapType_None)
|
||||
forcestateupdate = true;
|
||||
mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype));
|
||||
|
||||
getWeaponGroup(weaptype, weapgroup);
|
||||
mAnimation->setWeaponGroup(weapgroup);
|
||||
|
||||
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
|
||||
{
|
||||
mAnimation->showWeapons(false);
|
||||
mAnimation->play(weapgroup, priorityWeapon,
|
||||
MWRender::Animation::BlendMask_All, true,
|
||||
1.0f, "equip start", "equip stop", 0.0f, 0);
|
||||
mUpperBodyState = UpperCharState_EquipingWeap;
|
||||
}
|
||||
}
|
||||
|
||||
if(isWerewolf)
|
||||
{
|
||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfEquip");
|
||||
if(sound)
|
||||
if(isWerewolf)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
movestate = mMovementState;
|
||||
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;
|
||||
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;
|
||||
{
|
||||
int value = actor.getClass().getSkill(actor, skill);
|
||||
rating *= MWMechanics::getHitChance(actor, enemy, value) / 100.f;
|
||||
}
|
||||
|
||||
// There is no need to apply bonus if weapon rating == 0
|
||||
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)
|
||||
|
|
|
@ -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 ©, const osg::CopyOp ©op)
|
||||
: mLand(nullptr)
|
||||
, mLoadFlags(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
128
components/misc/debugging.hpp
Normal file
|
@ -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.
|
||||
|
|
35
extern/oics/tinyxmlparser.cpp
vendored
|
@ -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;
|
||||
|
|
Before Width: | Height: | Size: 590 B After Width: | Height: | Size: 699 B |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 362 B |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 4 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 236 KiB After Width: | Height: | Size: 228 KiB |
BIN
files/launcher/images/preferences-advanced.png
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
files/launcher/images/preferences-video.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
files/launcher/images/preferences.png
Normal file
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><html><head/><body><p>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.</p></body></html></string>
|
||||
<string><html><head/><body><p>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.</p></body></html></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><html><head/><body><p>If this setting is true, the value of filled soul gems is dependent only on soul magnitude.</p><p>The default value is false.</p></body></html></string>
|
||||
<string><html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Rebalance soul gem values</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="chargeForEveryFollowerCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Account for the first follower in fast travel cost calculations.</p></body></html></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><html><head/><body><p>Make enchanted weaponry without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enchanted weapons are magical</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="permanentBarterDispositionChangeCheckBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html></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>
|
||||
|
|