forked from teamnwah/openmw-tes3coop
d9dd7073cf
Previously, client mods adding packet-sending scripts to the spawn area made clients send the associated packets as soon as they inputted their character name when joining a server using those mods. This made the clients either get disconnected for not replying to a handshake first, or it made them get kicked for sending object packets that are disallowed for players who are not logged in. To fix this, LocalPlayer's hasFinishedCharGen() has been replaced with isLoggedIn(), because the former was already returning true when players inputted their names.
971 lines
35 KiB
C++
971 lines
35 KiB
C++
#include "scene.hpp"
|
|
|
|
#include <limits>
|
|
#include <iostream>
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Include additional headers for multiplayer purposes
|
|
*/
|
|
#include "../mwmp/Main.hpp"
|
|
#include "../mwmp/LocalPlayer.hpp"
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
|
|
#include <components/loadinglistener/loadinglistener.hpp>
|
|
#include <components/misc/resourcehelpers.hpp>
|
|
#include <components/settings/settings.hpp>
|
|
#include <components/resource/resourcesystem.hpp>
|
|
#include <components/resource/scenemanager.hpp>
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
#include "../mwbase/soundmanager.hpp"
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
|
|
#include "../mwrender/renderingmanager.hpp"
|
|
#include "../mwrender/landmanager.hpp"
|
|
|
|
#include "../mwphysics/physicssystem.hpp"
|
|
|
|
#include "player.hpp"
|
|
#include "localscripts.hpp"
|
|
#include "esmstore.hpp"
|
|
#include "class.hpp"
|
|
#include "cellvisitors.hpp"
|
|
#include "cellstore.hpp"
|
|
#include "cellpreloader.hpp"
|
|
|
|
namespace
|
|
{
|
|
|
|
void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, bool inverseRotationOrder)
|
|
{
|
|
if (!ptr.getRefData().getBaseNode())
|
|
return;
|
|
|
|
osg::Quat worldRotQuat(ptr.getRefData().getPosition().rot[2], osg::Vec3(0,0,-1));
|
|
if (!ptr.getClass().isActor())
|
|
{
|
|
float xr = ptr.getRefData().getPosition().rot[0];
|
|
float yr = ptr.getRefData().getPosition().rot[1];
|
|
if (!inverseRotationOrder)
|
|
worldRotQuat = worldRotQuat * osg::Quat(yr, osg::Vec3(0,-1,0)) *
|
|
osg::Quat(xr, osg::Vec3(-1,0,0));
|
|
else
|
|
worldRotQuat = osg::Quat(xr, osg::Vec3(-1,0,0)) * osg::Quat(yr, osg::Vec3(0,-1,0)) * worldRotQuat;
|
|
}
|
|
|
|
rendering.rotateObject(ptr, worldRotQuat);
|
|
}
|
|
|
|
void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics,
|
|
MWRender::RenderingManager& rendering)
|
|
{
|
|
if (ptr.getRefData().getBaseNode() || physics.getActor(ptr))
|
|
{
|
|
std::cerr << "Warning: Tried to add " << ptr.getCellRef().getRefId() << " to the scene twice" << std::endl;
|
|
return;
|
|
}
|
|
|
|
bool useAnim = ptr.getClass().useAnim();
|
|
std::string model = ptr.getClass().getModel(ptr);
|
|
if (useAnim)
|
|
model = Misc::ResourceHelpers::correctActorModelPath(model, rendering.getResourceSystem()->getVFS());
|
|
|
|
std::string id = ptr.getCellRef().getRefId();
|
|
if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker")
|
|
model = ""; // marker objects that have a hardcoded function in the game logic, should be hidden from the player
|
|
|
|
ptr.getClass().insertObjectRendering(ptr, model, rendering);
|
|
setNodeRotation(ptr, rendering, false);
|
|
|
|
ptr.getClass().insertObject (ptr, model, physics);
|
|
|
|
if (useAnim)
|
|
MWBase::Environment::get().getMechanicsManager()->add(ptr);
|
|
|
|
if (ptr.getClass().isActor())
|
|
rendering.addWaterRippleEmitter(ptr);
|
|
|
|
// Restore effect particles
|
|
MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr);
|
|
}
|
|
|
|
void updateObjectRotation (const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics,
|
|
MWRender::RenderingManager& rendering, bool inverseRotationOrder)
|
|
{
|
|
setNodeRotation(ptr, rendering, inverseRotationOrder);
|
|
physics.updateRotation(ptr);
|
|
}
|
|
|
|
void updateObjectScale(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics,
|
|
MWRender::RenderingManager& rendering)
|
|
{
|
|
if (ptr.getRefData().getBaseNode() != NULL)
|
|
{
|
|
float scale = ptr.getCellRef().getScale();
|
|
osg::Vec3f scaleVec (scale, scale, scale);
|
|
ptr.getClass().adjustScale(ptr, scaleVec, true);
|
|
rendering.scaleObject(ptr, scaleVec);
|
|
|
|
physics.updateScale(ptr);
|
|
}
|
|
}
|
|
|
|
struct InsertVisitor
|
|
{
|
|
MWWorld::CellStore& mCell;
|
|
bool mRescale;
|
|
Loading::Listener& mLoadingListener;
|
|
MWPhysics::PhysicsSystem& mPhysics;
|
|
MWRender::RenderingManager& mRendering;
|
|
|
|
std::vector<MWWorld::Ptr> mToInsert;
|
|
|
|
InsertVisitor (MWWorld::CellStore& cell, bool rescale, Loading::Listener& loadingListener,
|
|
MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering);
|
|
|
|
bool operator() (const MWWorld::Ptr& ptr);
|
|
void insert();
|
|
};
|
|
|
|
InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, bool rescale,
|
|
Loading::Listener& loadingListener, MWPhysics::PhysicsSystem& physics,
|
|
MWRender::RenderingManager& rendering)
|
|
: mCell (cell), mRescale (rescale), mLoadingListener (loadingListener),
|
|
mPhysics (physics),
|
|
mRendering (rendering)
|
|
{}
|
|
|
|
bool InsertVisitor::operator() (const MWWorld::Ptr& ptr)
|
|
{
|
|
// do not insert directly as we can't modify the cell from within the visitation
|
|
// CreatureLevList::insertObjectRendering may spawn a new creature
|
|
mToInsert.push_back(ptr);
|
|
return true;
|
|
}
|
|
|
|
void InsertVisitor::insert()
|
|
{
|
|
for (std::vector<MWWorld::Ptr>::iterator it = mToInsert.begin(); it != mToInsert.end(); ++it)
|
|
{
|
|
MWWorld::Ptr ptr = *it;
|
|
if (mRescale)
|
|
{
|
|
if (ptr.getCellRef().getScale()<0.5)
|
|
ptr.getCellRef().setScale(0.5);
|
|
else if (ptr.getCellRef().getScale()>2)
|
|
ptr.getCellRef().setScale(2);
|
|
}
|
|
|
|
if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled())
|
|
{
|
|
try
|
|
{
|
|
addObject(ptr, mPhysics, mRendering);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
std::string error ("failed to render '" + ptr.getCellRef().getRefId() + "': ");
|
|
std::cerr << error + e.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
mLoadingListener.increaseProgress (1);
|
|
}
|
|
}
|
|
|
|
struct AdjustPositionVisitor
|
|
{
|
|
bool operator() (const MWWorld::Ptr& ptr)
|
|
{
|
|
if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled())
|
|
ptr.getClass().adjustPosition (ptr, false);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
|
|
namespace MWWorld
|
|
{
|
|
|
|
void Scene::updateObjectRotation (const Ptr& ptr, bool inverseRotationOrder)
|
|
{
|
|
::updateObjectRotation(ptr, *mPhysics, mRendering, inverseRotationOrder);
|
|
}
|
|
|
|
void Scene::updateObjectScale(const Ptr &ptr)
|
|
{
|
|
::updateObjectScale(ptr, *mPhysics, mRendering);
|
|
}
|
|
|
|
void Scene::getGridCenter(int &cellX, int &cellY)
|
|
{
|
|
int maxX = std::numeric_limits<int>::min();
|
|
int maxY = std::numeric_limits<int>::min();
|
|
int minX = std::numeric_limits<int>::max();
|
|
int minY = std::numeric_limits<int>::max();
|
|
CellStoreCollection::iterator iter = mActiveCells.begin();
|
|
while (iter!=mActiveCells.end())
|
|
{
|
|
assert ((*iter)->getCell()->isExterior());
|
|
int x = (*iter)->getCell()->getGridX();
|
|
int y = (*iter)->getCell()->getGridY();
|
|
maxX = std::max(x, maxX);
|
|
maxY = std::max(y, maxY);
|
|
minX = std::min(x, minX);
|
|
minY = std::min(y, minY);
|
|
++iter;
|
|
}
|
|
cellX = (minX + maxX) / 2;
|
|
cellY = (minY + maxY) / 2;
|
|
}
|
|
|
|
void Scene::update (float duration, bool paused)
|
|
{
|
|
mPreloadTimer += duration;
|
|
if (mPreloadTimer > 0.1f)
|
|
{
|
|
preloadCells(0.1f);
|
|
mPreloadTimer = 0.f;
|
|
}
|
|
|
|
mRendering.update (duration, paused);
|
|
|
|
mPreloader->updateCache(mRendering.getReferenceTime());
|
|
}
|
|
|
|
void Scene::unloadCell (CellStoreCollection::iterator iter)
|
|
{
|
|
std::cout << "Unloading cell\n";
|
|
ListAndResetObjectsVisitor visitor;
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Set a const pointer to the iterator's ESM::Cell here, because
|
|
(*iter)->getCell() can become invalid later down
|
|
*/
|
|
const ESM::Cell* cell = (*iter)->getCell();
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
|
|
(*iter)->forEach<ListAndResetObjectsVisitor>(visitor);
|
|
for (std::vector<MWWorld::Ptr>::const_iterator iter2 (visitor.mObjects.begin());
|
|
iter2!=visitor.mObjects.end(); ++iter2)
|
|
{
|
|
mPhysics->remove(*iter2);
|
|
}
|
|
|
|
if ((*iter)->getCell()->isExterior())
|
|
{
|
|
const ESM::Land* land =
|
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Land>().search(
|
|
(*iter)->getCell()->getGridX(),
|
|
(*iter)->getCell()->getGridY()
|
|
);
|
|
if (land && land->mDataTypes&ESM::Land::DATA_VHGT)
|
|
mPhysics->removeHeightField ((*iter)->getCell()->getGridX(), (*iter)->getCell()->getGridY());
|
|
}
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->drop (*iter);
|
|
|
|
mRendering.removeCell(*iter);
|
|
MWBase::Environment::get().getWindowManager()->removeCell(*iter);
|
|
|
|
MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter);
|
|
|
|
MWBase::Environment::get().getSoundManager()->stopSound (*iter);
|
|
mActiveCells.erase(*iter);
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Store a cell unload for the LocalPlayer
|
|
*/
|
|
mwmp::Main::get().getLocalPlayer()->storeCellState(*cell, mwmp::CellState::UNLOAD);
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
}
|
|
|
|
void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn)
|
|
{
|
|
std::pair<CellStoreCollection::iterator, bool> result = mActiveCells.insert(cell);
|
|
|
|
if(result.second)
|
|
{
|
|
std::cout << "Loading cell " << cell->getCell()->getDescription() << std::endl;
|
|
|
|
float verts = ESM::Land::LAND_SIZE;
|
|
float worldsize = ESM::Land::REAL_SIZE;
|
|
|
|
// Load terrain physics first...
|
|
if (cell->getCell()->isExterior())
|
|
{
|
|
int cellX = cell->getCell()->getGridX();
|
|
int cellY = cell->getCell()->getGridY();
|
|
osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellX, cellY);
|
|
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : 0;
|
|
if (data)
|
|
{
|
|
mPhysics->addHeightField (data->mHeights, cellX, cell->getCell()->getGridY(), worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get());
|
|
}
|
|
else
|
|
{
|
|
static std::vector<float> defaultHeight;
|
|
defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT);
|
|
mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get());
|
|
}
|
|
}
|
|
|
|
// register local scripts
|
|
// do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice
|
|
MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell);
|
|
|
|
if (respawn)
|
|
cell->respawn();
|
|
|
|
// ... then references. This is important for adjustPosition to work correctly.
|
|
/// \todo rescale depending on the state of a new GMST
|
|
|
|
/*
|
|
Start of tes3mp change (major)
|
|
|
|
Instead of always rescaling objects as in the original code, never rescale them,
|
|
so they can maintain their server-set scales when their cells are reloaded
|
|
*/
|
|
insertCell(*cell, false, loadingListener);
|
|
/*
|
|
End of tes3mp change (major)
|
|
*/
|
|
|
|
|
|
mRendering.addCell(cell);
|
|
bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior();
|
|
float waterLevel = cell->getWaterLevel();
|
|
mRendering.setWaterEnabled(waterEnabled);
|
|
if (waterEnabled)
|
|
{
|
|
mPhysics->enableWater(waterLevel);
|
|
mRendering.setWaterHeight(waterLevel);
|
|
}
|
|
else
|
|
mPhysics->disableWater();
|
|
|
|
if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
|
|
mRendering.configureAmbient(cell->getCell());
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Store a cell load for the LocalPlayer
|
|
*/
|
|
mwmp::Main::get().getLocalPlayer()->storeCellState(*cell->getCell(), mwmp::CellState::LOAD);
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
}
|
|
|
|
mPreloader->notifyLoaded(cell);
|
|
}
|
|
|
|
void Scene::clear()
|
|
{
|
|
CellStoreCollection::iterator active = mActiveCells.begin();
|
|
while (active!=mActiveCells.end())
|
|
unloadCell (active++);
|
|
assert(mActiveCells.empty());
|
|
mCurrentCell = NULL;
|
|
|
|
mPreloader->clear();
|
|
}
|
|
|
|
void Scene::playerMoved(const osg::Vec3f &pos)
|
|
{
|
|
if (!mCurrentCell || !mCurrentCell->isExterior())
|
|
return;
|
|
|
|
// figure out the center of the current cell grid (*not* necessarily mCurrentCell, which is the cell the player is in)
|
|
int cellX, cellY;
|
|
getGridCenter(cellX, cellY);
|
|
float centerX, centerY;
|
|
MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true);
|
|
const float maxDistance = 8192/2 + mCellLoadingThreshold; // 1/2 cell size + threshold
|
|
float distance = std::max(std::abs(centerX-pos.x()), std::abs(centerY-pos.y()));
|
|
if (distance > maxDistance)
|
|
{
|
|
int newX, newY;
|
|
MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newX, newY);
|
|
changeCellGrid(newX, newY);
|
|
}
|
|
}
|
|
|
|
void Scene::changeCellGrid (int X, int Y, bool changeEvent)
|
|
{
|
|
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
|
Loading::ScopedLoad load(loadingListener);
|
|
|
|
std::string loadingExteriorText = "#{sLoadingMessage3}";
|
|
loadingListener->setLabel(loadingExteriorText);
|
|
|
|
CellStoreCollection::iterator active = mActiveCells.begin();
|
|
while (active!=mActiveCells.end())
|
|
{
|
|
if ((*active)->getCell()->isExterior())
|
|
{
|
|
if (std::abs (X-(*active)->getCell()->getGridX())<=mHalfGridSize &&
|
|
std::abs (Y-(*active)->getCell()->getGridY())<=mHalfGridSize)
|
|
{
|
|
// keep cells within the new grid
|
|
++active;
|
|
continue;
|
|
}
|
|
}
|
|
unloadCell (active++);
|
|
}
|
|
|
|
int refsToLoad = 0;
|
|
// get the number of refs to load
|
|
for (int x=X-mHalfGridSize; x<=X+mHalfGridSize; ++x)
|
|
{
|
|
for (int y=Y-mHalfGridSize; y<=Y+mHalfGridSize; ++y)
|
|
{
|
|
CellStoreCollection::iterator iter = mActiveCells.begin();
|
|
|
|
while (iter!=mActiveCells.end())
|
|
{
|
|
assert ((*iter)->getCell()->isExterior());
|
|
|
|
if (x==(*iter)->getCell()->getGridX() &&
|
|
y==(*iter)->getCell()->getGridY())
|
|
break;
|
|
|
|
++iter;
|
|
}
|
|
|
|
if (iter==mActiveCells.end())
|
|
refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count();
|
|
}
|
|
}
|
|
|
|
loadingListener->setProgressRange(refsToLoad);
|
|
|
|
// Load cells
|
|
for (int x=X-mHalfGridSize; x<=X+mHalfGridSize; ++x)
|
|
{
|
|
for (int y=Y-mHalfGridSize; y<=Y+mHalfGridSize; ++y)
|
|
{
|
|
CellStoreCollection::iterator iter = mActiveCells.begin();
|
|
|
|
while (iter!=mActiveCells.end())
|
|
{
|
|
assert ((*iter)->getCell()->isExterior());
|
|
|
|
if (x==(*iter)->getCell()->getGridX() &&
|
|
y==(*iter)->getCell()->getGridY())
|
|
break;
|
|
|
|
++iter;
|
|
}
|
|
|
|
if (iter==mActiveCells.end())
|
|
{
|
|
CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y);
|
|
|
|
loadCell (cell, loadingListener, changeEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Send an ID_PLAYER_CELL_STATE packet with all cell states stored in LocalPlayer
|
|
and then clear them, but only if the player is logged in on the server
|
|
*/
|
|
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
|
|
{
|
|
mwmp::Main::get().getLocalPlayer()->sendCellStates();
|
|
mwmp::Main::get().getLocalPlayer()->clearCellStates();
|
|
}
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
|
|
CellStore* current = MWBase::Environment::get().getWorld()->getExterior(X,Y);
|
|
MWBase::Environment::get().getWindowManager()->changeCell(current);
|
|
|
|
if (changeEvent)
|
|
mCellChanged = true;
|
|
}
|
|
|
|
void Scene::changePlayerCell(CellStore *cell, const ESM::Position &pos, bool adjustPlayerPos)
|
|
{
|
|
mCurrentCell = cell;
|
|
|
|
mRendering.enableTerrain(cell->isExterior());
|
|
|
|
MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
MWWorld::Ptr old = world->getPlayerPtr();
|
|
world->getPlayer().setCell(cell);
|
|
|
|
MWWorld::Ptr player = world->getPlayerPtr();
|
|
mRendering.updatePlayerPtr(player);
|
|
|
|
if (adjustPlayerPos) {
|
|
world->moveObject(player, pos.pos[0], pos.pos[1], pos.pos[2]);
|
|
|
|
float x = pos.rot[0];
|
|
float y = pos.rot[1];
|
|
float z = pos.rot[2];
|
|
world->rotateObject(player, x, y, z);
|
|
|
|
player.getClass().adjustPosition(player, true);
|
|
}
|
|
|
|
MWBase::MechanicsManager *mechMgr =
|
|
MWBase::Environment::get().getMechanicsManager();
|
|
|
|
mechMgr->updateCell(old, player);
|
|
mechMgr->watchActor(player);
|
|
|
|
mPhysics->updatePtr(old, player);
|
|
|
|
MWBase::Environment::get().getWorld()->adjustSky();
|
|
|
|
mLastPlayerPos = pos.asVec3();
|
|
}
|
|
|
|
Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics)
|
|
: mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering)
|
|
, mPreloadTimer(0.f)
|
|
, mHalfGridSize(Settings::Manager::getInt("exterior cell load distance", "Cells"))
|
|
, mCellLoadingThreshold(1024.f)
|
|
, mPreloadDistance(Settings::Manager::getInt("preload distance", "Cells"))
|
|
, mPreloadEnabled(Settings::Manager::getBool("preload enabled", "Cells"))
|
|
, mPreloadExteriorGrid(Settings::Manager::getBool("preload exterior grid", "Cells"))
|
|
, mPreloadDoors(Settings::Manager::getBool("preload doors", "Cells"))
|
|
, mPreloadFastTravel(Settings::Manager::getBool("preload fast travel", "Cells"))
|
|
, mPredictionTime(Settings::Manager::getFloat("prediction time", "Cells"))
|
|
{
|
|
mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain(), rendering.getLandManager()));
|
|
mPreloader->setWorkQueue(mRendering.getWorkQueue());
|
|
|
|
mPreloader->setUnrefQueue(rendering.getUnrefQueue());
|
|
mPhysics->setUnrefQueue(rendering.getUnrefQueue());
|
|
|
|
rendering.getResourceSystem()->setExpiryDelay(Settings::Manager::getFloat("cache expiry delay", "Cells"));
|
|
|
|
mPreloader->setExpiryDelay(Settings::Manager::getFloat("preload cell expiry delay", "Cells"));
|
|
mPreloader->setMinCacheSize(Settings::Manager::getInt("preload cell cache min", "Cells"));
|
|
mPreloader->setMaxCacheSize(Settings::Manager::getInt("preload cell cache max", "Cells"));
|
|
mPreloader->setPreloadInstances(Settings::Manager::getBool("preload instances", "Cells"));
|
|
}
|
|
|
|
Scene::~Scene()
|
|
{
|
|
}
|
|
|
|
bool Scene::hasCellChanged() const
|
|
{
|
|
return mCellChanged;
|
|
}
|
|
|
|
const Scene::CellStoreCollection& Scene::getActiveCells() const
|
|
{
|
|
return mActiveCells;
|
|
}
|
|
|
|
void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
|
|
{
|
|
CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName);
|
|
bool loadcell = (mCurrentCell == NULL);
|
|
if(!loadcell)
|
|
loadcell = *mCurrentCell != *cell;
|
|
|
|
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5);
|
|
|
|
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
|
std::string loadingInteriorText = "#{sLoadingMessage2}";
|
|
loadingListener->setLabel(loadingInteriorText);
|
|
Loading::ScopedLoad load(loadingListener);
|
|
|
|
if(!loadcell)
|
|
{
|
|
MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
world->moveObject(world->getPlayerPtr(), position.pos[0], position.pos[1], position.pos[2]);
|
|
|
|
float x = position.rot[0];
|
|
float y = position.rot[1];
|
|
float z = position.rot[2];
|
|
world->rotateObject(world->getPlayerPtr(), x, y, z);
|
|
|
|
if (adjustPlayerPos)
|
|
world->getPlayerPtr().getClass().adjustPosition(world->getPlayerPtr(), true);
|
|
MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5);
|
|
return;
|
|
}
|
|
|
|
std::cout << "Changing to interior\n";
|
|
|
|
// unload
|
|
CellStoreCollection::iterator active = mActiveCells.begin();
|
|
while (active!=mActiveCells.end())
|
|
unloadCell (active++);
|
|
|
|
int refsToLoad = cell->count();
|
|
loadingListener->setProgressRange(refsToLoad);
|
|
|
|
// Load cell.
|
|
loadCell (cell, loadingListener, changeEvent);
|
|
|
|
/*
|
|
Start of tes3mp addition
|
|
|
|
Send an ID_PLAYER_CELL_STATE packet with all cell states stored in LocalPlayer
|
|
and then clear them, but only if the player is logged in on the server
|
|
*/
|
|
if (mwmp::Main::get().getLocalPlayer()->isLoggedIn())
|
|
{
|
|
mwmp::Main::get().getLocalPlayer()->sendCellStates();
|
|
mwmp::Main::get().getLocalPlayer()->clearCellStates();
|
|
}
|
|
/*
|
|
End of tes3mp addition
|
|
*/
|
|
|
|
changePlayerCell(cell, position, adjustPlayerPos);
|
|
|
|
// adjust fog
|
|
mRendering.configureFog(mCurrentCell->getCell());
|
|
|
|
// Sky system
|
|
MWBase::Environment::get().getWorld()->adjustSky();
|
|
|
|
if (changeEvent)
|
|
mCellChanged = true;
|
|
|
|
MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5);
|
|
|
|
MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell);
|
|
}
|
|
|
|
void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
|
|
{
|
|
int x = 0;
|
|
int y = 0;
|
|
|
|
MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y);
|
|
|
|
if (changeEvent)
|
|
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5);
|
|
|
|
changeCellGrid(x, y, changeEvent);
|
|
|
|
CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y);
|
|
changePlayerCell(current, position, adjustPlayerPos);
|
|
|
|
if (changeEvent)
|
|
MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5);
|
|
}
|
|
|
|
CellStore* Scene::getCurrentCell ()
|
|
{
|
|
return mCurrentCell;
|
|
}
|
|
|
|
void Scene::markCellAsUnchanged()
|
|
{
|
|
mCellChanged = false;
|
|
}
|
|
|
|
void Scene::insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener)
|
|
{
|
|
InsertVisitor insertVisitor (cell, rescale, *loadingListener, *mPhysics, mRendering);
|
|
cell.forEach (insertVisitor);
|
|
insertVisitor.insert();
|
|
|
|
// do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order
|
|
AdjustPositionVisitor adjustPosVisitor;
|
|
cell.forEach (adjustPosVisitor);
|
|
}
|
|
|
|
void Scene::addObjectToScene (const Ptr& ptr)
|
|
{
|
|
try
|
|
{
|
|
addObject(ptr, *mPhysics, mRendering);
|
|
MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale());
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
std::cerr << "failed to render '" << ptr.getCellRef().getRefId() << "': " << e.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
void Scene::removeObjectFromScene (const Ptr& ptr)
|
|
{
|
|
MWBase::Environment::get().getMechanicsManager()->remove (ptr);
|
|
MWBase::Environment::get().getSoundManager()->stopSound3D (ptr);
|
|
mPhysics->remove(ptr);
|
|
mRendering.removeObject (ptr);
|
|
if (ptr.getClass().isActor())
|
|
mRendering.removeWaterRippleEmitter(ptr);
|
|
}
|
|
|
|
bool Scene::isCellActive(const CellStore &cell)
|
|
{
|
|
CellStoreCollection::iterator active = mActiveCells.begin();
|
|
while (active != mActiveCells.end()) {
|
|
if (**active == cell) {
|
|
return true;
|
|
}
|
|
++active;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Ptr Scene::searchPtrViaActorId (int actorId)
|
|
{
|
|
for (CellStoreCollection::const_iterator iter (mActiveCells.begin());
|
|
iter!=mActiveCells.end(); ++iter)
|
|
if (Ptr ptr = (*iter)->searchViaActorId (actorId))
|
|
return ptr;
|
|
|
|
return Ptr();
|
|
}
|
|
|
|
class PreloadMeshItem : public SceneUtil::WorkItem
|
|
{
|
|
public:
|
|
PreloadMeshItem(const std::string& mesh, Resource::SceneManager* sceneManager)
|
|
: mMesh(mesh), mSceneManager(sceneManager)
|
|
{
|
|
}
|
|
|
|
virtual void doWork()
|
|
{
|
|
try
|
|
{
|
|
mSceneManager->getTemplate(mMesh);
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
}
|
|
}
|
|
private:
|
|
std::string mMesh;
|
|
Resource::SceneManager* mSceneManager;
|
|
};
|
|
|
|
void Scene::preload(const std::string &mesh, bool useAnim)
|
|
{
|
|
std::string mesh_ = mesh;
|
|
if (useAnim)
|
|
mesh_ = Misc::ResourceHelpers::correctActorModelPath(mesh_, mRendering.getResourceSystem()->getVFS());
|
|
|
|
if (!mRendering.getResourceSystem()->getSceneManager()->checkLoaded(mesh_, mRendering.getReferenceTime()))
|
|
mRendering.getWorkQueue()->addWorkItem(new PreloadMeshItem(mesh_, mRendering.getResourceSystem()->getSceneManager()));
|
|
}
|
|
|
|
void Scene::preloadCells(float dt)
|
|
{
|
|
std::vector<osg::Vec3f> exteriorPositions;
|
|
|
|
const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
|
osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
|
|
osg::Vec3f moved = playerPos - mLastPlayerPos;
|
|
osg::Vec3f predictedPos = playerPos + moved / dt * mPredictionTime;
|
|
|
|
if (mCurrentCell->isExterior())
|
|
exteriorPositions.push_back(predictedPos);
|
|
|
|
mLastPlayerPos = playerPos;
|
|
|
|
if (mPreloadEnabled)
|
|
{
|
|
if (mPreloadDoors)
|
|
preloadTeleportDoorDestinations(playerPos, predictedPos, exteriorPositions);
|
|
if (mPreloadExteriorGrid)
|
|
preloadExteriorGrid(playerPos, predictedPos);
|
|
if (mPreloadFastTravel)
|
|
preloadFastTravelDestinations(playerPos, predictedPos, exteriorPositions);
|
|
}
|
|
|
|
mPreloader->setTerrainPreloadPositions(exteriorPositions);
|
|
}
|
|
|
|
void Scene::preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector<osg::Vec3f>& exteriorPositions)
|
|
{
|
|
std::vector<MWWorld::ConstPtr> teleportDoors;
|
|
for (CellStoreCollection::const_iterator iter (mActiveCells.begin());
|
|
iter!=mActiveCells.end(); ++iter)
|
|
{
|
|
const MWWorld::CellStore* cellStore = *iter;
|
|
typedef MWWorld::CellRefList<ESM::Door>::List DoorList;
|
|
const DoorList &doors = cellStore->getReadOnlyDoors().mList;
|
|
for (DoorList::const_iterator doorIt = doors.begin(); doorIt != doors.end(); ++doorIt)
|
|
{
|
|
if (!doorIt->mRef.getTeleport()) {
|
|
continue;
|
|
}
|
|
teleportDoors.push_back(MWWorld::ConstPtr(&*doorIt, cellStore));
|
|
}
|
|
}
|
|
|
|
for (std::vector<MWWorld::ConstPtr>::iterator it = teleportDoors.begin(); it != teleportDoors.end(); ++it)
|
|
{
|
|
const MWWorld::ConstPtr& door = *it;
|
|
float sqrDistToPlayer = (playerPos - door.getRefData().getPosition().asVec3()).length2();
|
|
sqrDistToPlayer = std::min(sqrDistToPlayer, (predictedPos - door.getRefData().getPosition().asVec3()).length2());
|
|
|
|
if (sqrDistToPlayer < mPreloadDistance*mPreloadDistance)
|
|
{
|
|
try
|
|
{
|
|
if (!door.getCellRef().getDestCell().empty())
|
|
preloadCell(MWBase::Environment::get().getWorld()->getInterior(door.getCellRef().getDestCell()));
|
|
else
|
|
{
|
|
osg::Vec3f pos = door.getCellRef().getDoorDest().asVec3();
|
|
int x,y;
|
|
MWBase::Environment::get().getWorld()->positionToIndex (pos.x(), pos.y(), x, y);
|
|
preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true);
|
|
exteriorPositions.push_back(pos);
|
|
}
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
// ignore error for now, would spam the log too much
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Scene::preloadExteriorGrid(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos)
|
|
{
|
|
if (!MWBase::Environment::get().getWorld()->isCellExterior())
|
|
return;
|
|
|
|
int halfGridSizePlusOne = mHalfGridSize + 1;
|
|
|
|
|
|
int cellX,cellY;
|
|
getGridCenter(cellX,cellY);
|
|
|
|
float centerX, centerY;
|
|
MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true);
|
|
|
|
for (int dx = -halfGridSizePlusOne; dx <= halfGridSizePlusOne; ++dx)
|
|
{
|
|
for (int dy = -halfGridSizePlusOne; dy <= halfGridSizePlusOne; ++dy)
|
|
{
|
|
if (dy != halfGridSizePlusOne && dy != -halfGridSizePlusOne && dx != halfGridSizePlusOne && dx != -halfGridSizePlusOne)
|
|
continue; // only care about the outer (not yet loaded) part of the grid
|
|
|
|
float thisCellCenterX, thisCellCenterY;
|
|
MWBase::Environment::get().getWorld()->indexToPosition(cellX+dx, cellY+dy, thisCellCenterX, thisCellCenterY, true);
|
|
|
|
float dist = std::max(std::abs(thisCellCenterX - playerPos.x()), std::abs(thisCellCenterY - playerPos.y()));
|
|
dist = std::min(dist,std::max(std::abs(thisCellCenterX - predictedPos.x()), std::abs(thisCellCenterY - predictedPos.y())));
|
|
float loadDist = 8192/2 + 8192 - mCellLoadingThreshold + mPreloadDistance;
|
|
|
|
if (dist < loadDist)
|
|
preloadCell(MWBase::Environment::get().getWorld()->getExterior(cellX+dx, cellY+dy));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Scene::preloadCell(CellStore *cell, bool preloadSurrounding)
|
|
{
|
|
if (preloadSurrounding && cell->isExterior())
|
|
{
|
|
int x = cell->getCell()->getGridX();
|
|
int y = cell->getCell()->getGridY();
|
|
unsigned int numpreloaded = 0;
|
|
for (int dx = -mHalfGridSize; dx <= mHalfGridSize; ++dx)
|
|
{
|
|
for (int dy = -mHalfGridSize; dy <= mHalfGridSize; ++dy)
|
|
{
|
|
mPreloader->preload(MWBase::Environment::get().getWorld()->getExterior(x+dx, y+dy), mRendering.getReferenceTime());
|
|
if (++numpreloaded >= mPreloader->getMaxCacheSize())
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
mPreloader->preload(cell, mRendering.getReferenceTime());
|
|
}
|
|
|
|
void Scene::preloadTerrain(const osg::Vec3f &pos)
|
|
{
|
|
std::vector<osg::Vec3f> vec;
|
|
vec.push_back(pos);
|
|
mPreloader->setTerrainPreloadPositions(vec);
|
|
}
|
|
|
|
struct ListFastTravelDestinationsVisitor
|
|
{
|
|
ListFastTravelDestinationsVisitor(float preloadDist, const osg::Vec3f& playerPos)
|
|
: mPreloadDist(preloadDist)
|
|
, mPlayerPos(playerPos)
|
|
{
|
|
}
|
|
|
|
bool operator()(const MWWorld::Ptr& ptr)
|
|
{
|
|
if ((ptr.getRefData().getPosition().asVec3() - mPlayerPos).length2() > mPreloadDist * mPreloadDist)
|
|
return true;
|
|
|
|
if (ptr.getClass().isNpc())
|
|
{
|
|
const std::vector<ESM::Transport::Dest>& transport = ptr.get<ESM::NPC>()->mBase->mTransport.mList;
|
|
mList.insert(mList.begin(), transport.begin(), transport.end());
|
|
}
|
|
else
|
|
{
|
|
const std::vector<ESM::Transport::Dest>& transport = ptr.get<ESM::Creature>()->mBase->mTransport.mList;
|
|
mList.insert(mList.begin(), transport.begin(), transport.end());
|
|
}
|
|
return true;
|
|
}
|
|
float mPreloadDist;
|
|
osg::Vec3f mPlayerPos;
|
|
std::vector<ESM::Transport::Dest> mList;
|
|
};
|
|
|
|
void Scene::preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& /*predictedPos*/, std::vector<osg::Vec3f>& exteriorPositions) // ignore predictedPos here since opening dialogue with travel service takes extra time
|
|
{
|
|
const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
|
ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3());
|
|
|
|
for (CellStoreCollection::const_iterator iter (mActiveCells.begin()); iter!=mActiveCells.end(); ++iter)
|
|
{
|
|
MWWorld::CellStore* cellStore = *iter;
|
|
cellStore->forEachType<ESM::NPC>(listVisitor);
|
|
cellStore->forEachType<ESM::Creature>(listVisitor);
|
|
}
|
|
|
|
for (std::vector<ESM::Transport::Dest>::const_iterator it = listVisitor.mList.begin(); it != listVisitor.mList.end(); ++it)
|
|
{
|
|
if (!it->mCellName.empty())
|
|
preloadCell(MWBase::Environment::get().getWorld()->getInterior(it->mCellName));
|
|
else
|
|
{
|
|
osg::Vec3f pos = it->mPos.asVec3();
|
|
int x,y;
|
|
MWBase::Environment::get().getWorld()->positionToIndex( pos.x(), pos.y(), x, y);
|
|
preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true);
|
|
exteriorPositions.push_back(pos);
|
|
}
|
|
}
|
|
}
|
|
}
|