2012-07-03 10:30:50 +00:00
|
|
|
#include "worldimp.hpp"
|
2014-06-14 11:54:01 +00:00
|
|
|
|
2015-04-12 13:34:50 +00:00
|
|
|
#include <osg/Group>
|
2015-05-21 23:43:45 +00:00
|
|
|
#include <osg/ComputeBoundsVisitor>
|
2020-11-20 12:11:53 +00:00
|
|
|
#include <osg/Timer>
|
2015-04-12 13:34:50 +00:00
|
|
|
|
2018-05-26 14:44:25 +00:00
|
|
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
2018-07-12 08:44:11 +00:00
|
|
|
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
|
2018-05-26 14:44:25 +00:00
|
|
|
|
2018-08-14 19:05:43 +00:00
|
|
|
#include <components/debug/debuglog.hpp>
|
|
|
|
|
2015-07-07 17:16:32 +00:00
|
|
|
#include <components/esm/esmreader.hpp>
|
|
|
|
#include <components/esm/esmwriter.hpp>
|
2015-07-24 16:23:23 +00:00
|
|
|
#include <components/esm/cellid.hpp>
|
2020-05-10 12:57:06 +00:00
|
|
|
#include <components/esm/cellref.hpp>
|
2015-07-07 17:16:32 +00:00
|
|
|
|
2018-09-17 10:52:43 +00:00
|
|
|
#include <components/misc/constants.hpp>
|
2017-02-02 07:20:34 +00:00
|
|
|
#include <components/misc/resourcehelpers.hpp>
|
2015-04-22 15:58:55 +00:00
|
|
|
#include <components/misc/rng.hpp>
|
2019-08-15 17:19:04 +00:00
|
|
|
#include <components/misc/convert.hpp>
|
2015-03-15 01:07:47 +00:00
|
|
|
|
2011-05-05 17:32:42 +00:00
|
|
|
#include <components/files/collections.hpp>
|
2017-02-02 07:20:34 +00:00
|
|
|
|
2018-07-12 08:44:11 +00:00
|
|
|
#include <components/resource/bulletshape.hpp>
|
2015-05-12 15:40:42 +00:00
|
|
|
#include <components/resource/resourcesystem.hpp>
|
2010-07-02 07:38:22 +00:00
|
|
|
|
2015-11-20 20:57:04 +00:00
|
|
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
|
|
|
|
2018-04-01 17:24:02 +00:00
|
|
|
#include <components/detournavigator/debug.hpp>
|
2019-02-16 12:27:02 +00:00
|
|
|
#include <components/detournavigator/navigatorimpl.hpp>
|
2019-02-16 12:50:58 +00:00
|
|
|
#include <components/detournavigator/navigatorstub.hpp>
|
2018-11-04 15:11:15 +00:00
|
|
|
#include <components/detournavigator/recastglobalallocator.hpp>
|
2018-03-13 22:49:08 +00:00
|
|
|
|
2012-04-23 13:27:03 +00:00
|
|
|
#include "../mwbase/environment.hpp"
|
2012-08-09 12:33:21 +00:00
|
|
|
#include "../mwbase/soundmanager.hpp"
|
2012-08-11 15:30:55 +00:00
|
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
2012-08-12 16:11:09 +00:00
|
|
|
#include "../mwbase/windowmanager.hpp"
|
2015-05-12 01:02:15 +00:00
|
|
|
#include "../mwbase/scriptmanager.hpp"
|
2012-04-23 13:27:03 +00:00
|
|
|
|
2013-02-20 09:55:12 +00:00
|
|
|
#include "../mwmechanics/creaturestats.hpp"
|
2013-08-05 21:23:39 +00:00
|
|
|
#include "../mwmechanics/npcstats.hpp"
|
2013-11-16 01:34:43 +00:00
|
|
|
#include "../mwmechanics/spellcasting.hpp"
|
2014-01-14 06:40:17 +00:00
|
|
|
#include "../mwmechanics/levelledlist.hpp"
|
2014-03-08 04:51:47 +00:00
|
|
|
#include "../mwmechanics/combat.hpp"
|
2014-05-13 07:58:32 +00:00
|
|
|
#include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors
|
2020-04-04 13:27:00 +00:00
|
|
|
#include "../mwmechanics/summoning.hpp"
|
2013-02-20 09:55:12 +00:00
|
|
|
|
2015-05-22 02:36:17 +00:00
|
|
|
#include "../mwrender/animation.hpp"
|
2017-02-22 13:54:40 +00:00
|
|
|
#include "../mwrender/npcanimation.hpp"
|
2015-04-12 13:34:50 +00:00
|
|
|
#include "../mwrender/renderingmanager.hpp"
|
2015-05-21 21:54:39 +00:00
|
|
|
#include "../mwrender/camera.hpp"
|
2020-04-20 16:47:14 +00:00
|
|
|
#include "../mwrender/vismask.hpp"
|
2010-07-27 12:05:05 +00:00
|
|
|
|
2015-05-12 01:02:15 +00:00
|
|
|
#include "../mwscript/globalscripts.hpp"
|
2014-06-12 22:01:29 +00:00
|
|
|
|
2012-12-23 19:23:24 +00:00
|
|
|
#include "../mwclass/door.hpp"
|
|
|
|
|
2015-05-12 01:02:15 +00:00
|
|
|
#include "../mwphysics/physicssystem.hpp"
|
2015-05-12 17:02:56 +00:00
|
|
|
#include "../mwphysics/actor.hpp"
|
2015-05-24 01:59:22 +00:00
|
|
|
#include "../mwphysics/collisiontype.hpp"
|
2018-05-26 14:44:25 +00:00
|
|
|
#include "../mwphysics/object.hpp"
|
2020-03-30 20:07:12 +00:00
|
|
|
#include "../mwphysics/constants.hpp"
|
2015-05-12 01:02:15 +00:00
|
|
|
|
2020-06-03 07:32:28 +00:00
|
|
|
#include "datetimemanager.hpp"
|
2011-01-04 14:58:22 +00:00
|
|
|
#include "player.hpp"
|
2012-05-16 19:27:02 +00:00
|
|
|
#include "manualref.hpp"
|
2015-02-09 14:12:10 +00:00
|
|
|
#include "cellstore.hpp"
|
2013-01-27 11:03:48 +00:00
|
|
|
#include "containerstore.hpp"
|
2013-04-15 00:56:23 +00:00
|
|
|
#include "inventorystore.hpp"
|
2014-01-01 01:22:11 +00:00
|
|
|
#include "actionteleport.hpp"
|
2015-06-01 19:41:13 +00:00
|
|
|
#include "projectilemanager.hpp"
|
2015-04-01 15:02:15 +00:00
|
|
|
#include "weather.hpp"
|
2010-11-06 17:11:09 +00:00
|
|
|
|
2013-09-29 07:11:57 +00:00
|
|
|
#include "contentloader.hpp"
|
|
|
|
#include "esmloader.hpp"
|
|
|
|
|
2014-06-13 21:17:54 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
|
|
|
// Wraps a value to (-PI, PI]
|
|
|
|
void wrap(float& rad)
|
|
|
|
{
|
2015-06-03 19:37:21 +00:00
|
|
|
const float pi = static_cast<float>(osg::PI);
|
2014-06-13 21:17:54 +00:00
|
|
|
if (rad>0)
|
2015-06-03 19:37:21 +00:00
|
|
|
rad = std::fmod(rad+pi, 2.0f*pi)-pi;
|
2014-06-13 21:17:54 +00:00
|
|
|
else
|
2015-06-03 19:37:21 +00:00
|
|
|
rad = std::fmod(rad-pi, 2.0f*pi)+pi;
|
2014-06-13 21:17:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2010-07-03 13:41:20 +00:00
|
|
|
namespace MWWorld
|
2010-07-02 07:38:22 +00:00
|
|
|
{
|
2013-09-29 07:11:57 +00:00
|
|
|
struct GameContentLoader : public ContentLoader
|
|
|
|
{
|
|
|
|
GameContentLoader(Loading::Listener& listener)
|
|
|
|
: ContentLoader(listener)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool addLoader(const std::string& extension, ContentLoader* loader)
|
|
|
|
{
|
|
|
|
return mLoaders.insert(std::make_pair(extension, loader)).second;
|
|
|
|
}
|
|
|
|
|
2020-10-16 18:18:54 +00:00
|
|
|
void load(const boost::filesystem::path& filepath, int& index) override
|
2013-09-29 07:11:57 +00:00
|
|
|
{
|
2013-11-30 13:25:29 +00:00
|
|
|
LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string())));
|
2013-09-29 07:11:57 +00:00
|
|
|
if (it != mLoaders.end())
|
|
|
|
{
|
|
|
|
it->second->load(filepath, index);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::string msg("Cannot load file: ");
|
|
|
|
msg += filepath.string();
|
|
|
|
throw std::runtime_error(msg.c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2016-05-11 00:35:17 +00:00
|
|
|
typedef std::map<std::string, ContentLoader*> LoadersContainer;
|
2013-09-29 07:11:57 +00:00
|
|
|
LoadersContainer mLoaders;
|
|
|
|
};
|
|
|
|
|
2010-08-22 18:55:22 +00:00
|
|
|
void World::adjustSky()
|
|
|
|
{
|
2012-02-26 17:21:11 +00:00
|
|
|
if (mSky && (isCellExterior() || isCellQuasiExterior()))
|
2010-08-22 18:55:22 +00:00
|
|
|
{
|
2020-06-03 07:32:28 +00:00
|
|
|
updateSkyDate();
|
2015-04-14 13:55:56 +00:00
|
|
|
mRendering->setSkyEnabled(true);
|
2010-08-22 18:55:22 +00:00
|
|
|
}
|
2012-02-26 17:21:11 +00:00
|
|
|
else
|
2015-04-14 13:55:56 +00:00
|
|
|
mRendering->setSkyEnabled(false);
|
2010-08-22 18:55:22 +00:00
|
|
|
}
|
|
|
|
|
2015-04-01 15:02:15 +00:00
|
|
|
World::World (
|
2015-05-14 19:42:04 +00:00
|
|
|
osgViewer::Viewer* viewer,
|
2015-04-12 13:34:50 +00:00
|
|
|
osg::ref_ptr<osg::Group> rootNode,
|
2017-02-14 02:37:45 +00:00
|
|
|
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
2011-05-05 17:32:42 +00:00
|
|
|
const Files::Collections& fileCollections,
|
2013-09-29 07:11:57 +00:00
|
|
|
const std::vector<std::string>& contentFiles,
|
2019-01-22 06:08:48 +00:00
|
|
|
ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride,
|
|
|
|
const std::string& startCell, const std::string& startupScript,
|
2019-02-23 19:39:13 +00:00
|
|
|
const std::string& resourcePath, const std::string& userDataPath)
|
2019-01-22 06:08:48 +00:00
|
|
|
: mResourceSystem(resourceSystem), mLocalScripts (mStore),
|
2020-05-29 09:37:13 +00:00
|
|
|
mCells (mStore, mEsm), mSky (true),
|
Process movement queue in one or several background threads
Before movement calculation, the main thread prepare a
vector of ActorFrameData, which contains all data necessary to perform
the simulation, and feed it to the solver. At the same time it fetches
the result from the previous background simulation, which in turn is
used by the game mechanics.
Other functions of the physics system (weapon hit for instance)
interrupt the background simulation, with some exceptions described
below.
The number of threads is controlled by the numeric setting
[Physics]
async num threads
In case 'async num threads' > 1 and Bullet doesn't support multiple threads,
1 async thread will be used. 0 means synchronous solver.
Additional settings (will be silently switched off if async num threads = 0)
[Physics]
defer aabb update
Update AABBs of actors and objects in the background thread(s). It is not an especially
costly operation, but it needs exclusive access to the collision world, which blocks
other operations. Since AABB needs to be updated for collision detection, one can queue
them to defer update before start of the movement solver. Extensive tests on as much
as one installation (mine) show no drawback having that switched on.
[Physics]
lineofsight keep inactive cache
Control for how long (how many frames) the line of sight (LOS) request will be kept updated.
When a request for LOS is made for the first time, the background threads are stopped to
service it. From now on, the LOS will be refreshed preemptively as part of the background
routine until it is not required for lineofsight keep inactive cache frames. This mean
that subsequent request will not interrupt the background computation.
2020-10-15 04:11:44 +00:00
|
|
|
mGodMode(false), mScriptsEnabled(true), mDiscardMovements(true), mContentFiles (contentFiles),
|
2020-05-29 09:37:13 +00:00
|
|
|
mUserDataPath(userDataPath), mShouldUpdateNavigator(false),
|
2019-02-23 19:39:13 +00:00
|
|
|
mActivationDistanceOverride (activationDistanceOverride),
|
2020-05-29 09:37:13 +00:00
|
|
|
mStartCell(startCell), mDistanceToFacedObject(-1.f), mTeleportEnabled(true),
|
2018-08-13 04:30:50 +00:00
|
|
|
mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0),
|
2018-08-13 07:10:01 +00:00
|
|
|
mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f)
|
2010-08-03 18:01:52 +00:00
|
|
|
{
|
2018-07-20 19:11:34 +00:00
|
|
|
mEsm.resize(contentFiles.size());
|
|
|
|
Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
|
|
|
listener->loadingOn();
|
|
|
|
|
|
|
|
GameContentLoader gameContentLoader(*listener);
|
|
|
|
EsmLoader esmLoader(mStore, mEsm, encoder, *listener);
|
|
|
|
|
|
|
|
gameContentLoader.addLoader(".esm", &esmLoader);
|
|
|
|
gameContentLoader.addLoader(".esp", &esmLoader);
|
|
|
|
gameContentLoader.addLoader(".omwgame", &esmLoader);
|
|
|
|
gameContentLoader.addLoader(".omwaddon", &esmLoader);
|
|
|
|
gameContentLoader.addLoader(".project", &esmLoader);
|
|
|
|
|
|
|
|
loadContentFiles(fileCollections, contentFiles, gameContentLoader);
|
|
|
|
|
|
|
|
listener->loadingOff();
|
|
|
|
|
|
|
|
// insert records that may not be present in all versions of MW
|
|
|
|
if (mEsm[0].getFormat() == 0)
|
|
|
|
ensureNeededRecords();
|
|
|
|
|
2020-06-03 07:32:28 +00:00
|
|
|
mCurrentDate.reset(new DateTimeManager());
|
|
|
|
|
2018-07-20 19:11:34 +00:00
|
|
|
fillGlobalVariables();
|
|
|
|
|
|
|
|
mStore.setUp(true);
|
|
|
|
mStore.movePlayerRecord();
|
|
|
|
|
|
|
|
mSwimHeightScale = mStore.get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
|
|
|
|
|
2018-01-30 22:05:16 +00:00
|
|
|
mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode));
|
2014-05-16 11:09:23 +00:00
|
|
|
|
2019-02-16 12:50:58 +00:00
|
|
|
if (auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager())
|
|
|
|
{
|
|
|
|
navigatorSettings->mMaxClimb = MWPhysics::sStepSizeUp;
|
|
|
|
navigatorSettings->mMaxSlope = MWPhysics::sMaxSlope;
|
|
|
|
navigatorSettings->mSwimHeightScale = mSwimHeightScale;
|
|
|
|
DetourNavigator::RecastGlobalAllocator::init();
|
|
|
|
mNavigator.reset(new DetourNavigator::NavigatorImpl(*navigatorSettings));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mNavigator.reset(new DetourNavigator::NavigatorStub());
|
|
|
|
}
|
2018-03-13 22:49:08 +00:00
|
|
|
|
2019-01-22 06:08:48 +00:00
|
|
|
mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, resourcePath, *mNavigator));
|
2018-04-07 13:11:23 +00:00
|
|
|
mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get()));
|
2017-02-09 01:59:10 +00:00
|
|
|
mRendering->preloadCommonAssets();
|
|
|
|
|
2019-01-22 06:08:48 +00:00
|
|
|
mWeatherManager.reset(new MWWorld::WeatherManager(*mRendering, mStore));
|
2015-07-10 00:34:00 +00:00
|
|
|
|
2018-08-26 20:27:38 +00:00
|
|
|
mWorldScene.reset(new Scene(*mRendering.get(), mPhysics.get(), *mNavigator));
|
2013-05-15 15:54:18 +00:00
|
|
|
}
|
|
|
|
|
2015-11-22 21:13:21 +00:00
|
|
|
void World::fillGlobalVariables()
|
|
|
|
{
|
|
|
|
mGlobalVariables.fill (mStore);
|
2020-06-03 07:32:28 +00:00
|
|
|
mCurrentDate->setup(mGlobalVariables);
|
2015-11-22 21:13:21 +00:00
|
|
|
}
|
|
|
|
|
2014-03-13 12:19:32 +00:00
|
|
|
void World::startNewGame (bool bypass)
|
2013-05-15 15:54:18 +00:00
|
|
|
{
|
2014-01-11 05:47:58 +00:00
|
|
|
mGoToJail = false;
|
|
|
|
mLevitationEnabled = true;
|
|
|
|
mTeleportEnabled = true;
|
2013-05-15 15:54:18 +00:00
|
|
|
|
2015-09-25 23:49:58 +00:00
|
|
|
mGodMode = false;
|
|
|
|
mScriptsEnabled = true;
|
|
|
|
mSky = true;
|
|
|
|
|
2013-05-15 15:54:18 +00:00
|
|
|
// Rebuild player
|
|
|
|
setupPlayer();
|
|
|
|
|
2013-08-20 09:04:26 +00:00
|
|
|
renderPlayer();
|
2020-07-25 21:33:50 +00:00
|
|
|
mRendering->getCamera()->reset();
|
2013-05-15 15:54:18 +00:00
|
|
|
|
2015-08-27 03:59:21 +00:00
|
|
|
// we don't want old weather to persist on a new game
|
|
|
|
// Note that if reset later, the initial ChangeWeather that the chargen script calls will be lost.
|
2018-01-30 22:05:16 +00:00
|
|
|
mWeatherManager.reset();
|
2019-01-22 06:08:48 +00:00
|
|
|
mWeatherManager.reset(new MWWorld::WeatherManager(*mRendering.get(), mStore));
|
2015-08-27 03:59:21 +00:00
|
|
|
|
2014-05-19 12:09:16 +00:00
|
|
|
if (!bypass)
|
|
|
|
{
|
|
|
|
// set new game mark
|
|
|
|
mGlobalVariables["chargenstate"].setInteger (1);
|
|
|
|
}
|
2014-07-25 10:23:18 +00:00
|
|
|
else
|
|
|
|
mGlobalVariables["chargenstate"].setInteger (-1);
|
2014-05-19 12:09:16 +00:00
|
|
|
|
2014-03-13 12:19:32 +00:00
|
|
|
if (bypass && !mStartCell.empty())
|
|
|
|
{
|
|
|
|
ESM::Position pos;
|
|
|
|
if (findExteriorPosition (mStartCell, pos))
|
|
|
|
{
|
2016-03-24 16:18:08 +00:00
|
|
|
changeToExteriorCell (pos, true);
|
2018-09-13 09:21:38 +00:00
|
|
|
adjustPosition(getPlayerPtr(), false);
|
2014-03-13 12:19:32 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
findInteriorPosition (mStartCell, pos);
|
2016-03-24 16:18:08 +00:00
|
|
|
changeToInteriorCell (mStartCell, pos, true);
|
2014-03-13 12:19:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-05-19 12:09:16 +00:00
|
|
|
for (int i=0; i<5; ++i)
|
|
|
|
MWBase::Environment::get().getScriptManager()->getGlobalScripts().run();
|
|
|
|
if (!getPlayerPtr().isInCell())
|
|
|
|
{
|
|
|
|
ESM::Position pos;
|
2018-09-17 10:52:43 +00:00
|
|
|
const int cellSize = Constants::CellSizeInUnits;
|
2014-05-19 12:09:16 +00:00
|
|
|
pos.pos[0] = cellSize/2;
|
|
|
|
pos.pos[1] = cellSize/2;
|
|
|
|
pos.pos[2] = 0;
|
|
|
|
pos.rot[0] = 0;
|
|
|
|
pos.rot[1] = 0;
|
|
|
|
pos.rot[2] = 0;
|
2014-10-02 14:30:23 +00:00
|
|
|
mWorldScene->changeToExteriorCell(pos, true);
|
2014-05-19 12:09:16 +00:00
|
|
|
}
|
2014-03-13 12:19:32 +00:00
|
|
|
}
|
2012-12-04 09:58:43 +00:00
|
|
|
|
2014-05-20 07:45:39 +00:00
|
|
|
if (!bypass)
|
2015-05-23 03:42:21 +00:00
|
|
|
{
|
2019-01-22 06:08:48 +00:00
|
|
|
const std::string& video = Fallback::Map::getString("Movies_New_Game");
|
2015-05-23 03:42:21 +00:00
|
|
|
if (!video.empty())
|
|
|
|
MWBase::Environment::get().getWindowManager()->playVideo(video, true);
|
|
|
|
}
|
2014-05-20 07:45:39 +00:00
|
|
|
|
2014-05-20 15:11:26 +00:00
|
|
|
// enable collision
|
2015-05-23 04:07:11 +00:00
|
|
|
if (!mPhysics->toggleCollisionMode())
|
|
|
|
mPhysics->toggleCollisionMode();
|
2014-05-20 15:11:26 +00:00
|
|
|
|
2017-04-20 22:51:13 +00:00
|
|
|
MWBase::Environment::get().getWindowManager()->updatePlayer();
|
2020-06-03 07:32:28 +00:00
|
|
|
mCurrentDate->setup(mGlobalVariables);
|
2010-07-02 07:38:22 +00:00
|
|
|
}
|
2012-04-04 14:08:46 +00:00
|
|
|
|
2013-12-05 11:49:25 +00:00
|
|
|
void World::clear()
|
|
|
|
{
|
2015-04-14 13:55:56 +00:00
|
|
|
mWeatherManager->clear();
|
2015-04-19 15:55:56 +00:00
|
|
|
mRendering->clear();
|
2014-05-17 03:21:17 +00:00
|
|
|
mProjectileManager->clear();
|
2013-12-05 11:49:25 +00:00
|
|
|
mLocalScripts.clear();
|
|
|
|
|
2017-02-09 03:03:38 +00:00
|
|
|
mWorldScene->clear();
|
2013-12-05 11:49:25 +00:00
|
|
|
|
2014-01-18 13:53:25 +00:00
|
|
|
mStore.clearDynamic();
|
|
|
|
|
2013-12-05 11:49:25 +00:00
|
|
|
if (mPlayer)
|
2013-12-07 12:17:28 +00:00
|
|
|
{
|
2015-09-16 18:45:37 +00:00
|
|
|
mPlayer->clear();
|
2020-11-13 07:39:47 +00:00
|
|
|
mPlayer->setCell(nullptr);
|
2013-12-05 11:49:25 +00:00
|
|
|
mPlayer->getPlayer().getRefData() = RefData();
|
2014-05-28 17:58:45 +00:00
|
|
|
mPlayer->set(mStore.get<ESM::NPC>().find ("player"));
|
2013-12-07 12:17:28 +00:00
|
|
|
}
|
2013-12-05 11:49:25 +00:00
|
|
|
|
|
|
|
mCells.clear();
|
2013-12-05 13:18:43 +00:00
|
|
|
|
|
|
|
mDoorStates.clear();
|
|
|
|
|
2017-06-01 21:33:52 +00:00
|
|
|
mGoToJail = false;
|
2013-12-05 13:18:43 +00:00
|
|
|
mTeleportEnabled = true;
|
2014-09-21 17:51:33 +00:00
|
|
|
mLevitationEnabled = true;
|
2018-08-13 04:30:50 +00:00
|
|
|
mPlayerTraveling = false;
|
2018-08-13 07:10:01 +00:00
|
|
|
mPlayerInJail = false;
|
2013-12-10 14:09:58 +00:00
|
|
|
|
2015-11-22 21:13:21 +00:00
|
|
|
fillGlobalVariables();
|
2010-07-02 07:38:22 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2013-12-07 12:17:28 +00:00
|
|
|
int World::countSavedGameRecords() const
|
|
|
|
{
|
2013-12-12 11:19:25 +00:00
|
|
|
return
|
2014-04-28 09:29:57 +00:00
|
|
|
mCells.countSavedGameRecords()
|
|
|
|
+mStore.countSavedGameRecords()
|
2014-01-23 10:29:40 +00:00
|
|
|
+mGlobalVariables.countSavedGameRecords()
|
2014-05-17 03:21:17 +00:00
|
|
|
+mProjectileManager->countSavedGameRecords()
|
2014-01-23 10:29:40 +00:00
|
|
|
+1 // player record
|
2014-05-14 07:47:49 +00:00
|
|
|
+1 // weather record
|
2014-09-21 11:37:20 +00:00
|
|
|
+1 // actorId counter
|
2015-01-06 18:29:33 +00:00
|
|
|
+1 // levitation/teleport enabled state
|
|
|
|
+1; // camera
|
2013-12-07 12:17:28 +00:00
|
|
|
}
|
|
|
|
|
2015-01-11 17:01:06 +00:00
|
|
|
int World::countSavedGameCells() const
|
|
|
|
{
|
|
|
|
return mCells.countSavedGameRecords();
|
|
|
|
}
|
|
|
|
|
2014-04-28 09:29:57 +00:00
|
|
|
void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const
|
2013-12-07 12:17:28 +00:00
|
|
|
{
|
2014-05-11 00:07:28 +00:00
|
|
|
// Active cells could have a dirty fog of war, sync it to the CellStore first
|
2018-12-29 22:27:16 +00:00
|
|
|
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
2014-05-11 00:07:28 +00:00
|
|
|
{
|
2015-05-28 01:50:44 +00:00
|
|
|
MWBase::Environment::get().getWindowManager()->writeFog(cellstore);
|
2014-05-11 00:07:28 +00:00
|
|
|
}
|
|
|
|
|
2014-05-14 07:47:49 +00:00
|
|
|
MWMechanics::CreatureStats::writeActorIdCounter(writer);
|
|
|
|
|
2014-07-27 22:32:59 +00:00
|
|
|
mStore.write (writer, progress); // dynamic Store must be written (and read) before Cells, so that
|
|
|
|
// references to custom made records will be recognized
|
2017-02-09 02:48:33 +00:00
|
|
|
mPlayer->write (writer, progress);
|
2014-04-28 09:29:57 +00:00
|
|
|
mCells.write (writer, progress);
|
|
|
|
mGlobalVariables.write (writer, progress);
|
2015-04-14 13:55:56 +00:00
|
|
|
mWeatherManager->write (writer, progress);
|
2014-05-17 03:21:17 +00:00
|
|
|
mProjectileManager->write (writer, progress);
|
2015-06-01 19:41:13 +00:00
|
|
|
|
2014-09-21 11:37:20 +00:00
|
|
|
writer.startRecord(ESM::REC_ENAB);
|
|
|
|
writer.writeHNT("TELE", mTeleportEnabled);
|
|
|
|
writer.writeHNT("LEVT", mLevitationEnabled);
|
|
|
|
writer.endRecord(ESM::REC_ENAB);
|
2015-01-06 18:29:33 +00:00
|
|
|
|
|
|
|
writer.startRecord(ESM::REC_CAM_);
|
|
|
|
writer.writeHNT("FIRS", isFirstPerson());
|
|
|
|
writer.endRecord(ESM::REC_CAM_);
|
2013-12-07 12:17:28 +00:00
|
|
|
}
|
|
|
|
|
2015-01-22 18:04:59 +00:00
|
|
|
void World::readRecord (ESM::ESMReader& reader, uint32_t type,
|
2014-01-27 12:27:42 +00:00
|
|
|
const std::map<int, int>& contentFileMap)
|
2013-12-07 12:17:28 +00:00
|
|
|
{
|
2014-09-21 11:37:20 +00:00
|
|
|
switch (type)
|
2013-12-07 12:17:28 +00:00
|
|
|
{
|
2014-09-21 11:37:20 +00:00
|
|
|
case ESM::REC_ACTC:
|
|
|
|
MWMechanics::CreatureStats::readActorIdCounter(reader);
|
|
|
|
return;
|
|
|
|
case ESM::REC_ENAB:
|
|
|
|
reader.getHNT(mTeleportEnabled, "TELE");
|
|
|
|
reader.getHNT(mLevitationEnabled, "LEVT");
|
|
|
|
return;
|
2017-02-09 02:47:36 +00:00
|
|
|
case ESM::REC_PLAY:
|
2020-06-02 19:59:37 +00:00
|
|
|
mStore.checkPlayer();
|
2017-02-09 02:47:36 +00:00
|
|
|
mPlayer->readRecord(reader, type);
|
2017-06-29 22:51:26 +00:00
|
|
|
if (getPlayerPtr().isInCell())
|
|
|
|
{
|
2019-02-20 13:37:00 +00:00
|
|
|
if (getPlayerPtr().getCell()->isExterior())
|
|
|
|
mWorldScene->preloadTerrain(getPlayerPtr().getRefData().getPosition().asVec3());
|
2020-05-07 13:37:00 +00:00
|
|
|
mWorldScene->preloadCell(getPlayerPtr().getCell(), true);
|
2017-06-29 22:51:26 +00:00
|
|
|
}
|
2017-02-09 02:47:36 +00:00
|
|
|
break;
|
2014-09-21 11:37:20 +00:00
|
|
|
default:
|
|
|
|
if (!mStore.readRecord (reader, type) &&
|
|
|
|
!mGlobalVariables.readRecord (reader, type) &&
|
2015-04-14 13:55:56 +00:00
|
|
|
!mWeatherManager->readRecord (reader, type) &&
|
2015-04-01 15:02:15 +00:00
|
|
|
!mCells.readRecord (reader, type, contentFileMap)
|
|
|
|
&& !mProjectileManager->readRecord (reader, type)
|
|
|
|
)
|
2014-09-21 11:37:20 +00:00
|
|
|
{
|
|
|
|
throw std::runtime_error ("unknown record in saved game");
|
|
|
|
}
|
|
|
|
break;
|
2013-12-07 12:17:28 +00:00
|
|
|
}
|
|
|
|
}
|
2012-01-29 22:50:51 +00:00
|
|
|
|
2013-05-01 10:47:50 +00:00
|
|
|
void World::ensureNeededRecords()
|
|
|
|
{
|
2014-08-13 15:32:52 +00:00
|
|
|
std::map<std::string, ESM::Variant> gmst;
|
|
|
|
// Companion (tribunal)
|
|
|
|
gmst["sCompanionShare"] = ESM::Variant("Companion Share");
|
|
|
|
gmst["sCompanionWarningMessage"] = ESM::Variant("Warning message");
|
|
|
|
gmst["sCompanionWarningButtonOne"] = ESM::Variant("Button 1");
|
|
|
|
gmst["sCompanionWarningButtonTwo"] = ESM::Variant("Button 2");
|
|
|
|
gmst["sProfitValue"] = ESM::Variant("Profit Value");
|
|
|
|
gmst["sTeleportDisabled"] = ESM::Variant("Teleport disabled");
|
|
|
|
gmst["sLevitateDisabled"] = ESM::Variant("Levitate disabled");
|
|
|
|
|
|
|
|
// Missing in unpatched MW 1.0
|
|
|
|
gmst["sDifficulty"] = ESM::Variant("Difficulty");
|
|
|
|
gmst["fDifficultyMult"] = ESM::Variant(5.f);
|
|
|
|
gmst["sAuto_Run"] = ESM::Variant("Auto Run");
|
|
|
|
gmst["sServiceRefusal"] = ESM::Variant("Service Refusal");
|
|
|
|
gmst["sNeedOneSkill"] = ESM::Variant("Need one skill");
|
|
|
|
gmst["sNeedTwoSkills"] = ESM::Variant("Need two skills");
|
|
|
|
gmst["sEasy"] = ESM::Variant("Easy");
|
|
|
|
gmst["sHard"] = ESM::Variant("Hard");
|
|
|
|
gmst["sDeleteNote"] = ESM::Variant("Delete Note");
|
|
|
|
gmst["sEditNote"] = ESM::Variant("Edit Note");
|
2014-08-17 01:41:15 +00:00
|
|
|
gmst["sAdmireSuccess"] = ESM::Variant("Admire Success");
|
|
|
|
gmst["sAdmireFail"] = ESM::Variant("Admire Fail");
|
|
|
|
gmst["sIntimidateSuccess"] = ESM::Variant("Intimidate Success");
|
|
|
|
gmst["sIntimidateFail"] = ESM::Variant("Intimidate Fail");
|
|
|
|
gmst["sTauntSuccess"] = ESM::Variant("Taunt Success");
|
|
|
|
gmst["sTauntFail"] = ESM::Variant("Taunt Fail");
|
|
|
|
gmst["sBribeSuccess"] = ESM::Variant("Bribe Success");
|
|
|
|
gmst["sBribeFail"] = ESM::Variant("Bribe Fail");
|
2015-01-01 21:54:39 +00:00
|
|
|
gmst["fNPCHealthBarTime"] = ESM::Variant(5.f);
|
|
|
|
gmst["fNPCHealthBarFade"] = ESM::Variant(1.f);
|
2016-11-16 19:15:25 +00:00
|
|
|
gmst["fFleeDistance"] = ESM::Variant(3000.f);
|
2016-11-17 18:43:02 +00:00
|
|
|
gmst["sMaxSale"] = ESM::Variant("Max Sale");
|
2018-01-14 00:27:05 +00:00
|
|
|
gmst["sAnd"] = ESM::Variant("and");
|
2014-08-13 15:32:52 +00:00
|
|
|
|
|
|
|
// Werewolf (BM)
|
2016-11-17 18:43:02 +00:00
|
|
|
gmst["fWereWolfRunMult"] = ESM::Variant(1.3f);
|
|
|
|
gmst["fWereWolfSilverWeaponDamageMult"] = ESM::Variant(2.f);
|
|
|
|
gmst["iWerewolfFightMod"] = ESM::Variant(100);
|
2016-11-16 19:15:25 +00:00
|
|
|
gmst["iWereWolfFleeMod"] = ESM::Variant(100);
|
|
|
|
gmst["iWereWolfLevelToAttack"] = ESM::Variant(20);
|
2016-11-17 18:43:02 +00:00
|
|
|
gmst["iWereWolfBounty"] = ESM::Variant(1000);
|
2016-11-16 19:15:25 +00:00
|
|
|
gmst["fCombatDistanceWerewolfMod"] = ESM::Variant(0.3f);
|
2014-08-13 15:32:52 +00:00
|
|
|
|
2019-03-21 20:08:35 +00:00
|
|
|
for (const auto ¶ms : gmst)
|
2019-01-02 10:49:04 +00:00
|
|
|
{
|
|
|
|
if (!mStore.get<ESM::GameSetting>().search(params.first))
|
|
|
|
{
|
|
|
|
ESM::GameSetting record;
|
|
|
|
record.mId = params.first;
|
|
|
|
record.mValue = params.second;
|
|
|
|
mStore.insertStatic(record);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-13 15:32:52 +00:00
|
|
|
std::map<std::string, ESM::Variant> globals;
|
|
|
|
// vanilla Morrowind does not define dayspassed.
|
|
|
|
globals["dayspassed"] = ESM::Variant(1); // but the addons start counting at 1 :(
|
2014-09-09 13:12:42 +00:00
|
|
|
globals["werewolfclawmult"] = ESM::Variant(25.f);
|
|
|
|
globals["pcknownwerewolf"] = ESM::Variant(0);
|
2014-08-13 15:32:52 +00:00
|
|
|
|
2015-02-25 19:32:11 +00:00
|
|
|
// following should exist in all versions of MW, but not necessarily in TCs
|
|
|
|
globals["gamehour"] = ESM::Variant(0.f);
|
|
|
|
globals["timescale"] = ESM::Variant(30.f);
|
|
|
|
globals["day"] = ESM::Variant(1);
|
|
|
|
globals["month"] = ESM::Variant(1);
|
|
|
|
globals["year"] = ESM::Variant(1);
|
|
|
|
globals["pcrace"] = ESM::Variant(0);
|
|
|
|
globals["pchascrimegold"] = ESM::Variant(0);
|
|
|
|
globals["pchasgolddiscount"] = ESM::Variant(0);
|
|
|
|
globals["crimegolddiscount"] = ESM::Variant(0);
|
|
|
|
globals["crimegoldturnin"] = ESM::Variant(0);
|
|
|
|
globals["pchasturnin"] = ESM::Variant(0);
|
|
|
|
|
2019-03-21 20:08:35 +00:00
|
|
|
for (const auto ¶ms : globals)
|
2019-01-02 10:49:04 +00:00
|
|
|
{
|
|
|
|
if (!mStore.get<ESM::Global>().search(params.first))
|
|
|
|
{
|
|
|
|
ESM::Global record;
|
|
|
|
record.mId = params.first;
|
|
|
|
record.mValue = params.second;
|
|
|
|
mStore.insertStatic(record);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::map<std::string, std::string> statics;
|
|
|
|
// Total conversions from SureAI lack marker records
|
|
|
|
statics["divinemarker"] = "marker_divine.nif";
|
|
|
|
statics["doormarker"] = "marker_arrow.nif";
|
|
|
|
statics["northmarker"] = "marker_north.nif";
|
|
|
|
statics["templemarker"] = "marker_temple.nif";
|
|
|
|
statics["travelmarker"] = "marker_travel.nif";
|
|
|
|
|
2019-03-21 20:08:35 +00:00
|
|
|
for (const auto ¶ms : statics)
|
2013-05-01 10:47:50 +00:00
|
|
|
{
|
2019-01-02 10:49:04 +00:00
|
|
|
if (!mStore.get<ESM::Static>().search(params.first))
|
2014-08-13 15:32:52 +00:00
|
|
|
{
|
2019-01-02 10:49:04 +00:00
|
|
|
ESM::Static record;
|
|
|
|
record.mId = params.first;
|
|
|
|
record.mModel = params.second;
|
|
|
|
mStore.insertStatic(record);
|
2014-08-13 15:32:52 +00:00
|
|
|
}
|
2013-05-01 10:47:50 +00:00
|
|
|
}
|
2014-08-13 15:32:52 +00:00
|
|
|
|
2019-01-02 10:49:04 +00:00
|
|
|
std::map<std::string, std::string> doors;
|
|
|
|
doors["prisonmarker"] = "marker_prison.nif";
|
|
|
|
|
2019-03-21 20:08:35 +00:00
|
|
|
for (const auto ¶ms : doors)
|
2013-05-01 10:47:50 +00:00
|
|
|
{
|
2019-01-02 10:49:04 +00:00
|
|
|
if (!mStore.get<ESM::Door>().search(params.first))
|
2014-08-13 15:32:52 +00:00
|
|
|
{
|
2019-01-02 10:49:04 +00:00
|
|
|
ESM::Door record;
|
|
|
|
record.mId = params.first;
|
|
|
|
record.mModel = params.second;
|
|
|
|
mStore.insertStatic(record);
|
2014-08-13 15:32:52 +00:00
|
|
|
}
|
2013-05-01 10:47:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-07-02 07:38:22 +00:00
|
|
|
World::~World()
|
|
|
|
{
|
2014-06-02 22:44:32 +00:00
|
|
|
// Must be cleared before mRendering is destroyed
|
|
|
|
mProjectileManager->clear();
|
2010-07-02 07:38:22 +00:00
|
|
|
}
|
2011-08-01 12:41:15 +00:00
|
|
|
|
|
|
|
const ESM::Cell *World::getExterior (const std::string& cellName) const
|
|
|
|
{
|
|
|
|
// first try named cells
|
2012-11-06 08:36:21 +00:00
|
|
|
const ESM::Cell *cell = mStore.get<ESM::Cell>().searchExtByName (cellName);
|
2018-12-29 22:27:16 +00:00
|
|
|
if (cell)
|
2011-08-01 12:41:15 +00:00
|
|
|
return cell;
|
|
|
|
|
|
|
|
// didn't work -> now check for regions
|
2018-12-29 22:27:16 +00:00
|
|
|
for (const ESM::Region ®ion : mStore.get<ESM::Region>())
|
2011-08-01 12:41:15 +00:00
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
if (Misc::StringUtils::ciEqual(cellName, region.mName))
|
2011-08-01 12:41:15 +00:00
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
return mStore.get<ESM::Cell>().searchExtByRegion(region.mId);
|
2011-08-01 12:41:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-29 22:27:16 +00:00
|
|
|
return nullptr;
|
2011-08-01 12:41:15 +00:00
|
|
|
}
|
2011-08-09 07:56:09 +00:00
|
|
|
|
2013-12-05 12:21:26 +00:00
|
|
|
CellStore *World::getExterior (int x, int y)
|
2011-08-01 12:34:50 +00:00
|
|
|
{
|
2011-09-08 09:02:55 +00:00
|
|
|
return mCells.getExterior (x, y);
|
2011-08-01 12:34:50 +00:00
|
|
|
}
|
2011-08-09 07:56:09 +00:00
|
|
|
|
2013-12-05 12:21:26 +00:00
|
|
|
CellStore *World::getInterior (const std::string& name)
|
2011-08-01 12:34:50 +00:00
|
|
|
{
|
2011-09-08 09:02:55 +00:00
|
|
|
return mCells.getInterior (name);
|
2011-08-01 12:34:50 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2014-01-16 11:03:23 +00:00
|
|
|
CellStore *World::getCell (const ESM::CellId& id)
|
|
|
|
{
|
|
|
|
if (id.mPaged)
|
|
|
|
return getExterior (id.mIndex.mX, id.mIndex.mY);
|
|
|
|
else
|
|
|
|
return getInterior (id.mWorldspace);
|
|
|
|
}
|
|
|
|
|
2019-11-24 13:40:19 +00:00
|
|
|
void World::testExteriorCells()
|
|
|
|
{
|
|
|
|
mWorldScene->testExteriorCells();
|
|
|
|
}
|
|
|
|
|
|
|
|
void World::testInteriorCells()
|
|
|
|
{
|
|
|
|
mWorldScene->testInteriorCells();
|
|
|
|
}
|
|
|
|
|
2013-12-16 13:40:47 +00:00
|
|
|
void World::useDeathCamera()
|
2013-12-15 16:50:25 +00:00
|
|
|
{
|
2013-12-16 13:40:47 +00:00
|
|
|
if(mRendering->getCamera()->isVanityOrPreviewModeEnabled() )
|
|
|
|
{
|
|
|
|
mRendering->getCamera()->togglePreviewMode(false);
|
|
|
|
mRendering->getCamera()->toggleVanityMode(false);
|
|
|
|
}
|
|
|
|
if(mRendering->getCamera()->isFirstPerson())
|
2014-06-05 15:21:02 +00:00
|
|
|
mRendering->getCamera()->toggleViewMode(true);
|
2013-12-15 16:50:25 +00:00
|
|
|
}
|
|
|
|
|
2011-01-04 14:58:22 +00:00
|
|
|
MWWorld::Player& World::getPlayer()
|
2010-07-02 07:38:22 +00:00
|
|
|
{
|
2011-01-04 14:58:22 +00:00
|
|
|
return *mPlayer;
|
2010-07-02 07:38:22 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2012-10-01 15:17:04 +00:00
|
|
|
const MWWorld::ESMStore& World::getStore() const
|
2010-07-02 14:18:25 +00:00
|
|
|
{
|
|
|
|
return mStore;
|
|
|
|
}
|
2011-08-09 07:56:09 +00:00
|
|
|
|
2012-11-10 20:43:41 +00:00
|
|
|
std::vector<ESM::ESMReader>& World::getEsmReader()
|
2011-07-31 15:07:11 +00:00
|
|
|
{
|
|
|
|
return mEsm;
|
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2011-10-06 10:29:59 +00:00
|
|
|
LocalScripts& World::getLocalScripts()
|
2010-07-02 14:18:25 +00:00
|
|
|
{
|
|
|
|
return mLocalScripts;
|
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2010-07-03 10:12:13 +00:00
|
|
|
bool World::hasCellChanged() const
|
|
|
|
{
|
2011-08-01 02:06:38 +00:00
|
|
|
return mWorldScene->hasCellChanged();
|
2010-07-03 10:12:13 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2013-11-28 08:10:38 +00:00
|
|
|
void World::setGlobalInt (const std::string& name, int value)
|
2010-07-04 11:33:33 +00:00
|
|
|
{
|
2020-06-03 07:32:28 +00:00
|
|
|
bool dateUpdated = mCurrentDate->updateGlobalInt(name, value);
|
|
|
|
if (dateUpdated)
|
|
|
|
updateSkyDate();
|
|
|
|
|
|
|
|
mGlobalVariables[name].setInteger (value);
|
2010-07-04 11:33:33 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2013-11-28 08:10:38 +00:00
|
|
|
void World::setGlobalFloat (const std::string& name, float value)
|
2011-04-21 09:00:00 +00:00
|
|
|
{
|
2020-06-03 07:32:28 +00:00
|
|
|
bool dateUpdated = mCurrentDate->updateGlobalFloat(name, value);
|
|
|
|
if (dateUpdated)
|
|
|
|
updateSkyDate();
|
|
|
|
|
|
|
|
mGlobalVariables[name].setFloat(value);
|
2011-04-21 09:00:00 +00:00
|
|
|
}
|
|
|
|
|
2013-11-28 08:10:38 +00:00
|
|
|
int World::getGlobalInt (const std::string& name) const
|
2010-07-21 13:01:35 +00:00
|
|
|
{
|
2013-12-10 14:09:58 +00:00
|
|
|
return mGlobalVariables[name].getInteger();
|
2010-08-03 18:01:52 +00:00
|
|
|
}
|
|
|
|
|
2013-11-28 08:10:38 +00:00
|
|
|
float World::getGlobalFloat (const std::string& name) const
|
2012-12-20 23:16:34 +00:00
|
|
|
{
|
2013-12-10 14:09:58 +00:00
|
|
|
return mGlobalVariables[name].getFloat();
|
2012-12-20 23:16:34 +00:00
|
|
|
}
|
2013-02-05 11:19:06 +00:00
|
|
|
|
2010-07-21 13:01:35 +00:00
|
|
|
char World::getGlobalVariableType (const std::string& name) const
|
2012-12-21 18:09:31 +00:00
|
|
|
{
|
2013-12-10 14:09:58 +00:00
|
|
|
return mGlobalVariables.getType (name);
|
2010-08-03 18:01:52 +00:00
|
|
|
}
|
2012-12-21 18:09:31 +00:00
|
|
|
|
2020-06-03 07:32:28 +00:00
|
|
|
std::string World::getMonthName (int month) const
|
|
|
|
{
|
|
|
|
return mCurrentDate->getMonthName(month);
|
|
|
|
}
|
|
|
|
|
2013-11-26 10:39:58 +00:00
|
|
|
std::string World::getCellName (const MWWorld::CellStore *cell) const
|
2012-12-21 18:09:31 +00:00
|
|
|
{
|
2013-11-26 10:39:58 +00:00
|
|
|
if (!cell)
|
|
|
|
cell = mWorldScene->getCurrentCell();
|
2012-12-21 18:09:31 +00:00
|
|
|
|
2014-02-21 10:35:46 +00:00
|
|
|
if (!cell->getCell()->isExterior() || !cell->getCell()->mName.empty())
|
|
|
|
return cell->getCell()->mName;
|
2012-12-21 18:09:31 +00:00
|
|
|
|
2019-01-07 14:48:41 +00:00
|
|
|
if (const ESM::Region* region = mStore.get<ESM::Region>().search (cell->getCell()->mRegion))
|
2013-11-26 10:39:58 +00:00
|
|
|
return region->mName;
|
2013-02-05 11:19:06 +00:00
|
|
|
|
2019-01-07 14:48:41 +00:00
|
|
|
return mStore.get<ESM::GameSetting>().find ("sDefaultCellname")->mValue.getString();
|
2012-12-21 18:09:31 +00:00
|
|
|
}
|
2012-12-20 23:16:34 +00:00
|
|
|
|
2013-01-13 17:05:12 +00:00
|
|
|
void World::removeRefScript (MWWorld::RefData *ref)
|
|
|
|
{
|
|
|
|
mLocalScripts.remove (ref);
|
|
|
|
}
|
|
|
|
|
2019-05-06 19:06:13 +00:00
|
|
|
Ptr World::searchPtr (const std::string& name, bool activeOnly, bool searchInContainers)
|
2010-07-05 10:09:04 +00:00
|
|
|
{
|
2014-01-11 02:33:17 +00:00
|
|
|
Ptr ret;
|
2010-07-05 10:09:04 +00:00
|
|
|
// the player is always in an active cell.
|
|
|
|
if (name=="player")
|
|
|
|
{
|
2011-01-04 14:58:22 +00:00
|
|
|
return mPlayer->getPlayer();
|
2010-07-05 10:09:04 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2014-01-01 01:22:11 +00:00
|
|
|
std::string lowerCaseName = Misc::StringUtils::lowerCase(name);
|
|
|
|
|
2018-12-29 22:27:16 +00:00
|
|
|
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
2010-07-05 10:09:04 +00:00
|
|
|
{
|
2014-09-07 17:09:23 +00:00
|
|
|
// TODO: caching still doesn't work efficiently here (only works for the one CellStore that the reference is in)
|
|
|
|
Ptr ptr = mCells.getPtr (lowerCaseName, *cellstore, false);
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2010-07-05 10:09:04 +00:00
|
|
|
if (!ptr.isEmpty())
|
2010-07-10 11:19:04 +00:00
|
|
|
return ptr;
|
2010-07-05 10:09:04 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2010-07-05 10:09:04 +00:00
|
|
|
if (!activeOnly)
|
|
|
|
{
|
2014-01-11 02:33:17 +00:00
|
|
|
ret = mCells.getPtr (lowerCaseName);
|
2014-09-07 17:09:23 +00:00
|
|
|
if (!ret.isEmpty())
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-05-06 19:06:13 +00:00
|
|
|
if (searchInContainers)
|
2014-09-07 17:09:23 +00:00
|
|
|
{
|
2019-05-06 19:06:13 +00:00
|
|
|
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
|
|
|
{
|
|
|
|
Ptr ptr = cellstore->searchInContainer(lowerCaseName);
|
|
|
|
if (!ptr.isEmpty())
|
|
|
|
return ptr;
|
|
|
|
}
|
2010-07-05 10:09:04 +00:00
|
|
|
}
|
2014-09-07 17:09:23 +00:00
|
|
|
|
|
|
|
Ptr ptr = mPlayer->getPlayer().getClass()
|
|
|
|
.getContainerStore(mPlayer->getPlayer()).search(lowerCaseName);
|
|
|
|
|
|
|
|
return ptr;
|
2014-01-11 02:33:17 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2014-01-11 02:33:17 +00:00
|
|
|
Ptr World::getPtr (const std::string& name, bool activeOnly)
|
|
|
|
{
|
|
|
|
Ptr ret = searchPtr(name, activeOnly);
|
|
|
|
if (!ret.isEmpty())
|
|
|
|
return ret;
|
2020-05-22 11:04:26 +00:00
|
|
|
std::string error = "failed to find an instance of object '" + name + "'";
|
|
|
|
if (activeOnly)
|
|
|
|
error += " in active cells";
|
|
|
|
throw std::runtime_error(error);
|
2010-07-05 10:09:04 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2014-04-29 13:27:49 +00:00
|
|
|
Ptr World::searchPtrViaActorId (int actorId)
|
|
|
|
{
|
2014-05-15 00:36:30 +00:00
|
|
|
// The player is not registered in any CellStore so must be checked manually
|
|
|
|
if (actorId == getPlayerPtr().getClass().getCreatureStats(getPlayerPtr()).getActorId())
|
|
|
|
return getPlayerPtr();
|
|
|
|
// Now search cells
|
2014-04-29 13:27:49 +00:00
|
|
|
return mWorldScene->searchPtrViaActorId (actorId);
|
2010-08-03 18:01:52 +00:00
|
|
|
}
|
|
|
|
|
2020-05-10 12:57:06 +00:00
|
|
|
Ptr World::searchPtrViaRefNum (const std::string& id, const ESM::RefNum& refNum)
|
|
|
|
{
|
|
|
|
return mCells.getPtr (id, refNum);
|
|
|
|
}
|
|
|
|
|
2015-12-06 17:13:04 +00:00
|
|
|
struct FindContainerVisitor
|
2014-10-24 16:49:38 +00:00
|
|
|
{
|
2015-12-18 16:56:48 +00:00
|
|
|
ConstPtr mContainedPtr;
|
2014-10-24 16:49:38 +00:00
|
|
|
Ptr mResult;
|
|
|
|
|
2015-12-18 16:56:48 +00:00
|
|
|
FindContainerVisitor(const ConstPtr& containedPtr) : mContainedPtr(containedPtr) {}
|
2014-10-24 16:49:38 +00:00
|
|
|
|
|
|
|
bool operator() (Ptr ptr)
|
|
|
|
{
|
|
|
|
if (mContainedPtr.getContainerStore() == &ptr.getClass().getContainerStore(ptr))
|
|
|
|
{
|
|
|
|
mResult = ptr;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-12-18 16:56:48 +00:00
|
|
|
Ptr World::findContainer(const ConstPtr& ptr)
|
2014-10-24 16:49:38 +00:00
|
|
|
{
|
|
|
|
if (ptr.isInCell())
|
|
|
|
return Ptr();
|
|
|
|
|
|
|
|
Ptr player = getPlayerPtr();
|
|
|
|
if (ptr.getContainerStore() == &player.getClass().getContainerStore(player))
|
|
|
|
return player;
|
|
|
|
|
2018-12-29 22:27:16 +00:00
|
|
|
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
2014-10-24 16:49:38 +00:00
|
|
|
{
|
2015-12-06 17:13:04 +00:00
|
|
|
FindContainerVisitor visitor(ptr);
|
2018-12-29 22:27:16 +00:00
|
|
|
cellstore->forEachType<ESM::Container>(visitor);
|
2015-12-07 02:47:40 +00:00
|
|
|
if (visitor.mResult.isEmpty())
|
2018-12-29 22:27:16 +00:00
|
|
|
cellstore->forEachType<ESM::Creature>(visitor);
|
2015-12-07 02:47:40 +00:00
|
|
|
if (visitor.mResult.isEmpty())
|
2018-12-29 22:27:16 +00:00
|
|
|
cellstore->forEachType<ESM::NPC>(visitor);
|
2014-10-24 16:49:38 +00:00
|
|
|
|
2015-12-06 17:13:04 +00:00
|
|
|
if (!visitor.mResult.isEmpty())
|
|
|
|
return visitor.mResult;
|
2014-10-24 16:49:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return Ptr();
|
|
|
|
}
|
|
|
|
|
2013-12-05 12:21:26 +00:00
|
|
|
void World::addContainerScripts(const Ptr& reference, CellStore * cell)
|
2013-01-20 16:38:56 +00:00
|
|
|
{
|
|
|
|
if( reference.getTypeName()==typeid (ESM::Container).name() ||
|
|
|
|
reference.getTypeName()==typeid (ESM::NPC).name() ||
|
|
|
|
reference.getTypeName()==typeid (ESM::Creature).name())
|
|
|
|
{
|
2014-05-22 18:37:22 +00:00
|
|
|
MWWorld::ContainerStore& container = reference.getClass().getContainerStore(reference);
|
2013-01-20 16:38:56 +00:00
|
|
|
for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it)
|
|
|
|
{
|
2014-05-22 18:37:22 +00:00
|
|
|
std::string script = it->getClass().getScript(*it);
|
2013-01-20 16:38:56 +00:00
|
|
|
if(script != "")
|
|
|
|
{
|
|
|
|
MWWorld::Ptr item = *it;
|
|
|
|
item.mCell = cell;
|
|
|
|
mLocalScripts.add (script, item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-02 23:06:38 +00:00
|
|
|
void World::enable (const Ptr& reference)
|
2010-07-09 14:07:03 +00:00
|
|
|
{
|
2014-02-13 19:24:27 +00:00
|
|
|
// enable is a no-op for items in containers
|
|
|
|
if (!reference.isInCell())
|
|
|
|
return;
|
|
|
|
|
2010-07-10 11:19:04 +00:00
|
|
|
if (!reference.getRefData().isEnabled())
|
2010-07-09 14:07:03 +00:00
|
|
|
{
|
2010-07-10 11:19:04 +00:00
|
|
|
reference.getRefData().enable();
|
2013-02-05 11:19:06 +00:00
|
|
|
|
2012-05-25 15:28:27 +00:00
|
|
|
if(mWorldScene->getActiveCells().find (reference.getCell()) != mWorldScene->getActiveCells().end() && reference.getRefData().getCount())
|
|
|
|
mWorldScene->addObjectToScene (reference);
|
2019-08-03 13:37:00 +00:00
|
|
|
|
|
|
|
if (reference.getCellRef().getRefNum().hasContentFile())
|
2020-05-08 13:37:00 +00:00
|
|
|
{
|
|
|
|
int type = mStore.find(Misc::StringUtils::lowerCase(reference.getCellRef().getRefId()));
|
2020-05-08 13:37:00 +00:00
|
|
|
if (mRendering->pagingEnableObject(type, reference, true))
|
|
|
|
mWorldScene->reloadTerrain();
|
2020-05-08 13:37:00 +00:00
|
|
|
}
|
2010-07-09 14:07:03 +00:00
|
|
|
}
|
|
|
|
}
|
2013-02-05 11:19:06 +00:00
|
|
|
|
2013-01-20 16:38:56 +00:00
|
|
|
void World::removeContainerScripts(const Ptr& reference)
|
|
|
|
{
|
|
|
|
if( reference.getTypeName()==typeid (ESM::Container).name() ||
|
|
|
|
reference.getTypeName()==typeid (ESM::NPC).name() ||
|
|
|
|
reference.getTypeName()==typeid (ESM::Creature).name())
|
|
|
|
{
|
2014-05-22 18:37:22 +00:00
|
|
|
MWWorld::ContainerStore& container = reference.getClass().getContainerStore(reference);
|
2013-01-20 16:38:56 +00:00
|
|
|
for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it)
|
2013-01-20 15:43:52 +00:00
|
|
|
{
|
2014-05-22 18:37:22 +00:00
|
|
|
std::string script = it->getClass().getScript(*it);
|
2013-01-20 16:38:56 +00:00
|
|
|
if(script != "")
|
2013-01-20 15:43:52 +00:00
|
|
|
{
|
2013-01-20 16:38:56 +00:00
|
|
|
MWWorld::Ptr item = *it;
|
|
|
|
mLocalScripts.remove (item);
|
2013-01-20 15:43:52 +00:00
|
|
|
}
|
|
|
|
}
|
2010-07-09 14:07:03 +00:00
|
|
|
}
|
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2012-07-02 23:06:38 +00:00
|
|
|
void World::disable (const Ptr& reference)
|
2010-07-09 14:07:03 +00:00
|
|
|
{
|
2019-08-03 13:37:00 +00:00
|
|
|
if (!reference.getRefData().isEnabled())
|
|
|
|
return;
|
|
|
|
|
2014-02-13 19:24:27 +00:00
|
|
|
// disable is a no-op for items in containers
|
|
|
|
if (!reference.isInCell())
|
|
|
|
return;
|
|
|
|
|
2019-08-03 13:37:00 +00:00
|
|
|
if (reference == getPlayerPtr())
|
|
|
|
throw std::runtime_error("can not disable player object");
|
2015-11-03 16:53:57 +00:00
|
|
|
|
2019-08-03 13:37:00 +00:00
|
|
|
reference.getRefData().disable();
|
2013-02-05 11:19:06 +00:00
|
|
|
|
2019-08-03 13:37:00 +00:00
|
|
|
if (reference.getCellRef().getRefNum().hasContentFile())
|
2020-05-08 13:37:00 +00:00
|
|
|
{
|
|
|
|
int type = mStore.find(Misc::StringUtils::lowerCase(reference.getCellRef().getRefId()));
|
2020-05-08 13:37:00 +00:00
|
|
|
if (mRendering->pagingEnableObject(type, reference, false))
|
|
|
|
mWorldScene->reloadTerrain();
|
2010-08-03 18:01:52 +00:00
|
|
|
}
|
2019-08-03 13:37:00 +00:00
|
|
|
|
|
|
|
if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount())
|
|
|
|
mWorldScene->removeObjectFromScene (reference);
|
2010-07-09 14:07:03 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2015-08-27 19:20:45 +00:00
|
|
|
void World::advanceTime (double hours, bool incremental)
|
2010-07-18 16:29:16 +00:00
|
|
|
{
|
2018-12-15 06:23:50 +00:00
|
|
|
if (!incremental)
|
|
|
|
{
|
|
|
|
// When we fast-forward time, we should recharge magic items
|
|
|
|
// in all loaded cells, using game world time
|
|
|
|
float duration = hours * 3600;
|
|
|
|
const float timeScaleFactor = getTimeScaleFactor();
|
|
|
|
if (timeScaleFactor != 0.0f)
|
|
|
|
duration /= timeScaleFactor;
|
|
|
|
|
|
|
|
rechargeItems(duration, false);
|
|
|
|
}
|
2013-11-19 15:42:24 +00:00
|
|
|
|
2015-08-27 19:20:45 +00:00
|
|
|
mWeatherManager->advanceTime (hours, incremental);
|
2020-06-03 07:32:28 +00:00
|
|
|
mCurrentDate->advanceTime(hours, mGlobalVariables);
|
|
|
|
updateSkyDate();
|
2012-09-25 19:28:25 +00:00
|
|
|
|
2016-07-05 22:11:58 +00:00
|
|
|
if (!incremental)
|
2017-07-28 12:50:52 +00:00
|
|
|
{
|
|
|
|
mRendering->notifyWorldSpaceChanged();
|
2016-07-05 22:11:58 +00:00
|
|
|
mProjectileManager->clear();
|
2020-11-23 06:05:45 +00:00
|
|
|
mDiscardMovements = true;
|
2017-07-28 12:50:52 +00:00
|
|
|
}
|
2010-07-22 09:48:27 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2020-06-03 07:32:28 +00:00
|
|
|
float World::getTimeScaleFactor() const
|
2012-09-18 18:53:32 +00:00
|
|
|
{
|
2020-06-03 07:32:28 +00:00
|
|
|
return mCurrentDate->getTimeScaleFactor();
|
2012-09-18 18:53:32 +00:00
|
|
|
}
|
|
|
|
|
2020-06-03 07:32:28 +00:00
|
|
|
TimeStamp World::getTimeStamp() const
|
2012-09-18 18:53:32 +00:00
|
|
|
{
|
2020-06-03 07:32:28 +00:00
|
|
|
return mCurrentDate->getTimeStamp();
|
2012-09-18 18:53:32 +00:00
|
|
|
}
|
|
|
|
|
2020-06-03 07:32:28 +00:00
|
|
|
ESM::EpochTimeStamp World::getEpochTimeStamp() const
|
2012-05-19 07:31:45 +00:00
|
|
|
{
|
2020-06-03 07:32:28 +00:00
|
|
|
return mCurrentDate->getEpochTimeStamp();
|
2012-05-19 07:31:45 +00:00
|
|
|
}
|
|
|
|
|
2011-04-26 19:38:21 +00:00
|
|
|
bool World::toggleSky()
|
2010-07-18 17:48:02 +00:00
|
|
|
{
|
2015-06-16 18:56:48 +00:00
|
|
|
mSky = !mSky;
|
|
|
|
mRendering->setSkyEnabled(mSky);
|
|
|
|
return mSky;
|
2010-07-18 17:48:02 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2010-07-18 17:48:02 +00:00
|
|
|
int World::getMasserPhase() const
|
|
|
|
{
|
2015-06-16 18:56:48 +00:00
|
|
|
return mRendering->skyGetMasserPhase();
|
2010-07-18 17:48:02 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2010-07-18 17:48:02 +00:00
|
|
|
int World::getSecundaPhase() const
|
|
|
|
{
|
2015-06-16 18:56:48 +00:00
|
|
|
return mRendering->skyGetSecundaPhase();
|
2010-07-18 17:48:02 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2010-07-18 17:48:02 +00:00
|
|
|
void World::setMoonColour (bool red)
|
|
|
|
{
|
2015-06-16 18:56:48 +00:00
|
|
|
mRendering->skySetMoonColour (red);
|
2010-07-18 17:48:02 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2016-03-24 16:18:08 +00:00
|
|
|
void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
|
2010-07-22 10:29:23 +00:00
|
|
|
{
|
2015-05-23 04:07:11 +00:00
|
|
|
mPhysics->clearQueuedMovement();
|
Process movement queue in one or several background threads
Before movement calculation, the main thread prepare a
vector of ActorFrameData, which contains all data necessary to perform
the simulation, and feed it to the solver. At the same time it fetches
the result from the previous background simulation, which in turn is
used by the game mechanics.
Other functions of the physics system (weapon hit for instance)
interrupt the background simulation, with some exceptions described
below.
The number of threads is controlled by the numeric setting
[Physics]
async num threads
In case 'async num threads' > 1 and Bullet doesn't support multiple threads,
1 async thread will be used. 0 means synchronous solver.
Additional settings (will be silently switched off if async num threads = 0)
[Physics]
defer aabb update
Update AABBs of actors and objects in the background thread(s). It is not an especially
costly operation, but it needs exclusive access to the collision world, which blocks
other operations. Since AABB needs to be updated for collision detection, one can queue
them to defer update before start of the movement solver. Extensive tests on as much
as one installation (mine) show no drawback having that switched on.
[Physics]
lineofsight keep inactive cache
Control for how long (how many frames) the line of sight (LOS) request will be kept updated.
When a request for LOS is made for the first time, the background threads are stopped to
service it. From now on, the LOS will be refreshed preemptively as part of the background
routine until it is not required for lineofsight keep inactive cache frames. This mean
that subsequent request will not interrupt the background computation.
2020-10-15 04:11:44 +00:00
|
|
|
mDiscardMovements = true;
|
2014-08-13 14:23:34 +00:00
|
|
|
|
2016-02-27 11:53:07 +00:00
|
|
|
if (changeEvent && mCurrentWorldSpace != cellName)
|
2014-05-17 03:21:17 +00:00
|
|
|
{
|
|
|
|
// changed worldspace
|
|
|
|
mProjectileManager->clear();
|
2015-04-19 15:55:56 +00:00
|
|
|
mRendering->notifyWorldSpaceChanged();
|
|
|
|
|
2014-05-17 03:21:17 +00:00
|
|
|
mCurrentWorldSpace = cellName;
|
|
|
|
}
|
2014-05-16 11:09:23 +00:00
|
|
|
|
2014-01-08 17:39:44 +00:00
|
|
|
removeContainerScripts(getPlayerPtr());
|
2016-03-24 16:18:08 +00:00
|
|
|
mWorldScene->changeToInteriorCell(cellName, position, adjustPlayerPos, changeEvent);
|
2014-01-08 17:39:44 +00:00
|
|
|
addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
|
2020-07-18 11:27:53 +00:00
|
|
|
mRendering->getCamera()->instantTransition();
|
2010-07-22 10:29:23 +00:00
|
|
|
}
|
2010-08-03 18:01:52 +00:00
|
|
|
|
2016-03-24 16:18:08 +00:00
|
|
|
void World::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
|
2010-08-20 11:33:03 +00:00
|
|
|
{
|
2015-05-23 04:07:11 +00:00
|
|
|
mPhysics->clearQueuedMovement();
|
Process movement queue in one or several background threads
Before movement calculation, the main thread prepare a
vector of ActorFrameData, which contains all data necessary to perform
the simulation, and feed it to the solver. At the same time it fetches
the result from the previous background simulation, which in turn is
used by the game mechanics.
Other functions of the physics system (weapon hit for instance)
interrupt the background simulation, with some exceptions described
below.
The number of threads is controlled by the numeric setting
[Physics]
async num threads
In case 'async num threads' > 1 and Bullet doesn't support multiple threads,
1 async thread will be used. 0 means synchronous solver.
Additional settings (will be silently switched off if async num threads = 0)
[Physics]
defer aabb update
Update AABBs of actors and objects in the background thread(s). It is not an especially
costly operation, but it needs exclusive access to the collision world, which blocks
other operations. Since AABB needs to be updated for collision detection, one can queue
them to defer update before start of the movement solver. Extensive tests on as much
as one installation (mine) show no drawback having that switched on.
[Physics]
lineofsight keep inactive cache
Control for how long (how many frames) the line of sight (LOS) request will be kept updated.
When a request for LOS is made for the first time, the background threads are stopped to
service it. From now on, the LOS will be refreshed preemptively as part of the background
routine until it is not required for lineofsight keep inactive cache frames. This mean
that subsequent request will not interrupt the background computation.
2020-10-15 04:11:44 +00:00
|
|
|
mDiscardMovements = true;
|
2014-08-13 14:23:34 +00:00
|
|
|
|
2016-07-02 17:48:11 +00:00
|
|
|
if (changeEvent && mCurrentWorldSpace != ESM::CellId::sDefaultWorldspace)
|
2014-05-16 11:09:23 +00:00
|
|
|
{
|
|
|
|
// changed worldspace
|
|
|
|
mProjectileManager->clear();
|
2015-04-19 15:55:56 +00:00
|
|
|
mRendering->notifyWorldSpaceChanged();
|
2014-05-16 11:09:23 +00:00
|
|
|
}
|
2014-01-08 17:39:44 +00:00
|
|
|
removeContainerScripts(getPlayerPtr());
|
2016-03-24 16:18:08 +00:00
|
|
|
mWorldScene->changeToExteriorCell(position, adjustPlayerPos, changeEvent);
|
2014-01-08 17:39:44 +00:00
|
|
|
addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
|
2020-07-18 11:27:53 +00:00
|
|
|
mRendering->getCamera()->instantTransition();
|
2010-08-20 11:33:03 +00:00
|
|
|
}
|
|
|
|
|
2016-03-24 16:18:08 +00:00
|
|
|
void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
|
2014-01-16 11:03:23 +00:00
|
|
|
{
|
2016-02-27 11:53:07 +00:00
|
|
|
if (!changeEvent)
|
2014-05-17 03:21:17 +00:00
|
|
|
mCurrentWorldSpace = cellId.mWorldspace;
|
|
|
|
|
2014-01-16 11:03:23 +00:00
|
|
|
if (cellId.mPaged)
|
2016-03-24 16:18:08 +00:00
|
|
|
changeToExteriorCell (position, adjustPlayerPos, changeEvent);
|
2014-01-16 11:03:23 +00:00
|
|
|
else
|
2016-05-08 16:59:41 +00:00
|
|
|
changeToInteriorCell (cellId.mWorldspace, position, adjustPlayerPos, changeEvent);
|
2020-06-03 07:32:28 +00:00
|
|
|
|
|
|
|
mCurrentDate->setup(mGlobalVariables);
|
2014-01-16 11:03:23 +00:00
|
|
|
}
|
|
|
|
|
2010-07-22 10:29:23 +00:00
|
|
|
void World::markCellAsUnchanged()
|
|
|
|
{
|
2011-08-01 02:06:38 +00:00
|
|
|
return mWorldScene->markCellAsUnchanged();
|
2010-07-22 10:29:23 +00:00
|
|
|
}
|
2010-08-05 11:29:49 +00:00
|
|
|
|
2013-01-08 07:40:17 +00:00
|
|
|
float World::getMaxActivationDistance ()
|
2010-08-05 11:29:49 +00:00
|
|
|
{
|
2013-01-09 03:52:18 +00:00
|
|
|
if (mActivationDistanceOverride >= 0)
|
2015-03-08 00:07:29 +00:00
|
|
|
return static_cast<float>(mActivationDistanceOverride);
|
2013-01-09 03:52:18 +00:00
|
|
|
|
2019-01-07 14:48:41 +00:00
|
|
|
static const int iMaxActivateDist = mStore.get<ESM::GameSetting>().find("iMaxActivateDist")->mValue.getInteger();
|
2016-06-07 12:52:44 +00:00
|
|
|
return static_cast<float>(iMaxActivateDist);
|
2013-01-08 07:40:17 +00:00
|
|
|
}
|
|
|
|
|
2013-01-08 07:27:37 +00:00
|
|
|
MWWorld::Ptr World::getFacedObject()
|
2010-08-05 11:29:49 +00:00
|
|
|
{
|
2015-05-24 01:36:34 +00:00
|
|
|
MWWorld::Ptr facedObject;
|
2014-09-09 15:50:56 +00:00
|
|
|
|
|
|
|
if (MWBase::Environment::get().getWindowManager()->isGuiMode() &&
|
|
|
|
MWBase::Environment::get().getWindowManager()->isConsoleMode())
|
2015-05-24 01:36:34 +00:00
|
|
|
facedObject = getFacedObject(getMaxActivationDistance() * 50, false);
|
2014-09-09 15:50:56 +00:00
|
|
|
else
|
|
|
|
{
|
2016-09-22 15:16:51 +00:00
|
|
|
float activationDistance = getActivationDistancePlusTelekinesis();
|
2014-09-09 15:50:56 +00:00
|
|
|
|
2016-07-08 17:12:58 +00:00
|
|
|
facedObject = getFacedObject(activationDistance, true);
|
2014-09-09 15:50:56 +00:00
|
|
|
|
2016-07-06 15:03:14 +00:00
|
|
|
if (!facedObject.isEmpty() && !facedObject.getClass().allowTelekinesis(facedObject)
|
2017-03-22 10:55:48 +00:00
|
|
|
&& mDistanceToFacedObject > getMaxActivationDistance() && !MWBase::Environment::get().getWindowManager()->isGuiMode())
|
2020-11-13 07:39:47 +00:00
|
|
|
return nullptr;
|
2014-09-09 15:50:56 +00:00
|
|
|
}
|
2015-05-24 01:36:34 +00:00
|
|
|
return facedObject;
|
2010-08-05 11:29:49 +00:00
|
|
|
}
|
2010-08-07 18:25:17 +00:00
|
|
|
|
2016-07-08 14:07:07 +00:00
|
|
|
float World::getDistanceToFacedObject()
|
|
|
|
{
|
|
|
|
return mDistanceToFacedObject;
|
|
|
|
}
|
|
|
|
|
2015-12-18 16:36:14 +00:00
|
|
|
osg::Matrixf World::getActorHeadTransform(const MWWorld::ConstPtr& actor) const
|
2013-07-25 19:58:43 +00:00
|
|
|
{
|
2015-12-18 16:36:14 +00:00
|
|
|
const MWRender::Animation *anim = mRendering->getAnimation(actor);
|
2015-12-11 01:48:45 +00:00
|
|
|
if(anim)
|
2013-07-25 19:58:43 +00:00
|
|
|
{
|
2015-12-11 01:48:45 +00:00
|
|
|
const osg::Node *node = anim->getNode("Head");
|
|
|
|
if(!node) node = anim->getNode("Bip01 Head");
|
|
|
|
if(node)
|
2015-05-22 02:36:17 +00:00
|
|
|
{
|
2016-02-22 17:58:19 +00:00
|
|
|
osg::NodePathList nodepaths = node->getParentalNodePaths();
|
|
|
|
if(!nodepaths.empty())
|
|
|
|
return osg::computeLocalToWorld(nodepaths[0]);
|
2015-05-22 02:36:17 +00:00
|
|
|
}
|
2013-07-25 19:58:43 +00:00
|
|
|
}
|
2015-12-11 01:48:45 +00:00
|
|
|
return osg::Matrixf::translate(actor.getRefData().getPosition().asVec3());
|
2015-09-16 23:08:16 +00:00
|
|
|
}
|
|
|
|
|
2017-02-02 07:20:34 +00:00
|
|
|
std::pair<MWWorld::Ptr,osg::Vec3f> World::getHitContact(const MWWorld::ConstPtr &ptr, float distance, std::vector<MWWorld::Ptr> &targets)
|
2015-09-16 23:08:16 +00:00
|
|
|
{
|
|
|
|
const ESM::Position &posdata = ptr.getRefData().getPosition();
|
|
|
|
|
|
|
|
osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1));
|
2016-06-10 21:30:41 +00:00
|
|
|
|
2018-05-09 06:31:53 +00:00
|
|
|
osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr);
|
2016-06-10 21:30:41 +00:00
|
|
|
|
2018-05-09 08:53:58 +00:00
|
|
|
// the origin of hitbox is an actor's front, not center
|
|
|
|
distance += halfExtents.y();
|
|
|
|
|
|
|
|
// special cased for better aiming with the camera
|
|
|
|
// if we do not hit anything, will use the default approach as fallback
|
2016-06-10 21:30:41 +00:00
|
|
|
if (ptr == getPlayerPtr())
|
|
|
|
{
|
2018-05-09 08:53:58 +00:00
|
|
|
osg::Vec3f pos = getActorHeadTransform(ptr).getTrans();
|
|
|
|
|
|
|
|
std::pair<MWWorld::Ptr,osg::Vec3f> result = mPhysics->getHitContact(ptr, pos, rot, distance, targets);
|
|
|
|
if(!result.first.isEmpty())
|
|
|
|
return std::make_pair(result.first, result.second);
|
2016-06-10 21:30:41 +00:00
|
|
|
}
|
2013-07-25 19:58:43 +00:00
|
|
|
|
2018-05-09 08:53:58 +00:00
|
|
|
osg::Vec3f pos = ptr.getRefData().getPosition().asVec3();
|
|
|
|
|
|
|
|
// general case, compatible with all types of different creatures
|
|
|
|
// note: we intentionally do *not* use the collision box offset here, this is required to make
|
|
|
|
// some flying creatures work that have their collision box offset in the air
|
|
|
|
pos.z() += halfExtents.z();
|
2018-05-09 06:31:53 +00:00
|
|
|
|
2017-02-02 07:20:34 +00:00
|
|
|
std::pair<MWWorld::Ptr,osg::Vec3f> result = mPhysics->getHitContact(ptr, pos, rot, distance, targets);
|
2015-05-22 02:36:17 +00:00
|
|
|
if(result.first.isEmpty())
|
|
|
|
return std::make_pair(MWWorld::Ptr(), osg::Vec3f());
|
2013-07-25 19:58:43 +00:00
|
|
|
|
2015-05-22 02:36:17 +00:00
|
|
|
return std::make_pair(result.first, result.second);
|
2013-07-25 19:58:43 +00:00
|
|
|
}
|
|
|
|
|
2012-07-02 23:06:38 +00:00
|
|
|
void World::deleteObject (const Ptr& ptr)
|
2010-08-07 18:25:17 +00:00
|
|
|
{
|
2018-10-09 06:21:12 +00:00
|
|
|
if (!ptr.getRefData().isDeleted() && ptr.getContainerStore() == nullptr)
|
2010-08-07 18:25:17 +00:00
|
|
|
{
|
2015-12-08 23:26:39 +00:00
|
|
|
if (ptr == getPlayerPtr())
|
|
|
|
throw std::runtime_error("can not delete player object");
|
|
|
|
|
2013-10-16 16:39:29 +00:00
|
|
|
ptr.getRefData().setCount(0);
|
2010-08-07 18:25:17 +00:00
|
|
|
|
2013-10-16 16:39:29 +00:00
|
|
|
if (ptr.isInCell()
|
|
|
|
&& mWorldScene->getActiveCells().find(ptr.getCell()) != mWorldScene->getActiveCells().end()
|
|
|
|
&& ptr.getRefData().isEnabled())
|
2012-05-24 11:26:07 +00:00
|
|
|
{
|
2012-05-25 15:28:27 +00:00
|
|
|
mWorldScene->removeObjectFromScene (ptr);
|
2012-05-24 11:26:07 +00:00
|
|
|
mLocalScripts.remove (ptr);
|
2013-01-20 17:01:30 +00:00
|
|
|
removeContainerScripts (ptr);
|
2012-05-24 11:26:07 +00:00
|
|
|
}
|
2010-08-07 18:25:17 +00:00
|
|
|
}
|
|
|
|
}
|
2010-08-21 10:31:04 +00:00
|
|
|
|
2014-12-06 20:08:18 +00:00
|
|
|
void World::undeleteObject(const Ptr& ptr)
|
|
|
|
{
|
2015-01-10 23:20:22 +00:00
|
|
|
if (!ptr.getCellRef().hasContentFile())
|
2014-12-06 20:08:18 +00:00
|
|
|
return;
|
|
|
|
if (ptr.getRefData().isDeleted())
|
|
|
|
{
|
|
|
|
ptr.getRefData().setCount(1);
|
|
|
|
if (mWorldScene->getActiveCells().find(ptr.getCell()) != mWorldScene->getActiveCells().end()
|
|
|
|
&& ptr.getRefData().isEnabled())
|
|
|
|
{
|
|
|
|
mWorldScene->addObjectToScene(ptr);
|
|
|
|
std::string script = ptr.getClass().getScript(ptr);
|
|
|
|
if (!script.empty())
|
|
|
|
mLocalScripts.add(script, ptr);
|
|
|
|
addContainerScripts(ptr, ptr.getCell());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-13 01:56:41 +00:00
|
|
|
MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z, bool movePhysics)
|
2010-08-21 10:31:04 +00:00
|
|
|
{
|
2014-06-14 15:56:41 +00:00
|
|
|
ESM::Position pos = ptr.getRefData().getPosition();
|
2013-04-04 14:51:22 +00:00
|
|
|
|
2013-02-05 11:19:06 +00:00
|
|
|
pos.pos[0] = x;
|
|
|
|
pos.pos[1] = y;
|
|
|
|
pos.pos[2] = z;
|
2013-04-03 21:55:57 +00:00
|
|
|
|
2014-06-14 15:56:41 +00:00
|
|
|
ptr.getRefData().setPosition(pos);
|
|
|
|
|
2015-05-23 23:32:00 +00:00
|
|
|
osg::Vec3f vec(x, y, z);
|
2012-07-30 19:28:14 +00:00
|
|
|
|
2018-09-08 14:17:11 +00:00
|
|
|
CellStore *currCell = ptr.isInCell() ? ptr.getCell() : nullptr; // currCell == nullptr should only happen for player, during initial startup
|
2012-08-08 10:51:33 +00:00
|
|
|
bool isPlayer = ptr == mPlayer->getPlayer();
|
2014-09-28 10:44:47 +00:00
|
|
|
bool haveToMove = isPlayer || (currCell && mWorldScene->isCellActive(*currCell));
|
2015-05-17 14:25:13 +00:00
|
|
|
MWWorld::Ptr newPtr = ptr;
|
2012-11-07 18:34:28 +00:00
|
|
|
|
2018-09-08 14:17:11 +00:00
|
|
|
if (!isPlayer && !currCell)
|
|
|
|
throw std::runtime_error("Can not move actor \"" + ptr.getCellRef().getRefId() + "\" to another cell: current cell is nullptr");
|
|
|
|
|
|
|
|
if (!newCell)
|
|
|
|
throw std::runtime_error("Can not move actor \"" + ptr.getCellRef().getRefId() + "\" to another cell: new cell is nullptr");
|
|
|
|
|
2014-03-07 05:11:00 +00:00
|
|
|
if (currCell != newCell)
|
2012-11-05 18:40:02 +00:00
|
|
|
{
|
2013-04-04 14:51:22 +00:00
|
|
|
removeContainerScripts(ptr);
|
2013-01-31 00:21:04 +00:00
|
|
|
|
2012-11-05 18:40:02 +00:00
|
|
|
if (isPlayer)
|
2013-01-29 07:39:11 +00:00
|
|
|
{
|
2014-03-07 05:11:00 +00:00
|
|
|
if (!newCell->isExterior())
|
2018-07-08 20:08:57 +00:00
|
|
|
{
|
2016-03-24 16:18:08 +00:00
|
|
|
changeToInteriorCell(Misc::StringUtils::lowerCase(newCell->getCell()->mName), pos, false);
|
2018-07-08 20:08:57 +00:00
|
|
|
removeContainerScripts(getPlayerPtr());
|
|
|
|
}
|
2012-11-05 18:40:02 +00:00
|
|
|
else
|
|
|
|
{
|
2014-10-02 14:30:23 +00:00
|
|
|
if (mWorldScene->isCellActive(*newCell))
|
|
|
|
mWorldScene->changePlayerCell(newCell, pos, false);
|
|
|
|
else
|
|
|
|
mWorldScene->changeToExteriorCell(pos, false);
|
2012-08-08 10:51:33 +00:00
|
|
|
}
|
2014-03-07 05:11:00 +00:00
|
|
|
addContainerScripts (getPlayerPtr(), newCell);
|
2015-05-17 14:25:13 +00:00
|
|
|
newPtr = getPlayerPtr();
|
2013-01-29 07:39:11 +00:00
|
|
|
}
|
2018-09-08 14:17:11 +00:00
|
|
|
else
|
2013-01-29 07:39:11 +00:00
|
|
|
{
|
2018-08-01 15:30:30 +00:00
|
|
|
bool currCellActive = mWorldScene->isCellActive(*currCell);
|
2018-09-08 14:17:11 +00:00
|
|
|
bool newCellActive = mWorldScene->isCellActive(*newCell);
|
2014-06-24 13:29:36 +00:00
|
|
|
if (!currCellActive && newCellActive)
|
2014-06-16 15:14:35 +00:00
|
|
|
{
|
2015-12-04 18:46:02 +00:00
|
|
|
newPtr = currCell->moveTo(ptr, newCell);
|
2014-06-16 15:14:35 +00:00
|
|
|
mWorldScene->addObjectToScene(newPtr);
|
|
|
|
|
|
|
|
std::string script = newPtr.getClass().getScript(newPtr);
|
2018-07-08 20:08:57 +00:00
|
|
|
if (!script.empty())
|
2018-07-08 20:13:24 +00:00
|
|
|
{
|
2014-06-16 15:14:35 +00:00
|
|
|
mLocalScripts.add(script, newPtr);
|
2018-07-08 20:13:24 +00:00
|
|
|
}
|
2014-06-16 15:14:35 +00:00
|
|
|
addContainerScripts(newPtr, newCell);
|
|
|
|
}
|
2014-06-24 13:29:36 +00:00
|
|
|
else if (!newCellActive && currCellActive)
|
2012-11-05 18:40:02 +00:00
|
|
|
{
|
2012-08-08 10:51:33 +00:00
|
|
|
mWorldScene->removeObjectFromScene(ptr);
|
|
|
|
mLocalScripts.remove(ptr);
|
2013-02-05 11:19:06 +00:00
|
|
|
removeContainerScripts (ptr);
|
2012-08-08 10:51:33 +00:00
|
|
|
haveToMove = false;
|
2013-11-28 16:31:17 +00:00
|
|
|
|
2015-12-04 18:46:02 +00:00
|
|
|
newPtr = currCell->moveTo(ptr, newCell);
|
2020-11-13 07:39:47 +00:00
|
|
|
newPtr.getRefData().setBaseNode(nullptr);
|
2012-11-05 18:40:02 +00:00
|
|
|
}
|
2014-06-24 13:29:36 +00:00
|
|
|
else if (!currCellActive && !newCellActive)
|
2015-12-04 18:46:02 +00:00
|
|
|
newPtr = currCell->moveTo(ptr, newCell);
|
2014-06-24 13:29:36 +00:00
|
|
|
else // both cells active
|
2012-11-05 18:40:02 +00:00
|
|
|
{
|
2015-12-04 18:46:02 +00:00
|
|
|
newPtr = currCell->moveTo(ptr, newCell);
|
2012-08-08 10:51:33 +00:00
|
|
|
|
2015-05-23 18:33:44 +00:00
|
|
|
mRendering->updatePtr(ptr, newPtr);
|
2015-05-17 14:25:13 +00:00
|
|
|
MWBase::Environment::get().getSoundManager()->updatePtr (ptr, newPtr);
|
2015-05-23 18:33:44 +00:00
|
|
|
mPhysics->updatePtr(ptr, newPtr);
|
2013-02-24 10:59:21 +00:00
|
|
|
|
2013-01-29 08:19:24 +00:00
|
|
|
MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager();
|
2015-05-17 14:25:13 +00:00
|
|
|
mechMgr->updateCell(ptr, newPtr);
|
2012-08-08 10:51:33 +00:00
|
|
|
|
2013-02-05 11:19:06 +00:00
|
|
|
std::string script =
|
2014-05-22 18:37:22 +00:00
|
|
|
ptr.getClass().getScript(ptr);
|
2013-02-05 11:19:06 +00:00
|
|
|
if (!script.empty())
|
2012-11-05 18:40:02 +00:00
|
|
|
{
|
2013-01-29 08:19:24 +00:00
|
|
|
mLocalScripts.remove(ptr);
|
2013-02-05 11:19:06 +00:00
|
|
|
removeContainerScripts (ptr);
|
2015-05-17 14:25:13 +00:00
|
|
|
mLocalScripts.add(script, newPtr);
|
|
|
|
addContainerScripts (newPtr, newCell);
|
2010-08-21 10:41:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-07-11 13:24:20 +00:00
|
|
|
|
|
|
|
MWBase::Environment::get().getWindowManager()->updateConsoleObjectPtr(ptr, newPtr);
|
2020-07-12 11:25:40 +00:00
|
|
|
MWBase::Environment::get().getScriptManager()->getGlobalScripts().updatePtrs(ptr, newPtr);
|
2010-08-21 10:41:59 +00:00
|
|
|
}
|
2015-05-17 14:25:13 +00:00
|
|
|
if (haveToMove && newPtr.getRefData().getBaseNode())
|
2012-11-05 18:40:02 +00:00
|
|
|
{
|
2020-05-11 13:37:00 +00:00
|
|
|
mWorldScene->updateObjectPosition(newPtr, vec, movePhysics);
|
2016-02-13 01:56:41 +00:00
|
|
|
if (movePhysics)
|
2019-01-13 21:38:34 +00:00
|
|
|
{
|
2020-05-11 13:37:00 +00:00
|
|
|
if (const auto object = mPhysics->getObject(ptr))
|
2019-03-03 12:59:02 +00:00
|
|
|
updateNavigatorObject(object);
|
2019-01-13 21:38:34 +00:00
|
|
|
}
|
2012-07-30 19:28:14 +00:00
|
|
|
}
|
2020-05-11 13:37:00 +00:00
|
|
|
|
2014-10-02 14:30:23 +00:00
|
|
|
if (isPlayer)
|
2015-05-23 23:32:00 +00:00
|
|
|
mWorldScene->playerMoved(vec);
|
2020-05-11 13:37:00 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
|
|
|
|
mWorldScene->removeFromPagedRefs(newPtr);
|
2014-10-02 14:30:23 +00:00
|
|
|
}
|
2020-05-11 13:37:00 +00:00
|
|
|
|
2015-05-17 14:25:13 +00:00
|
|
|
return newPtr;
|
2012-08-08 10:51:33 +00:00
|
|
|
}
|
|
|
|
|
2019-03-31 11:02:24 +00:00
|
|
|
MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive)
|
2012-08-08 10:51:33 +00:00
|
|
|
{
|
2019-03-31 11:02:24 +00:00
|
|
|
int cellX, cellY;
|
|
|
|
positionToIndex(x, y, cellX, cellY);
|
2013-04-04 14:51:22 +00:00
|
|
|
|
2019-03-31 11:02:24 +00:00
|
|
|
CellStore* cell = ptr.getCell();
|
|
|
|
CellStore* newCell = getExterior(cellX, cellY);
|
2019-05-05 11:02:14 +00:00
|
|
|
bool isCellActive = getPlayerPtr().isInCell() && getPlayerPtr().getCell()->isExterior() && mWorldScene->isCellActive(*newCell);
|
2012-08-11 15:30:55 +00:00
|
|
|
|
2019-03-31 11:02:24 +00:00
|
|
|
if (cell->isExterior() || (moveToActive && isCellActive && ptr.getClass().isActor()))
|
|
|
|
cell = newCell;
|
2013-04-04 14:51:22 +00:00
|
|
|
|
2016-02-13 01:56:41 +00:00
|
|
|
return moveObject(ptr, cell, x, y, z, movePhysics);
|
2011-08-02 16:50:31 +00:00
|
|
|
}
|
|
|
|
|
2019-03-31 11:02:24 +00:00
|
|
|
MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool moveToActive)
|
2011-08-02 16:50:31 +00:00
|
|
|
{
|
2019-03-31 11:02:24 +00:00
|
|
|
return moveObjectImp(ptr, x, y, z, true, moveToActive);
|
2010-08-21 10:31:04 +00:00
|
|
|
}
|
2010-08-22 19:30:48 +00:00
|
|
|
|
2020-12-18 07:18:07 +00:00
|
|
|
MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec)
|
|
|
|
{
|
|
|
|
auto* actor = mPhysics->getActor(ptr);
|
|
|
|
if (actor)
|
|
|
|
{
|
|
|
|
actor->adjustPosition(vec);
|
|
|
|
return ptr;
|
|
|
|
}
|
|
|
|
osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec;
|
|
|
|
return moveObject(ptr, newpos.x(), newpos.y(), newpos.z());
|
|
|
|
}
|
|
|
|
|
2012-07-09 16:47:59 +00:00
|
|
|
void World::scaleObject (const Ptr& ptr, float scale)
|
2012-05-25 16:23:06 +00:00
|
|
|
{
|
2019-03-03 12:14:51 +00:00
|
|
|
if (mPhysics->getActor(ptr))
|
|
|
|
mNavigator->removeAgent(getPathfindingHalfExtents(ptr));
|
|
|
|
|
2020-05-11 13:37:00 +00:00
|
|
|
if (scale != ptr.getCellRef().getScale())
|
|
|
|
{
|
|
|
|
ptr.getCellRef().setScale(scale);
|
|
|
|
mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
|
|
|
|
mWorldScene->removeFromPagedRefs(ptr);
|
|
|
|
}
|
2012-12-04 09:58:43 +00:00
|
|
|
|
2020-11-13 07:39:47 +00:00
|
|
|
if(ptr.getRefData().getBaseNode() != nullptr)
|
2020-05-11 13:37:00 +00:00
|
|
|
mWorldScene->updateObjectScale(ptr);
|
2019-03-03 12:14:51 +00:00
|
|
|
|
|
|
|
if (mPhysics->getActor(ptr))
|
|
|
|
mNavigator->addAgent(getPathfindingHalfExtents(ptr));
|
2019-03-03 12:51:02 +00:00
|
|
|
else if (const auto object = mPhysics->getObject(ptr))
|
|
|
|
mShouldUpdateNavigator = updateNavigatorObject(object) || mShouldUpdateNavigator;
|
2012-05-25 16:23:06 +00:00
|
|
|
}
|
|
|
|
|
2019-11-30 11:29:31 +00:00
|
|
|
void World::rotateObjectImp(const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags)
|
2012-05-29 08:15:29 +00:00
|
|
|
{
|
2015-06-03 19:37:21 +00:00
|
|
|
const float pi = static_cast<float>(osg::PI);
|
2013-04-28 08:14:58 +00:00
|
|
|
|
2014-06-14 15:56:41 +00:00
|
|
|
ESM::Position pos = ptr.getRefData().getPosition();
|
|
|
|
float *objRot = pos.rot;
|
2019-11-30 11:29:31 +00:00
|
|
|
if (flags & MWBase::RotationFlag_adjust)
|
2013-04-28 08:14:58 +00:00
|
|
|
{
|
2015-06-03 19:37:21 +00:00
|
|
|
objRot[0] += rot.x();
|
|
|
|
objRot[1] += rot.y();
|
|
|
|
objRot[2] += rot.z();
|
2013-04-28 08:14:58 +00:00
|
|
|
}
|
|
|
|
else
|
2012-11-05 19:48:07 +00:00
|
|
|
{
|
2015-06-03 19:37:21 +00:00
|
|
|
objRot[0] = rot.x();
|
|
|
|
objRot[1] = rot.y();
|
|
|
|
objRot[2] = rot.z();
|
2013-04-28 08:14:58 +00:00
|
|
|
}
|
2012-09-01 21:28:12 +00:00
|
|
|
|
2014-05-22 18:37:22 +00:00
|
|
|
if(ptr.getClass().isActor())
|
2013-04-28 08:14:58 +00:00
|
|
|
{
|
|
|
|
/* HACK? Actors shouldn't really be rotating around X (or Y), but
|
|
|
|
* currently it's done so for rotating the camera, which needs
|
|
|
|
* clamping.
|
|
|
|
*/
|
2015-06-03 19:37:21 +00:00
|
|
|
const float half_pi = pi/2.f;
|
2013-04-28 08:14:58 +00:00
|
|
|
|
|
|
|
if(objRot[0] < -half_pi) objRot[0] = -half_pi;
|
|
|
|
else if(objRot[0] > half_pi) objRot[0] = half_pi;
|
2013-04-25 18:52:38 +00:00
|
|
|
|
2015-11-11 23:52:36 +00:00
|
|
|
wrap(objRot[1]);
|
|
|
|
wrap(objRot[2]);
|
|
|
|
}
|
2013-04-28 08:14:58 +00:00
|
|
|
|
2014-06-14 15:56:41 +00:00
|
|
|
ptr.getRefData().setPosition(pos);
|
|
|
|
|
2020-05-11 13:37:00 +00:00
|
|
|
mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
|
|
|
|
mWorldScene->removeFromPagedRefs(ptr);
|
|
|
|
|
2020-11-13 07:39:47 +00:00
|
|
|
if(ptr.getRefData().getBaseNode() != nullptr)
|
2019-03-03 13:01:52 +00:00
|
|
|
{
|
2019-11-30 11:29:31 +00:00
|
|
|
const auto order = flags & MWBase::RotationFlag_inverseOrder
|
|
|
|
? RotationOrder::inverse : RotationOrder::direct;
|
|
|
|
mWorldScene->updateObjectRotation(ptr, order);
|
2019-03-03 13:01:52 +00:00
|
|
|
|
|
|
|
if (const auto object = mPhysics->getObject(ptr))
|
|
|
|
updateNavigatorObject(object);
|
|
|
|
}
|
2012-05-29 08:15:29 +00:00
|
|
|
}
|
|
|
|
|
2014-07-31 02:24:45 +00:00
|
|
|
void World::adjustPosition(const Ptr &ptr, bool force)
|
2013-04-03 21:55:57 +00:00
|
|
|
{
|
2017-02-10 01:43:49 +00:00
|
|
|
osg::Vec3f pos (ptr.getRefData().getPosition().asVec3());
|
2013-04-03 21:55:57 +00:00
|
|
|
|
2015-05-12 17:02:56 +00:00
|
|
|
if(!ptr.getRefData().getBaseNode())
|
2013-04-04 14:51:22 +00:00
|
|
|
{
|
|
|
|
// will be adjusted when Ptr's cell becomes active
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-06 12:24:42 +00:00
|
|
|
const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits<float>::max();
|
|
|
|
pos.z() = std::max(pos.z(), terrainHeight + 20); // place slightly above terrain. will snap down to ground with code below
|
2018-05-26 11:35:48 +00:00
|
|
|
|
2020-03-21 06:32:47 +00:00
|
|
|
// We still should trace down dead persistent actors - they do not use the "swimdeath" animation.
|
|
|
|
bool swims = ptr.getClass().isActor() && isSwimming(ptr) && !(ptr.getClass().isPersistent(ptr) && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished());
|
|
|
|
if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !swims && isActorCollisionEnabled(ptr)))
|
2013-04-03 21:55:57 +00:00
|
|
|
{
|
2018-10-08 12:50:54 +00:00
|
|
|
osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits);
|
2020-12-06 12:24:42 +00:00
|
|
|
pos.z() = std::min(pos.z(), traced.z());
|
2013-04-03 21:55:57 +00:00
|
|
|
}
|
|
|
|
|
2017-02-10 01:43:49 +00:00
|
|
|
moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z());
|
2020-12-18 08:22:02 +00:00
|
|
|
mPhysics->resetPosition(ptr);
|
2013-04-03 21:55:57 +00:00
|
|
|
}
|
|
|
|
|
2018-10-23 22:40:57 +00:00
|
|
|
void World::fixPosition()
|
2014-06-28 12:59:33 +00:00
|
|
|
{
|
2018-10-23 22:40:57 +00:00
|
|
|
const MWWorld::Ptr actor = getPlayerPtr();
|
2018-09-13 09:21:38 +00:00
|
|
|
const float distance = 128.f;
|
|
|
|
ESM::Position esmPos = actor.getRefData().getPosition();
|
|
|
|
osg::Quat orientation(esmPos.rot[2], osg::Vec3f(0,0,-1));
|
|
|
|
osg::Vec3f pos (esmPos.asVec3());
|
2014-06-28 12:59:33 +00:00
|
|
|
|
2018-09-13 09:21:38 +00:00
|
|
|
int direction = 0;
|
|
|
|
int fallbackDirections[4] = {direction, (direction+3)%4, (direction+2)%4, (direction+1)%4};
|
|
|
|
|
|
|
|
osg::Vec3f targetPos = pos;
|
|
|
|
for (int i=0; i<4; ++i)
|
|
|
|
{
|
|
|
|
direction = fallbackDirections[i];
|
|
|
|
if (direction == 0) targetPos = pos + (orientation * osg::Vec3f(0,1,0)) * distance;
|
|
|
|
else if(direction == 1) targetPos = pos - (orientation * osg::Vec3f(0,1,0)) * distance;
|
|
|
|
else if(direction == 2) targetPos = pos - (orientation * osg::Vec3f(1,0,0)) * distance;
|
|
|
|
else if(direction == 3) targetPos = pos + (orientation * osg::Vec3f(1,0,0)) * distance;
|
|
|
|
|
|
|
|
// destination is free
|
|
|
|
if (!castRay(pos.x(), pos.y(), pos.z(), targetPos.x(), targetPos.y(), targetPos.z()))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
targetPos.z() += distance / 2.f; // move up a bit to get out from geometry, will snap down later
|
|
|
|
osg::Vec3f traced = mPhysics->traceDown(actor, targetPos, Constants::CellSizeInUnits);
|
2017-02-10 01:43:49 +00:00
|
|
|
if (traced != pos)
|
2018-09-13 09:21:38 +00:00
|
|
|
{
|
|
|
|
esmPos.pos[0] = traced.x();
|
|
|
|
esmPos.pos[1] = traced.y();
|
|
|
|
esmPos.pos[2] = traced.z();
|
2018-10-23 22:40:57 +00:00
|
|
|
MWWorld::ActionTeleport(actor.getCell()->isExterior() ? "" : actor.getCell()->getCell()->mName, esmPos, false).execute(actor);
|
2018-09-13 09:21:38 +00:00
|
|
|
}
|
2014-06-28 12:59:33 +00:00
|
|
|
}
|
|
|
|
|
2019-11-30 11:29:31 +00:00
|
|
|
void World::rotateObject (const Ptr& ptr, float x, float y, float z, MWBase::RotationFlags flags)
|
2013-03-31 09:24:44 +00:00
|
|
|
{
|
2019-11-30 11:29:31 +00:00
|
|
|
rotateObjectImp(ptr, osg::Vec3f(x, y, z), flags);
|
2013-03-31 09:24:44 +00:00
|
|
|
}
|
|
|
|
|
2018-05-30 10:43:07 +00:00
|
|
|
void World::rotateWorldObject (const Ptr& ptr, osg::Quat rotate)
|
|
|
|
{
|
2020-11-13 07:39:47 +00:00
|
|
|
if(ptr.getRefData().getBaseNode() != nullptr)
|
2018-05-30 10:43:07 +00:00
|
|
|
{
|
2020-07-09 20:36:57 +00:00
|
|
|
mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
|
|
|
|
mWorldScene->removeFromPagedRefs(ptr);
|
|
|
|
|
2018-05-30 10:43:07 +00:00
|
|
|
mRendering->rotateObject(ptr, rotate);
|
|
|
|
mPhysics->updateRotation(ptr);
|
2019-08-15 16:18:53 +00:00
|
|
|
|
|
|
|
if (const auto object = mPhysics->getObject(ptr))
|
|
|
|
updateNavigatorObject(object);
|
2018-05-30 10:43:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-29 16:05:18 +00:00
|
|
|
MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos)
|
2012-09-17 11:36:48 +00:00
|
|
|
{
|
2015-12-19 15:47:55 +00:00
|
|
|
return copyObjectToCell(ptr,cell,pos,ptr.getRefData().getCount(),false);
|
2012-09-17 11:36:48 +00:00
|
|
|
}
|
|
|
|
|
2016-02-29 16:16:33 +00:00
|
|
|
MWWorld::Ptr World::safePlaceObject(const ConstPtr &ptr, const ConstPtr &referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance)
|
|
|
|
{
|
|
|
|
ESM::Position ipos = referenceObject.getRefData().getPosition();
|
|
|
|
osg::Vec3f pos(ipos.asVec3());
|
|
|
|
osg::Quat orientation(ipos.rot[2], osg::Vec3f(0,0,-1));
|
|
|
|
|
2016-02-29 16:26:52 +00:00
|
|
|
int fallbackDirections[4] = {direction, (direction+3)%4, (direction+2)%4, (direction+1)%4};
|
|
|
|
|
2016-06-02 19:24:19 +00:00
|
|
|
osg::Vec3f spawnPoint = pos;
|
|
|
|
|
2016-02-29 16:16:33 +00:00
|
|
|
for (int i=0; i<4; ++i)
|
|
|
|
{
|
2016-02-29 16:26:52 +00:00
|
|
|
direction = fallbackDirections[i];
|
2016-02-29 16:16:33 +00:00
|
|
|
if (direction == 0) spawnPoint = pos + (orientation * osg::Vec3f(0,1,0)) * distance;
|
|
|
|
else if(direction == 1) spawnPoint = pos - (orientation * osg::Vec3f(0,1,0)) * distance;
|
|
|
|
else if(direction == 2) spawnPoint = pos - (orientation * osg::Vec3f(1,0,0)) * distance;
|
|
|
|
else if(direction == 3) spawnPoint = pos + (orientation * osg::Vec3f(1,0,0)) * distance;
|
2016-03-04 11:25:22 +00:00
|
|
|
|
|
|
|
if (!ptr.getClass().isActor())
|
|
|
|
break;
|
|
|
|
|
|
|
|
// check if spawn point is safe, fall back to another direction if not
|
2016-02-29 16:16:33 +00:00
|
|
|
spawnPoint.z() += 30; // move up a little to account for slopes, will snap down later
|
|
|
|
|
|
|
|
if (!castRay(spawnPoint.x(), spawnPoint.y(), spawnPoint.z(),
|
|
|
|
pos.x(), pos.y(), pos.z() + 20))
|
|
|
|
{
|
|
|
|
// safe
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-06-02 19:24:19 +00:00
|
|
|
ipos.pos[0] = spawnPoint.x();
|
|
|
|
ipos.pos[1] = spawnPoint.y();
|
|
|
|
ipos.pos[2] = spawnPoint.z();
|
2016-02-29 16:16:33 +00:00
|
|
|
|
2020-12-06 12:26:43 +00:00
|
|
|
if (referenceObject.getClass().isActor())
|
2016-03-04 11:22:13 +00:00
|
|
|
{
|
|
|
|
ipos.rot[0] = 0;
|
|
|
|
ipos.rot[1] = 0;
|
|
|
|
}
|
2016-02-29 16:16:33 +00:00
|
|
|
|
|
|
|
MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false);
|
2018-09-13 09:21:38 +00:00
|
|
|
adjustPosition(placed, true); // snap to ground
|
2016-02-29 16:16:33 +00:00
|
|
|
return placed;
|
|
|
|
}
|
|
|
|
|
2010-09-11 10:21:55 +00:00
|
|
|
void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const
|
2010-08-22 19:30:48 +00:00
|
|
|
{
|
2018-09-17 10:52:43 +00:00
|
|
|
const int cellSize = Constants::CellSizeInUnits;
|
2010-08-22 19:30:48 +00:00
|
|
|
|
2015-03-08 00:07:29 +00:00
|
|
|
x = static_cast<float>(cellSize * cellX);
|
|
|
|
y = static_cast<float>(cellSize * cellY);
|
2010-09-11 10:21:55 +00:00
|
|
|
|
|
|
|
if (centre)
|
|
|
|
{
|
|
|
|
x += cellSize/2;
|
|
|
|
y += cellSize/2;
|
|
|
|
}
|
2010-08-22 19:30:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void World::positionToIndex (float x, float y, int &cellX, int &cellY) const
|
|
|
|
{
|
2018-09-17 10:52:43 +00:00
|
|
|
cellX = static_cast<int>(std::floor(x / Constants::CellSizeInUnits));
|
|
|
|
cellY = static_cast<int>(std::floor(y / Constants::CellSizeInUnits));
|
2010-08-22 19:30:48 +00:00
|
|
|
}
|
2011-01-29 16:39:34 +00:00
|
|
|
|
2015-05-12 01:02:15 +00:00
|
|
|
void World::queueMovement(const Ptr &ptr, const osg::Vec3f &velocity)
|
2013-08-18 05:34:38 +00:00
|
|
|
{
|
2015-05-12 01:02:15 +00:00
|
|
|
mPhysics->queueObjectMovement(ptr, velocity);
|
2013-08-18 05:34:38 +00:00
|
|
|
}
|
2018-09-22 08:57:50 +00:00
|
|
|
|
|
|
|
void World::updateAnimatedCollisionShape(const Ptr &ptr)
|
|
|
|
{
|
|
|
|
mPhysics->updateAnimatedCollisionShape(ptr);
|
|
|
|
}
|
2013-08-18 05:34:38 +00:00
|
|
|
|
2020-11-20 12:11:53 +00:00
|
|
|
void World::doPhysics(float duration, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
|
2011-01-29 16:39:34 +00:00
|
|
|
{
|
2020-10-14 09:32:12 +00:00
|
|
|
mPhysics->stepSimulation();
|
2013-04-30 18:26:59 +00:00
|
|
|
processDoors(duration);
|
|
|
|
|
2015-06-01 19:41:13 +00:00
|
|
|
mProjectileManager->update(duration);
|
2013-11-13 13:02:15 +00:00
|
|
|
|
2020-12-10 21:00:33 +00:00
|
|
|
const auto& results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats);
|
2020-10-23 18:27:07 +00:00
|
|
|
mProjectileManager->processHits();
|
Process movement queue in one or several background threads
Before movement calculation, the main thread prepare a
vector of ActorFrameData, which contains all data necessary to perform
the simulation, and feed it to the solver. At the same time it fetches
the result from the previous background simulation, which in turn is
used by the game mechanics.
Other functions of the physics system (weapon hit for instance)
interrupt the background simulation, with some exceptions described
below.
The number of threads is controlled by the numeric setting
[Physics]
async num threads
In case 'async num threads' > 1 and Bullet doesn't support multiple threads,
1 async thread will be used. 0 means synchronous solver.
Additional settings (will be silently switched off if async num threads = 0)
[Physics]
defer aabb update
Update AABBs of actors and objects in the background thread(s). It is not an especially
costly operation, but it needs exclusive access to the collision world, which blocks
other operations. Since AABB needs to be updated for collision detection, one can queue
them to defer update before start of the movement solver. Extensive tests on as much
as one installation (mine) show no drawback having that switched on.
[Physics]
lineofsight keep inactive cache
Control for how long (how many frames) the line of sight (LOS) request will be kept updated.
When a request for LOS is made for the first time, the background threads are stopped to
service it. From now on, the LOS will be refreshed preemptively as part of the background
routine until it is not required for lineofsight keep inactive cache frames. This mean
that subsequent request will not interrupt the background computation.
2020-10-15 04:11:44 +00:00
|
|
|
mDiscardMovements = false;
|
2020-10-14 09:32:12 +00:00
|
|
|
|
2020-12-18 08:22:02 +00:00
|
|
|
for(const auto& actor : results)
|
2013-02-05 20:45:10 +00:00
|
|
|
{
|
2020-10-14 09:32:12 +00:00
|
|
|
// Handle player last, in case a cell transition occurs
|
2020-11-01 16:14:59 +00:00
|
|
|
if(actor != getPlayerPtr())
|
2020-12-18 08:22:02 +00:00
|
|
|
{
|
|
|
|
auto* physactor = mPhysics->getActor(actor);
|
|
|
|
assert(physactor);
|
|
|
|
const auto position = physactor->getSimulationPosition();
|
2020-11-01 16:14:59 +00:00
|
|
|
moveObjectImp(actor, position.x(), position.y(), position.z(), false);
|
2020-12-18 08:22:02 +00:00
|
|
|
}
|
2013-02-05 20:45:10 +00:00
|
|
|
}
|
2020-10-14 09:32:12 +00:00
|
|
|
|
2020-12-18 08:22:02 +00:00
|
|
|
const auto player = std::find(results.begin(), results.end(), getPlayerPtr());
|
2020-10-14 09:32:12 +00:00
|
|
|
if (player != results.end())
|
2020-12-18 08:22:02 +00:00
|
|
|
{
|
|
|
|
auto* physactor = mPhysics->getActor(*player);
|
|
|
|
assert(physactor);
|
|
|
|
const auto position = physactor->getSimulationPosition();
|
|
|
|
moveObjectImp(*player, position.x(), position.y(), position.z(), false);
|
|
|
|
}
|
2018-08-25 20:51:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void World::updateNavigator()
|
|
|
|
{
|
2018-05-26 14:44:25 +00:00
|
|
|
mPhysics->forEachAnimatedObject([&] (const MWPhysics::Object* object)
|
|
|
|
{
|
2019-03-03 12:51:02 +00:00
|
|
|
mShouldUpdateNavigator = updateNavigatorObject(object) || mShouldUpdateNavigator;
|
2018-05-26 14:44:25 +00:00
|
|
|
});
|
2018-08-25 20:51:54 +00:00
|
|
|
|
|
|
|
for (const auto& door : mDoorStates)
|
|
|
|
if (const auto object = mPhysics->getObject(door.first))
|
2019-03-03 12:51:02 +00:00
|
|
|
mShouldUpdateNavigator = updateNavigatorObject(object) || mShouldUpdateNavigator;
|
2018-08-25 20:51:54 +00:00
|
|
|
|
2019-03-03 12:51:02 +00:00
|
|
|
if (mShouldUpdateNavigator)
|
|
|
|
{
|
2018-05-26 14:44:25 +00:00
|
|
|
mNavigator->update(getPlayerPtr().getRefData().getPosition().asVec3());
|
2019-03-03 12:51:02 +00:00
|
|
|
mShouldUpdateNavigator = false;
|
|
|
|
}
|
2013-04-28 13:36:37 +00:00
|
|
|
}
|
|
|
|
|
2018-08-25 20:51:54 +00:00
|
|
|
bool World::updateNavigatorObject(const MWPhysics::Object* object)
|
|
|
|
{
|
|
|
|
const DetourNavigator::ObjectShapes shapes {
|
|
|
|
*object->getShapeInstance()->getCollisionShape(),
|
|
|
|
object->getShapeInstance()->getAvoidCollisionShape()
|
|
|
|
};
|
2020-10-14 13:55:15 +00:00
|
|
|
return mNavigator->updateObject(DetourNavigator::ObjectId(object), shapes, object->getTransform());
|
2018-08-25 20:51:54 +00:00
|
|
|
}
|
|
|
|
|
2020-08-03 20:44:16 +00:00
|
|
|
const MWPhysics::RayCastingInterface* World::getRayCasting() const
|
|
|
|
{
|
|
|
|
return mPhysics.get();
|
|
|
|
}
|
|
|
|
|
2018-09-30 04:38:55 +00:00
|
|
|
bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2)
|
|
|
|
{
|
|
|
|
int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_Door;
|
|
|
|
bool result = castRay(x1, y1, z1, x2, y2, z2, mask);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2, int mask)
|
2013-05-05 23:46:50 +00:00
|
|
|
{
|
2015-05-31 23:57:15 +00:00
|
|
|
osg::Vec3f a(x1,y1,z1);
|
|
|
|
osg::Vec3f b(x2,y2,z2);
|
2017-07-31 15:28:02 +00:00
|
|
|
|
2020-08-03 20:44:16 +00:00
|
|
|
MWPhysics::RayCastingResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), std::vector<MWWorld::Ptr>(), mask);
|
2015-05-31 23:57:15 +00:00
|
|
|
return result.mHit;
|
2013-05-05 23:46:50 +00:00
|
|
|
}
|
|
|
|
|
2020-02-02 19:02:25 +00:00
|
|
|
bool World::castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore)
|
|
|
|
{
|
|
|
|
return mPhysics->castRay(from, to, ignore, std::vector<MWWorld::Ptr>(), mask).mHit;
|
|
|
|
}
|
|
|
|
|
2019-08-25 13:20:14 +00:00
|
|
|
bool World::rotateDoor(const Ptr door, MWWorld::DoorState state, float duration)
|
2019-08-15 06:46:47 +00:00
|
|
|
{
|
|
|
|
const ESM::Position& objPos = door.getRefData().getPosition();
|
|
|
|
float oldRot = objPos.rot[2];
|
|
|
|
|
|
|
|
float minRot = door.getCellRef().getPosition().rot[2];
|
|
|
|
float maxRot = minRot + osg::DegreesToRadians(90.f);
|
|
|
|
|
2020-11-01 21:30:48 +00:00
|
|
|
float diff = duration * osg::DegreesToRadians(90.f) * (state == MWWorld::DoorState::Opening ? 1 : -1);
|
|
|
|
float targetRot = std::min(std::max(minRot, oldRot + diff), maxRot);
|
2019-11-30 11:29:31 +00:00
|
|
|
rotateObject(door, objPos.rot[0], objPos.rot[1], targetRot, MWBase::RotationFlag_none);
|
2019-08-15 06:46:47 +00:00
|
|
|
|
2019-08-25 13:20:14 +00:00
|
|
|
bool reached = (targetRot == maxRot && state != MWWorld::DoorState::Idle) || targetRot == minRot;
|
2019-08-15 06:46:47 +00:00
|
|
|
|
|
|
|
/// \todo should use convexSweepTest here
|
2019-09-05 07:49:53 +00:00
|
|
|
bool collisionWithActor = false;
|
2020-11-01 21:30:48 +00:00
|
|
|
for (auto& [ptr, point, normal] : mPhysics->getCollisionsPoints(door, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor))
|
2019-08-15 06:46:47 +00:00
|
|
|
{
|
2020-11-01 21:30:48 +00:00
|
|
|
|
2019-08-15 06:46:47 +00:00
|
|
|
if (ptr.getClass().isActor())
|
|
|
|
{
|
2020-11-02 12:52:36 +00:00
|
|
|
auto localPoint = objPos.asVec3() - point;
|
|
|
|
osg::Vec3f direction = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * localPoint - localPoint;
|
2020-11-01 21:30:48 +00:00
|
|
|
direction.normalize();
|
2020-11-02 12:52:36 +00:00
|
|
|
mPhysics->reportCollision(Misc::Convert::toBullet(point), Misc::Convert::toBullet(normal));
|
2020-11-01 21:30:48 +00:00
|
|
|
if (direction * normal < 0) // door is turning away from actor
|
|
|
|
continue;
|
|
|
|
|
2019-09-05 07:49:53 +00:00
|
|
|
collisionWithActor = true;
|
2020-11-01 21:30:48 +00:00
|
|
|
|
2019-08-15 06:46:47 +00:00
|
|
|
// Collided with actor, ask actor to try to avoid door
|
|
|
|
if(ptr != getPlayerPtr() )
|
|
|
|
{
|
|
|
|
MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence();
|
2020-05-16 19:52:16 +00:00
|
|
|
if(seq.getTypeId() != MWMechanics::AiPackageTypeId::AvoidDoor) //Only add it once
|
2019-08-15 06:46:47 +00:00
|
|
|
seq.stack(MWMechanics::AiAvoidDoor(door),ptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
// we need to undo the rotation
|
|
|
|
reached = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-05 07:49:53 +00:00
|
|
|
// Cancel door closing sound if collision with actor is detected
|
|
|
|
if (collisionWithActor)
|
|
|
|
{
|
|
|
|
const ESM::Door* ref = door.get<ESM::Door>()->mBase;
|
|
|
|
|
|
|
|
if (state == MWWorld::DoorState::Opening)
|
|
|
|
{
|
|
|
|
const std::string& openSound = ref->mOpenSound;
|
|
|
|
if (!openSound.empty() && MWBase::Environment::get().getSoundManager()->getSoundPlaying(door, openSound))
|
|
|
|
MWBase::Environment::get().getSoundManager()->stopSound3D(door, openSound);
|
|
|
|
}
|
|
|
|
else if (state == MWWorld::DoorState::Closing)
|
|
|
|
{
|
|
|
|
const std::string& closeSound = ref->mCloseSound;
|
|
|
|
if (!closeSound.empty() && MWBase::Environment::get().getSoundManager()->getSoundPlaying(door, closeSound))
|
|
|
|
MWBase::Environment::get().getSoundManager()->stopSound3D(door, closeSound);
|
|
|
|
}
|
2019-09-29 08:28:02 +00:00
|
|
|
|
2019-11-30 11:29:31 +00:00
|
|
|
rotateObject(door, objPos.rot[0], objPos.rot[1], oldRot, MWBase::RotationFlag_none);
|
2019-09-05 07:49:53 +00:00
|
|
|
}
|
|
|
|
|
2019-08-15 06:46:47 +00:00
|
|
|
return reached;
|
|
|
|
}
|
|
|
|
|
2013-04-28 13:36:37 +00:00
|
|
|
void World::processDoors(float duration)
|
|
|
|
{
|
2019-08-25 13:20:14 +00:00
|
|
|
auto it = mDoorStates.begin();
|
2013-04-28 13:59:59 +00:00
|
|
|
while (it != mDoorStates.end())
|
2013-04-28 12:59:15 +00:00
|
|
|
{
|
2014-01-28 18:03:20 +00:00
|
|
|
if (!mWorldScene->isCellActive(*it->first.getCell()) || !it->first.getRefData().getBaseNode())
|
2014-05-14 23:58:44 +00:00
|
|
|
{
|
|
|
|
// The door is no longer in an active cell, or it was disabled.
|
|
|
|
// Erase from mDoorStates, since we no longer need to move it.
|
|
|
|
// Once we load the door's cell again (or re-enable the door), Door::insertObject will reinsert to mDoorStates.
|
2013-04-28 13:36:37 +00:00
|
|
|
mDoorStates.erase(it++);
|
2014-05-14 23:58:44 +00:00
|
|
|
}
|
2013-04-28 12:59:15 +00:00
|
|
|
else
|
|
|
|
{
|
2019-08-15 06:46:47 +00:00
|
|
|
bool reached = rotateDoor(it->first, it->second, duration);
|
2017-01-19 01:19:44 +00:00
|
|
|
|
2014-05-14 23:58:44 +00:00
|
|
|
if (reached)
|
|
|
|
{
|
|
|
|
// Mark as non-moving
|
2019-08-25 13:20:14 +00:00
|
|
|
it->first.getClass().setDoorState(it->first, MWWorld::DoorState::Idle);
|
2013-04-28 13:59:59 +00:00
|
|
|
mDoorStates.erase(it++);
|
2014-05-14 23:58:44 +00:00
|
|
|
}
|
2013-04-28 13:59:59 +00:00
|
|
|
else
|
|
|
|
++it;
|
2013-04-28 12:59:15 +00:00
|
|
|
}
|
|
|
|
}
|
2011-01-29 16:39:34 +00:00
|
|
|
}
|
2011-03-13 21:33:55 +00:00
|
|
|
|
2018-12-31 15:39:20 +00:00
|
|
|
void World::setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external)
|
2018-09-21 12:34:23 +00:00
|
|
|
{
|
2018-12-31 15:39:20 +00:00
|
|
|
MWPhysics::Actor *physicActor = mPhysics->getActor(ptr);
|
|
|
|
if (physicActor && physicActor->getCollisionMode() != internal)
|
|
|
|
{
|
|
|
|
physicActor->enableCollisionMode(internal);
|
|
|
|
physicActor->enableCollisionBody(external);
|
|
|
|
}
|
2018-09-21 12:34:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool World::isActorCollisionEnabled(const MWWorld::Ptr& ptr)
|
|
|
|
{
|
2018-12-31 15:39:20 +00:00
|
|
|
MWPhysics::Actor *physicActor = mPhysics->getActor(ptr);
|
|
|
|
return physicActor && physicActor->getCollisionMode();
|
2018-09-21 12:34:23 +00:00
|
|
|
}
|
|
|
|
|
2011-04-26 19:38:21 +00:00
|
|
|
bool World::toggleCollisionMode()
|
2011-03-13 21:33:55 +00:00
|
|
|
{
|
2018-10-08 12:50:54 +00:00
|
|
|
if (mPhysics->toggleCollisionMode())
|
|
|
|
{
|
|
|
|
adjustPosition(getPlayerPtr(), true);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2011-03-13 21:33:55 +00:00
|
|
|
}
|
2011-03-16 08:09:45 +00:00
|
|
|
|
2015-05-02 20:45:27 +00:00
|
|
|
bool World::toggleRenderMode (MWRender::RenderMode mode)
|
2011-03-16 08:09:45 +00:00
|
|
|
{
|
2015-05-02 22:39:01 +00:00
|
|
|
switch (mode)
|
|
|
|
{
|
|
|
|
case MWRender::Render_CollisionDebug:
|
|
|
|
return mPhysics->toggleDebugRendering();
|
|
|
|
default:
|
|
|
|
return mRendering->toggleRenderMode(mode);
|
|
|
|
}
|
2011-03-16 08:09:45 +00:00
|
|
|
}
|
2011-07-22 13:56:54 +00:00
|
|
|
|
2012-11-07 21:52:34 +00:00
|
|
|
const ESM::Potion *World::createRecord (const ESM::Potion& record)
|
2011-07-22 13:56:54 +00:00
|
|
|
{
|
2012-12-04 09:58:43 +00:00
|
|
|
return mStore.insert(record);
|
2011-07-22 13:56:54 +00:00
|
|
|
}
|
|
|
|
|
2012-11-07 21:52:34 +00:00
|
|
|
const ESM::Class *World::createRecord (const ESM::Class& record)
|
2011-07-22 13:56:54 +00:00
|
|
|
{
|
2012-11-07 21:52:34 +00:00
|
|
|
return mStore.insert(record);
|
2011-07-22 13:56:54 +00:00
|
|
|
}
|
|
|
|
|
2012-11-07 21:52:34 +00:00
|
|
|
const ESM::Spell *World::createRecord (const ESM::Spell& record)
|
2012-10-15 19:54:19 +00:00
|
|
|
{
|
2012-11-07 21:52:34 +00:00
|
|
|
return mStore.insert(record);
|
2011-07-22 13:56:54 +00:00
|
|
|
}
|
2011-09-26 09:11:30 +00:00
|
|
|
|
|
|
|
const ESM::Cell *World::createRecord (const ESM::Cell& record)
|
|
|
|
{
|
2012-11-06 13:51:38 +00:00
|
|
|
return mStore.insert(record);
|
2011-09-26 09:11:30 +00:00
|
|
|
}
|
|
|
|
|
2014-12-17 00:05:32 +00:00
|
|
|
const ESM::CreatureLevList *World::createOverrideRecord(const ESM::CreatureLevList &record)
|
|
|
|
{
|
|
|
|
return mStore.overrideRecord(record);
|
|
|
|
}
|
|
|
|
|
|
|
|
const ESM::ItemLevList *World::createOverrideRecord(const ESM::ItemLevList &record)
|
|
|
|
{
|
|
|
|
return mStore.overrideRecord(record);
|
|
|
|
}
|
|
|
|
|
2020-06-02 19:59:37 +00:00
|
|
|
const ESM::Creature *World::createOverrideRecord(const ESM::Creature &record)
|
|
|
|
{
|
|
|
|
return mStore.overrideRecord(record);
|
|
|
|
}
|
|
|
|
|
|
|
|
const ESM::NPC *World::createOverrideRecord(const ESM::NPC &record)
|
|
|
|
{
|
|
|
|
return mStore.overrideRecord(record);
|
|
|
|
}
|
|
|
|
|
2020-10-20 09:22:43 +00:00
|
|
|
const ESM::Container *World::createOverrideRecord(const ESM::Container &record)
|
|
|
|
{
|
|
|
|
return mStore.overrideRecord(record);
|
|
|
|
}
|
|
|
|
|
2012-11-08 12:37:57 +00:00
|
|
|
const ESM::NPC *World::createRecord(const ESM::NPC &record)
|
|
|
|
{
|
2012-11-10 07:51:48 +00:00
|
|
|
bool update = false;
|
2012-12-04 09:58:43 +00:00
|
|
|
|
2012-12-23 19:23:24 +00:00
|
|
|
if (Misc::StringUtils::ciEqual(record.mId, "player"))
|
2012-12-04 09:58:43 +00:00
|
|
|
{
|
2012-11-10 07:51:48 +00:00
|
|
|
const ESM::NPC *player =
|
|
|
|
mPlayer->getPlayer().get<ESM::NPC>()->mBase;
|
2011-09-26 09:11:30 +00:00
|
|
|
|
2012-11-10 07:51:48 +00:00
|
|
|
update = record.isMale() != player->isMale() ||
|
2012-12-23 19:23:24 +00:00
|
|
|
!Misc::StringUtils::ciEqual(record.mRace, player->mRace) ||
|
|
|
|
!Misc::StringUtils::ciEqual(record.mHead, player->mHead) ||
|
|
|
|
!Misc::StringUtils::ciEqual(record.mHair, player->mHair);
|
2011-09-26 09:11:30 +00:00
|
|
|
}
|
2012-11-10 07:51:48 +00:00
|
|
|
const ESM::NPC *ret = mStore.insert(record);
|
|
|
|
if (update) {
|
2015-05-31 22:50:48 +00:00
|
|
|
renderPlayer();
|
2012-11-10 07:51:48 +00:00
|
|
|
}
|
|
|
|
return ret;
|
2011-09-26 09:11:30 +00:00
|
|
|
}
|
2011-11-21 13:08:44 +00:00
|
|
|
|
2013-03-28 16:41:00 +00:00
|
|
|
const ESM::Armor *World::createRecord (const ESM::Armor& record)
|
|
|
|
{
|
|
|
|
return mStore.insert(record);
|
|
|
|
}
|
|
|
|
|
|
|
|
const ESM::Weapon *World::createRecord (const ESM::Weapon& record)
|
|
|
|
{
|
|
|
|
return mStore.insert(record);
|
|
|
|
}
|
|
|
|
|
|
|
|
const ESM::Clothing *World::createRecord (const ESM::Clothing& record)
|
|
|
|
{
|
|
|
|
return mStore.insert(record);
|
|
|
|
}
|
|
|
|
|
|
|
|
const ESM::Enchantment *World::createRecord (const ESM::Enchantment& record)
|
|
|
|
{
|
|
|
|
return mStore.insert(record);
|
|
|
|
}
|
|
|
|
|
|
|
|
const ESM::Book *World::createRecord (const ESM::Book& record)
|
|
|
|
{
|
|
|
|
return mStore.insert(record);
|
|
|
|
}
|
|
|
|
|
2012-11-03 19:29:55 +00:00
|
|
|
void World::update (float duration, bool paused)
|
2012-01-23 13:33:06 +00:00
|
|
|
{
|
2014-01-11 05:47:58 +00:00
|
|
|
if (mGoToJail && !paused)
|
|
|
|
goToJail();
|
|
|
|
|
2018-08-13 04:30:50 +00:00
|
|
|
// Reset "traveling" flag - there was a frame to detect traveling.
|
|
|
|
mPlayerTraveling = false;
|
|
|
|
|
2018-08-13 07:10:01 +00:00
|
|
|
// The same thing for "in jail" flag: reset it if:
|
|
|
|
// 1. Player was in jail
|
|
|
|
// 2. Jailing window was closed
|
|
|
|
if (mPlayerInJail && !mGoToJail && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Jail))
|
|
|
|
mPlayerInJail = false;
|
2014-01-11 05:47:58 +00:00
|
|
|
|
2014-10-09 06:41:51 +00:00
|
|
|
updateWeather(duration, paused);
|
2013-03-03 14:10:40 +00:00
|
|
|
|
2015-05-02 22:39:01 +00:00
|
|
|
if (!paused)
|
2018-08-25 20:51:54 +00:00
|
|
|
{
|
|
|
|
updateNavigator();
|
|
|
|
}
|
2013-08-18 05:34:38 +00:00
|
|
|
|
2018-05-20 07:24:20 +00:00
|
|
|
updatePlayer();
|
2017-07-19 11:05:51 +00:00
|
|
|
|
2017-02-08 17:52:46 +00:00
|
|
|
mPhysics->debugDraw();
|
|
|
|
|
2014-05-26 14:43:19 +00:00
|
|
|
mWorldScene->update (duration, paused);
|
|
|
|
|
2014-12-08 22:26:09 +00:00
|
|
|
updateSoundListener();
|
2015-05-26 14:40:44 +00:00
|
|
|
|
2017-02-14 23:55:35 +00:00
|
|
|
mSpellPreloadTimer -= duration;
|
|
|
|
if (mSpellPreloadTimer <= 0.f)
|
|
|
|
{
|
|
|
|
mSpellPreloadTimer = 0.1f;
|
|
|
|
preloadSpells();
|
|
|
|
}
|
2019-01-07 10:27:31 +00:00
|
|
|
}
|
|
|
|
|
2020-11-20 12:11:53 +00:00
|
|
|
void World::updatePhysics (float duration, bool paused, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
|
2019-01-07 10:27:31 +00:00
|
|
|
{
|
|
|
|
if (!paused)
|
|
|
|
{
|
2020-11-20 12:11:53 +00:00
|
|
|
doPhysics (duration, frameStart, frameNumber, stats);
|
2019-01-07 10:27:31 +00:00
|
|
|
}
|
2015-05-26 14:40:44 +00:00
|
|
|
}
|
|
|
|
|
2018-05-20 07:24:20 +00:00
|
|
|
void World::updatePlayer()
|
2015-05-26 14:40:44 +00:00
|
|
|
{
|
|
|
|
MWWorld::Ptr player = getPlayerPtr();
|
|
|
|
|
|
|
|
// TODO: move to MWWorld::Player
|
|
|
|
|
|
|
|
if (player.getCell()->isExterior())
|
2013-12-31 23:12:31 +00:00
|
|
|
{
|
2015-05-26 14:40:44 +00:00
|
|
|
ESM::Position pos = player.getRefData().getPosition();
|
2015-06-03 17:41:19 +00:00
|
|
|
mPlayer->setLastKnownExteriorPosition(pos.asVec3());
|
2013-12-31 23:12:31 +00:00
|
|
|
}
|
2015-05-26 14:40:44 +00:00
|
|
|
|
2015-12-07 15:11:47 +00:00
|
|
|
bool isWerewolf = player.getClass().getNpcStats(player).isWerewolf();
|
|
|
|
bool isFirstPerson = mRendering->getCamera()->isFirstPerson();
|
|
|
|
if (isWerewolf && isFirstPerson)
|
|
|
|
{
|
2019-01-22 06:08:48 +00:00
|
|
|
float werewolfFov = Fallback::Map::getFloat("General_Werewolf_FOV");
|
2015-12-07 15:11:47 +00:00
|
|
|
if (werewolfFov != 0)
|
|
|
|
mRendering->overrideFieldOfView(werewolfFov);
|
|
|
|
MWBase::Environment::get().getWindowManager()->setWerewolfOverlay(true);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mRendering->resetFieldOfView();
|
|
|
|
MWBase::Environment::get().getWindowManager()->setWerewolfOverlay(false);
|
|
|
|
}
|
2015-04-12 13:34:50 +00:00
|
|
|
|
2015-05-21 21:54:39 +00:00
|
|
|
// Sink the camera while sneaking
|
2015-05-26 14:40:44 +00:00
|
|
|
bool sneaking = player.getClass().getCreatureStats(getPlayerPtr()).getStance(MWMechanics::CreatureStats::Stance_Sneak);
|
|
|
|
bool swimming = isSwimming(player);
|
2018-08-28 11:28:27 +00:00
|
|
|
bool flying = isFlying(player);
|
2015-05-21 21:54:39 +00:00
|
|
|
|
2019-01-07 14:48:41 +00:00
|
|
|
static const float i1stPersonSneakDelta = mStore.get<ESM::GameSetting>().find("i1stPersonSneakDelta")->mValue.getFloat();
|
2018-08-28 11:28:27 +00:00
|
|
|
if (sneaking && !swimming && !flying)
|
2015-05-21 21:54:39 +00:00
|
|
|
mRendering->getCamera()->setSneakOffset(i1stPersonSneakDelta);
|
|
|
|
else
|
|
|
|
mRendering->getCamera()->setSneakOffset(0.f);
|
2015-05-26 14:40:44 +00:00
|
|
|
|
2020-10-09 17:34:36 +00:00
|
|
|
int blind = 0;
|
|
|
|
auto& magicEffects = player.getClass().getCreatureStats(player).getMagicEffects();
|
|
|
|
if (!mGodMode)
|
|
|
|
blind = static_cast<int>(magicEffects.get(ESM::MagicEffect::Blind).getMagnitude());
|
2015-05-26 14:40:44 +00:00
|
|
|
MWBase::Environment::get().getWindowManager()->setBlindness(std::max(0, std::min(100, blind)));
|
2015-06-01 13:34:46 +00:00
|
|
|
|
2020-10-09 17:34:36 +00:00
|
|
|
int nightEye = static_cast<int>(magicEffects.get(ESM::MagicEffect::NightEye).getMagnitude());
|
2015-06-11 21:16:05 +00:00
|
|
|
mRendering->setNightEyeFactor(std::min(1.f, (nightEye/100.f)));
|
2017-02-14 23:55:35 +00:00
|
|
|
}
|
2015-06-01 13:34:46 +00:00
|
|
|
|
2017-02-14 23:55:35 +00:00
|
|
|
void World::preloadSpells()
|
|
|
|
{
|
|
|
|
std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell();
|
|
|
|
if (!selectedSpell.empty())
|
|
|
|
{
|
|
|
|
const ESM::Spell* spell = mStore.get<ESM::Spell>().search(selectedSpell);
|
|
|
|
if (spell)
|
|
|
|
preloadEffects(&spell->mEffects);
|
|
|
|
}
|
|
|
|
const MWWorld::Ptr& selectedEnchantItem = MWBase::Environment::get().getWindowManager()->getSelectedEnchantItem();
|
|
|
|
if (!selectedEnchantItem.isEmpty())
|
|
|
|
{
|
|
|
|
std::string enchantId = selectedEnchantItem.getClass().getEnchantment(selectedEnchantItem);
|
|
|
|
if (!enchantId.empty())
|
|
|
|
{
|
2020-07-08 10:12:55 +00:00
|
|
|
const ESM::Enchantment* ench = mStore.get<ESM::Enchantment>().search(enchantId);
|
2017-02-14 23:55:35 +00:00
|
|
|
if (ench)
|
|
|
|
preloadEffects(&ench->mEffects);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const MWWorld::Ptr& selectedWeapon = MWBase::Environment::get().getWindowManager()->getSelectedWeapon();
|
|
|
|
if (!selectedWeapon.isEmpty())
|
|
|
|
{
|
|
|
|
std::string enchantId = selectedWeapon.getClass().getEnchantment(selectedWeapon);
|
|
|
|
if (!enchantId.empty())
|
|
|
|
{
|
|
|
|
const ESM::Enchantment* ench = mStore.get<ESM::Enchantment>().search(enchantId);
|
|
|
|
if (ench && ench->mData.mType == ESM::Enchantment::WhenStrikes)
|
|
|
|
preloadEffects(&ench->mEffects);
|
|
|
|
}
|
|
|
|
}
|
2013-01-08 06:48:24 +00:00
|
|
|
}
|
|
|
|
|
2014-12-08 22:26:09 +00:00
|
|
|
void World::updateSoundListener()
|
|
|
|
{
|
2015-05-12 17:02:56 +00:00
|
|
|
const ESM::Position& refpos = getPlayerPtr().getRefData().getPosition();
|
2015-12-03 14:16:20 +00:00
|
|
|
osg::Vec3f listenerPos;
|
2015-05-12 17:02:56 +00:00
|
|
|
|
2015-12-03 14:16:20 +00:00
|
|
|
if (isFirstPerson())
|
|
|
|
listenerPos = mRendering->getCameraPosition();
|
|
|
|
else
|
|
|
|
listenerPos = refpos.asVec3() + osg::Vec3f(0, 0, 1.85f * mPhysics->getHalfExtents(getPlayerPtr()).z());
|
2015-05-12 17:02:56 +00:00
|
|
|
|
2015-12-03 14:16:20 +00:00
|
|
|
osg::Quat listenerOrient = osg::Quat(refpos.rot[1], osg::Vec3f(0,-1,0)) *
|
2015-05-12 17:02:56 +00:00
|
|
|
osg::Quat(refpos.rot[0], osg::Vec3f(-1,0,0)) *
|
|
|
|
osg::Quat(refpos.rot[2], osg::Vec3f(0,0,-1));
|
|
|
|
|
2015-12-03 14:16:20 +00:00
|
|
|
osg::Vec3f forward = listenerOrient * osg::Vec3f(0,1,0);
|
|
|
|
osg::Vec3f up = listenerOrient * osg::Vec3f(0,0,1);
|
|
|
|
|
2018-09-15 20:20:28 +00:00
|
|
|
bool underwater = isUnderwater(getPlayerPtr().getCell(), mRendering->getCameraPosition());
|
2015-05-12 17:02:56 +00:00
|
|
|
|
2015-12-03 14:16:20 +00:00
|
|
|
MWBase::Environment::get().getSoundManager()->setListenerPosDir(listenerPos, forward, up, underwater);
|
2014-12-08 22:26:09 +00:00
|
|
|
}
|
|
|
|
|
2013-01-08 06:48:24 +00:00
|
|
|
void World::updateWindowManager ()
|
|
|
|
{
|
2018-01-14 00:26:37 +00:00
|
|
|
try
|
2012-10-18 20:21:39 +00:00
|
|
|
{
|
2018-01-14 00:26:37 +00:00
|
|
|
// inform the GUI about focused object
|
|
|
|
MWWorld::Ptr object = getFacedObject ();
|
2012-04-15 15:10:08 +00:00
|
|
|
|
2018-01-14 00:26:37 +00:00
|
|
|
// retrieve object dimensions so we know where to place the floating label
|
|
|
|
if (!object.isEmpty ())
|
|
|
|
{
|
2020-05-11 13:37:00 +00:00
|
|
|
osg::BoundingBox bb = mPhysics->getBoundingBox(object);
|
|
|
|
if (!bb.valid() && object.getRefData().getBaseNode())
|
|
|
|
{
|
|
|
|
osg::ComputeBoundsVisitor computeBoundsVisitor;
|
|
|
|
computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect));
|
|
|
|
object.getRefData().getBaseNode()->accept(computeBoundsVisitor);
|
|
|
|
bb = computeBoundsVisitor.getBoundingBox();
|
|
|
|
}
|
|
|
|
osg::Vec4f screenBounds = mRendering->getScreenBounds(bb);
|
2018-01-14 00:26:37 +00:00
|
|
|
MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords(
|
|
|
|
screenBounds.x(), screenBounds.y(), screenBounds.z(), screenBounds.w());
|
|
|
|
}
|
2017-07-20 18:59:36 +00:00
|
|
|
|
2018-01-14 00:26:37 +00:00
|
|
|
MWBase::Environment::get().getWindowManager()->setFocusObject(object);
|
|
|
|
}
|
|
|
|
catch (std::exception& e)
|
|
|
|
{
|
2018-08-14 19:05:43 +00:00
|
|
|
Log(Debug::Error) << "Error updating window manager: " << e.what();
|
2018-01-14 00:26:37 +00:00
|
|
|
}
|
2013-01-08 06:48:24 +00:00
|
|
|
}
|
2012-03-25 18:52:56 +00:00
|
|
|
|
2015-05-24 01:36:34 +00:00
|
|
|
MWWorld::Ptr World::getFacedObject(float maxDistance, bool ignorePlayer)
|
2013-01-08 06:48:24 +00:00
|
|
|
{
|
2020-07-25 21:33:50 +00:00
|
|
|
const float camDist = mRendering->getCamera()->getCameraDistance();
|
2016-07-10 15:09:21 +00:00
|
|
|
maxDistance += camDist;
|
2016-07-03 17:11:30 +00:00
|
|
|
MWWorld::Ptr facedObject;
|
2016-07-06 13:09:39 +00:00
|
|
|
MWRender::RenderingManager::RayResult rayToObject;
|
2014-01-01 22:30:58 +00:00
|
|
|
|
2013-01-08 06:48:24 +00:00
|
|
|
if (MWBase::Environment::get().getWindowManager()->isGuiMode())
|
|
|
|
{
|
|
|
|
float x, y;
|
|
|
|
MWBase::Environment::get().getWindowManager()->getMousePosition(x, y);
|
2016-07-06 13:09:39 +00:00
|
|
|
rayToObject = mRendering->castCameraToViewportRay(x, y, maxDistance, ignorePlayer);
|
2013-01-08 06:48:24 +00:00
|
|
|
}
|
|
|
|
else
|
2016-07-06 13:09:39 +00:00
|
|
|
rayToObject = mRendering->castCameraToViewportRay(0.5f, 0.5f, maxDistance, ignorePlayer);
|
|
|
|
|
|
|
|
facedObject = rayToObject.mHitObject;
|
2020-05-11 13:37:00 +00:00
|
|
|
if (facedObject.isEmpty() && rayToObject.mHitRefnum.hasContentFile())
|
|
|
|
{
|
|
|
|
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
|
|
|
{
|
|
|
|
facedObject = cellstore->searchViaRefNum(rayToObject.mHitRefnum);
|
|
|
|
if (!facedObject.isEmpty()) break;
|
|
|
|
}
|
|
|
|
}
|
2016-07-08 17:12:58 +00:00
|
|
|
if (rayToObject.mHit)
|
2016-07-10 15:09:21 +00:00
|
|
|
mDistanceToFacedObject = (rayToObject.mRatio * maxDistance) - camDist;
|
2016-07-07 12:46:58 +00:00
|
|
|
else
|
2016-07-08 17:12:58 +00:00
|
|
|
mDistanceToFacedObject = -1;
|
2016-07-06 13:09:39 +00:00
|
|
|
return facedObject;
|
2014-07-26 16:01:53 +00:00
|
|
|
}
|
|
|
|
|
2012-02-23 20:12:06 +00:00
|
|
|
bool World::isCellExterior() const
|
|
|
|
{
|
2014-10-02 14:30:23 +00:00
|
|
|
const CellStore *currentCell = mWorldScene->getCurrentCell();
|
2012-02-23 20:12:06 +00:00
|
|
|
if (currentCell)
|
|
|
|
{
|
2014-02-21 10:35:46 +00:00
|
|
|
return currentCell->getCell()->isExterior();
|
2012-02-23 20:12:06 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2012-03-15 15:01:41 +00:00
|
|
|
|
2012-02-23 20:12:06 +00:00
|
|
|
bool World::isCellQuasiExterior() const
|
|
|
|
{
|
2014-10-02 14:30:23 +00:00
|
|
|
const CellStore *currentCell = mWorldScene->getCurrentCell();
|
2012-02-23 20:12:06 +00:00
|
|
|
if (currentCell)
|
|
|
|
{
|
2014-02-21 10:35:46 +00:00
|
|
|
if (!(currentCell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
|
2012-02-23 20:12:06 +00:00
|
|
|
return false;
|
|
|
|
else
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2012-03-15 15:01:41 +00:00
|
|
|
|
2012-02-25 20:34:38 +00:00
|
|
|
int World::getCurrentWeather() const
|
|
|
|
{
|
2015-04-14 13:55:56 +00:00
|
|
|
return mWeatherManager->getWeatherID();
|
2012-02-25 20:34:38 +00:00
|
|
|
}
|
2012-03-15 15:01:41 +00:00
|
|
|
|
2019-01-27 12:55:36 +00:00
|
|
|
unsigned int World::getNightDayMode() const
|
|
|
|
{
|
|
|
|
return mWeatherManager->getNightDayMode();
|
|
|
|
}
|
|
|
|
|
2012-02-26 10:51:02 +00:00
|
|
|
void World::changeWeather(const std::string& region, const unsigned int id)
|
|
|
|
{
|
2015-04-14 13:55:56 +00:00
|
|
|
mWeatherManager->changeWeather(region, id);
|
2012-02-26 10:51:02 +00:00
|
|
|
}
|
2012-03-15 15:01:41 +00:00
|
|
|
|
2013-07-27 14:10:18 +00:00
|
|
|
void World::modRegion(const std::string ®ionid, const std::vector<char> &chances)
|
|
|
|
{
|
2015-04-14 13:55:56 +00:00
|
|
|
mWeatherManager->modRegion(regionid, chances);
|
2013-07-27 14:10:18 +00:00
|
|
|
}
|
|
|
|
|
2016-02-04 23:25:14 +00:00
|
|
|
osg::Vec2f World::getNorthVector (const CellStore* cell)
|
2012-03-28 01:15:10 +00:00
|
|
|
{
|
2016-02-04 23:25:14 +00:00
|
|
|
MWWorld::ConstPtr northmarker = cell->searchConst("northmarker");
|
2015-12-06 19:43:50 +00:00
|
|
|
|
|
|
|
if (northmarker.isEmpty())
|
2015-05-26 14:40:44 +00:00
|
|
|
return osg::Vec2f(0, 1);
|
2014-12-27 13:43:07 +00:00
|
|
|
|
2015-12-06 19:43:50 +00:00
|
|
|
osg::Quat orient (-northmarker.getRefData().getPosition().rot[2], osg::Vec3f(0,0,1));
|
2015-05-26 14:40:44 +00:00
|
|
|
osg::Vec3f dir = orient * osg::Vec3f(0,1,0);
|
|
|
|
osg::Vec2f d (dir.x(), dir.y());
|
2012-03-28 01:15:10 +00:00
|
|
|
return d;
|
|
|
|
}
|
2012-03-30 13:31:07 +00:00
|
|
|
|
2015-12-06 19:43:50 +00:00
|
|
|
struct GetDoorMarkerVisitor
|
2012-08-28 15:30:34 +00:00
|
|
|
{
|
2015-12-06 19:43:50 +00:00
|
|
|
GetDoorMarkerVisitor(std::vector<World::DoorMarker>& out)
|
|
|
|
: mOut(out)
|
2012-08-28 15:30:34 +00:00
|
|
|
{
|
2015-12-06 19:43:50 +00:00
|
|
|
}
|
2012-08-28 15:30:34 +00:00
|
|
|
|
2015-12-06 19:43:50 +00:00
|
|
|
std::vector<World::DoorMarker>& mOut;
|
|
|
|
|
|
|
|
bool operator()(const MWWorld::Ptr& ptr)
|
|
|
|
{
|
|
|
|
MWWorld::LiveCellRef<ESM::Door>& ref = *static_cast<MWWorld::LiveCellRef<ESM::Door>* >(ptr.getBase());
|
|
|
|
|
|
|
|
if (!ref.mData.isEnabled() || ref.mData.isDeleted())
|
|
|
|
return true;
|
2014-05-11 13:22:46 +00:00
|
|
|
|
2014-05-25 12:13:07 +00:00
|
|
|
if (ref.mRef.getTeleport())
|
2012-08-28 15:30:34 +00:00
|
|
|
{
|
|
|
|
World::DoorMarker newMarker;
|
2012-12-23 19:23:24 +00:00
|
|
|
newMarker.name = MWClass::Door::getDestination(ref);
|
2012-08-28 15:30:34 +00:00
|
|
|
|
2015-01-31 16:50:19 +00:00
|
|
|
ESM::CellId cellid;
|
|
|
|
if (!ref.mRef.getDestCell().empty())
|
|
|
|
{
|
|
|
|
cellid.mWorldspace = ref.mRef.getDestCell();
|
|
|
|
cellid.mPaged = false;
|
2016-01-21 15:08:04 +00:00
|
|
|
cellid.mIndex.mX = 0;
|
|
|
|
cellid.mIndex.mY = 0;
|
2015-01-31 16:50:19 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
cellid.mPaged = true;
|
2015-12-06 19:43:50 +00:00
|
|
|
MWBase::Environment::get().getWorld()->positionToIndex(
|
2015-01-31 16:50:19 +00:00
|
|
|
ref.mRef.getDoorDest().pos[0],
|
|
|
|
ref.mRef.getDoorDest().pos[1],
|
|
|
|
cellid.mIndex.mX,
|
|
|
|
cellid.mIndex.mY);
|
|
|
|
}
|
|
|
|
newMarker.dest = cellid;
|
|
|
|
|
2012-08-28 15:30:34 +00:00
|
|
|
ESM::Position pos = ref.mData.getPosition ();
|
|
|
|
|
|
|
|
newMarker.x = pos.pos[0];
|
|
|
|
newMarker.y = pos.pos[1];
|
2015-12-06 19:43:50 +00:00
|
|
|
mOut.push_back(newMarker);
|
2012-08-28 15:30:34 +00:00
|
|
|
}
|
2015-12-06 19:43:50 +00:00
|
|
|
return true;
|
2012-08-28 15:30:34 +00:00
|
|
|
}
|
2015-12-06 19:43:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
void World::getDoorMarkers (CellStore* cell, std::vector<World::DoorMarker>& out)
|
|
|
|
{
|
|
|
|
GetDoorMarkerVisitor visitor(out);
|
|
|
|
cell->forEachType<ESM::Door>(visitor);
|
2012-08-28 15:30:34 +00:00
|
|
|
}
|
|
|
|
|
2012-03-29 13:49:24 +00:00
|
|
|
void World::setWaterHeight(const float height)
|
|
|
|
{
|
2015-05-26 14:40:44 +00:00
|
|
|
mPhysics->setWaterHeight(height);
|
2015-06-02 14:35:35 +00:00
|
|
|
mRendering->setWaterHeight(height);
|
2012-03-29 13:49:24 +00:00
|
|
|
}
|
|
|
|
|
2014-05-16 07:21:08 +00:00
|
|
|
bool World::toggleWater()
|
2012-03-29 16:33:08 +00:00
|
|
|
{
|
2015-06-02 14:35:35 +00:00
|
|
|
return mRendering->toggleRenderMode(MWRender::Render_Water);
|
2012-03-29 16:33:08 +00:00
|
|
|
}
|
|
|
|
|
2014-09-30 13:53:27 +00:00
|
|
|
bool World::toggleWorld()
|
|
|
|
{
|
2015-06-02 14:35:35 +00:00
|
|
|
return mRendering->toggleRenderMode(MWRender::Render_Scene);
|
2014-09-30 13:53:27 +00:00
|
|
|
}
|
|
|
|
|
2018-06-12 23:48:31 +00:00
|
|
|
bool World::toggleBorders()
|
|
|
|
{
|
|
|
|
return mRendering->toggleBorders();
|
|
|
|
}
|
|
|
|
|
2013-01-31 18:45:32 +00:00
|
|
|
void World::PCDropped (const Ptr& item)
|
|
|
|
{
|
2014-05-22 18:37:22 +00:00
|
|
|
std::string script = item.getClass().getScript(item);
|
2013-02-06 09:12:40 +00:00
|
|
|
|
|
|
|
// Set OnPCDrop Variable on item's script, if it has a script with that variable declared
|
2013-01-31 18:45:32 +00:00
|
|
|
if(script != "")
|
2013-08-15 00:05:42 +00:00
|
|
|
item.getRefData().getLocals().setVarByInt(script, "onpcdrop", 1);
|
2013-01-31 18:45:32 +00:00
|
|
|
}
|
|
|
|
|
2015-12-18 16:42:59 +00:00
|
|
|
MWWorld::Ptr World::placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount)
|
2012-05-14 15:41:17 +00:00
|
|
|
{
|
2015-05-31 23:57:15 +00:00
|
|
|
const float maxDist = 200.f;
|
2012-05-15 14:47:23 +00:00
|
|
|
|
2015-06-05 00:26:16 +00:00
|
|
|
MWRender::RenderingManager::RayResult result = mRendering->castCameraToViewportRay(cursorX, cursorY, maxDist, true, true);
|
2012-05-15 14:47:23 +00:00
|
|
|
|
2014-03-07 05:11:00 +00:00
|
|
|
CellStore* cell = getPlayerPtr().getCell();
|
2012-05-15 14:47:23 +00:00
|
|
|
|
2014-01-08 17:39:44 +00:00
|
|
|
ESM::Position pos = getPlayerPtr().getRefData().getPosition();
|
2015-05-31 23:57:15 +00:00
|
|
|
if (result.mHit)
|
|
|
|
{
|
2015-06-05 00:26:16 +00:00
|
|
|
pos.pos[0] = result.mHitPointWorld.x();
|
|
|
|
pos.pos[1] = result.mHitPointWorld.y();
|
|
|
|
pos.pos[2] = result.mHitPointWorld.z();
|
2015-05-31 23:57:15 +00:00
|
|
|
}
|
2013-03-09 12:27:24 +00:00
|
|
|
// We want only the Z part of the player's rotation
|
|
|
|
pos.rot[0] = 0;
|
|
|
|
pos.rot[1] = 0;
|
2012-05-15 14:47:23 +00:00
|
|
|
|
2013-08-12 23:19:33 +00:00
|
|
|
// copy the object and set its count
|
2015-12-19 15:47:55 +00:00
|
|
|
Ptr dropped = copyObjectToCell(object, cell, pos, amount, true);
|
2013-08-12 23:19:33 +00:00
|
|
|
|
|
|
|
// only the player place items in the world, so no need to check actor
|
2013-01-31 18:45:32 +00:00
|
|
|
PCDropped(dropped);
|
2012-05-15 14:47:23 +00:00
|
|
|
|
2014-05-16 01:19:38 +00:00
|
|
|
return dropped;
|
2012-05-14 15:41:17 +00:00
|
|
|
}
|
|
|
|
|
2012-05-15 14:47:23 +00:00
|
|
|
bool World::canPlaceObject(float cursorX, float cursorY)
|
|
|
|
{
|
2015-05-31 23:57:15 +00:00
|
|
|
const float maxDist = 200.f;
|
2015-06-05 00:26:16 +00:00
|
|
|
MWRender::RenderingManager::RayResult result = mRendering->castCameraToViewportRay(cursorX, cursorY, maxDist, true, true);
|
2015-05-31 23:57:15 +00:00
|
|
|
|
|
|
|
if (result.mHit)
|
2014-05-27 15:39:04 +00:00
|
|
|
{
|
|
|
|
// check if the wanted position is on a flat surface, and not e.g. against a vertical wall
|
2015-06-05 01:41:10 +00:00
|
|
|
if (std::acos((result.mHitNormalWorld/result.mHitNormalWorld.length()) * osg::Vec3f(0,0,1)) >= osg::DegreesToRadians(30.f))
|
2014-06-05 22:43:07 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
2014-05-27 15:39:04 +00:00
|
|
|
}
|
|
|
|
else
|
2012-05-15 14:47:23 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-02-06 09:12:40 +00:00
|
|
|
|
2015-12-19 15:47:55 +00:00
|
|
|
Ptr World::copyObjectToCell(const ConstPtr &object, CellStore* cell, ESM::Position pos, int count, bool adjustPos)
|
2012-07-25 06:47:59 +00:00
|
|
|
{
|
2020-10-17 22:40:58 +00:00
|
|
|
if (!cell)
|
|
|
|
throw std::runtime_error("copyObjectToCell(): cannot copy object to null cell");
|
2014-03-07 05:11:00 +00:00
|
|
|
if (cell->isExterior())
|
2014-01-14 06:40:17 +00:00
|
|
|
{
|
|
|
|
int cellX, cellY;
|
|
|
|
positionToIndex(pos.pos[0], pos.pos[1], cellX, cellY);
|
2014-03-07 05:11:00 +00:00
|
|
|
cell = mCells.getExterior(cellX, cellY);
|
2014-01-14 06:40:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
MWWorld::Ptr dropped =
|
2015-12-19 15:47:55 +00:00
|
|
|
object.getClass().copyToCell(object, *cell, pos, count);
|
2014-01-14 06:40:17 +00:00
|
|
|
|
2014-06-13 21:40:49 +00:00
|
|
|
// Reset some position values that could be uninitialized if this item came from a container
|
|
|
|
dropped.getCellRef().setPosition(pos);
|
2014-07-29 13:55:58 +00:00
|
|
|
dropped.getCellRef().unsetRefNum();
|
2014-06-13 21:40:49 +00:00
|
|
|
|
2014-03-07 05:11:00 +00:00
|
|
|
if (mWorldScene->isCellActive(*cell)) {
|
2012-07-26 15:06:48 +00:00
|
|
|
if (dropped.getRefData().isEnabled()) {
|
|
|
|
mWorldScene->addObjectToScene(dropped);
|
|
|
|
}
|
2014-05-22 18:37:22 +00:00
|
|
|
std::string script = dropped.getClass().getScript(dropped);
|
2012-07-26 12:14:11 +00:00
|
|
|
if (!script.empty()) {
|
|
|
|
mLocalScripts.add(script, dropped);
|
|
|
|
}
|
2014-03-07 05:11:00 +00:00
|
|
|
addContainerScripts(dropped, cell);
|
2012-07-25 06:47:59 +00:00
|
|
|
}
|
2013-01-31 18:45:32 +00:00
|
|
|
|
2015-05-21 23:43:45 +00:00
|
|
|
if (!object.getClass().isActor() && adjustPos && dropped.getRefData().getBaseNode())
|
|
|
|
{
|
|
|
|
// Adjust position so the location we wanted ends up in the middle of the object bounding box
|
|
|
|
osg::ComputeBoundsVisitor computeBounds;
|
2020-04-20 16:47:14 +00:00
|
|
|
computeBounds.setTraversalMask(~MWRender::Mask_ParticleSystem);
|
2015-05-21 23:43:45 +00:00
|
|
|
dropped.getRefData().getBaseNode()->accept(computeBounds);
|
|
|
|
osg::BoundingBox bounds = computeBounds.getBoundingBox();
|
|
|
|
if (bounds.valid())
|
|
|
|
{
|
|
|
|
bounds.set(bounds._min - pos.asVec3(), bounds._max - pos.asVec3());
|
|
|
|
|
|
|
|
osg::Vec3f adjust (
|
|
|
|
(bounds.xMin() + bounds.xMax()) / 2,
|
|
|
|
(bounds.yMin() + bounds.yMax()) / 2,
|
|
|
|
bounds.zMin()
|
|
|
|
);
|
|
|
|
pos.pos[0] -= adjust.x();
|
|
|
|
pos.pos[1] -= adjust.y();
|
|
|
|
pos.pos[2] -= adjust.z();
|
|
|
|
moveObject(dropped, pos.pos[0], pos.pos[1], pos.pos[2]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-31 18:45:32 +00:00
|
|
|
return dropped;
|
2012-07-25 06:47:59 +00:00
|
|
|
}
|
|
|
|
|
2015-12-18 16:42:59 +00:00
|
|
|
MWWorld::Ptr World::dropObjectOnGround (const Ptr& actor, const ConstPtr& object, int amount)
|
2012-05-15 14:47:23 +00:00
|
|
|
{
|
2013-12-05 12:21:26 +00:00
|
|
|
MWWorld::CellStore* cell = actor.getCell();
|
2012-05-15 14:47:23 +00:00
|
|
|
|
2012-07-25 16:25:53 +00:00
|
|
|
ESM::Position pos =
|
2013-01-09 21:16:45 +00:00
|
|
|
actor.getRefData().getPosition();
|
2013-03-09 12:27:24 +00:00
|
|
|
// We want only the Z part of the actor's rotation
|
|
|
|
pos.rot[0] = 0;
|
|
|
|
pos.rot[1] = 0;
|
2012-05-15 14:47:23 +00:00
|
|
|
|
2015-05-21 23:43:45 +00:00
|
|
|
osg::Vec3f orig = pos.asVec3();
|
|
|
|
orig.z() += 20;
|
|
|
|
osg::Vec3f dir (0, 0, -1);
|
2012-08-04 07:34:49 +00:00
|
|
|
|
2015-11-12 22:05:44 +00:00
|
|
|
float len = 1000000.0;
|
2012-07-25 16:25:53 +00:00
|
|
|
|
2015-06-05 00:26:16 +00:00
|
|
|
MWRender::RenderingManager::RayResult result = mRendering->castRay(orig, orig+dir*len, true, true);
|
2015-05-31 23:57:15 +00:00
|
|
|
if (result.mHit)
|
2015-06-05 00:26:16 +00:00
|
|
|
pos.pos[2] = result.mHitPointWorld.z();
|
2012-07-25 16:25:53 +00:00
|
|
|
|
2013-08-12 23:19:33 +00:00
|
|
|
// copy the object and set its count
|
2015-12-19 15:47:55 +00:00
|
|
|
Ptr dropped = copyObjectToCell(object, cell, pos, amount, true);
|
2013-08-12 23:19:33 +00:00
|
|
|
|
2013-01-31 18:45:32 +00:00
|
|
|
if(actor == mPlayer->getPlayer()) // Only call if dropped by player
|
|
|
|
PCDropped(dropped);
|
2014-05-16 01:19:38 +00:00
|
|
|
return dropped;
|
2012-05-15 14:47:23 +00:00
|
|
|
}
|
2012-05-22 23:32:36 +00:00
|
|
|
|
|
|
|
void World::processChangedSettings(const Settings::CategorySettingVector& settings)
|
|
|
|
{
|
2015-05-14 16:46:04 +00:00
|
|
|
mRendering->processChangedSettings(settings);
|
2012-05-22 23:32:36 +00:00
|
|
|
}
|
2012-06-22 10:56:04 +00:00
|
|
|
|
2015-01-31 19:54:08 +00:00
|
|
|
bool World::isFlying(const MWWorld::Ptr &ptr) const
|
2013-02-19 06:39:43 +00:00
|
|
|
{
|
2013-08-21 14:24:02 +00:00
|
|
|
if(!ptr.getClass().isActor())
|
|
|
|
return false;
|
|
|
|
|
2018-12-05 20:28:26 +00:00
|
|
|
const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr);
|
|
|
|
|
2015-08-20 06:12:37 +00:00
|
|
|
if (stats.isDead())
|
2014-01-05 14:38:12 +00:00
|
|
|
return false;
|
|
|
|
|
2014-03-15 21:09:14 +00:00
|
|
|
if (ptr.getClass().canFly(ptr))
|
2015-08-20 06:12:37 +00:00
|
|
|
return !stats.isParalyzed();
|
2014-01-05 14:38:12 +00:00
|
|
|
|
2014-08-16 20:38:22 +00:00
|
|
|
if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0
|
2014-01-02 15:38:23 +00:00
|
|
|
&& isLevitationEnabled())
|
2013-08-21 14:24:02 +00:00
|
|
|
return true;
|
|
|
|
|
2015-05-12 17:02:56 +00:00
|
|
|
const MWPhysics::Actor* actor = mPhysics->getActor(ptr);
|
2018-12-05 20:28:26 +00:00
|
|
|
if(!actor)
|
2015-05-12 17:02:56 +00:00
|
|
|
return true;
|
2013-08-21 14:24:02 +00:00
|
|
|
|
2013-02-20 09:55:12 +00:00
|
|
|
return false;
|
2013-02-19 06:39:43 +00:00
|
|
|
}
|
|
|
|
|
2015-01-31 19:54:08 +00:00
|
|
|
bool World::isSlowFalling(const MWWorld::Ptr &ptr) const
|
2013-10-02 20:53:29 +00:00
|
|
|
{
|
|
|
|
if(!ptr.getClass().isActor())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr);
|
2014-08-16 20:38:22 +00:00
|
|
|
if(stats.getMagicEffects().get(ESM::MagicEffect::SlowFall).getMagnitude() > 0)
|
2013-10-02 20:53:29 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-12-18 16:56:48 +00:00
|
|
|
bool World::isSubmerged(const MWWorld::ConstPtr &object) const
|
2013-08-07 13:34:11 +00:00
|
|
|
{
|
2015-03-08 00:07:29 +00:00
|
|
|
return isUnderwater(object, 1.0f/mSwimHeightScale);
|
2013-08-07 13:34:11 +00:00
|
|
|
}
|
|
|
|
|
2015-12-18 16:56:48 +00:00
|
|
|
bool World::isSwimming(const MWWorld::ConstPtr &object) const
|
2012-08-03 10:42:09 +00:00
|
|
|
{
|
2015-01-31 19:54:08 +00:00
|
|
|
return isUnderwater(object, mSwimHeightScale);
|
2015-01-09 08:40:53 +00:00
|
|
|
}
|
|
|
|
|
2015-12-18 16:56:48 +00:00
|
|
|
bool World::isWading(const MWWorld::ConstPtr &object) const
|
2015-01-09 08:40:53 +00:00
|
|
|
{
|
2015-01-31 19:54:08 +00:00
|
|
|
const float kneeDeep = 0.25f;
|
2015-01-09 09:17:53 +00:00
|
|
|
return isUnderwater(object, kneeDeep);
|
2015-01-09 08:40:53 +00:00
|
|
|
}
|
|
|
|
|
2015-12-18 16:56:48 +00:00
|
|
|
bool World::isUnderwater(const MWWorld::ConstPtr &object, const float heightRatio) const
|
2015-01-09 08:40:53 +00:00
|
|
|
{
|
2015-06-01 19:41:13 +00:00
|
|
|
osg::Vec3f pos (object.getRefData().getPosition().asVec3());
|
2012-08-03 10:42:09 +00:00
|
|
|
|
2015-11-01 20:45:58 +00:00
|
|
|
pos.z() += heightRatio*2*mPhysics->getRenderingHalfExtents(object).z();
|
2012-08-03 10:42:09 +00:00
|
|
|
|
2018-10-09 06:21:12 +00:00
|
|
|
const CellStore *currCell = object.isInCell() ? object.getCell() : nullptr; // currCell == nullptr should only happen for player, during initial startup
|
2017-09-29 18:28:09 +00:00
|
|
|
|
|
|
|
return isUnderwater(currCell, pos);
|
2012-08-03 10:42:09 +00:00
|
|
|
}
|
|
|
|
|
2015-06-01 19:41:13 +00:00
|
|
|
bool World::isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const
|
2012-08-03 10:42:09 +00:00
|
|
|
{
|
2017-09-24 12:28:05 +00:00
|
|
|
if (!cell)
|
|
|
|
return false;
|
|
|
|
|
2016-03-02 16:02:30 +00:00
|
|
|
if (!(cell->getCell()->hasWater())) {
|
2012-08-03 10:42:09 +00:00
|
|
|
return false;
|
|
|
|
}
|
2015-06-01 19:41:13 +00:00
|
|
|
return pos.z() < cell->getWaterLevel();
|
2012-08-03 10:42:09 +00:00
|
|
|
}
|
2012-08-14 16:33:29 +00:00
|
|
|
|
2016-08-30 16:05:54 +00:00
|
|
|
bool World::isWaterWalkingCastableOnTarget(const MWWorld::ConstPtr &target) const
|
|
|
|
{
|
|
|
|
const MWWorld::CellStore* cell = target.getCell();
|
|
|
|
if (!cell->getCell()->hasWater())
|
|
|
|
return true;
|
|
|
|
|
2016-11-14 15:20:17 +00:00
|
|
|
float waterlevel = cell->getWaterLevel();
|
|
|
|
|
2018-05-26 14:44:25 +00:00
|
|
|
// SwimHeightScale affects the upper z position an actor can swim to
|
2016-11-14 15:20:17 +00:00
|
|
|
// while in water. Based on observation from the original engine,
|
|
|
|
// the upper z position you get with a +1 SwimHeightScale is the depth
|
|
|
|
// limit for being able to cast water walking on an underwater target.
|
|
|
|
if (isUnderwater(target, mSwimHeightScale + 1) || (isUnderwater(cell, target.getRefData().getPosition().asVec3()) && !mPhysics->canMoveToWaterSurface(target, waterlevel)))
|
|
|
|
return false; // not castable if too deep or if not enough room to move actor to surface
|
|
|
|
else
|
|
|
|
return true;
|
2016-08-30 16:05:54 +00:00
|
|
|
}
|
|
|
|
|
2013-02-15 06:35:15 +00:00
|
|
|
bool World::isOnGround(const MWWorld::Ptr &ptr) const
|
|
|
|
{
|
2015-06-01 00:40:42 +00:00
|
|
|
return mPhysics->isOnGround(ptr);
|
2013-02-15 06:35:15 +00:00
|
|
|
}
|
|
|
|
|
2019-09-19 17:48:43 +00:00
|
|
|
void World::togglePOV(bool force)
|
2015-05-21 21:54:39 +00:00
|
|
|
{
|
2020-07-25 21:33:50 +00:00
|
|
|
mRendering->getCamera()->toggleViewMode(force);
|
2015-05-21 21:54:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool World::isFirstPerson() const
|
|
|
|
{
|
|
|
|
return mRendering->getCamera()->isFirstPerson();
|
|
|
|
}
|
2020-08-04 06:04:59 +00:00
|
|
|
|
|
|
|
bool World::isPreviewModeEnabled() const
|
|
|
|
{
|
|
|
|
return mRendering->getCamera()->getMode() == MWRender::Camera::Mode::Preview;
|
|
|
|
}
|
2015-05-21 21:54:39 +00:00
|
|
|
|
|
|
|
void World::togglePreviewMode(bool enable)
|
|
|
|
{
|
2020-07-25 21:33:50 +00:00
|
|
|
mRendering->getCamera()->togglePreviewMode(enable);
|
2015-05-21 21:54:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool World::toggleVanityMode(bool enable)
|
|
|
|
{
|
2020-07-25 21:33:50 +00:00
|
|
|
return mRendering->getCamera()->toggleVanityMode(enable);
|
2015-05-21 21:54:39 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 22:58:33 +00:00
|
|
|
void World::disableDeferredPreviewRotation()
|
|
|
|
{
|
2020-07-18 09:48:46 +00:00
|
|
|
mRendering->getCamera()->disableDeferredPreviewRotation();
|
2020-06-26 22:58:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void World::applyDeferredPreviewRotationToPlayer(float dt)
|
|
|
|
{
|
2020-07-18 09:48:46 +00:00
|
|
|
mRendering->getCamera()->applyDeferredPreviewRotationToPlayer(dt);
|
2020-06-26 22:58:33 +00:00
|
|
|
}
|
|
|
|
|
2015-05-21 21:54:39 +00:00
|
|
|
void World::allowVanityMode(bool allow)
|
|
|
|
{
|
2020-07-25 21:33:50 +00:00
|
|
|
mRendering->getCamera()->allowVanityMode(allow);
|
2015-05-21 21:54:39 +00:00
|
|
|
}
|
|
|
|
|
2013-04-09 18:24:41 +00:00
|
|
|
bool World::vanityRotateCamera(float * rot)
|
|
|
|
{
|
2020-07-25 21:33:50 +00:00
|
|
|
if(!mRendering->getCamera()->isVanityOrPreviewModeEnabled())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
mRendering->getCamera()->rotateCamera(rot[0], rot[2], true);
|
|
|
|
return true;
|
2013-04-09 18:24:41 +00:00
|
|
|
}
|
|
|
|
|
2020-07-25 20:42:58 +00:00
|
|
|
void World::adjustCameraDistance(float dist)
|
2013-07-16 16:38:18 +00:00
|
|
|
{
|
2020-07-25 20:42:58 +00:00
|
|
|
mRendering->getCamera()->adjustCameraDistance(dist);
|
2013-07-16 16:38:18 +00:00
|
|
|
}
|
|
|
|
|
2013-04-29 15:56:16 +00:00
|
|
|
void World::setupPlayer()
|
2013-04-15 00:56:23 +00:00
|
|
|
{
|
2013-04-29 06:44:44 +00:00
|
|
|
const ESM::NPC *player = mStore.get<ESM::NPC>().find("player");
|
2013-05-15 15:54:18 +00:00
|
|
|
if (!mPlayer)
|
2018-01-30 22:05:16 +00:00
|
|
|
mPlayer.reset(new MWWorld::Player(player));
|
2013-05-15 15:54:18 +00:00
|
|
|
else
|
2014-06-05 15:01:22 +00:00
|
|
|
{
|
|
|
|
// Remove the old CharacterController
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr());
|
2019-03-03 11:58:03 +00:00
|
|
|
mNavigator->removeAgent(getPathfindingHalfExtents(getPlayerConstPtr()));
|
2015-05-28 00:45:38 +00:00
|
|
|
mPhysics->remove(getPlayerPtr());
|
2015-06-16 18:36:48 +00:00
|
|
|
mRendering->removePlayer(getPlayerPtr());
|
2014-06-05 15:01:22 +00:00
|
|
|
|
2013-05-15 15:54:18 +00:00
|
|
|
mPlayer->set(player);
|
2014-06-05 15:01:22 +00:00
|
|
|
}
|
2013-04-29 06:44:44 +00:00
|
|
|
|
2015-05-01 16:21:50 +00:00
|
|
|
Ptr ptr = mPlayer->getPlayer();
|
|
|
|
mRendering->setupPlayer(ptr);
|
2013-04-15 00:56:23 +00:00
|
|
|
}
|
|
|
|
|
2012-08-14 16:33:29 +00:00
|
|
|
void World::renderPlayer()
|
|
|
|
{
|
2015-05-31 22:50:48 +00:00
|
|
|
MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr());
|
|
|
|
|
2017-02-22 13:54:40 +00:00
|
|
|
MWWorld::Ptr player = getPlayerPtr();
|
|
|
|
|
|
|
|
mRendering->renderPlayer(player);
|
|
|
|
MWRender::NpcAnimation* anim = static_cast<MWRender::NpcAnimation*>(mRendering->getAnimation(player));
|
|
|
|
player.getClass().getInventoryStore(player).setInvListener(anim, player);
|
|
|
|
player.getClass().getInventoryStore(player).setContListener(anim);
|
2015-05-01 16:21:50 +00:00
|
|
|
|
2019-01-07 08:58:47 +00:00
|
|
|
scaleObject(player, player.getCellRef().getScale()); // apply race height
|
2019-11-30 11:29:31 +00:00
|
|
|
rotateObject(player, 0.f, 0.f, 0.f, MWBase::RotationFlag_inverseOrder | MWBase::RotationFlag_adjust);
|
2017-02-08 03:42:15 +00:00
|
|
|
|
2015-05-01 16:21:50 +00:00
|
|
|
MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr());
|
2020-06-05 14:22:53 +00:00
|
|
|
MWBase::Environment::get().getWindowManager()->watchActor(getPlayerPtr());
|
2014-01-25 15:13:45 +00:00
|
|
|
|
2015-01-12 10:29:56 +00:00
|
|
|
std::string model = getPlayerPtr().getClass().getModel(getPlayerPtr());
|
2015-05-12 15:40:42 +00:00
|
|
|
model = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS());
|
2015-05-28 00:45:38 +00:00
|
|
|
mPhysics->remove(getPlayerPtr());
|
2015-05-12 15:40:42 +00:00
|
|
|
mPhysics->addActor(getPlayerPtr(), model);
|
2018-03-17 09:41:13 +00:00
|
|
|
|
|
|
|
applyLoopingParticles(player);
|
2018-04-22 16:22:00 +00:00
|
|
|
|
2019-03-03 11:45:36 +00:00
|
|
|
mDefaultHalfExtents = mPhysics->getOriginalHalfExtents(getPlayerPtr());
|
2019-03-03 11:58:03 +00:00
|
|
|
mNavigator->addAgent(getPathfindingHalfExtents(getPlayerConstPtr()));
|
2012-08-14 16:33:29 +00:00
|
|
|
}
|
2012-09-12 22:54:32 +00:00
|
|
|
|
2018-09-23 03:25:20 +00:00
|
|
|
World::RestPermitted World::canRest () const
|
2012-09-18 18:53:32 +00:00
|
|
|
{
|
2013-12-05 12:21:26 +00:00
|
|
|
CellStore *currentCell = mWorldScene->getCurrentCell();
|
2012-09-19 00:53:06 +00:00
|
|
|
|
2013-08-09 01:30:47 +00:00
|
|
|
Ptr player = mPlayer->getPlayer();
|
2015-05-12 17:02:56 +00:00
|
|
|
RefData &refdata = player.getRefData();
|
2015-06-01 19:41:13 +00:00
|
|
|
osg::Vec3f playerPos(refdata.getPosition().asVec3());
|
2012-09-19 00:53:06 +00:00
|
|
|
|
2015-05-12 17:02:56 +00:00
|
|
|
const MWPhysics::Actor* actor = mPhysics->getActor(player);
|
|
|
|
if (!actor)
|
2015-01-15 15:05:25 +00:00
|
|
|
throw std::runtime_error("can't find player");
|
2015-01-15 01:03:27 +00:00
|
|
|
|
2018-09-22 23:46:29 +00:00
|
|
|
if(mPlayer->enemiesNearby())
|
|
|
|
return Rest_EnemiesAreNearby;
|
2018-09-22 22:52:56 +00:00
|
|
|
|
2019-04-04 14:18:49 +00:00
|
|
|
if (isUnderwater(currentCell, playerPos) || isWalkingOnWater(player))
|
2018-09-22 22:52:56 +00:00
|
|
|
return Rest_PlayerIsUnderwater;
|
2015-05-12 17:02:56 +00:00
|
|
|
|
2019-12-12 12:20:23 +00:00
|
|
|
float fallHeight = player.getClass().getCreatureStats(player).getFallHeight();
|
|
|
|
float epsilon = 1e-4;
|
|
|
|
if ((actor->getCollisionMode() && (!mPhysics->isOnSolidGround(player) || fallHeight >= epsilon)) || isFlying(player))
|
2019-04-04 14:18:49 +00:00
|
|
|
return Rest_PlayerIsInAir;
|
|
|
|
|
2015-11-20 18:22:31 +00:00
|
|
|
if((currentCell->getCell()->mData.mFlags&ESM::Cell::NoSleep) || player.getClass().getNpcStats(player).isWerewolf())
|
2018-09-22 22:52:56 +00:00
|
|
|
return Rest_OnlyWaiting;
|
2012-09-19 00:53:06 +00:00
|
|
|
|
2018-09-22 22:52:56 +00:00
|
|
|
return Rest_Allowed;
|
2012-09-18 18:53:32 +00:00
|
|
|
}
|
2012-09-25 00:35:50 +00:00
|
|
|
|
2013-01-16 17:59:19 +00:00
|
|
|
MWRender::Animation* World::getAnimation(const MWWorld::Ptr &ptr)
|
|
|
|
{
|
2015-04-25 13:19:17 +00:00
|
|
|
return mRendering->getAnimation(ptr);
|
2013-01-16 17:59:19 +00:00
|
|
|
}
|
|
|
|
|
2015-12-19 15:02:47 +00:00
|
|
|
const MWRender::Animation* World::getAnimation(const MWWorld::ConstPtr &ptr) const
|
|
|
|
{
|
|
|
|
return mRendering->getAnimation(ptr);
|
|
|
|
}
|
|
|
|
|
2015-06-03 14:40:16 +00:00
|
|
|
void World::screenshot(osg::Image* image, int w, int h)
|
2014-01-24 16:49:16 +00:00
|
|
|
{
|
2020-04-22 18:29:50 +00:00
|
|
|
mRendering->screenshotFramebuffer(image, w, h);
|
2014-01-24 16:49:16 +00:00
|
|
|
}
|
2018-05-26 14:44:25 +00:00
|
|
|
|
2017-11-11 13:32:28 +00:00
|
|
|
bool World::screenshot360(osg::Image* image, std::string settingStr)
|
2017-11-07 12:07:11 +00:00
|
|
|
{
|
2017-11-11 13:32:28 +00:00
|
|
|
return mRendering->screenshot360(image,settingStr);
|
2017-11-07 12:07:11 +00:00
|
|
|
}
|
2014-01-24 16:49:16 +00:00
|
|
|
|
2013-04-28 12:59:15 +00:00
|
|
|
void World::activateDoor(const MWWorld::Ptr& door)
|
|
|
|
{
|
2019-08-25 13:20:14 +00:00
|
|
|
auto state = door.getClass().getDoorState(door);
|
2014-05-14 23:58:44 +00:00
|
|
|
switch (state)
|
2013-04-28 12:59:15 +00:00
|
|
|
{
|
2019-08-25 13:20:14 +00:00
|
|
|
case MWWorld::DoorState::Idle:
|
2015-11-11 23:58:29 +00:00
|
|
|
if (door.getRefData().getPosition().rot[2] == door.getCellRef().getPosition().rot[2])
|
2019-08-25 13:20:14 +00:00
|
|
|
state = MWWorld::DoorState::Opening; // if closed, then open
|
2013-04-28 12:59:15 +00:00
|
|
|
else
|
2019-08-25 13:20:14 +00:00
|
|
|
state = MWWorld::DoorState::Closing; // if open, then close
|
2014-05-14 23:58:44 +00:00
|
|
|
break;
|
2019-08-25 13:20:14 +00:00
|
|
|
case MWWorld::DoorState::Closing:
|
|
|
|
state = MWWorld::DoorState::Opening; // if closing, then open
|
2014-05-14 23:58:44 +00:00
|
|
|
break;
|
2019-08-25 13:20:14 +00:00
|
|
|
case MWWorld::DoorState::Opening:
|
2014-05-14 23:58:44 +00:00
|
|
|
default:
|
2019-08-25 13:20:14 +00:00
|
|
|
state = MWWorld::DoorState::Closing; // if opening, then close
|
2014-05-14 23:58:44 +00:00
|
|
|
break;
|
2013-04-28 12:59:15 +00:00
|
|
|
}
|
2014-05-14 23:58:44 +00:00
|
|
|
door.getClass().setDoorState(door, state);
|
|
|
|
mDoorStates[door] = state;
|
2013-04-28 12:59:15 +00:00
|
|
|
}
|
2013-04-29 09:00:15 +00:00
|
|
|
|
2019-08-25 13:20:14 +00:00
|
|
|
void World::activateDoor(const Ptr &door, MWWorld::DoorState state)
|
2014-05-13 07:58:32 +00:00
|
|
|
{
|
2014-05-14 23:58:44 +00:00
|
|
|
door.getClass().setDoorState(door, state);
|
|
|
|
mDoorStates[door] = state;
|
2019-08-25 13:20:14 +00:00
|
|
|
if (state == MWWorld::DoorState::Idle)
|
2019-08-15 06:46:47 +00:00
|
|
|
{
|
2014-07-22 15:55:54 +00:00
|
|
|
mDoorStates.erase(door);
|
2019-08-15 06:46:47 +00:00
|
|
|
rotateDoor(door, state, 1);
|
|
|
|
}
|
2014-05-13 07:58:32 +00:00
|
|
|
}
|
|
|
|
|
2015-12-18 16:56:48 +00:00
|
|
|
bool World::getPlayerStandingOn (const MWWorld::ConstPtr& object)
|
2013-05-01 09:15:43 +00:00
|
|
|
{
|
2015-05-29 23:32:00 +00:00
|
|
|
MWWorld::Ptr player = getPlayerPtr();
|
|
|
|
return mPhysics->isActorStandingOn(player, object);
|
2013-05-01 09:15:43 +00:00
|
|
|
}
|
|
|
|
|
2015-12-18 16:56:48 +00:00
|
|
|
bool World::getActorStandingOn (const MWWorld::ConstPtr& object)
|
2013-05-01 09:15:43 +00:00
|
|
|
{
|
2015-05-29 23:32:00 +00:00
|
|
|
std::vector<MWWorld::Ptr> actors;
|
|
|
|
mPhysics->getActorsStandingOn(object, actors);
|
2014-07-29 17:01:40 +00:00
|
|
|
return !actors.empty();
|
|
|
|
}
|
|
|
|
|
2018-08-25 06:34:33 +00:00
|
|
|
void World::getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector<MWWorld::Ptr> &actors)
|
|
|
|
{
|
|
|
|
mPhysics->getActorsStandingOn(object, actors);
|
|
|
|
}
|
|
|
|
|
2015-12-18 16:56:48 +00:00
|
|
|
bool World::getPlayerCollidingWith (const MWWorld::ConstPtr& object)
|
2014-07-29 17:01:40 +00:00
|
|
|
{
|
2015-05-29 23:32:00 +00:00
|
|
|
MWWorld::Ptr player = getPlayerPtr();
|
|
|
|
return mPhysics->isActorCollidingWith(player, object);
|
2014-07-29 17:01:40 +00:00
|
|
|
}
|
|
|
|
|
2015-12-18 16:56:48 +00:00
|
|
|
bool World::getActorCollidingWith (const MWWorld::ConstPtr& object)
|
2014-07-29 17:01:40 +00:00
|
|
|
{
|
2015-05-29 23:32:00 +00:00
|
|
|
std::vector<MWWorld::Ptr> actors;
|
|
|
|
mPhysics->getActorsCollidingWith(object, actors);
|
2014-07-29 17:01:40 +00:00
|
|
|
return !actors.empty();
|
|
|
|
}
|
|
|
|
|
2015-12-18 16:56:48 +00:00
|
|
|
void World::hurtStandingActors(const ConstPtr &object, float healthPerSecond)
|
2014-07-29 17:01:40 +00:00
|
|
|
{
|
2014-10-11 22:13:24 +00:00
|
|
|
if (MWBase::Environment::get().getWindowManager()->isGuiMode())
|
|
|
|
return;
|
|
|
|
|
2015-05-29 23:32:00 +00:00
|
|
|
std::vector<MWWorld::Ptr> actors;
|
2015-05-12 02:04:54 +00:00
|
|
|
mPhysics->getActorsStandingOn(object, actors);
|
2018-12-29 22:27:16 +00:00
|
|
|
for (const Ptr &actor : actors)
|
2014-07-29 17:01:40 +00:00
|
|
|
{
|
|
|
|
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
2014-10-11 20:21:48 +00:00
|
|
|
if (stats.isDead())
|
|
|
|
continue;
|
2017-03-25 18:40:11 +00:00
|
|
|
|
|
|
|
mPhysics->markAsNonSolid (object);
|
|
|
|
|
2019-01-07 14:48:41 +00:00
|
|
|
if (actor == getPlayerPtr() && mGodMode)
|
2018-12-29 22:27:16 +00:00
|
|
|
continue;
|
2017-03-25 18:40:11 +00:00
|
|
|
|
2014-07-29 17:01:40 +00:00
|
|
|
MWMechanics::DynamicStat<float> health = stats.getHealth();
|
|
|
|
health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration());
|
|
|
|
stats.setHealth(health);
|
2014-10-11 20:21:48 +00:00
|
|
|
|
|
|
|
if (healthPerSecond > 0.0f)
|
|
|
|
{
|
2015-03-11 22:07:39 +00:00
|
|
|
if (actor == getPlayerPtr())
|
2014-10-11 20:21:48 +00:00
|
|
|
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
|
|
|
|
|
|
|
|
if (!MWBase::Environment::get().getSoundManager()->getSoundPlaying(actor, "Health Damage"))
|
|
|
|
MWBase::Environment::get().getSoundManager()->playSound3D(actor, "Health Damage", 1.0f, 1.0f);
|
|
|
|
}
|
2014-07-29 17:01:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-18 16:56:48 +00:00
|
|
|
void World::hurtCollidingActors(const ConstPtr &object, float healthPerSecond)
|
2014-07-29 17:01:40 +00:00
|
|
|
{
|
2014-10-11 22:13:24 +00:00
|
|
|
if (MWBase::Environment::get().getWindowManager()->isGuiMode())
|
|
|
|
return;
|
|
|
|
|
2018-12-29 22:27:16 +00:00
|
|
|
std::vector<Ptr> actors;
|
2015-05-12 02:04:54 +00:00
|
|
|
mPhysics->getActorsCollidingWith(object, actors);
|
2018-12-29 22:27:16 +00:00
|
|
|
for (const Ptr &actor : actors)
|
2014-07-29 17:01:40 +00:00
|
|
|
{
|
|
|
|
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
2014-10-11 20:21:48 +00:00
|
|
|
if (stats.isDead())
|
|
|
|
continue;
|
2017-03-25 18:40:11 +00:00
|
|
|
|
|
|
|
mPhysics->markAsNonSolid (object);
|
|
|
|
|
2019-01-07 14:48:41 +00:00
|
|
|
if (actor == getPlayerPtr() && mGodMode)
|
|
|
|
continue;
|
2017-03-25 18:40:11 +00:00
|
|
|
|
2014-07-29 17:01:40 +00:00
|
|
|
MWMechanics::DynamicStat<float> health = stats.getHealth();
|
|
|
|
health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration());
|
|
|
|
stats.setHealth(health);
|
2014-10-11 20:21:48 +00:00
|
|
|
|
|
|
|
if (healthPerSecond > 0.0f)
|
|
|
|
{
|
2015-03-11 22:07:39 +00:00
|
|
|
if (actor == getPlayerPtr())
|
2014-10-11 20:21:48 +00:00
|
|
|
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
|
|
|
|
|
|
|
|
if (!MWBase::Environment::get().getSoundManager()->getSoundPlaying(actor, "Health Damage"))
|
|
|
|
MWBase::Environment::get().getSoundManager()->playSound3D(actor, "Health Damage", 1.0f, 1.0f);
|
|
|
|
}
|
2014-07-29 17:01:40 +00:00
|
|
|
}
|
2013-05-01 09:15:43 +00:00
|
|
|
}
|
2013-05-01 09:42:24 +00:00
|
|
|
|
|
|
|
float World::getWindSpeed()
|
|
|
|
{
|
|
|
|
if (isCellExterior() || isCellQuasiExterior())
|
2015-04-25 13:19:17 +00:00
|
|
|
return mWeatherManager->getWindSpeed();
|
2013-05-01 09:42:24 +00:00
|
|
|
else
|
|
|
|
return 0.f;
|
|
|
|
}
|
2013-05-11 16:38:27 +00:00
|
|
|
|
2014-06-24 16:37:38 +00:00
|
|
|
bool World::isInStorm() const
|
|
|
|
{
|
|
|
|
if (isCellExterior() || isCellQuasiExterior())
|
2015-04-25 13:19:17 +00:00
|
|
|
return mWeatherManager->isInStorm();
|
2014-06-24 16:37:38 +00:00
|
|
|
else
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-05-12 01:02:15 +00:00
|
|
|
osg::Vec3f World::getStormDirection() const
|
2014-06-24 16:37:38 +00:00
|
|
|
{
|
|
|
|
if (isCellExterior() || isCellQuasiExterior())
|
2015-04-25 13:19:17 +00:00
|
|
|
return mWeatherManager->getStormDirection();
|
2014-06-24 16:37:38 +00:00
|
|
|
else
|
2015-05-12 01:02:15 +00:00
|
|
|
return osg::Vec3f(0,1,0);
|
2014-06-24 16:37:38 +00:00
|
|
|
}
|
|
|
|
|
2015-12-06 19:43:50 +00:00
|
|
|
struct GetContainersOwnedByVisitor
|
|
|
|
{
|
2015-12-18 17:00:18 +00:00
|
|
|
GetContainersOwnedByVisitor(const MWWorld::ConstPtr& owner, std::vector<MWWorld::Ptr>& out)
|
2015-12-06 19:43:50 +00:00
|
|
|
: mOwner(owner)
|
|
|
|
, mOut(out)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-12-18 17:00:18 +00:00
|
|
|
MWWorld::ConstPtr mOwner;
|
2015-12-06 19:43:50 +00:00
|
|
|
std::vector<MWWorld::Ptr>& mOut;
|
|
|
|
|
|
|
|
bool operator()(const MWWorld::Ptr& ptr)
|
|
|
|
{
|
|
|
|
if (ptr.getRefData().isDeleted())
|
|
|
|
return true;
|
|
|
|
|
2017-12-17 14:09:25 +00:00
|
|
|
// vanilla Morrowind does not allow to sell items from containers with zero capacity
|
|
|
|
if (ptr.getClass().getCapacity(ptr) <= 0.f)
|
2017-12-05 06:24:58 +00:00
|
|
|
return true;
|
|
|
|
|
2015-12-06 19:43:50 +00:00
|
|
|
if (Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), mOwner.getCellRef().getRefId()))
|
|
|
|
mOut.push_back(ptr);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-12-18 17:00:18 +00:00
|
|
|
void World::getContainersOwnedBy (const MWWorld::ConstPtr& owner, std::vector<MWWorld::Ptr>& out)
|
2013-05-11 16:38:27 +00:00
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
2013-05-11 16:38:27 +00:00
|
|
|
{
|
2015-12-06 19:43:50 +00:00
|
|
|
GetContainersOwnedByVisitor visitor (owner, out);
|
2018-12-29 22:27:16 +00:00
|
|
|
cellstore->forEachType<ESM::Container>(visitor);
|
2013-05-11 16:38:27 +00:00
|
|
|
}
|
|
|
|
}
|
2013-05-27 00:18:36 +00:00
|
|
|
|
2015-12-18 17:00:18 +00:00
|
|
|
void World::getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector<MWWorld::Ptr>& out)
|
2013-05-27 00:18:36 +00:00
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
2013-05-27 00:18:36 +00:00
|
|
|
{
|
2020-02-11 21:24:18 +00:00
|
|
|
cellstore->forEach([&] (const auto& ptr) {
|
|
|
|
if (ptr.getRefData().getBaseNode() && Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), npc.getCellRef().getRefId()))
|
|
|
|
out.push_back(ptr);
|
|
|
|
return true;
|
|
|
|
});
|
2013-05-27 00:18:36 +00:00
|
|
|
}
|
|
|
|
}
|
2014-04-29 13:27:49 +00:00
|
|
|
|
2015-12-18 17:02:57 +00:00
|
|
|
bool World::getLOS(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& targetActor)
|
2013-09-10 14:16:13 +00:00
|
|
|
{
|
2014-06-19 02:53:25 +00:00
|
|
|
if (!targetActor.getRefData().isEnabled() || !actor.getRefData().isEnabled())
|
2014-03-07 08:33:46 +00:00
|
|
|
return false; // cannot get LOS unless both NPC's are enabled
|
2015-11-27 18:40:31 +00:00
|
|
|
if (!targetActor.getRefData().getBaseNode() || !actor.getRefData().getBaseNode())
|
2014-06-19 02:53:25 +00:00
|
|
|
return false; // not in active cell
|
2013-10-28 17:01:40 +00:00
|
|
|
|
2015-05-31 23:57:15 +00:00
|
|
|
return mPhysics->getLineOfSight(actor, targetActor);
|
2013-09-10 14:16:13 +00:00
|
|
|
}
|
|
|
|
|
2016-04-16 21:39:13 +00:00
|
|
|
float World::getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater)
|
2014-04-20 16:35:07 +00:00
|
|
|
{
|
2015-06-03 17:41:19 +00:00
|
|
|
osg::Vec3f to (dir);
|
|
|
|
to.normalize();
|
|
|
|
to = from + (to * maxDist);
|
2014-04-20 16:35:07 +00:00
|
|
|
|
2016-04-16 21:39:13 +00:00
|
|
|
int collisionTypes = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door;
|
|
|
|
if (includeWater) {
|
|
|
|
collisionTypes |= MWPhysics::CollisionType_Water;
|
|
|
|
}
|
2020-08-03 20:44:16 +00:00
|
|
|
MWPhysics::RayCastingResult result = mPhysics->castRay(from, to, MWWorld::Ptr(), std::vector<MWWorld::Ptr>(), collisionTypes);
|
2014-04-20 16:35:07 +00:00
|
|
|
|
2015-05-31 23:57:15 +00:00
|
|
|
if (!result.mHit)
|
|
|
|
return maxDist;
|
|
|
|
else
|
2015-06-03 17:41:19 +00:00
|
|
|
return (result.mHitPos - from).length();
|
2014-04-20 16:35:07 +00:00
|
|
|
}
|
|
|
|
|
2013-06-27 21:11:20 +00:00
|
|
|
void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable)
|
|
|
|
{
|
2015-05-12 17:02:56 +00:00
|
|
|
MWPhysics::Actor *physicActor = mPhysics->getActor(actor);
|
2014-07-27 21:10:58 +00:00
|
|
|
if (physicActor)
|
|
|
|
physicActor->enableCollisionBody(enable);
|
2013-06-27 21:11:20 +00:00
|
|
|
}
|
2013-07-07 11:03:06 +00:00
|
|
|
|
|
|
|
bool World::findInteriorPosition(const std::string &name, ESM::Position &pos)
|
|
|
|
{
|
|
|
|
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
|
|
|
|
pos.pos[0] = pos.pos[1] = pos.pos[2] = 0;
|
|
|
|
|
|
|
|
MWWorld::CellStore *cellStore = getInterior(name);
|
|
|
|
|
2018-12-29 22:27:16 +00:00
|
|
|
if (!cellStore)
|
2013-07-07 11:03:06 +00:00
|
|
|
return false;
|
2015-12-06 20:58:25 +00:00
|
|
|
|
2018-09-13 09:21:38 +00:00
|
|
|
std::vector<const MWWorld::CellRef *> sortedDoors;
|
2018-12-29 22:27:16 +00:00
|
|
|
for (const MWWorld::LiveCellRef<ESM::Door>& door : cellStore->getReadOnlyDoors().mList)
|
2018-09-13 09:21:38 +00:00
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
if (!door.mRef.getTeleport())
|
2013-07-07 11:03:06 +00:00
|
|
|
continue;
|
|
|
|
|
2018-12-29 22:27:16 +00:00
|
|
|
sortedDoors.push_back(&door.mRef);
|
2018-09-13 09:21:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Sort teleporting doors alphabetically, first by ID, then by destination cell to make search consistent
|
|
|
|
std::sort(sortedDoors.begin(), sortedDoors.end(), [] (const MWWorld::CellRef *lhs, const MWWorld::CellRef *rhs)
|
|
|
|
{
|
|
|
|
if (lhs->getRefId() != rhs->getRefId())
|
|
|
|
return lhs->getRefId() < rhs->getRefId();
|
|
|
|
|
|
|
|
return lhs->getDestCell() < rhs->getDestCell();
|
|
|
|
});
|
|
|
|
|
2018-12-29 22:27:16 +00:00
|
|
|
for (const MWWorld::CellRef* door : sortedDoors)
|
2018-09-13 09:21:38 +00:00
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
MWWorld::CellStore *source = nullptr;
|
2013-07-07 11:03:06 +00:00
|
|
|
|
|
|
|
// door to exterior
|
2018-12-29 22:27:16 +00:00
|
|
|
if (door->getDestCell().empty())
|
2018-09-13 09:21:38 +00:00
|
|
|
{
|
2013-07-07 11:03:06 +00:00
|
|
|
int x, y;
|
2018-12-29 22:27:16 +00:00
|
|
|
ESM::Position doorDest = door->getDoorDest();
|
2014-06-01 20:21:52 +00:00
|
|
|
positionToIndex(doorDest.pos[0], doorDest.pos[1], x, y);
|
2013-07-07 11:03:06 +00:00
|
|
|
source = getExterior(x, y);
|
|
|
|
}
|
|
|
|
// door to interior
|
2018-09-13 09:21:38 +00:00
|
|
|
else
|
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
source = getInterior(door->getDestCell());
|
2013-07-07 11:03:06 +00:00
|
|
|
}
|
2018-12-29 22:27:16 +00:00
|
|
|
if (source)
|
2018-09-13 09:21:38 +00:00
|
|
|
{
|
2013-07-07 11:03:06 +00:00
|
|
|
// Find door leading to our current teleport door
|
2016-10-02 08:48:54 +00:00
|
|
|
// and use its destination to position inside cell.
|
2018-12-29 22:27:16 +00:00
|
|
|
for (const MWWorld::LiveCellRef<ESM::Door>& destDoor : source->getReadOnlyDoors().mList)
|
2018-09-13 09:21:38 +00:00
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
if (Misc::StringUtils::ciEqual(name, destDoor.mRef.getDestCell()))
|
2013-07-07 11:03:06 +00:00
|
|
|
{
|
|
|
|
/// \note Using _any_ door pointed to the interior,
|
|
|
|
/// not the one pointed to current door.
|
2018-12-29 22:27:16 +00:00
|
|
|
pos = destDoor.mRef.getDoorDest();
|
2019-06-30 13:03:24 +00:00
|
|
|
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
|
2013-07-07 11:03:06 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-02-04 18:06:29 +00:00
|
|
|
// Fall back to the first static location.
|
2018-12-29 22:27:16 +00:00
|
|
|
const MWWorld::CellRefList<ESM::Static>::List &statics = cellStore->getReadOnlyStatics().mList;
|
|
|
|
if (!statics.empty())
|
|
|
|
{
|
2015-02-04 18:06:29 +00:00
|
|
|
pos = statics.begin()->mRef.getPosition();
|
2019-06-30 13:03:24 +00:00
|
|
|
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
|
2015-02-04 18:06:29 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-07-07 11:03:06 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::findExteriorPosition(const std::string &name, ESM::Position &pos)
|
|
|
|
{
|
|
|
|
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
|
|
|
|
|
2017-08-06 13:03:48 +00:00
|
|
|
const ESM::Cell *ext = getExterior(name);
|
|
|
|
|
|
|
|
if (!ext && name.find(',') != std::string::npos) {
|
|
|
|
try {
|
|
|
|
int x = std::stoi(name.substr(0, name.find(',')));
|
|
|
|
int y = std::stoi(name.substr(name.find(',')+1));
|
|
|
|
ext = getExterior(x, y)->getCell();
|
|
|
|
}
|
2018-06-02 11:28:05 +00:00
|
|
|
catch (const std::invalid_argument&)
|
2017-08-06 13:03:48 +00:00
|
|
|
{
|
|
|
|
// This exception can be ignored, as this means that name probably refers to a interior cell instead of comma separated coordinates
|
|
|
|
}
|
2018-06-02 11:28:05 +00:00
|
|
|
catch (const std::out_of_range&)
|
2017-08-06 13:03:48 +00:00
|
|
|
{
|
|
|
|
throw std::runtime_error("Cell coordinates out of range.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ext) {
|
2013-07-07 11:03:06 +00:00
|
|
|
int x = ext->getGridX();
|
|
|
|
int y = ext->getGridY();
|
|
|
|
indexToPosition(x, y, pos.pos[0], pos.pos[1], true);
|
|
|
|
|
2014-01-01 17:05:28 +00:00
|
|
|
// Note: Z pos will be adjusted by adjustPosition later
|
|
|
|
pos.pos[2] = 0;
|
2013-07-07 11:03:06 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2017-08-06 13:03:48 +00:00
|
|
|
|
2013-07-07 11:03:06 +00:00
|
|
|
return false;
|
|
|
|
}
|
2013-07-27 07:14:55 +00:00
|
|
|
|
|
|
|
void World::enableTeleporting(bool enable)
|
|
|
|
{
|
|
|
|
mTeleportEnabled = enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::isTeleportingEnabled() const
|
|
|
|
{
|
|
|
|
return mTeleportEnabled;
|
|
|
|
}
|
|
|
|
|
2013-10-02 13:12:41 +00:00
|
|
|
void World::enableLevitation(bool enable)
|
|
|
|
{
|
|
|
|
mLevitationEnabled = enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::isLevitationEnabled() const
|
|
|
|
{
|
|
|
|
return mLevitationEnabled;
|
|
|
|
}
|
|
|
|
|
2015-01-28 18:14:57 +00:00
|
|
|
void World::reattachPlayerCamera()
|
|
|
|
{
|
2015-05-21 21:54:39 +00:00
|
|
|
mRendering->rebuildPtr(getPlayerPtr());
|
2015-01-28 18:14:57 +00:00
|
|
|
}
|
|
|
|
|
2013-09-12 12:30:00 +00:00
|
|
|
bool World::getGodModeState()
|
|
|
|
{
|
|
|
|
return mGodMode;
|
|
|
|
}
|
|
|
|
|
2013-08-25 01:19:12 +00:00
|
|
|
bool World::toggleGodMode()
|
|
|
|
{
|
2013-09-15 19:48:32 +00:00
|
|
|
mGodMode = !mGodMode;
|
2013-08-30 00:25:36 +00:00
|
|
|
|
|
|
|
return mGodMode;
|
2013-08-25 01:19:12 +00:00
|
|
|
}
|
|
|
|
|
2015-02-10 19:25:57 +00:00
|
|
|
bool World::toggleScripts()
|
|
|
|
{
|
|
|
|
mScriptsEnabled = !mScriptsEnabled;
|
|
|
|
return mScriptsEnabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::getScriptsEnabled() const
|
|
|
|
{
|
|
|
|
return mScriptsEnabled;
|
|
|
|
}
|
|
|
|
|
2013-09-29 07:11:57 +00:00
|
|
|
void World::loadContentFiles(const Files::Collections& fileCollections,
|
|
|
|
const std::vector<std::string>& content, ContentLoader& contentLoader)
|
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
int idx = 0;
|
|
|
|
for (const std::string &file : content)
|
2013-09-29 07:11:57 +00:00
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
boost::filesystem::path filename(file);
|
2013-09-29 07:11:57 +00:00
|
|
|
const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string());
|
2018-12-29 22:27:16 +00:00
|
|
|
if (col.doesExist(file))
|
2013-09-29 07:11:57 +00:00
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
contentLoader.load(col.getPath(file), idx);
|
2013-09-29 07:11:57 +00:00
|
|
|
}
|
2014-04-08 18:19:09 +00:00
|
|
|
else
|
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
std::string message = "Failed loading " + file + ": the content file does not exist";
|
|
|
|
throw std::runtime_error(message);
|
2014-04-08 18:19:09 +00:00
|
|
|
}
|
2018-12-29 22:27:16 +00:00
|
|
|
idx++;
|
2013-09-29 07:11:57 +00:00
|
|
|
}
|
|
|
|
}
|
2013-11-09 09:54:51 +00:00
|
|
|
|
2013-12-26 21:06:13 +00:00
|
|
|
bool World::startSpellCast(const Ptr &actor)
|
|
|
|
{
|
|
|
|
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
|
|
|
|
|
|
|
std::string message;
|
|
|
|
bool fail = false;
|
2014-01-08 17:39:44 +00:00
|
|
|
bool isPlayer = (actor == getPlayerPtr());
|
2013-12-26 21:06:13 +00:00
|
|
|
|
|
|
|
std::string selectedSpell = stats.getSpells().getSelectedSpell();
|
|
|
|
|
|
|
|
if (!selectedSpell.empty())
|
|
|
|
{
|
2019-01-07 14:48:41 +00:00
|
|
|
const ESM::Spell* spell = mStore.get<ESM::Spell>().find(selectedSpell);
|
2013-12-26 21:06:13 +00:00
|
|
|
|
|
|
|
// Check mana
|
2019-01-07 14:48:41 +00:00
|
|
|
bool godmode = (isPlayer && mGodMode);
|
2013-12-26 21:06:13 +00:00
|
|
|
MWMechanics::DynamicStat<float> magicka = stats.getMagicka();
|
2017-02-21 15:33:18 +00:00
|
|
|
if (magicka.getCurrent() < spell->mData.mCost && !godmode)
|
2013-12-26 21:06:13 +00:00
|
|
|
{
|
|
|
|
message = "#{sMagicInsufficientSP}";
|
|
|
|
fail = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this is a power, check if it was already used in the last 24h
|
2015-11-27 00:02:29 +00:00
|
|
|
if (!fail && spell->mData.mType == ESM::Spell::ST_Power && !stats.getSpells().canUsePower(spell))
|
2013-12-26 21:06:13 +00:00
|
|
|
{
|
2014-07-21 22:56:38 +00:00
|
|
|
message = "#{sPowerAlreadyUsed}";
|
|
|
|
fail = true;
|
2013-12-26 21:06:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Reduce mana
|
2017-02-21 15:33:18 +00:00
|
|
|
if (!fail && !godmode)
|
2014-01-14 02:34:11 +00:00
|
|
|
{
|
|
|
|
magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost);
|
|
|
|
stats.setMagicka(magicka);
|
|
|
|
}
|
2013-12-26 21:06:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isPlayer && fail)
|
|
|
|
MWBase::Environment::get().getWindowManager()->messageBox(message);
|
|
|
|
|
|
|
|
return !fail;
|
|
|
|
}
|
|
|
|
|
2018-06-28 12:58:51 +00:00
|
|
|
void World::castSpell(const Ptr &actor, bool manualSpell)
|
2013-11-09 06:51:46 +00:00
|
|
|
{
|
|
|
|
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
2013-11-17 22:15:57 +00:00
|
|
|
|
2017-02-02 07:20:34 +00:00
|
|
|
// 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;
|
2018-06-28 12:58:51 +00:00
|
|
|
if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell)
|
2018-07-26 20:24:26 +00:00
|
|
|
stats.getAiSequence().getCombatTargets(targetActors);
|
2017-02-02 07:20:34 +00:00
|
|
|
|
2019-01-07 14:48:41 +00:00
|
|
|
const float fCombatDistance = mStore.get<ESM::GameSetting>().find("fCombatDistance")->mValue.getFloat();
|
2015-08-16 00:06:04 +00:00
|
|
|
|
2018-05-15 14:57:36 +00:00
|
|
|
osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3();
|
2015-08-16 00:06:04 +00:00
|
|
|
|
2018-05-15 14:57:36 +00:00
|
|
|
// for player we can take faced object first
|
|
|
|
MWWorld::Ptr target;
|
|
|
|
if (actor == MWMechanics::getPlayer())
|
|
|
|
target = getFacedObject();
|
2015-08-16 00:06:04 +00:00
|
|
|
|
2018-05-15 14:57:36 +00:00
|
|
|
// if the faced object can not be activated, do not use it
|
2019-09-10 18:56:10 +00:00
|
|
|
if (!target.isEmpty() && !target.getClass().hasToolTip(target))
|
2018-10-09 06:21:12 +00:00
|
|
|
target = nullptr;
|
2013-11-11 22:43:28 +00:00
|
|
|
|
2018-05-15 14:57:36 +00:00
|
|
|
if (target.isEmpty())
|
2015-08-16 00:06:04 +00:00
|
|
|
{
|
2018-06-28 12:58:51 +00:00
|
|
|
// For scripted spells we should not use hit contact
|
|
|
|
if (manualSpell)
|
|
|
|
{
|
|
|
|
if (actor != MWMechanics::getPlayer())
|
|
|
|
{
|
2020-05-16 22:29:21 +00:00
|
|
|
for (const auto& package : stats.getAiSequence())
|
2018-06-28 12:58:51 +00:00
|
|
|
{
|
2020-05-16 19:52:16 +00:00
|
|
|
if (package->getTypeId() == MWMechanics::AiPackageTypeId::Cast)
|
2018-06-28 12:58:51 +00:00
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
target = package->getTarget();
|
2018-06-28 12:58:51 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// For actor targets, we want to use hit contact with bounding boxes.
|
|
|
|
// This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise.
|
|
|
|
// For object targets, we want the detailed shapes (rendering raycast).
|
|
|
|
// If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf.
|
|
|
|
std::pair<MWWorld::Ptr,osg::Vec3f> result1 = getHitContact(actor, fCombatDistance, targetActors);
|
2015-06-30 00:52:09 +00:00
|
|
|
|
2018-06-28 12:58:51 +00:00
|
|
|
// Get the target to use for "on touch" effects, using the facing direction from Head node
|
|
|
|
osg::Vec3f origin = getActorHeadTransform(actor).getTrans();
|
2016-09-21 14:34:32 +00:00
|
|
|
|
2018-06-28 12:58:51 +00:00
|
|
|
osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
|
|
|
|
* osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
|
2018-05-15 14:57:36 +00:00
|
|
|
|
2018-06-28 12:58:51 +00:00
|
|
|
osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
|
|
|
|
float distance = getMaxActivationDistance();
|
|
|
|
osg::Vec3f dest = origin + direction * distance;
|
2018-05-15 14:57:36 +00:00
|
|
|
|
2018-06-28 12:58:51 +00:00
|
|
|
MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true);
|
2018-05-15 14:57:36 +00:00
|
|
|
|
2018-06-28 12:58:51 +00:00
|
|
|
float dist1 = std::numeric_limits<float>::max();
|
|
|
|
float dist2 = std::numeric_limits<float>::max();
|
2018-05-15 14:57:36 +00:00
|
|
|
|
2018-06-28 12:58:51 +00:00
|
|
|
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
|
|
|
|
dist1 = (origin - result1.second).length();
|
|
|
|
if (result2.mHit)
|
|
|
|
dist2 = (origin - result2.mHitPointWorld).length();
|
2018-05-15 14:57:36 +00:00
|
|
|
|
2018-06-28 12:58:51 +00:00
|
|
|
if (!result1.first.isEmpty() && result1.first.getClass().isActor())
|
|
|
|
{
|
|
|
|
target = result1.first;
|
|
|
|
hitPosition = result1.second;
|
|
|
|
if (dist1 > getMaxActivationDistance())
|
2018-10-09 06:21:12 +00:00
|
|
|
target = nullptr;
|
2018-06-28 12:58:51 +00:00
|
|
|
}
|
|
|
|
else if (result2.mHit)
|
|
|
|
{
|
|
|
|
target = result2.mHitObject;
|
|
|
|
hitPosition = result2.mHitPointWorld;
|
2019-09-10 18:56:10 +00:00
|
|
|
if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().hasToolTip(target))
|
2018-10-09 06:21:12 +00:00
|
|
|
target = nullptr;
|
2018-06-28 12:58:51 +00:00
|
|
|
}
|
2018-05-15 14:57:36 +00:00
|
|
|
}
|
2016-09-21 14:34:32 +00:00
|
|
|
}
|
|
|
|
|
2013-11-09 06:51:46 +00:00
|
|
|
std::string selectedSpell = stats.getSpells().getSelectedSpell();
|
|
|
|
|
2018-06-28 12:58:51 +00:00
|
|
|
MWMechanics::CastSpell cast(actor, target, false, manualSpell);
|
2015-06-03 01:04:39 +00:00
|
|
|
cast.mHitPosition = hitPosition;
|
2013-11-09 06:51:46 +00:00
|
|
|
|
2013-11-17 22:15:57 +00:00
|
|
|
if (!selectedSpell.empty())
|
2013-11-09 06:51:46 +00:00
|
|
|
{
|
2019-01-07 14:48:41 +00:00
|
|
|
const ESM::Spell* spell = mStore.get<ESM::Spell>().find(selectedSpell);
|
2013-11-17 22:15:57 +00:00
|
|
|
cast.cast(spell);
|
2013-11-10 14:40:31 +00:00
|
|
|
}
|
2014-01-19 10:42:58 +00:00
|
|
|
else if (actor.getClass().hasInventoryStore(actor))
|
2013-11-10 14:40:31 +00:00
|
|
|
{
|
2014-01-19 10:42:58 +00:00
|
|
|
MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor);
|
|
|
|
if (inv.getSelectedEnchantItem() != inv.end())
|
|
|
|
cast.cast(*inv.getSelectedEnchantItem());
|
2013-11-09 06:51:46 +00:00
|
|
|
}
|
2013-11-13 13:02:15 +00:00
|
|
|
}
|
|
|
|
|
2018-08-24 11:03:54 +00:00
|
|
|
void World::launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile,
|
|
|
|
const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength)
|
2014-03-07 05:11:00 +00:00
|
|
|
{
|
2018-08-24 11:03:54 +00:00
|
|
|
// An initial position of projectile can be outside shooter's collision box, so any object between shooter and launch position will be ignored.
|
|
|
|
// To avoid this issue, we should check for impact immediately before launch the projectile.
|
|
|
|
// So we cast a 1-yard-length ray from shooter to launch position and check if there are collisions in this area.
|
|
|
|
// TODO: as a better solutuon we should handle projectiles during physics update, not during world update.
|
|
|
|
const osg::Vec3f sourcePos = worldPos + orient * osg::Vec3f(0,-1,0) * 64.f;
|
|
|
|
|
|
|
|
// Early out if the launch position is underwater
|
|
|
|
bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), worldPos);
|
|
|
|
if (underwater)
|
|
|
|
{
|
|
|
|
mRendering->emitWaterRipple(worldPos);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.getClass().isActor() && actor != MWMechanics::getPlayer())
|
|
|
|
actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors);
|
|
|
|
|
|
|
|
// Check for impact, if yes, handle hit, if not, launch projectile
|
2020-08-03 20:44:16 +00:00
|
|
|
MWPhysics::RayCastingResult result = mPhysics->castRay(sourcePos, worldPos, actor, targetActors, 0xff, MWPhysics::CollisionType_Projectile);
|
2018-08-24 11:03:54 +00:00
|
|
|
if (result.mHit)
|
|
|
|
MWMechanics::projectileHit(actor, result.mHitObject, bow, projectile, result.mHitPos, attackStrength);
|
|
|
|
else
|
|
|
|
mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength);
|
2014-03-07 05:11:00 +00:00
|
|
|
}
|
|
|
|
|
2017-09-20 16:56:32 +00:00
|
|
|
void World::launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection)
|
2013-11-13 13:02:15 +00:00
|
|
|
{
|
2017-09-20 16:56:32 +00:00
|
|
|
mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection);
|
2013-11-13 13:02:15 +00:00
|
|
|
}
|
2013-12-08 22:36:37 +00:00
|
|
|
|
2020-04-26 17:46:51 +00:00
|
|
|
class ApplyLoopingParticlesVisitor : public MWMechanics::EffectSourceVisitor
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
MWWorld::Ptr mActor;
|
|
|
|
|
|
|
|
public:
|
|
|
|
ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor)
|
|
|
|
: mActor(actor)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-10-16 18:18:54 +00:00
|
|
|
void visit (MWMechanics::EffectKey key, int /*effectIndex*/,
|
2020-04-26 17:46:51 +00:00
|
|
|
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
|
2020-10-16 18:18:54 +00:00
|
|
|
float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1) override
|
2020-04-26 17:46:51 +00:00
|
|
|
{
|
|
|
|
const ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
|
|
|
const auto magicEffect = store.get<ESM::MagicEffect>().find(key.mId);
|
|
|
|
if ((magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) == 0)
|
|
|
|
return;
|
|
|
|
const ESM::Static* castStatic;
|
|
|
|
if (!magicEffect->mHit.empty())
|
|
|
|
castStatic = store.get<ESM::Static>().find (magicEffect->mHit);
|
|
|
|
else
|
|
|
|
castStatic = store.get<ESM::Static>().find ("VFX_DefaultHit");
|
|
|
|
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor);
|
|
|
|
if (anim && !castStatic->mModel.empty())
|
|
|
|
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, /*loop*/true, "", magicEffect->mParticle);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-03-17 09:41:13 +00:00
|
|
|
void World::applyLoopingParticles(const MWWorld::Ptr& ptr)
|
|
|
|
{
|
|
|
|
const MWWorld::Class &cls = ptr.getClass();
|
|
|
|
if (cls.isActor())
|
|
|
|
{
|
2020-04-26 17:46:51 +00:00
|
|
|
ApplyLoopingParticlesVisitor visitor(ptr);
|
2018-03-17 09:41:13 +00:00
|
|
|
cls.getCreatureStats(ptr).getActiveSpells().visitEffectSources(visitor);
|
|
|
|
cls.getCreatureStats(ptr).getSpells().visitEffectSources(visitor);
|
|
|
|
if (cls.hasInventoryStore(ptr))
|
|
|
|
cls.getInventoryStore(ptr).visitEffectSources(visitor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-25 12:00:05 +00:00
|
|
|
const std::vector<std::string>& World::getContentFiles() const
|
|
|
|
{
|
|
|
|
return mContentFiles;
|
|
|
|
}
|
2014-01-05 23:02:03 +00:00
|
|
|
|
2013-12-08 22:36:37 +00:00
|
|
|
void World::breakInvisibility(const Ptr &actor)
|
|
|
|
{
|
2016-07-01 16:50:28 +00:00
|
|
|
actor.getClass().getCreatureStats(actor).getSpells().purgeEffect(ESM::MagicEffect::Invisibility);
|
2013-12-08 22:36:37 +00:00
|
|
|
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility);
|
2014-01-19 10:42:58 +00:00
|
|
|
if (actor.getClass().hasInventoryStore(actor))
|
2014-01-17 15:31:27 +00:00
|
|
|
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility);
|
2014-06-17 19:27:41 +00:00
|
|
|
|
|
|
|
// Normally updated once per frame, but here it is kinda important to do it right away.
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor);
|
2013-12-08 22:36:37 +00:00
|
|
|
}
|
2013-12-10 22:48:49 +00:00
|
|
|
|
2018-03-03 13:18:40 +00:00
|
|
|
bool World::useTorches() const
|
2013-12-10 22:48:49 +00:00
|
|
|
{
|
2018-03-03 13:18:40 +00:00
|
|
|
// If we are in exterior, check the weather manager.
|
|
|
|
// In interiors there are no precipitations and sun, so check the ambient
|
2018-03-19 19:08:15 +00:00
|
|
|
// Looks like pseudo-exteriors considered as interiors in this case
|
2014-04-28 16:33:42 +00:00
|
|
|
MWWorld::CellStore* cell = mPlayer->getPlayer().getCell();
|
|
|
|
if (cell->isExterior())
|
2018-03-19 19:08:15 +00:00
|
|
|
{
|
|
|
|
float hour = getTimeStamp().getHour();
|
|
|
|
return mWeatherManager->useTorches(hour);
|
|
|
|
}
|
2014-04-28 16:33:42 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
uint32_t ambient = cell->getCell()->mAmbi.mAmbient;
|
|
|
|
int ambientTotal = (ambient & 0xff)
|
|
|
|
+ ((ambient>>8) & 0xff)
|
|
|
|
+ ((ambient>>16) & 0xff);
|
|
|
|
return !(cell->getCell()->mData.mFlags & ESM::Cell::NoSleep) && ambientTotal <= 201;
|
|
|
|
}
|
2013-12-31 17:35:46 +00:00
|
|
|
}
|
|
|
|
|
2016-02-04 23:25:47 +00:00
|
|
|
bool World::findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result)
|
2013-12-31 17:35:46 +00:00
|
|
|
{
|
2014-01-09 00:49:58 +00:00
|
|
|
if (cell->isExterior())
|
|
|
|
return false;
|
2013-12-31 17:35:46 +00:00
|
|
|
|
2015-02-13 04:27:47 +00:00
|
|
|
// Search for a 'nearest' exterior, counting each cell between the starting
|
|
|
|
// cell and the exterior as a distance of 1. Will fail for isolated interiors.
|
|
|
|
std::set< std::string >checkedCells;
|
|
|
|
std::set< std::string >currentCells;
|
|
|
|
std::set< std::string >nextCells;
|
|
|
|
nextCells.insert( cell->getCell()->mName );
|
|
|
|
|
|
|
|
while ( !nextCells.empty() ) {
|
|
|
|
currentCells = nextCells;
|
|
|
|
nextCells.clear();
|
2019-01-07 17:35:36 +00:00
|
|
|
for (const std::string ¤tCell : currentCells)
|
|
|
|
{
|
|
|
|
MWWorld::CellStore *next = getInterior(currentCell);
|
2015-02-13 04:27:47 +00:00
|
|
|
if ( !next ) continue;
|
|
|
|
|
|
|
|
// Check if any door in the cell leads to an exterior directly
|
2018-12-29 22:27:16 +00:00
|
|
|
for (const MWWorld::LiveCellRef<ESM::Door>& ref : next->getReadOnlyDoors().mList)
|
2015-02-13 04:27:47 +00:00
|
|
|
{
|
|
|
|
if (!ref.mRef.getTeleport()) continue;
|
|
|
|
|
|
|
|
if (ref.mRef.getDestCell().empty())
|
|
|
|
{
|
|
|
|
ESM::Position pos = ref.mRef.getDoorDest();
|
2015-06-03 17:41:19 +00:00
|
|
|
result = pos.asVec3();
|
2015-02-13 04:27:47 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::string dest = ref.mRef.getDestCell();
|
|
|
|
if ( !checkedCells.count(dest) && !currentCells.count(dest) )
|
|
|
|
nextCells.insert(dest);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-07 17:35:36 +00:00
|
|
|
checkedCells.insert(currentCell);
|
2013-12-31 17:35:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// No luck :(
|
|
|
|
return false;
|
2013-12-10 22:48:49 +00:00
|
|
|
}
|
2014-01-01 01:22:11 +00:00
|
|
|
|
2015-12-17 22:59:18 +00:00
|
|
|
MWWorld::ConstPtr World::getClosestMarker( const MWWorld::Ptr &ptr, const std::string &id )
|
2014-01-01 01:22:11 +00:00
|
|
|
{
|
2015-02-25 02:51:57 +00:00
|
|
|
if ( ptr.getCell()->isExterior() ) {
|
|
|
|
return getClosestMarkerFromExteriorPosition(mPlayer->getLastKnownExteriorPosition(), id);
|
|
|
|
}
|
|
|
|
|
2015-02-14 21:43:09 +00:00
|
|
|
// Search for a 'nearest' marker, counting each cell between the starting
|
|
|
|
// cell and the exterior as a distance of 1. If an exterior is found, jump
|
|
|
|
// to the nearest exterior marker, without further interior searching.
|
|
|
|
std::set< std::string >checkedCells;
|
|
|
|
std::set< std::string >currentCells;
|
|
|
|
std::set< std::string >nextCells;
|
2015-12-17 22:59:18 +00:00
|
|
|
MWWorld::ConstPtr closestMarker;
|
2014-01-01 01:22:11 +00:00
|
|
|
|
2015-02-14 21:43:09 +00:00
|
|
|
nextCells.insert( ptr.getCell()->getCell()->mName );
|
|
|
|
while ( !nextCells.empty() ) {
|
|
|
|
currentCells = nextCells;
|
|
|
|
nextCells.clear();
|
2018-12-29 22:27:16 +00:00
|
|
|
for (const std::string &cell : currentCells) {
|
|
|
|
MWWorld::CellStore *next = getInterior(cell);
|
|
|
|
checkedCells.insert(cell);
|
2015-02-14 21:43:09 +00:00
|
|
|
if ( !next ) continue;
|
2014-01-09 00:49:58 +00:00
|
|
|
|
2015-12-17 21:37:18 +00:00
|
|
|
closestMarker = next->searchConst( id );
|
2015-02-21 21:55:22 +00:00
|
|
|
if ( !closestMarker.isEmpty() )
|
2015-02-14 21:43:09 +00:00
|
|
|
{
|
2015-02-21 21:55:22 +00:00
|
|
|
return closestMarker;
|
2015-02-14 21:43:09 +00:00
|
|
|
}
|
2014-01-01 01:22:11 +00:00
|
|
|
|
2015-02-14 21:43:09 +00:00
|
|
|
// Check if any door in the cell leads to an exterior directly
|
2018-12-29 22:27:16 +00:00
|
|
|
for (const MWWorld::LiveCellRef<ESM::Door>& ref : next->getReadOnlyDoors().mList)
|
2015-02-14 21:43:09 +00:00
|
|
|
{
|
|
|
|
if (!ref.mRef.getTeleport()) continue;
|
|
|
|
|
|
|
|
if (ref.mRef.getDestCell().empty())
|
|
|
|
{
|
2015-06-03 17:41:19 +00:00
|
|
|
osg::Vec3f worldPos = ref.mRef.getDoorDest().asVec3();
|
2015-02-25 02:51:57 +00:00
|
|
|
return getClosestMarkerFromExteriorPosition(worldPos, id);
|
2015-02-14 21:43:09 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::string dest = ref.mRef.getDestCell();
|
|
|
|
if ( !checkedCells.count(dest) && !currentCells.count(dest) )
|
|
|
|
nextCells.insert(dest);
|
|
|
|
}
|
|
|
|
}
|
2014-01-01 01:22:11 +00:00
|
|
|
}
|
2015-02-14 21:43:09 +00:00
|
|
|
}
|
|
|
|
return MWWorld::Ptr();
|
|
|
|
}
|
|
|
|
|
2015-12-17 22:59:18 +00:00
|
|
|
MWWorld::ConstPtr World::getClosestMarkerFromExteriorPosition( const osg::Vec3f& worldPos, const std::string &id ) {
|
|
|
|
MWWorld::ConstPtr closestMarker;
|
2015-08-21 07:34:28 +00:00
|
|
|
float closestDistance = std::numeric_limits<float>::max();
|
2015-02-25 02:51:57 +00:00
|
|
|
|
|
|
|
std::vector<MWWorld::Ptr> markers;
|
|
|
|
mCells.getExteriorPtrs(id, markers);
|
2018-12-29 22:27:16 +00:00
|
|
|
for (const Ptr& marker : markers)
|
2015-02-25 02:51:57 +00:00
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
osg::Vec3f markerPos = marker.getRefData().getPosition().asVec3();
|
2015-06-03 17:41:19 +00:00
|
|
|
float distance = (worldPos - markerPos).length2();
|
2015-02-25 02:51:57 +00:00
|
|
|
if (distance < closestDistance)
|
|
|
|
{
|
|
|
|
closestDistance = distance;
|
2018-12-29 22:27:16 +00:00
|
|
|
closestMarker = marker;
|
2015-02-25 02:51:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return closestMarker;
|
|
|
|
}
|
|
|
|
|
2019-01-25 16:04:35 +00:00
|
|
|
void World::rest(double hours)
|
2018-09-23 18:03:43 +00:00
|
|
|
{
|
2019-01-25 16:04:35 +00:00
|
|
|
mCells.rest(hours);
|
2018-09-23 18:03:43 +00:00
|
|
|
}
|
2015-02-25 02:51:57 +00:00
|
|
|
|
2018-12-15 06:23:50 +00:00
|
|
|
void World::rechargeItems(double duration, bool activeOnly)
|
|
|
|
{
|
|
|
|
MWWorld::Ptr player = getPlayerPtr();
|
|
|
|
player.getClass().getInventoryStore(player).rechargeItems(duration);
|
|
|
|
|
|
|
|
if (activeOnly)
|
|
|
|
{
|
|
|
|
for (auto &cell : mWorldScene->getActiveCells())
|
|
|
|
{
|
|
|
|
cell->recharge(duration);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
mCells.recharge(duration);
|
|
|
|
}
|
|
|
|
|
2015-02-14 21:43:09 +00:00
|
|
|
void World::teleportToClosestMarker (const MWWorld::Ptr& ptr,
|
|
|
|
const std::string& id)
|
|
|
|
{
|
2015-12-17 22:59:18 +00:00
|
|
|
MWWorld::ConstPtr closestMarker = getClosestMarker( ptr, id );
|
2014-01-01 01:22:11 +00:00
|
|
|
|
2015-02-14 21:43:09 +00:00
|
|
|
if ( closestMarker.isEmpty() )
|
|
|
|
{
|
2018-08-14 19:05:43 +00:00
|
|
|
Log(Debug::Warning) << "Failed to teleport: no closest marker found";
|
2015-02-14 21:43:09 +00:00
|
|
|
return;
|
2014-01-01 01:22:11 +00:00
|
|
|
}
|
|
|
|
|
2015-02-14 21:43:09 +00:00
|
|
|
std::string cellName;
|
|
|
|
if ( !closestMarker.mCell->isExterior() )
|
|
|
|
cellName = closestMarker.mCell->getCell()->mName;
|
|
|
|
|
2015-05-22 17:56:39 +00:00
|
|
|
MWWorld::ActionTeleport action(cellName, closestMarker.getRefData().getPosition(), false);
|
2014-01-01 01:22:11 +00:00
|
|
|
action.execute(ptr);
|
2013-12-10 22:48:49 +00:00
|
|
|
}
|
2015-08-27 03:59:21 +00:00
|
|
|
|
2014-10-09 06:41:51 +00:00
|
|
|
void World::updateWeather(float duration, bool paused)
|
2013-12-29 11:47:44 +00:00
|
|
|
{
|
2018-03-19 19:08:15 +00:00
|
|
|
bool isExterior = isCellExterior() || isCellQuasiExterior();
|
2013-12-31 19:40:23 +00:00
|
|
|
if (mPlayer->wasTeleported())
|
|
|
|
{
|
|
|
|
mPlayer->setTeleported(false);
|
2018-03-19 19:08:15 +00:00
|
|
|
|
|
|
|
const std::string playerRegion = Misc::StringUtils::lowerCase(getPlayerPtr().getCell()->getCell()->mRegion);
|
|
|
|
mWeatherManager->playerTeleported(playerRegion, isExterior);
|
2013-12-31 19:40:23 +00:00
|
|
|
}
|
2015-08-27 03:59:21 +00:00
|
|
|
|
2018-03-19 19:08:15 +00:00
|
|
|
const TimeStamp time = getTimeStamp();
|
|
|
|
mWeatherManager->update(duration, paused, time, isExterior);
|
2013-12-29 11:47:44 +00:00
|
|
|
}
|
2014-01-01 21:37:52 +00:00
|
|
|
|
2015-12-06 17:13:04 +00:00
|
|
|
struct AddDetectedReferenceVisitor
|
2014-01-01 21:37:52 +00:00
|
|
|
{
|
2017-04-20 11:36:14 +00:00
|
|
|
AddDetectedReferenceVisitor(std::vector<Ptr>& out, const Ptr& detector, World::DetectionType type, float squaredDist)
|
2015-05-01 00:24:27 +00:00
|
|
|
: mOut(out), mDetector(detector), mSquaredDist(squaredDist), mType(type)
|
2014-01-01 21:37:52 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<Ptr>& mOut;
|
|
|
|
Ptr mDetector;
|
|
|
|
float mSquaredDist;
|
|
|
|
World::DetectionType mType;
|
2015-12-06 20:58:25 +00:00
|
|
|
bool operator() (const MWWorld::Ptr& ptr)
|
2014-01-01 21:37:52 +00:00
|
|
|
{
|
2015-06-03 19:37:21 +00:00
|
|
|
if ((ptr.getRefData().getPosition().asVec3() - mDetector.getRefData().getPosition().asVec3()).length2() >= mSquaredDist)
|
2014-01-01 21:37:52 +00:00
|
|
|
return true;
|
|
|
|
|
2015-05-06 00:12:55 +00:00
|
|
|
if (!ptr.getRefData().isEnabled() || ptr.getRefData().isDeleted())
|
2014-01-01 21:37:52 +00:00
|
|
|
return true;
|
|
|
|
|
2014-06-17 14:27:33 +00:00
|
|
|
// Consider references inside containers as well (except if we are looking for a Creature, they cannot be in containers)
|
2019-05-04 10:09:52 +00:00
|
|
|
bool isContainer = ptr.getClass().getTypeName() == typeid(ESM::Container).name();
|
|
|
|
if (mType != World::Detect_Creature && (ptr.getClass().isActor() || isContainer))
|
2014-01-01 21:37:52 +00:00
|
|
|
{
|
2019-05-04 10:09:52 +00:00
|
|
|
// but ignore containers without resolved content
|
|
|
|
if (isContainer && ptr.getRefData().getCustomData() == nullptr)
|
|
|
|
return true;
|
|
|
|
|
2014-01-01 21:37:52 +00:00
|
|
|
MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
|
|
|
|
{
|
|
|
|
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
|
|
|
|
{
|
2014-06-17 14:27:33 +00:00
|
|
|
if (needToAdd(*it, mDetector))
|
2014-01-01 21:37:52 +00:00
|
|
|
{
|
|
|
|
mOut.push_back(ptr);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-17 14:27:33 +00:00
|
|
|
if (needToAdd(ptr, mDetector))
|
2014-01-01 21:37:52 +00:00
|
|
|
mOut.push_back(ptr);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-12-06 20:58:25 +00:00
|
|
|
bool needToAdd (const MWWorld::Ptr& ptr, const MWWorld::Ptr& detector)
|
2014-01-01 21:37:52 +00:00
|
|
|
{
|
2014-06-17 14:27:33 +00:00
|
|
|
if (mType == World::Detect_Creature)
|
|
|
|
{
|
|
|
|
// If in werewolf form, this detects only NPCs, otherwise only creatures
|
|
|
|
if (detector.getClass().isNpc() && detector.getClass().getNpcStats(detector).isWerewolf())
|
|
|
|
{
|
|
|
|
if (ptr.getClass().getTypeName() != typeid(ESM::NPC).name())
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (ptr.getClass().getTypeName() != typeid(ESM::Creature).name())
|
|
|
|
return false;
|
2015-02-06 16:34:08 +00:00
|
|
|
|
|
|
|
if (ptr.getClass().getCreatureStats(ptr).isDead())
|
|
|
|
return false;
|
2014-06-17 14:27:33 +00:00
|
|
|
}
|
2014-01-01 21:37:52 +00:00
|
|
|
if (mType == World::Detect_Key && !ptr.getClass().isKey(ptr))
|
|
|
|
return false;
|
|
|
|
if (mType == World::Detect_Enchantment && ptr.getClass().getEnchantment(ptr).empty())
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
void World::listDetectedReferences(const Ptr &ptr, std::vector<Ptr> &out, DetectionType type)
|
|
|
|
{
|
|
|
|
const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects();
|
|
|
|
float dist=0;
|
|
|
|
if (type == World::Detect_Creature)
|
2014-08-16 20:38:22 +00:00
|
|
|
dist = effects.get(ESM::MagicEffect::DetectAnimal).getMagnitude();
|
2014-01-01 21:37:52 +00:00
|
|
|
else if (type == World::Detect_Key)
|
2014-08-16 20:38:22 +00:00
|
|
|
dist = effects.get(ESM::MagicEffect::DetectKey).getMagnitude();
|
2014-01-01 21:37:52 +00:00
|
|
|
else if (type == World::Detect_Enchantment)
|
2014-08-16 20:38:22 +00:00
|
|
|
dist = effects.get(ESM::MagicEffect::DetectEnchantment).getMagnitude();
|
2014-01-01 21:37:52 +00:00
|
|
|
|
|
|
|
if (!dist)
|
|
|
|
return;
|
|
|
|
|
2014-01-01 22:34:18 +00:00
|
|
|
dist = feetToGameUnits(dist);
|
2014-01-01 21:37:52 +00:00
|
|
|
|
2015-12-06 17:13:04 +00:00
|
|
|
AddDetectedReferenceVisitor visitor (out, ptr, type, dist*dist);
|
2014-01-01 21:37:52 +00:00
|
|
|
|
2018-12-29 22:27:16 +00:00
|
|
|
for (CellStore* cellStore : mWorldScene->getActiveCells())
|
2014-01-01 21:37:52 +00:00
|
|
|
{
|
2015-12-06 17:13:04 +00:00
|
|
|
cellStore->forEach(visitor);
|
2014-01-01 21:37:52 +00:00
|
|
|
}
|
|
|
|
}
|
2014-01-01 22:34:18 +00:00
|
|
|
|
|
|
|
float World::feetToGameUnits(float feet)
|
|
|
|
{
|
2018-09-17 10:52:43 +00:00
|
|
|
// Original engine rounds size upward
|
|
|
|
static const int unitsPerFoot = ceil(Constants::UnitsPerFoot);
|
|
|
|
return feet * unitsPerFoot;
|
2014-01-01 22:34:18 +00:00
|
|
|
}
|
2014-01-08 17:39:44 +00:00
|
|
|
|
2016-09-22 15:16:51 +00:00
|
|
|
float World::getActivationDistancePlusTelekinesis()
|
|
|
|
{
|
|
|
|
float telekinesisRangeBonus =
|
|
|
|
mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).getMagicEffects()
|
|
|
|
.get(ESM::MagicEffect::Telekinesis).getMagnitude();
|
|
|
|
telekinesisRangeBonus = feetToGameUnits(telekinesisRangeBonus);
|
|
|
|
|
|
|
|
float activationDistance = getMaxActivationDistance() + telekinesisRangeBonus;
|
|
|
|
|
|
|
|
return activationDistance;
|
|
|
|
}
|
|
|
|
|
2014-01-08 17:39:44 +00:00
|
|
|
MWWorld::Ptr World::getPlayerPtr()
|
|
|
|
{
|
|
|
|
return mPlayer->getPlayer();
|
|
|
|
}
|
2014-01-08 23:40:25 +00:00
|
|
|
|
2019-03-03 11:58:03 +00:00
|
|
|
MWWorld::ConstPtr World::getPlayerConstPtr() const
|
|
|
|
{
|
|
|
|
return mPlayer->getConstPlayer();
|
|
|
|
}
|
|
|
|
|
2014-01-08 23:40:25 +00:00
|
|
|
void World::updateDialogueGlobals()
|
|
|
|
{
|
|
|
|
MWWorld::Ptr player = getPlayerPtr();
|
|
|
|
int bounty = player.getClass().getNpcStats(player).getBounty();
|
|
|
|
int playerGold = player.getClass().getContainerStore(player).count(ContainerStore::sGoldId);
|
|
|
|
|
2019-01-07 14:48:41 +00:00
|
|
|
static float fCrimeGoldDiscountMult = mStore.get<ESM::GameSetting>().find("fCrimeGoldDiscountMult")->mValue.getFloat();
|
|
|
|
static float fCrimeGoldTurnInMult = mStore.get<ESM::GameSetting>().find("fCrimeGoldTurnInMult")->mValue.getFloat();
|
2014-01-08 23:40:25 +00:00
|
|
|
|
2015-03-08 00:07:29 +00:00
|
|
|
int discount = static_cast<int>(bounty * fCrimeGoldDiscountMult);
|
|
|
|
int turnIn = static_cast<int>(bounty * fCrimeGoldTurnInMult);
|
2014-01-08 23:40:25 +00:00
|
|
|
|
2014-07-19 21:49:54 +00:00
|
|
|
if (bounty > 0)
|
|
|
|
{
|
|
|
|
discount = std::max(1, discount);
|
|
|
|
turnIn = std::max(1, turnIn);
|
|
|
|
}
|
|
|
|
|
2014-01-24 17:21:52 +00:00
|
|
|
mGlobalVariables["pchascrimegold"].setInteger((bounty <= playerGold) ? 1 : 0);
|
2014-01-08 23:40:25 +00:00
|
|
|
|
2014-01-24 17:21:52 +00:00
|
|
|
mGlobalVariables["pchasgolddiscount"].setInteger((discount <= playerGold) ? 1 : 0);
|
|
|
|
mGlobalVariables["crimegolddiscount"].setInteger(discount);
|
2014-01-08 23:40:25 +00:00
|
|
|
|
2014-01-24 17:21:52 +00:00
|
|
|
mGlobalVariables["crimegoldturnin"].setInteger(turnIn);
|
|
|
|
mGlobalVariables["pchasturnin"].setInteger((turnIn <= playerGold) ? 1 : 0);
|
2014-01-08 23:40:25 +00:00
|
|
|
}
|
2014-01-11 02:29:41 +00:00
|
|
|
|
|
|
|
void World::confiscateStolenItems(const Ptr &ptr)
|
|
|
|
{
|
2015-12-17 22:59:18 +00:00
|
|
|
MWWorld::ConstPtr prisonMarker = getClosestMarker( ptr, "prisonmarker" );
|
2015-02-25 03:10:01 +00:00
|
|
|
if ( prisonMarker.isEmpty() )
|
|
|
|
{
|
2018-08-14 19:05:43 +00:00
|
|
|
Log(Debug::Warning) << "Failed to confiscate items: no closest prison marker found.";
|
2015-02-25 03:10:01 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-12-17 22:59:18 +00:00
|
|
|
std::string prisonName = prisonMarker.getCellRef().getDestCell();
|
2015-02-14 22:09:17 +00:00
|
|
|
if ( prisonName.empty() )
|
2014-01-11 02:29:41 +00:00
|
|
|
{
|
2018-08-14 19:05:43 +00:00
|
|
|
Log(Debug::Warning) << "Failed to confiscate items: prison marker not linked to prison interior";
|
2015-02-14 22:09:17 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
MWWorld::CellStore *prison = getInterior( prisonName );
|
|
|
|
if ( !prison )
|
|
|
|
{
|
2018-08-14 19:05:43 +00:00
|
|
|
Log(Debug::Warning) << "Failed to confiscate items: failed to load cell " << prisonName;
|
2015-02-14 22:09:17 +00:00
|
|
|
return;
|
2014-01-11 02:29:41 +00:00
|
|
|
}
|
|
|
|
|
2015-02-21 21:55:22 +00:00
|
|
|
MWWorld::Ptr closestChest = prison->search( "stolen_goods" );
|
2014-04-23 17:20:43 +00:00
|
|
|
if (!closestChest.isEmpty()) //Found a close chest
|
2014-01-11 02:29:41 +00:00
|
|
|
{
|
2015-02-04 20:18:43 +00:00
|
|
|
MWBase::Environment::get().getMechanicsManager()->confiscateStolenItems(ptr, closestChest);
|
2014-01-11 02:29:41 +00:00
|
|
|
}
|
2015-02-04 20:18:43 +00:00
|
|
|
else
|
2018-08-14 19:05:43 +00:00
|
|
|
Log(Debug::Warning) << "Failed to confiscate items: no stolen_goods container found";
|
2014-01-11 02:29:41 +00:00
|
|
|
}
|
2014-01-11 05:47:58 +00:00
|
|
|
|
|
|
|
void World::goToJail()
|
|
|
|
{
|
|
|
|
if (!mGoToJail)
|
|
|
|
{
|
2014-09-08 22:00:55 +00:00
|
|
|
// Reset bounty and forget the crime now, but don't change cell yet (the player should be able to read the dialog text first)
|
2014-01-11 05:47:58 +00:00
|
|
|
mGoToJail = true;
|
2018-08-13 07:10:01 +00:00
|
|
|
mPlayerInJail = true;
|
2014-09-08 22:00:55 +00:00
|
|
|
|
|
|
|
MWWorld::Ptr player = getPlayerPtr();
|
|
|
|
|
|
|
|
int bounty = player.getClass().getNpcStats(player).getBounty();
|
|
|
|
player.getClass().getNpcStats(player).setBounty(0);
|
|
|
|
mPlayer->recordCrimeId();
|
|
|
|
confiscateStolenItems(player);
|
|
|
|
|
2019-01-07 14:48:41 +00:00
|
|
|
static int iDaysinPrisonMod = mStore.get<ESM::GameSetting>().find("iDaysinPrisonMod")->mValue.getInteger();
|
2014-09-08 22:00:55 +00:00
|
|
|
mDaysInPrison = std::max(1, bounty / iDaysinPrisonMod);
|
|
|
|
|
2014-01-11 05:47:58 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mGoToJail = false;
|
|
|
|
|
2014-07-11 02:25:56 +00:00
|
|
|
MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue);
|
|
|
|
|
2015-02-07 03:05:28 +00:00
|
|
|
MWBase::Environment::get().getWindowManager()->goToJail(mDaysInPrison);
|
2014-01-11 05:47:58 +00:00
|
|
|
}
|
|
|
|
}
|
2014-01-14 06:40:17 +00:00
|
|
|
|
2016-01-05 22:27:42 +00:00
|
|
|
bool World::isPlayerInJail() const
|
|
|
|
{
|
2018-08-13 07:10:01 +00:00
|
|
|
return mPlayerInJail;
|
2016-01-05 22:27:42 +00:00
|
|
|
}
|
|
|
|
|
2018-08-13 04:30:50 +00:00
|
|
|
void World::setPlayerTraveling(bool traveling)
|
|
|
|
{
|
|
|
|
mPlayerTraveling = traveling;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool World::isPlayerTraveling() const
|
|
|
|
{
|
|
|
|
return mPlayerTraveling;
|
2016-01-05 22:27:42 +00:00
|
|
|
}
|
|
|
|
|
2016-11-16 19:15:25 +00:00
|
|
|
float World::getTerrainHeightAt(const osg::Vec3f& worldPos) const
|
|
|
|
{
|
|
|
|
return mRendering->getTerrainHeightAt(worldPos);
|
|
|
|
}
|
|
|
|
|
2018-06-16 06:18:04 +00:00
|
|
|
osg::Vec3f World::getHalfExtents(const ConstPtr& object, bool rendering) const
|
2016-11-16 19:15:25 +00:00
|
|
|
{
|
2018-06-16 06:18:04 +00:00
|
|
|
if (!object.getClass().isActor())
|
|
|
|
return mRendering->getHalfExtents(object);
|
|
|
|
|
|
|
|
// Handle actors separately because of bodyparts
|
2016-11-16 19:15:25 +00:00
|
|
|
if (rendering)
|
2018-06-16 06:18:04 +00:00
|
|
|
return mPhysics->getRenderingHalfExtents(object);
|
2016-11-16 19:15:25 +00:00
|
|
|
else
|
2018-06-16 06:18:04 +00:00
|
|
|
return mPhysics->getHalfExtents(object);
|
2016-11-16 19:15:25 +00:00
|
|
|
}
|
|
|
|
|
2017-02-01 02:00:33 +00:00
|
|
|
std::string World::exportSceneGraph(const Ptr &ptr)
|
|
|
|
{
|
|
|
|
std::string file = mUserDataPath + "/openmw.osgt";
|
2020-08-12 07:16:10 +00:00
|
|
|
if (!ptr.isEmpty())
|
|
|
|
{
|
|
|
|
mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
|
|
|
|
mWorldScene->removeFromPagedRefs(ptr);
|
|
|
|
}
|
2017-02-01 02:00:33 +00:00
|
|
|
mRendering->exportSceneGraph(ptr, file, "Ascii");
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
2014-01-14 06:40:17 +00:00
|
|
|
void World::spawnRandomCreature(const std::string &creatureList)
|
|
|
|
{
|
2019-01-07 14:48:41 +00:00
|
|
|
const ESM::CreatureLevList* list = mStore.get<ESM::CreatureLevList>().find(creatureList);
|
2014-01-14 06:40:17 +00:00
|
|
|
|
2019-01-07 14:48:41 +00:00
|
|
|
static int iNumberCreatures = mStore.get<ESM::GameSetting>().find("iNumberCreatures")->mValue.getInteger();
|
2015-04-22 15:58:55 +00:00
|
|
|
int numCreatures = 1 + Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures]
|
2014-01-14 06:40:17 +00:00
|
|
|
|
|
|
|
for (int i=0; i<numCreatures; ++i)
|
|
|
|
{
|
|
|
|
std::string selectedCreature = MWMechanics::getLevelledItem(list, true);
|
|
|
|
if (selectedCreature.empty())
|
2019-01-07 14:48:41 +00:00
|
|
|
continue;
|
2014-01-14 06:40:17 +00:00
|
|
|
|
2019-01-07 14:48:41 +00:00
|
|
|
MWWorld::ManualRef ref(mStore, selectedCreature, 1);
|
2014-01-14 06:40:17 +00:00
|
|
|
|
2016-02-29 16:30:38 +00:00
|
|
|
safePlaceObject(ref.getPtr(), getPlayerPtr(), getPlayerPtr().getCell(), 0, 220.f);
|
2014-01-14 06:40:17 +00:00
|
|
|
}
|
|
|
|
}
|
2014-01-17 09:52:44 +00:00
|
|
|
|
2015-04-19 15:55:56 +00:00
|
|
|
void World::spawnBloodEffect(const Ptr &ptr, const osg::Vec3f &worldPosition)
|
2014-01-17 09:52:44 +00:00
|
|
|
{
|
2015-03-11 22:07:39 +00:00
|
|
|
if (ptr == getPlayerPtr() && Settings::Manager::getBool("hit fader", "GUI"))
|
2014-10-06 16:33:41 +00:00
|
|
|
return;
|
|
|
|
|
2019-01-22 06:08:48 +00:00
|
|
|
std::string texture = Fallback::Map::getString("Blood_Texture_" + std::to_string(ptr.getClass().getBloodTexture(ptr)));
|
2018-12-31 14:55:46 +00:00
|
|
|
if (texture.empty())
|
|
|
|
texture = Fallback::Map::getString("Blood_Texture_0");
|
|
|
|
|
2019-01-22 06:08:48 +00:00
|
|
|
std::string model = "meshes\\" + Fallback::Map::getString("Blood_Model_" + std::to_string(Misc::Rng::rollDice(3))); // [0, 2]
|
2018-12-31 14:55:46 +00:00
|
|
|
|
2016-09-14 14:18:29 +00:00
|
|
|
mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false);
|
2014-01-17 09:52:44 +00:00
|
|
|
}
|
2014-01-20 14:48:06 +00:00
|
|
|
|
2018-07-15 08:44:25 +00:00
|
|
|
void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos, float scale, bool isMagicVFX)
|
2014-06-15 19:19:37 +00:00
|
|
|
{
|
2018-07-15 08:44:25 +00:00
|
|
|
mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX);
|
2014-06-15 19:19:37 +00:00
|
|
|
}
|
|
|
|
|
2016-12-19 09:15:19 +00:00
|
|
|
void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType,
|
|
|
|
const std::string& id, const std::string& sourceName, const bool fromProjectile)
|
2014-01-20 14:48:06 +00:00
|
|
|
{
|
|
|
|
std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> > toApply;
|
2019-03-07 08:38:55 +00:00
|
|
|
for (const ESM::ENAMstruct& effectInfo : effects.mList)
|
2014-01-20 14:48:06 +00:00
|
|
|
{
|
2019-03-07 08:38:55 +00:00
|
|
|
const ESM::MagicEffect* effect = mStore.get<ESM::MagicEffect>().find(effectInfo.mEffectID);
|
2014-01-20 14:48:06 +00:00
|
|
|
|
2019-03-07 08:38:55 +00:00
|
|
|
if (effectInfo.mRange != rangeType || (effectInfo.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor()))
|
2016-09-07 17:05:24 +00:00
|
|
|
continue; // Not right range type, or not area effect and hit an actor
|
2014-01-20 14:48:06 +00:00
|
|
|
|
2019-03-07 08:38:55 +00:00
|
|
|
if (fromProjectile && effectInfo.mArea <= 0)
|
2017-03-25 15:53:24 +00:00
|
|
|
continue; // Don't play explosion for projectiles with 0-area effects
|
|
|
|
|
2019-09-10 18:56:10 +00:00
|
|
|
if (!fromProjectile && effectInfo.mRange == ESM::RT_Touch && !ignore.isEmpty() && !ignore.getClass().isActor() && !ignore.getClass().hasToolTip(ignore))
|
2016-12-19 09:15:19 +00:00
|
|
|
continue; // Don't play explosion for touch spells on non-activatable objects except when spell is from the projectile enchantment
|
2016-09-22 16:58:12 +00:00
|
|
|
|
2014-01-20 14:48:06 +00:00
|
|
|
// Spawn the explosion orb effect
|
|
|
|
const ESM::Static* areaStatic;
|
2015-09-04 21:27:33 +00:00
|
|
|
if (!effect->mArea.empty())
|
2019-01-07 14:48:41 +00:00
|
|
|
areaStatic = mStore.get<ESM::Static>().find (effect->mArea);
|
2014-01-20 14:48:06 +00:00
|
|
|
else
|
2019-01-07 14:48:41 +00:00
|
|
|
areaStatic = mStore.get<ESM::Static>().find ("VFX_DefaultArea");
|
2014-01-20 14:48:06 +00:00
|
|
|
|
2016-09-12 10:20:33 +00:00
|
|
|
std::string texture = effect->mParticle;
|
2016-09-08 09:43:26 +00:00
|
|
|
|
2019-03-07 08:38:55 +00:00
|
|
|
if (effectInfo.mArea <= 0)
|
2016-09-07 15:05:45 +00:00
|
|
|
{
|
2019-03-07 08:38:55 +00:00
|
|
|
if (effectInfo.mRange == ESM::RT_Target)
|
2016-09-21 15:00:11 +00:00
|
|
|
mRendering->spawnEffect("meshes\\" + areaStatic->mModel, texture, origin, 1.0f);
|
2016-09-07 15:05:45 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else
|
2019-03-07 08:38:55 +00:00
|
|
|
mRendering->spawnEffect("meshes\\" + areaStatic->mModel, texture, origin, static_cast<float>(effectInfo.mArea * 2));
|
2014-01-20 14:48:06 +00:00
|
|
|
|
|
|
|
// Play explosion sound (make sure to use NoTrack, since we will delete the projectile now)
|
|
|
|
static const std::string schools[] = {
|
|
|
|
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
|
|
|
};
|
2014-05-16 11:09:23 +00:00
|
|
|
{
|
|
|
|
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
|
|
|
if(!effect->mAreaSound.empty())
|
2015-11-25 07:24:42 +00:00
|
|
|
sndMgr->playSound3D(origin, effect->mAreaSound, 1.0f, 1.0f);
|
2014-05-16 11:09:23 +00:00
|
|
|
else
|
2015-11-25 07:24:42 +00:00
|
|
|
sndMgr->playSound3D(origin, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f);
|
2014-05-16 11:09:23 +00:00
|
|
|
}
|
2014-01-20 14:48:06 +00:00
|
|
|
// Get the actors in range of the effect
|
|
|
|
std::vector<MWWorld::Ptr> objects;
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(
|
2019-03-07 08:38:55 +00:00
|
|
|
origin, feetToGameUnits(static_cast<float>(effectInfo.mArea)), objects);
|
2018-12-29 22:27:16 +00:00
|
|
|
for (const Ptr& affected : objects)
|
2018-09-21 12:34:23 +00:00
|
|
|
{
|
|
|
|
// Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing range.
|
2018-12-29 22:27:16 +00:00
|
|
|
if (affected.getClass().isActor() && !isActorCollisionEnabled(affected))
|
2018-09-21 12:34:23 +00:00
|
|
|
continue;
|
|
|
|
|
2019-03-07 08:38:55 +00:00
|
|
|
toApply[affected].push_back(effectInfo);
|
2018-09-21 12:34:23 +00:00
|
|
|
}
|
2014-01-20 14:48:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now apply the appropriate effects to each actor in range
|
2019-03-07 08:38:55 +00:00
|
|
|
for (auto& applyPair : toApply)
|
2014-01-20 14:48:06 +00:00
|
|
|
{
|
|
|
|
MWWorld::Ptr source = caster;
|
|
|
|
// Vanilla-compatible behaviour of never applying the spell to the caster
|
|
|
|
// (could be changed by mods later)
|
2019-03-07 08:38:55 +00:00
|
|
|
if (applyPair.first == caster)
|
2014-01-20 14:48:06 +00:00
|
|
|
continue;
|
|
|
|
|
2019-03-07 08:38:55 +00:00
|
|
|
if (applyPair.first == ignore)
|
2016-01-11 23:31:34 +00:00
|
|
|
continue;
|
|
|
|
|
2014-01-20 14:48:06 +00:00
|
|
|
if (source.isEmpty())
|
2019-03-07 08:38:55 +00:00
|
|
|
source = applyPair.first;
|
2014-01-20 14:48:06 +00:00
|
|
|
|
2019-03-07 08:38:55 +00:00
|
|
|
MWMechanics::CastSpell cast(source, applyPair.first);
|
2014-01-20 14:48:06 +00:00
|
|
|
cast.mHitPosition = origin;
|
|
|
|
cast.mId = id;
|
|
|
|
cast.mSourceName = sourceName;
|
|
|
|
cast.mStack = false;
|
2016-10-02 08:48:54 +00:00
|
|
|
ESM::EffectList effectsToApply;
|
2019-03-07 08:38:55 +00:00
|
|
|
effectsToApply.mList = applyPair.second;
|
|
|
|
cast.inflict(applyPair.first, caster, effectsToApply, rangeType, false, true);
|
2014-01-20 14:48:06 +00:00
|
|
|
}
|
|
|
|
}
|
2014-06-12 22:01:29 +00:00
|
|
|
|
|
|
|
void World::activate(const Ptr &object, const Ptr &actor)
|
|
|
|
{
|
|
|
|
breakInvisibility(actor);
|
|
|
|
|
2016-05-25 19:37:56 +00:00
|
|
|
if (object.getRefData().activate())
|
2014-06-12 22:01:29 +00:00
|
|
|
{
|
2017-05-05 19:21:11 +00:00
|
|
|
std::shared_ptr<MWWorld::Action> action = (object.getClass().activate(object, actor));
|
2016-05-25 19:37:56 +00:00
|
|
|
action->execute (actor);
|
2014-06-12 22:01:29 +00:00
|
|
|
}
|
|
|
|
}
|
2014-08-30 15:55:35 +00:00
|
|
|
|
2015-12-06 17:13:04 +00:00
|
|
|
struct ResetActorsVisitor
|
2014-08-30 15:55:35 +00:00
|
|
|
{
|
|
|
|
bool operator() (Ptr ptr)
|
|
|
|
{
|
2015-01-10 23:20:22 +00:00
|
|
|
if (ptr.getClass().isActor() && ptr.getCellRef().hasContentFile())
|
2014-08-30 15:55:35 +00:00
|
|
|
{
|
2018-11-20 17:53:27 +00:00
|
|
|
if (ptr.getCell()->movedHere(ptr))
|
|
|
|
return true;
|
|
|
|
|
2014-08-30 15:55:35 +00:00
|
|
|
const ESM::Position& origPos = ptr.getCellRef().getPosition();
|
|
|
|
MWBase::Environment::get().getWorld()->moveObject(ptr, origPos.pos[0], origPos.pos[1], origPos.pos[2]);
|
|
|
|
MWBase::Environment::get().getWorld()->rotateObject(ptr, origPos.rot[0], origPos.rot[1], origPos.rot[2]);
|
2018-11-20 17:53:27 +00:00
|
|
|
ptr.getClass().adjustPosition(ptr, true);
|
2014-08-30 15:55:35 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
void World::resetActors()
|
|
|
|
{
|
2018-12-29 22:27:16 +00:00
|
|
|
for (CellStore* cellstore : mWorldScene->getActiveCells())
|
2014-08-30 15:55:35 +00:00
|
|
|
{
|
2015-12-06 17:13:04 +00:00
|
|
|
ResetActorsVisitor visitor;
|
|
|
|
cellstore->forEach(visitor);
|
2014-08-30 15:55:35 +00:00
|
|
|
}
|
|
|
|
}
|
2014-10-05 20:24:11 +00:00
|
|
|
|
2016-11-20 17:46:58 +00:00
|
|
|
bool World::isWalkingOnWater(const ConstPtr &actor) const
|
2014-10-05 20:24:11 +00:00
|
|
|
{
|
2015-12-18 16:36:14 +00:00
|
|
|
const MWPhysics::Actor* physicActor = mPhysics->getActor(actor);
|
2014-10-05 20:24:11 +00:00
|
|
|
if (physicActor && physicActor->isWalkingOnWater())
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
2015-09-16 23:08:16 +00:00
|
|
|
|
2015-12-18 16:36:14 +00:00
|
|
|
osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target)
|
2015-09-16 23:08:16 +00:00
|
|
|
{
|
2016-06-10 21:30:41 +00:00
|
|
|
osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3();
|
2018-05-09 07:35:51 +00:00
|
|
|
weaponPos.z() += mPhysics->getHalfExtents(actor).z();
|
2016-02-13 01:56:41 +00:00
|
|
|
osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target);
|
2015-09-16 23:08:16 +00:00
|
|
|
return (targetPos - weaponPos);
|
|
|
|
}
|
2015-11-18 18:00:43 +00:00
|
|
|
|
2015-12-18 16:36:14 +00:00
|
|
|
float World::getHitDistance(const ConstPtr &actor, const ConstPtr &target)
|
2015-11-18 18:00:43 +00:00
|
|
|
{
|
2016-06-10 21:30:41 +00:00
|
|
|
osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3();
|
|
|
|
osg::Vec3f halfExtents = mPhysics->getHalfExtents(actor);
|
2018-05-09 07:35:51 +00:00
|
|
|
weaponPos.z() += halfExtents.z();
|
2016-06-10 21:30:41 +00:00
|
|
|
|
|
|
|
return mPhysics->getHitDistance(weaponPos, target) - halfExtents.y();
|
2015-11-18 18:00:43 +00:00
|
|
|
}
|
|
|
|
|
2017-02-14 23:55:35 +00:00
|
|
|
void preload(MWWorld::Scene* scene, const ESMStore& store, const std::string& obj)
|
|
|
|
{
|
|
|
|
if (obj.empty())
|
|
|
|
return;
|
2017-02-20 18:58:00 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
MWWorld::ManualRef ref(store, obj);
|
|
|
|
std::string model = ref.getPtr().getClass().getModel(ref.getPtr());
|
|
|
|
if (!model.empty())
|
|
|
|
scene->preload(model, ref.getPtr().getClass().useAnim());
|
|
|
|
}
|
|
|
|
catch(std::exception& e)
|
|
|
|
{
|
|
|
|
}
|
2017-02-14 23:55:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void World::preloadEffects(const ESM::EffectList *effectList)
|
|
|
|
{
|
2019-03-07 08:38:55 +00:00
|
|
|
for (const ESM::ENAMstruct& effectInfo : effectList->mList)
|
2017-02-14 23:55:35 +00:00
|
|
|
{
|
2019-03-07 08:38:55 +00:00
|
|
|
const ESM::MagicEffect *effect = mStore.get<ESM::MagicEffect>().find(effectInfo.mEffectID);
|
2017-02-14 23:55:35 +00:00
|
|
|
|
2019-03-07 08:38:55 +00:00
|
|
|
if (MWMechanics::isSummoningEffect(effectInfo.mEffectID))
|
2017-02-20 18:58:00 +00:00
|
|
|
{
|
2018-01-30 22:05:16 +00:00
|
|
|
preload(mWorldScene.get(), mStore, "VFX_Summon_Start");
|
2019-03-07 08:38:55 +00:00
|
|
|
preload(mWorldScene.get(), mStore, MWMechanics::getSummonedCreature(effectInfo.mEffectID));
|
2017-02-20 18:58:00 +00:00
|
|
|
}
|
|
|
|
|
2018-01-30 22:05:16 +00:00
|
|
|
preload(mWorldScene.get(), mStore, effect->mCasting);
|
|
|
|
preload(mWorldScene.get(), mStore, effect->mHit);
|
2017-02-14 23:55:35 +00:00
|
|
|
|
2019-03-07 08:38:55 +00:00
|
|
|
if (effectInfo.mArea > 0)
|
2018-01-30 22:05:16 +00:00
|
|
|
preload(mWorldScene.get(), mStore, effect->mArea);
|
2019-03-07 08:38:55 +00:00
|
|
|
if (effectInfo.mRange == ESM::RT_Target)
|
2018-01-30 22:05:16 +00:00
|
|
|
preload(mWorldScene.get(), mStore, effect->mBolt);
|
2017-02-14 23:55:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-13 22:49:08 +00:00
|
|
|
DetourNavigator::Navigator* World::getNavigator() const
|
|
|
|
{
|
|
|
|
return mNavigator.get();
|
|
|
|
}
|
|
|
|
|
2018-07-21 10:37:02 +00:00
|
|
|
void World::updateActorPath(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
|
|
|
|
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const
|
|
|
|
{
|
|
|
|
mRendering->updateActorPath(actor, path, halfExtents, start, end);
|
|
|
|
}
|
|
|
|
|
2018-11-03 06:42:14 +00:00
|
|
|
void World::removeActorPath(const MWWorld::ConstPtr& actor) const
|
|
|
|
{
|
|
|
|
mRendering->removeActorPath(actor);
|
|
|
|
}
|
|
|
|
|
2018-08-30 22:39:44 +00:00
|
|
|
void World::setNavMeshNumberToRender(const std::size_t value)
|
|
|
|
{
|
|
|
|
mRendering->setNavMeshNumber(value);
|
|
|
|
}
|
|
|
|
|
2019-03-03 11:58:03 +00:00
|
|
|
osg::Vec3f World::getPathfindingHalfExtents(const MWWorld::ConstPtr& actor) const
|
|
|
|
{
|
|
|
|
if (actor.isInCell() && actor.getCell()->isExterior())
|
2019-03-03 11:45:36 +00:00
|
|
|
return mDefaultHalfExtents; // Using default half extents for better performance
|
2019-03-03 11:58:03 +00:00
|
|
|
else
|
|
|
|
return getHalfExtents(actor);
|
|
|
|
}
|
|
|
|
|
2019-08-15 17:19:04 +00:00
|
|
|
bool World::hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const
|
|
|
|
{
|
|
|
|
const auto object = mPhysics->getObject(door);
|
|
|
|
|
|
|
|
if (!object)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
btVector3 aabbMin;
|
|
|
|
btVector3 aabbMax;
|
|
|
|
object->getShapeInstance()->getCollisionShape()->getAabb(btTransform::getIdentity(), aabbMin, aabbMax);
|
|
|
|
|
2020-10-14 13:55:15 +00:00
|
|
|
const auto toLocal = object->getTransform().inverse();
|
2019-08-15 17:19:04 +00:00
|
|
|
const auto localFrom = toLocal(Misc::Convert::toBullet(position));
|
|
|
|
const auto localTo = toLocal(Misc::Convert::toBullet(destination));
|
|
|
|
|
|
|
|
btScalar hitDistance = 1;
|
|
|
|
btVector3 hitNormal;
|
|
|
|
return btRayAabb(localFrom, localTo, aabbMin, aabbMax, hitDistance, hitNormal);
|
|
|
|
}
|
2020-02-09 17:24:08 +00:00
|
|
|
|
|
|
|
bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const
|
|
|
|
{
|
|
|
|
return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore);
|
|
|
|
}
|
2020-05-21 22:11:23 +00:00
|
|
|
|
|
|
|
void World::reportStats(unsigned int frameNumber, osg::Stats& stats) const
|
|
|
|
{
|
|
|
|
mNavigator->reportStats(frameNumber, stats);
|
|
|
|
mPhysics->reportStats(frameNumber, stats);
|
|
|
|
}
|
2020-06-03 07:32:28 +00:00
|
|
|
|
|
|
|
void World::updateSkyDate()
|
|
|
|
{
|
|
|
|
ESM::EpochTimeStamp currentDate = mCurrentDate->getEpochTimeStamp();
|
|
|
|
mRendering->skySetDate(currentDate.mDay, currentDate.mMonth);
|
|
|
|
}
|
2020-10-20 09:22:43 +00:00
|
|
|
|
|
|
|
std::vector<MWWorld::Ptr> World::getAll(const std::string& id)
|
|
|
|
{
|
|
|
|
return mCells.getAll(id);
|
|
|
|
}
|
2010-07-02 07:38:22 +00:00
|
|
|
}
|