1 #include "worldimp.hpp"
2 
3 #include <osg/Group>
4 #include <osg/ComputeBoundsVisitor>
5 #include <osg/Timer>
6 
7 #include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
8 #include <BulletCollision/CollisionShapes/btCompoundShape.h>
9 
10 #include <components/debug/debuglog.hpp>
11 
12 #include <components/esm/esmreader.hpp>
13 #include <components/esm/esmwriter.hpp>
14 #include <components/esm/cellid.hpp>
15 #include <components/esm/cellref.hpp>
16 
17 #include <components/misc/constants.hpp>
18 #include <components/misc/resourcehelpers.hpp>
19 #include <components/misc/rng.hpp>
20 #include <components/misc/convert.hpp>
21 
22 #include <components/files/collections.hpp>
23 
24 #include <components/resource/bulletshape.hpp>
25 #include <components/resource/resourcesystem.hpp>
26 
27 #include <components/sceneutil/positionattitudetransform.hpp>
28 
29 #include <components/detournavigator/debug.hpp>
30 #include <components/detournavigator/navigatorimpl.hpp>
31 #include <components/detournavigator/navigatorstub.hpp>
32 #include <components/detournavigator/recastglobalallocator.hpp>
33 
34 #include "../mwbase/environment.hpp"
35 #include "../mwbase/soundmanager.hpp"
36 #include "../mwbase/mechanicsmanager.hpp"
37 #include "../mwbase/windowmanager.hpp"
38 #include "../mwbase/scriptmanager.hpp"
39 
40 #include "../mwmechanics/creaturestats.hpp"
41 #include "../mwmechanics/npcstats.hpp"
42 #include "../mwmechanics/spellcasting.hpp"
43 #include "../mwmechanics/levelledlist.hpp"
44 #include "../mwmechanics/combat.hpp"
45 #include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors
46 #include "../mwmechanics/summoning.hpp"
47 
48 #include "../mwrender/animation.hpp"
49 #include "../mwrender/npcanimation.hpp"
50 #include "../mwrender/renderingmanager.hpp"
51 #include "../mwrender/camera.hpp"
52 #include "../mwrender/vismask.hpp"
53 
54 #include "../mwscript/globalscripts.hpp"
55 
56 #include "../mwclass/door.hpp"
57 
58 #include "../mwphysics/physicssystem.hpp"
59 #include "../mwphysics/actor.hpp"
60 #include "../mwphysics/collisiontype.hpp"
61 #include "../mwphysics/object.hpp"
62 #include "../mwphysics/constants.hpp"
63 
64 #include "datetimemanager.hpp"
65 #include "player.hpp"
66 #include "manualref.hpp"
67 #include "cellstore.hpp"
68 #include "containerstore.hpp"
69 #include "inventorystore.hpp"
70 #include "actionteleport.hpp"
71 #include "projectilemanager.hpp"
72 #include "weather.hpp"
73 
74 #include "contentloader.hpp"
75 #include "esmloader.hpp"
76 
77 namespace
78 {
79 
80 // Wraps a value to (-PI, PI]
wrap(float & rad)81 void wrap(float& rad)
82 {
83     const float pi = static_cast<float>(osg::PI);
84     if (rad>0)
85         rad = std::fmod(rad+pi, 2.0f*pi)-pi;
86     else
87         rad = std::fmod(rad-pi, 2.0f*pi)+pi;
88 }
89 
90 }
91 
92 namespace MWWorld
93 {
94     struct GameContentLoader : public ContentLoader
95     {
GameContentLoaderMWWorld::GameContentLoader96         GameContentLoader(Loading::Listener& listener)
97           : ContentLoader(listener)
98         {
99         }
100 
addLoaderMWWorld::GameContentLoader101         bool addLoader(const std::string& extension, ContentLoader* loader)
102         {
103             return mLoaders.insert(std::make_pair(extension, loader)).second;
104         }
105 
loadMWWorld::GameContentLoader106         void load(const boost::filesystem::path& filepath, int& index) override
107         {
108             LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string())));
109             if (it != mLoaders.end())
110             {
111                 it->second->load(filepath, index);
112             }
113             else
114             {
115               std::string msg("Cannot load file: ");
116               msg += filepath.string();
117               throw std::runtime_error(msg.c_str());
118             }
119         }
120 
121         private:
122           typedef std::map<std::string, ContentLoader*> LoadersContainer;
123           LoadersContainer mLoaders;
124     };
125 
adjustSky()126     void World::adjustSky()
127     {
128         if (mSky && (isCellExterior() || isCellQuasiExterior()))
129         {
130             updateSkyDate();
131             mRendering->setSkyEnabled(true);
132         }
133         else
134             mRendering->setSkyEnabled(false);
135     }
136 
World(osgViewer::Viewer * viewer,osg::ref_ptr<osg::Group> rootNode,Resource::ResourceSystem * resourceSystem,SceneUtil::WorkQueue * workQueue,const Files::Collections & fileCollections,const std::vector<std::string> & contentFiles,const std::vector<std::string> & groundcoverFiles,ToUTF8::Utf8Encoder * encoder,int activationDistanceOverride,const std::string & startCell,const std::string & startupScript,const std::string & resourcePath,const std::string & userDataPath)137     World::World (
138         osgViewer::Viewer* viewer,
139         osg::ref_ptr<osg::Group> rootNode,
140         Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
141         const Files::Collections& fileCollections,
142         const std::vector<std::string>& contentFiles,
143         const std::vector<std::string>& groundcoverFiles,
144         ToUTF8::Utf8Encoder* encoder, int activationDistanceOverride,
145         const std::string& startCell, const std::string& startupScript,
146         const std::string& resourcePath, const std::string& userDataPath)
147     : mResourceSystem(resourceSystem), mLocalScripts (mStore),
148       mCells (mStore, mEsm), mSky (true),
149       mGodMode(false), mScriptsEnabled(true), mDiscardMovements(true), mContentFiles (contentFiles),
150       mUserDataPath(userDataPath), mShouldUpdateNavigator(false),
151       mActivationDistanceOverride (activationDistanceOverride),
152       mStartCell(startCell), mDistanceToFacedObject(-1.f), mTeleportEnabled(true),
153       mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0),
154       mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f)
155     {
156         mEsm.resize(contentFiles.size() + groundcoverFiles.size());
157         Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
158         listener->loadingOn();
159 
160         GameContentLoader gameContentLoader(*listener);
161         EsmLoader esmLoader(mStore, mEsm, encoder, *listener);
162 
163         gameContentLoader.addLoader(".esm", &esmLoader);
164         gameContentLoader.addLoader(".esp", &esmLoader);
165         gameContentLoader.addLoader(".omwgame", &esmLoader);
166         gameContentLoader.addLoader(".omwaddon", &esmLoader);
167         gameContentLoader.addLoader(".project", &esmLoader);
168 
169         loadContentFiles(fileCollections, contentFiles, groundcoverFiles, gameContentLoader);
170 
171         listener->loadingOff();
172 
173         // insert records that may not be present in all versions of MW
174         if (mEsm[0].getFormat() == 0)
175             ensureNeededRecords();
176 
177         mCurrentDate.reset(new DateTimeManager());
178 
179         fillGlobalVariables();
180 
181         mStore.setUp(true);
182         mStore.movePlayerRecord();
183 
184         mSwimHeightScale = mStore.get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
185 
186         mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode));
187 
188         if (auto navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager())
189         {
190             navigatorSettings->mMaxClimb = MWPhysics::sStepSizeUp;
191             navigatorSettings->mMaxSlope = MWPhysics::sMaxSlope;
192             navigatorSettings->mSwimHeightScale = mSwimHeightScale;
193             DetourNavigator::RecastGlobalAllocator::init();
194             mNavigator.reset(new DetourNavigator::NavigatorImpl(*navigatorSettings));
195         }
196         else
197         {
198             mNavigator.reset(new DetourNavigator::NavigatorStub());
199         }
200 
201         mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, resourcePath, *mNavigator));
202         mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get()));
203         mRendering->preloadCommonAssets();
204 
205         mWeatherManager.reset(new MWWorld::WeatherManager(*mRendering, mStore));
206 
207         mWorldScene.reset(new Scene(*mRendering.get(), mPhysics.get(), *mNavigator));
208     }
209 
fillGlobalVariables()210     void World::fillGlobalVariables()
211     {
212         mGlobalVariables.fill (mStore);
213         mCurrentDate->setup(mGlobalVariables);
214     }
215 
startNewGame(bool bypass)216     void World::startNewGame (bool bypass)
217     {
218         mGoToJail = false;
219         mLevitationEnabled = true;
220         mTeleportEnabled = true;
221 
222         mGodMode = false;
223         mScriptsEnabled = true;
224         mSky = true;
225 
226         // Rebuild player
227         setupPlayer();
228 
229         renderPlayer();
230         mRendering->getCamera()->reset();
231 
232         // we don't want old weather to persist on a new game
233         // Note that if reset later, the initial ChangeWeather that the chargen script calls will be lost.
234         mWeatherManager.reset();
235         mWeatherManager.reset(new MWWorld::WeatherManager(*mRendering.get(), mStore));
236 
237         if (!bypass)
238         {
239             // set new game mark
240             mGlobalVariables["chargenstate"].setInteger (1);
241         }
242         else
243             mGlobalVariables["chargenstate"].setInteger (-1);
244 
245         if (bypass && !mStartCell.empty())
246         {
247             ESM::Position pos;
248             if (findExteriorPosition (mStartCell, pos))
249             {
250                 changeToExteriorCell (pos, true);
251                 adjustPosition(getPlayerPtr(), false);
252             }
253             else
254             {
255                 findInteriorPosition (mStartCell, pos);
256                 changeToInteriorCell (mStartCell, pos, true);
257             }
258         }
259         else
260         {
261             for (int i=0; i<5; ++i)
262                 MWBase::Environment::get().getScriptManager()->getGlobalScripts().run();
263             if (!getPlayerPtr().isInCell())
264             {
265                 ESM::Position pos;
266                 const int cellSize = Constants::CellSizeInUnits;
267                 pos.pos[0] = cellSize/2;
268                 pos.pos[1] = cellSize/2;
269                 pos.pos[2] = 0;
270                 pos.rot[0] = 0;
271                 pos.rot[1] = 0;
272                 pos.rot[2] = 0;
273                 mWorldScene->changeToExteriorCell(pos, true);
274             }
275         }
276 
277         if (!bypass)
278         {
279             const std::string& video = Fallback::Map::getString("Movies_New_Game");
280             if (!video.empty())
281                 MWBase::Environment::get().getWindowManager()->playVideo(video, true);
282         }
283 
284         // enable collision
285         if (!mPhysics->toggleCollisionMode())
286             mPhysics->toggleCollisionMode();
287 
288         MWBase::Environment::get().getWindowManager()->updatePlayer();
289         mCurrentDate->setup(mGlobalVariables);
290     }
291 
clear()292     void World::clear()
293     {
294         mWeatherManager->clear();
295         mRendering->clear();
296         mProjectileManager->clear();
297         mLocalScripts.clear();
298 
299         mWorldScene->clear();
300 
301         mStore.clearDynamic();
302 
303         if (mPlayer)
304         {
305             mPlayer->clear();
306             mPlayer->setCell(nullptr);
307             mPlayer->getPlayer().getRefData() = RefData();
308             mPlayer->set(mStore.get<ESM::NPC>().find ("player"));
309         }
310 
311         mCells.clear();
312 
313         mDoorStates.clear();
314 
315         mGoToJail = false;
316         mTeleportEnabled = true;
317         mLevitationEnabled = true;
318         mPlayerTraveling = false;
319         mPlayerInJail = false;
320 
321         fillGlobalVariables();
322     }
323 
countSavedGameRecords() const324     int World::countSavedGameRecords() const
325     {
326         return
327             mCells.countSavedGameRecords()
328             +mStore.countSavedGameRecords()
329             +mGlobalVariables.countSavedGameRecords()
330             +mProjectileManager->countSavedGameRecords()
331             +1 // player record
332             +1 // weather record
333             +1 // actorId counter
334             +1 // levitation/teleport enabled state
335             +1; // camera
336     }
337 
countSavedGameCells() const338     int World::countSavedGameCells() const
339     {
340         return mCells.countSavedGameRecords();
341     }
342 
write(ESM::ESMWriter & writer,Loading::Listener & progress) const343     void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const
344     {
345         // Active cells could have a dirty fog of war, sync it to the CellStore first
346         for (CellStore* cellstore : mWorldScene->getActiveCells())
347         {
348             MWBase::Environment::get().getWindowManager()->writeFog(cellstore);
349         }
350 
351         MWMechanics::CreatureStats::writeActorIdCounter(writer);
352 
353         mStore.write (writer, progress); // dynamic Store must be written (and read) before Cells, so that
354                                          // references to custom made records will be recognized
355         mPlayer->write (writer, progress);
356         mCells.write (writer, progress);
357         mGlobalVariables.write (writer, progress);
358         mWeatherManager->write (writer, progress);
359         mProjectileManager->write (writer, progress);
360 
361         writer.startRecord(ESM::REC_ENAB);
362         writer.writeHNT("TELE", mTeleportEnabled);
363         writer.writeHNT("LEVT", mLevitationEnabled);
364         writer.endRecord(ESM::REC_ENAB);
365 
366         writer.startRecord(ESM::REC_CAM_);
367         writer.writeHNT("FIRS", isFirstPerson());
368         writer.endRecord(ESM::REC_CAM_);
369     }
370 
readRecord(ESM::ESMReader & reader,uint32_t type,const std::map<int,int> & contentFileMap)371     void World::readRecord (ESM::ESMReader& reader, uint32_t type,
372         const std::map<int, int>& contentFileMap)
373     {
374         switch (type)
375         {
376             case ESM::REC_ACTC:
377                 MWMechanics::CreatureStats::readActorIdCounter(reader);
378                 return;
379             case ESM::REC_ENAB:
380                 reader.getHNT(mTeleportEnabled, "TELE");
381                 reader.getHNT(mLevitationEnabled, "LEVT");
382                 return;
383             case ESM::REC_PLAY:
384                 mStore.checkPlayer();
385                 mPlayer->readRecord(reader, type);
386                 if (getPlayerPtr().isInCell())
387                 {
388                     if (getPlayerPtr().getCell()->isExterior())
389                         mWorldScene->preloadTerrain(getPlayerPtr().getRefData().getPosition().asVec3());
390                     mWorldScene->preloadCell(getPlayerPtr().getCell(), true);
391                 }
392                 break;
393             default:
394                 if (!mStore.readRecord (reader, type) &&
395                     !mGlobalVariables.readRecord (reader, type) &&
396                     !mWeatherManager->readRecord (reader, type) &&
397                     !mCells.readRecord (reader, type, contentFileMap)
398                      && !mProjectileManager->readRecord (reader, type)
399                         )
400                 {
401                     throw std::runtime_error ("unknown record in saved game");
402                 }
403                 break;
404         }
405     }
406 
ensureNeededRecords()407     void World::ensureNeededRecords()
408     {
409         std::map<std::string, ESM::Variant> gmst;
410         // Companion (tribunal)
411         gmst["sCompanionShare"] = ESM::Variant("Companion Share");
412         gmst["sCompanionWarningMessage"] = ESM::Variant("Warning message");
413         gmst["sCompanionWarningButtonOne"] = ESM::Variant("Button 1");
414         gmst["sCompanionWarningButtonTwo"] = ESM::Variant("Button 2");
415         gmst["sProfitValue"] = ESM::Variant("Profit Value");
416         gmst["sTeleportDisabled"] = ESM::Variant("Teleport disabled");
417         gmst["sLevitateDisabled"] = ESM::Variant("Levitate disabled");
418 
419         // Missing in unpatched MW 1.0
420         gmst["sDifficulty"] = ESM::Variant("Difficulty");
421         gmst["fDifficultyMult"] = ESM::Variant(5.f);
422         gmst["sAuto_Run"] = ESM::Variant("Auto Run");
423         gmst["sServiceRefusal"] = ESM::Variant("Service Refusal");
424         gmst["sNeedOneSkill"] = ESM::Variant("Need one skill");
425         gmst["sNeedTwoSkills"] = ESM::Variant("Need two skills");
426         gmst["sEasy"] = ESM::Variant("Easy");
427         gmst["sHard"] = ESM::Variant("Hard");
428         gmst["sDeleteNote"] = ESM::Variant("Delete Note");
429         gmst["sEditNote"] = ESM::Variant("Edit Note");
430         gmst["sAdmireSuccess"] = ESM::Variant("Admire Success");
431         gmst["sAdmireFail"] = ESM::Variant("Admire Fail");
432         gmst["sIntimidateSuccess"] = ESM::Variant("Intimidate Success");
433         gmst["sIntimidateFail"] = ESM::Variant("Intimidate Fail");
434         gmst["sTauntSuccess"] = ESM::Variant("Taunt Success");
435         gmst["sTauntFail"] = ESM::Variant("Taunt Fail");
436         gmst["sBribeSuccess"] = ESM::Variant("Bribe Success");
437         gmst["sBribeFail"] = ESM::Variant("Bribe Fail");
438         gmst["fNPCHealthBarTime"] = ESM::Variant(5.f);
439         gmst["fNPCHealthBarFade"] = ESM::Variant(1.f);
440         gmst["fFleeDistance"] = ESM::Variant(3000.f);
441         gmst["sMaxSale"] = ESM::Variant("Max Sale");
442         gmst["sAnd"] = ESM::Variant("and");
443 
444         // Werewolf (BM)
445         gmst["fWereWolfRunMult"] = ESM::Variant(1.3f);
446         gmst["fWereWolfSilverWeaponDamageMult"] = ESM::Variant(2.f);
447         gmst["iWerewolfFightMod"] = ESM::Variant(100);
448         gmst["iWereWolfFleeMod"] = ESM::Variant(100);
449         gmst["iWereWolfLevelToAttack"] = ESM::Variant(20);
450         gmst["iWereWolfBounty"] = ESM::Variant(1000);
451         gmst["fCombatDistanceWerewolfMod"] = ESM::Variant(0.3f);
452 
453         for (const auto &params : gmst)
454         {
455             if (!mStore.get<ESM::GameSetting>().search(params.first))
456             {
457                 ESM::GameSetting record;
458                 record.mId = params.first;
459                 record.mValue = params.second;
460                 mStore.insertStatic(record);
461             }
462         }
463 
464         std::map<std::string, ESM::Variant> globals;
465         // vanilla Morrowind does not define dayspassed.
466         globals["dayspassed"] = ESM::Variant(1); // but the addons start counting at 1 :(
467         globals["werewolfclawmult"] = ESM::Variant(25.f);
468         globals["pcknownwerewolf"] = ESM::Variant(0);
469 
470         // following should exist in all versions of MW, but not necessarily in TCs
471         globals["gamehour"] = ESM::Variant(0.f);
472         globals["timescale"] = ESM::Variant(30.f);
473         globals["day"] = ESM::Variant(1);
474         globals["month"] = ESM::Variant(1);
475         globals["year"] = ESM::Variant(1);
476         globals["pcrace"] = ESM::Variant(0);
477         globals["pchascrimegold"] = ESM::Variant(0);
478         globals["pchasgolddiscount"] = ESM::Variant(0);
479         globals["crimegolddiscount"] = ESM::Variant(0);
480         globals["crimegoldturnin"] = ESM::Variant(0);
481         globals["pchasturnin"] = ESM::Variant(0);
482 
483         for (const auto &params : globals)
484         {
485             if (!mStore.get<ESM::Global>().search(params.first))
486             {
487                 ESM::Global record;
488                 record.mId = params.first;
489                 record.mValue = params.second;
490                 mStore.insertStatic(record);
491             }
492         }
493 
494         std::map<std::string, std::string> statics;
495         // Total conversions from SureAI lack marker records
496         statics["divinemarker"] = "marker_divine.nif";
497         statics["doormarker"] = "marker_arrow.nif";
498         statics["northmarker"] = "marker_north.nif";
499         statics["templemarker"] = "marker_temple.nif";
500         statics["travelmarker"] = "marker_travel.nif";
501 
502         for (const auto &params : statics)
503         {
504             if (!mStore.get<ESM::Static>().search(params.first))
505             {
506                 ESM::Static record;
507                 record.mId = params.first;
508                 record.mModel = params.second;
509                 mStore.insertStatic(record);
510             }
511         }
512 
513         std::map<std::string, std::string> doors;
514         doors["prisonmarker"] = "marker_prison.nif";
515 
516         for (const auto &params : doors)
517         {
518             if (!mStore.get<ESM::Door>().search(params.first))
519             {
520                 ESM::Door record;
521                 record.mId = params.first;
522                 record.mModel = params.second;
523                 mStore.insertStatic(record);
524             }
525         }
526     }
527 
~World()528     World::~World()
529     {
530         // Must be cleared before mRendering is destroyed
531         mProjectileManager->clear();
532     }
533 
getExterior(const std::string & cellName) const534     const ESM::Cell *World::getExterior (const std::string& cellName) const
535     {
536         // first try named cells
537         const ESM::Cell *cell = mStore.get<ESM::Cell>().searchExtByName (cellName);
538         if (cell)
539             return cell;
540 
541         // didn't work -> now check for regions
542         for (const ESM::Region &region : mStore.get<ESM::Region>())
543         {
544             if (Misc::StringUtils::ciEqual(cellName, region.mName))
545             {
546                 return mStore.get<ESM::Cell>().searchExtByRegion(region.mId);
547             }
548         }
549 
550         return nullptr;
551     }
552 
getExterior(int x,int y)553     CellStore *World::getExterior (int x, int y)
554     {
555         return mCells.getExterior (x, y);
556     }
557 
getInterior(const std::string & name)558     CellStore *World::getInterior (const std::string& name)
559     {
560         return mCells.getInterior (name);
561     }
562 
getCell(const ESM::CellId & id)563     CellStore *World::getCell (const ESM::CellId& id)
564     {
565         if (id.mPaged)
566             return getExterior (id.mIndex.mX, id.mIndex.mY);
567         else
568             return getInterior (id.mWorldspace);
569     }
570 
testExteriorCells()571     void World::testExteriorCells()
572     {
573         mWorldScene->testExteriorCells();
574     }
575 
testInteriorCells()576     void World::testInteriorCells()
577     {
578         mWorldScene->testInteriorCells();
579     }
580 
useDeathCamera()581     void World::useDeathCamera()
582     {
583         if(mRendering->getCamera()->isVanityOrPreviewModeEnabled() )
584         {
585             mRendering->getCamera()->togglePreviewMode(false);
586             mRendering->getCamera()->toggleVanityMode(false);
587         }
588         if(mRendering->getCamera()->isFirstPerson())
589             mRendering->getCamera()->toggleViewMode(true);
590     }
591 
getPlayer()592     MWWorld::Player& World::getPlayer()
593     {
594         return *mPlayer;
595     }
596 
getStore() const597     const MWWorld::ESMStore& World::getStore() const
598     {
599         return mStore;
600     }
601 
getEsmReader()602     std::vector<ESM::ESMReader>& World::getEsmReader()
603     {
604         return mEsm;
605     }
606 
getLocalScripts()607     LocalScripts& World::getLocalScripts()
608     {
609         return mLocalScripts;
610     }
611 
hasCellChanged() const612     bool World::hasCellChanged() const
613     {
614         return mWorldScene->hasCellChanged();
615     }
616 
setGlobalInt(const std::string & name,int value)617     void World::setGlobalInt (const std::string& name, int value)
618     {
619         bool dateUpdated = mCurrentDate->updateGlobalInt(name, value);
620         if (dateUpdated)
621             updateSkyDate();
622 
623         mGlobalVariables[name].setInteger (value);
624     }
625 
setGlobalFloat(const std::string & name,float value)626     void World::setGlobalFloat (const std::string& name, float value)
627     {
628         bool dateUpdated = mCurrentDate->updateGlobalFloat(name, value);
629         if (dateUpdated)
630             updateSkyDate();
631 
632         mGlobalVariables[name].setFloat(value);
633     }
634 
getGlobalInt(const std::string & name) const635     int World::getGlobalInt (const std::string& name) const
636     {
637         return mGlobalVariables[name].getInteger();
638     }
639 
getGlobalFloat(const std::string & name) const640     float World::getGlobalFloat (const std::string& name) const
641     {
642         return mGlobalVariables[name].getFloat();
643     }
644 
getGlobalVariableType(const std::string & name) const645     char World::getGlobalVariableType (const std::string& name) const
646     {
647         return mGlobalVariables.getType (name);
648     }
649 
getMonthName(int month) const650     std::string World::getMonthName (int month) const
651     {
652         return mCurrentDate->getMonthName(month);
653     }
654 
getCellName(const MWWorld::CellStore * cell) const655     std::string World::getCellName (const MWWorld::CellStore *cell) const
656     {
657         if (!cell)
658             cell = mWorldScene->getCurrentCell();
659         return getCellName(cell->getCell());
660     }
661 
getCellName(const ESM::Cell * cell) const662     std::string World::getCellName(const ESM::Cell* cell) const
663     {
664         if (cell)
665         {
666             if (!cell->isExterior() || !cell->mName.empty())
667                 return cell->mName;
668 
669             if (const ESM::Region* region = mStore.get<ESM::Region>().search (cell->mRegion))
670                 return region->mName;
671         }
672         return mStore.get<ESM::GameSetting>().find ("sDefaultCellname")->mValue.getString();
673     }
674 
removeRefScript(MWWorld::RefData * ref)675     void World::removeRefScript (MWWorld::RefData *ref)
676     {
677         mLocalScripts.remove (ref);
678     }
679 
searchPtr(const std::string & name,bool activeOnly,bool searchInContainers)680     Ptr World::searchPtr (const std::string& name, bool activeOnly, bool searchInContainers)
681     {
682         Ptr ret;
683         // the player is always in an active cell.
684         if (name=="player")
685         {
686             return mPlayer->getPlayer();
687         }
688 
689         std::string lowerCaseName = Misc::StringUtils::lowerCase(name);
690 
691         for (CellStore* cellstore : mWorldScene->getActiveCells())
692         {
693             // TODO: caching still doesn't work efficiently here (only works for the one CellStore that the reference is in)
694             Ptr ptr = mCells.getPtr (lowerCaseName, *cellstore, false);
695 
696             if (!ptr.isEmpty())
697                 return ptr;
698         }
699 
700         if (!activeOnly)
701         {
702             ret = mCells.getPtr (lowerCaseName);
703             if (!ret.isEmpty())
704                 return ret;
705         }
706 
707         if (searchInContainers)
708         {
709             for (CellStore* cellstore : mWorldScene->getActiveCells())
710             {
711                 Ptr ptr = cellstore->searchInContainer(lowerCaseName);
712                 if (!ptr.isEmpty())
713                     return ptr;
714             }
715         }
716 
717         Ptr ptr = mPlayer->getPlayer().getClass()
718             .getContainerStore(mPlayer->getPlayer()).search(lowerCaseName);
719 
720         return ptr;
721     }
722 
getPtr(const std::string & name,bool activeOnly)723     Ptr World::getPtr (const std::string& name, bool activeOnly)
724     {
725         Ptr ret = searchPtr(name, activeOnly);
726         if (!ret.isEmpty())
727             return ret;
728         std::string error = "failed to find an instance of object '" + name + "'";
729         if (activeOnly)
730             error += " in active cells";
731         throw std::runtime_error(error);
732     }
733 
searchPtrViaActorId(int actorId)734     Ptr World::searchPtrViaActorId (int actorId)
735     {
736         // The player is not registered in any CellStore so must be checked manually
737         if (actorId == getPlayerPtr().getClass().getCreatureStats(getPlayerPtr()).getActorId())
738             return getPlayerPtr();
739         // Now search cells
740         return mWorldScene->searchPtrViaActorId (actorId);
741     }
742 
searchPtrViaRefNum(const std::string & id,const ESM::RefNum & refNum)743     Ptr World::searchPtrViaRefNum (const std::string& id, const ESM::RefNum& refNum)
744     {
745         return mCells.getPtr (id, refNum);
746     }
747 
748     struct FindContainerVisitor
749     {
750         ConstPtr mContainedPtr;
751         Ptr mResult;
752 
FindContainerVisitorMWWorld::FindContainerVisitor753         FindContainerVisitor(const ConstPtr& containedPtr) : mContainedPtr(containedPtr) {}
754 
operator ()MWWorld::FindContainerVisitor755         bool operator() (Ptr ptr)
756         {
757             if (mContainedPtr.getContainerStore() == &ptr.getClass().getContainerStore(ptr))
758             {
759                 mResult = ptr;
760                 return false;
761             }
762 
763             return true;
764         }
765     };
766 
findContainer(const ConstPtr & ptr)767     Ptr World::findContainer(const ConstPtr& ptr)
768     {
769         if (ptr.isInCell())
770             return Ptr();
771 
772         Ptr player = getPlayerPtr();
773         if (ptr.getContainerStore() == &player.getClass().getContainerStore(player))
774             return player;
775 
776         for (CellStore* cellstore : mWorldScene->getActiveCells())
777         {
778             FindContainerVisitor visitor(ptr);
779             cellstore->forEachType<ESM::Container>(visitor);
780             if (visitor.mResult.isEmpty())
781                 cellstore->forEachType<ESM::Creature>(visitor);
782             if (visitor.mResult.isEmpty())
783                 cellstore->forEachType<ESM::NPC>(visitor);
784 
785             if (!visitor.mResult.isEmpty())
786                 return visitor.mResult;
787         }
788 
789         return Ptr();
790     }
791 
addContainerScripts(const Ptr & reference,CellStore * cell)792     void World::addContainerScripts(const Ptr& reference, CellStore * cell)
793     {
794         if( reference.getTypeName()==typeid (ESM::Container).name() ||
795             reference.getTypeName()==typeid (ESM::NPC).name() ||
796             reference.getTypeName()==typeid (ESM::Creature).name())
797         {
798             MWWorld::ContainerStore& container = reference.getClass().getContainerStore(reference);
799             for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it)
800             {
801                 std::string script = it->getClass().getScript(*it);
802                 if(script != "")
803                 {
804                     MWWorld::Ptr item = *it;
805                     item.mCell = cell;
806                     mLocalScripts.add (script, item);
807                 }
808             }
809         }
810     }
811 
enable(const Ptr & reference)812     void World::enable (const Ptr& reference)
813     {
814         // enable is a no-op for items in containers
815         if (!reference.isInCell())
816             return;
817 
818         if (!reference.getRefData().isEnabled())
819         {
820             reference.getRefData().enable();
821 
822             if(mWorldScene->getActiveCells().find (reference.getCell()) != mWorldScene->getActiveCells().end() && reference.getRefData().getCount())
823                 mWorldScene->addObjectToScene (reference);
824 
825             if (reference.getCellRef().getRefNum().hasContentFile())
826             {
827                 int type = mStore.find(Misc::StringUtils::lowerCase(reference.getCellRef().getRefId()));
828                 if (mRendering->pagingEnableObject(type, reference, true))
829                     mWorldScene->reloadTerrain();
830             }
831         }
832     }
833 
removeContainerScripts(const Ptr & reference)834     void World::removeContainerScripts(const Ptr& reference)
835     {
836         if( reference.getTypeName()==typeid (ESM::Container).name() ||
837             reference.getTypeName()==typeid (ESM::NPC).name() ||
838             reference.getTypeName()==typeid (ESM::Creature).name())
839         {
840             MWWorld::ContainerStore& container = reference.getClass().getContainerStore(reference);
841             for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it)
842             {
843                 std::string script = it->getClass().getScript(*it);
844                 if(script != "")
845                 {
846                     MWWorld::Ptr item = *it;
847                     mLocalScripts.remove (item);
848                 }
849             }
850         }
851     }
852 
disable(const Ptr & reference)853     void World::disable (const Ptr& reference)
854     {
855         if (!reference.getRefData().isEnabled())
856             return;
857 
858         // disable is a no-op for items in containers
859         if (!reference.isInCell())
860             return;
861 
862         if (reference == getPlayerPtr())
863             throw std::runtime_error("can not disable player object");
864 
865         reference.getRefData().disable();
866 
867         if (reference.getCellRef().getRefNum().hasContentFile())
868         {
869             int type = mStore.find(Misc::StringUtils::lowerCase(reference.getCellRef().getRefId()));
870             if (mRendering->pagingEnableObject(type, reference, false))
871                 mWorldScene->reloadTerrain();
872         }
873 
874         if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount())
875             mWorldScene->removeObjectFromScene (reference);
876     }
877 
advanceTime(double hours,bool incremental)878     void World::advanceTime (double hours, bool incremental)
879     {
880         if (!incremental)
881         {
882             // When we fast-forward time, we should recharge magic items
883             // in all loaded cells, using game world time
884             float duration = hours * 3600;
885             const float timeScaleFactor = getTimeScaleFactor();
886             if (timeScaleFactor != 0.0f)
887                 duration /= timeScaleFactor;
888 
889             rechargeItems(duration, false);
890         }
891 
892         mWeatherManager->advanceTime (hours, incremental);
893         mCurrentDate->advanceTime(hours, mGlobalVariables);
894         updateSkyDate();
895 
896         if (!incremental)
897         {
898             mRendering->notifyWorldSpaceChanged();
899             mProjectileManager->clear();
900             mDiscardMovements = true;
901         }
902     }
903 
getTimeScaleFactor() const904     float World::getTimeScaleFactor() const
905     {
906         return mCurrentDate->getTimeScaleFactor();
907     }
908 
getTimeStamp() const909     TimeStamp World::getTimeStamp() const
910     {
911         return mCurrentDate->getTimeStamp();
912     }
913 
getEpochTimeStamp() const914     ESM::EpochTimeStamp World::getEpochTimeStamp() const
915     {
916         return mCurrentDate->getEpochTimeStamp();
917     }
918 
toggleSky()919     bool World::toggleSky()
920     {
921         mSky = !mSky;
922         mRendering->setSkyEnabled(mSky);
923         return mSky;
924     }
925 
getMasserPhase() const926     int World::getMasserPhase() const
927     {
928         return mRendering->skyGetMasserPhase();
929     }
930 
getSecundaPhase() const931     int World::getSecundaPhase() const
932     {
933         return mRendering->skyGetSecundaPhase();
934     }
935 
setMoonColour(bool red)936     void World::setMoonColour (bool red)
937     {
938         mRendering->skySetMoonColour (red);
939     }
940 
changeToInteriorCell(const std::string & cellName,const ESM::Position & position,bool adjustPlayerPos,bool changeEvent)941     void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
942     {
943         mPhysics->clearQueuedMovement();
944         mDiscardMovements = true;
945 
946         if (changeEvent && mCurrentWorldSpace != cellName)
947         {
948             // changed worldspace
949             mProjectileManager->clear();
950             mRendering->notifyWorldSpaceChanged();
951 
952             mCurrentWorldSpace = cellName;
953         }
954 
955         removeContainerScripts(getPlayerPtr());
956         mWorldScene->changeToInteriorCell(cellName, position, adjustPlayerPos, changeEvent);
957         addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
958         mRendering->getCamera()->instantTransition();
959     }
960 
changeToExteriorCell(const ESM::Position & position,bool adjustPlayerPos,bool changeEvent)961     void World::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
962     {
963         mPhysics->clearQueuedMovement();
964         mDiscardMovements = true;
965 
966         if (changeEvent && mCurrentWorldSpace != ESM::CellId::sDefaultWorldspace)
967         {
968             // changed worldspace
969             mProjectileManager->clear();
970             mRendering->notifyWorldSpaceChanged();
971         }
972         removeContainerScripts(getPlayerPtr());
973         mWorldScene->changeToExteriorCell(position, adjustPlayerPos, changeEvent);
974         addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
975         mRendering->getCamera()->instantTransition();
976     }
977 
changeToCell(const ESM::CellId & cellId,const ESM::Position & position,bool adjustPlayerPos,bool changeEvent)978     void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
979     {
980         if (!changeEvent)
981             mCurrentWorldSpace = cellId.mWorldspace;
982 
983         if (cellId.mPaged)
984             changeToExteriorCell (position, adjustPlayerPos, changeEvent);
985         else
986             changeToInteriorCell (cellId.mWorldspace, position, adjustPlayerPos, changeEvent);
987 
988         mCurrentDate->setup(mGlobalVariables);
989     }
990 
markCellAsUnchanged()991     void World::markCellAsUnchanged()
992     {
993         return mWorldScene->markCellAsUnchanged();
994     }
995 
getMaxActivationDistance()996     float World::getMaxActivationDistance ()
997     {
998         if (mActivationDistanceOverride >= 0)
999             return static_cast<float>(mActivationDistanceOverride);
1000 
1001         static const int iMaxActivateDist = mStore.get<ESM::GameSetting>().find("iMaxActivateDist")->mValue.getInteger();
1002         return static_cast<float>(iMaxActivateDist);
1003     }
1004 
getFacedObject()1005     MWWorld::Ptr World::getFacedObject()
1006     {
1007         MWWorld::Ptr facedObject;
1008 
1009         if (MWBase::Environment::get().getWindowManager()->isGuiMode() &&
1010                 MWBase::Environment::get().getWindowManager()->isConsoleMode())
1011             facedObject = getFacedObject(getMaxActivationDistance() * 50, false);
1012         else
1013         {
1014             float activationDistance = getActivationDistancePlusTelekinesis();
1015 
1016             facedObject = getFacedObject(activationDistance, true);
1017 
1018             if (!facedObject.isEmpty() && !facedObject.getClass().allowTelekinesis(facedObject)
1019                 && mDistanceToFacedObject > getMaxActivationDistance() && !MWBase::Environment::get().getWindowManager()->isGuiMode())
1020                 return nullptr;
1021         }
1022         return facedObject;
1023     }
1024 
getDistanceToFacedObject()1025    float World::getDistanceToFacedObject()
1026    {
1027         return mDistanceToFacedObject;
1028    }
1029 
getActorHeadTransform(const MWWorld::ConstPtr & actor) const1030     osg::Matrixf World::getActorHeadTransform(const MWWorld::ConstPtr& actor) const
1031     {
1032         const MWRender::Animation *anim = mRendering->getAnimation(actor);
1033         if(anim)
1034         {
1035             const osg::Node *node = anim->getNode("Head");
1036             if(!node) node = anim->getNode("Bip01 Head");
1037             if(node)
1038             {
1039                 osg::NodePathList nodepaths = node->getParentalNodePaths();
1040                 if(!nodepaths.empty())
1041                     return osg::computeLocalToWorld(nodepaths[0]);
1042             }
1043         }
1044         return osg::Matrixf::translate(actor.getRefData().getPosition().asVec3());
1045     }
1046 
getHitContact(const MWWorld::ConstPtr & ptr,float distance,std::vector<MWWorld::Ptr> & targets)1047     std::pair<MWWorld::Ptr,osg::Vec3f> World::getHitContact(const MWWorld::ConstPtr &ptr, float distance, std::vector<MWWorld::Ptr> &targets)
1048     {
1049         const ESM::Position &posdata = ptr.getRefData().getPosition();
1050 
1051         osg::Quat rot = osg::Quat(posdata.rot[0], osg::Vec3f(-1,0,0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0,0,-1));
1052 
1053         osg::Vec3f halfExtents = mPhysics->getHalfExtents(ptr);
1054 
1055         // the origin of hitbox is an actor's front, not center
1056         distance += halfExtents.y();
1057 
1058         // special cased for better aiming with the camera
1059         // if we do not hit anything, will use the default approach as fallback
1060         if (ptr == getPlayerPtr())
1061         {
1062             osg::Vec3f pos = getActorHeadTransform(ptr).getTrans();
1063 
1064             std::pair<MWWorld::Ptr,osg::Vec3f> result = mPhysics->getHitContact(ptr, pos, rot, distance, targets);
1065             if(!result.first.isEmpty())
1066                 return std::make_pair(result.first, result.second);
1067         }
1068 
1069         osg::Vec3f pos = ptr.getRefData().getPosition().asVec3();
1070 
1071         // general case, compatible with all types of different creatures
1072         // note: we intentionally do *not* use the collision box offset here, this is required to make
1073         // some flying creatures work that have their collision box offset in the air
1074         pos.z() += halfExtents.z();
1075 
1076         std::pair<MWWorld::Ptr,osg::Vec3f> result = mPhysics->getHitContact(ptr, pos, rot, distance, targets);
1077         if(result.first.isEmpty())
1078             return std::make_pair(MWWorld::Ptr(), osg::Vec3f());
1079 
1080         return std::make_pair(result.first, result.second);
1081     }
1082 
deleteObject(const Ptr & ptr)1083     void World::deleteObject (const Ptr& ptr)
1084     {
1085         if (!ptr.getRefData().isDeleted() && ptr.getContainerStore() == nullptr)
1086         {
1087             if (ptr == getPlayerPtr())
1088                 throw std::runtime_error("can not delete player object");
1089 
1090             ptr.getRefData().setCount(0);
1091 
1092             if (ptr.isInCell()
1093                 && mWorldScene->getActiveCells().find(ptr.getCell()) != mWorldScene->getActiveCells().end()
1094                 && ptr.getRefData().isEnabled())
1095             {
1096                 mWorldScene->removeObjectFromScene (ptr);
1097                 mLocalScripts.remove (ptr);
1098                 removeContainerScripts (ptr);
1099             }
1100         }
1101     }
1102 
undeleteObject(const Ptr & ptr)1103     void World::undeleteObject(const Ptr& ptr)
1104     {
1105         if (!ptr.getCellRef().hasContentFile())
1106             return;
1107         if (ptr.getRefData().isDeleted())
1108         {
1109             ptr.getRefData().setCount(1);
1110             if (mWorldScene->getActiveCells().find(ptr.getCell()) != mWorldScene->getActiveCells().end()
1111                     && ptr.getRefData().isEnabled())
1112             {
1113                 mWorldScene->addObjectToScene(ptr);
1114                 std::string script = ptr.getClass().getScript(ptr);
1115                 if (!script.empty())
1116                     mLocalScripts.add(script, ptr);
1117                 addContainerScripts(ptr, ptr.getCell());
1118             }
1119         }
1120     }
1121 
moveObject(const Ptr & ptr,CellStore * newCell,float x,float y,float z,bool movePhysics)1122     MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z, bool movePhysics)
1123     {
1124         ESM::Position pos = ptr.getRefData().getPosition();
1125 
1126         pos.pos[0] = x;
1127         pos.pos[1] = y;
1128         pos.pos[2] = z;
1129 
1130         ptr.getRefData().setPosition(pos);
1131 
1132         osg::Vec3f vec(x, y, z);
1133 
1134         CellStore *currCell = ptr.isInCell() ? ptr.getCell() : nullptr; // currCell == nullptr should only happen for player, during initial startup
1135         bool isPlayer = ptr == mPlayer->getPlayer();
1136         bool haveToMove = isPlayer || (currCell && mWorldScene->isCellActive(*currCell));
1137         MWWorld::Ptr newPtr = ptr;
1138 
1139         if (!isPlayer && !currCell)
1140            throw std::runtime_error("Can not move actor \"" + ptr.getCellRef().getRefId() + "\" to another cell: current cell is nullptr");
1141 
1142         if (!newCell)
1143            throw std::runtime_error("Can not move actor \"" + ptr.getCellRef().getRefId() + "\" to another cell: new cell is nullptr");
1144 
1145         if (currCell != newCell)
1146         {
1147             removeContainerScripts(ptr);
1148 
1149             if (isPlayer)
1150             {
1151                 if (!newCell->isExterior())
1152                 {
1153                     changeToInteriorCell(Misc::StringUtils::lowerCase(newCell->getCell()->mName), pos, false);
1154                     removeContainerScripts(getPlayerPtr());
1155                 }
1156                 else
1157                 {
1158                     if (mWorldScene->isCellActive(*newCell))
1159                         mWorldScene->changePlayerCell(newCell, pos, false);
1160                     else
1161                         mWorldScene->changeToExteriorCell(pos, false);
1162                 }
1163                 addContainerScripts (getPlayerPtr(), newCell);
1164                 newPtr = getPlayerPtr();
1165             }
1166             else
1167             {
1168                 bool currCellActive = mWorldScene->isCellActive(*currCell);
1169                 bool newCellActive = mWorldScene->isCellActive(*newCell);
1170                 if (!currCellActive && newCellActive)
1171                 {
1172                     newPtr = currCell->moveTo(ptr, newCell);
1173                     mWorldScene->addObjectToScene(newPtr);
1174 
1175                     std::string script = newPtr.getClass().getScript(newPtr);
1176                     if (!script.empty())
1177                     {
1178                         mLocalScripts.add(script, newPtr);
1179                     }
1180                     addContainerScripts(newPtr, newCell);
1181                 }
1182                 else if (!newCellActive && currCellActive)
1183                 {
1184                     mWorldScene->removeObjectFromScene(ptr);
1185                     mLocalScripts.remove(ptr);
1186                     removeContainerScripts (ptr);
1187                     haveToMove = false;
1188 
1189                     newPtr = currCell->moveTo(ptr, newCell);
1190                     newPtr.getRefData().setBaseNode(nullptr);
1191                 }
1192                 else if (!currCellActive && !newCellActive)
1193                     newPtr = currCell->moveTo(ptr, newCell);
1194                 else // both cells active
1195                 {
1196                     newPtr = currCell->moveTo(ptr, newCell);
1197 
1198                     mRendering->updatePtr(ptr, newPtr);
1199                     MWBase::Environment::get().getSoundManager()->updatePtr (ptr, newPtr);
1200                     mPhysics->updatePtr(ptr, newPtr);
1201 
1202                     MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager();
1203                     mechMgr->updateCell(ptr, newPtr);
1204 
1205                     std::string script =
1206                         ptr.getClass().getScript(ptr);
1207                     if (!script.empty())
1208                     {
1209                         mLocalScripts.remove(ptr);
1210                         removeContainerScripts (ptr);
1211                         mLocalScripts.add(script, newPtr);
1212                         addContainerScripts (newPtr, newCell);
1213                     }
1214                 }
1215             }
1216 
1217             MWBase::Environment::get().getWindowManager()->updateConsoleObjectPtr(ptr, newPtr);
1218             MWBase::Environment::get().getScriptManager()->getGlobalScripts().updatePtrs(ptr, newPtr);
1219         }
1220         if (haveToMove && newPtr.getRefData().getBaseNode())
1221         {
1222             mWorldScene->updateObjectPosition(newPtr, vec, movePhysics);
1223             if (movePhysics)
1224             {
1225                 if (const auto object = mPhysics->getObject(ptr))
1226                     updateNavigatorObject(*object);
1227             }
1228         }
1229 
1230         if (isPlayer)
1231             mWorldScene->playerMoved(vec);
1232         else
1233         {
1234             mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
1235             mWorldScene->removeFromPagedRefs(newPtr);
1236         }
1237 
1238         return newPtr;
1239     }
1240 
moveObject(const Ptr & ptr,float x,float y,float z,bool movePhysics,bool moveToActive)1241     MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z, bool movePhysics, bool moveToActive)
1242     {
1243         int cellX, cellY;
1244         positionToIndex(x, y, cellX, cellY);
1245 
1246         CellStore* cell = ptr.getCell();
1247         CellStore* newCell = getExterior(cellX, cellY);
1248         bool isCellActive = getPlayerPtr().isInCell() && getPlayerPtr().getCell()->isExterior() && mWorldScene->isCellActive(*newCell);
1249 
1250         if (cell->isExterior() || (moveToActive && isCellActive && ptr.getClass().isActor()))
1251             cell = newCell;
1252 
1253         return moveObject(ptr, cell, x, y, z, movePhysics);
1254     }
1255 
moveObjectBy(const Ptr & ptr,osg::Vec3f vec,bool moveToActive,bool ignoreCollisions)1256     MWWorld::Ptr World::moveObjectBy(const Ptr& ptr, osg::Vec3f vec, bool moveToActive, bool ignoreCollisions)
1257     {
1258         auto* actor = mPhysics->getActor(ptr);
1259         osg::Vec3f newpos = ptr.getRefData().getPosition().asVec3() + vec;
1260         if (actor)
1261             actor->adjustPosition(vec, ignoreCollisions);
1262         if (ptr.getClass().isActor())
1263             return moveObject(ptr, newpos.x(), newpos.y(), newpos.z(), false, moveToActive && ptr != getPlayerPtr());
1264         return moveObject(ptr, newpos.x(), newpos.y(), newpos.z());
1265     }
1266 
scaleObject(const Ptr & ptr,float scale)1267     void World::scaleObject (const Ptr& ptr, float scale)
1268     {
1269         if (mPhysics->getActor(ptr))
1270             mNavigator->removeAgent(getPathfindingHalfExtents(ptr));
1271 
1272         if (scale != ptr.getCellRef().getScale())
1273         {
1274             ptr.getCellRef().setScale(scale);
1275             mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
1276             mWorldScene->removeFromPagedRefs(ptr);
1277         }
1278 
1279         if(ptr.getRefData().getBaseNode() != nullptr)
1280             mWorldScene->updateObjectScale(ptr);
1281 
1282         if (mPhysics->getActor(ptr))
1283             mNavigator->addAgent(getPathfindingHalfExtents(ptr));
1284         else if (const auto object = mPhysics->getObject(ptr))
1285             updateNavigatorObject(*object);
1286     }
1287 
rotateObjectImp(const Ptr & ptr,const osg::Vec3f & rot,MWBase::RotationFlags flags)1288     void World::rotateObjectImp(const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags)
1289     {
1290         const float pi = static_cast<float>(osg::PI);
1291 
1292         ESM::Position pos = ptr.getRefData().getPosition();
1293         float *objRot = pos.rot;
1294         if (flags & MWBase::RotationFlag_adjust)
1295         {
1296             objRot[0] += rot.x();
1297             objRot[1] += rot.y();
1298             objRot[2] += rot.z();
1299         }
1300         else
1301         {
1302             objRot[0] = rot.x();
1303             objRot[1] = rot.y();
1304             objRot[2] = rot.z();
1305         }
1306 
1307         if(ptr.getClass().isActor())
1308         {
1309             /* HACK? Actors shouldn't really be rotating around X (or Y), but
1310              * currently it's done so for rotating the camera, which needs
1311              * clamping.
1312              */
1313             const float half_pi = pi/2.f;
1314 
1315             if(objRot[0] < -half_pi)     objRot[0] = -half_pi;
1316             else if(objRot[0] > half_pi) objRot[0] =  half_pi;
1317 
1318             wrap(objRot[1]);
1319             wrap(objRot[2]);
1320         }
1321 
1322         ptr.getRefData().setPosition(pos);
1323 
1324         mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
1325         mWorldScene->removeFromPagedRefs(ptr);
1326 
1327         if(ptr.getRefData().getBaseNode() != nullptr)
1328         {
1329             const auto order = flags & MWBase::RotationFlag_inverseOrder
1330                 ? RotationOrder::inverse : RotationOrder::direct;
1331             mWorldScene->updateObjectRotation(ptr, order);
1332 
1333             if (const auto object = mPhysics->getObject(ptr))
1334                 updateNavigatorObject(*object);
1335         }
1336     }
1337 
adjustPosition(const Ptr & ptr,bool force)1338     void World::adjustPosition(const Ptr &ptr, bool force)
1339     {
1340         if (ptr.isEmpty())
1341         {
1342             Log(Debug::Warning) << "Unable to adjust position for empty object";
1343             return;
1344         }
1345 
1346         osg::Vec3f pos (ptr.getRefData().getPosition().asVec3());
1347 
1348         if(!ptr.getRefData().getBaseNode())
1349         {
1350             // will be adjusted when Ptr's cell becomes active
1351             return;
1352         }
1353 
1354         if (!ptr.isInCell())
1355         {
1356             Log(Debug::Warning) << "Unable to adjust position for object '" << ptr.getCellRef().getRefId() << "' - it has no cell";
1357             return;
1358         }
1359 
1360         const float terrainHeight = ptr.getCell()->isExterior() ? getTerrainHeightAt(pos) : -std::numeric_limits<float>::max();
1361         pos.z() = std::max(pos.z(), terrainHeight) + 20; // place slightly above terrain. will snap down to ground with code below
1362 
1363         // We still should trace down dead persistent actors - they do not use the "swimdeath" animation.
1364         bool swims = ptr.getClass().isActor() && isSwimming(ptr) && !(ptr.getClass().isPersistent(ptr) && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished());
1365         if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !swims && isActorCollisionEnabled(ptr)))
1366         {
1367             osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits);
1368             pos.z() = std::min(pos.z(), traced.z());
1369         }
1370 
1371         moveObject(ptr, ptr.getCell(), pos.x(), pos.y(), pos.z());
1372     }
1373 
fixPosition()1374     void World::fixPosition()
1375     {
1376         const MWWorld::Ptr actor = getPlayerPtr();
1377         const float distance = 128.f;
1378         ESM::Position esmPos = actor.getRefData().getPosition();
1379         osg::Quat orientation(esmPos.rot[2], osg::Vec3f(0,0,-1));
1380         osg::Vec3f pos (esmPos.asVec3());
1381 
1382         int direction = 0;
1383         int fallbackDirections[4] = {direction, (direction+3)%4, (direction+2)%4, (direction+1)%4};
1384 
1385         osg::Vec3f targetPos = pos;
1386         for (int i=0; i<4; ++i)
1387         {
1388             direction = fallbackDirections[i];
1389             if (direction == 0) targetPos = pos + (orientation * osg::Vec3f(0,1,0)) * distance;
1390             else if(direction == 1) targetPos = pos - (orientation * osg::Vec3f(0,1,0)) * distance;
1391             else if(direction == 2) targetPos = pos - (orientation * osg::Vec3f(1,0,0)) * distance;
1392             else if(direction == 3) targetPos = pos + (orientation * osg::Vec3f(1,0,0)) * distance;
1393 
1394             // destination is free
1395             if (!castRay(pos.x(), pos.y(), pos.z(), targetPos.x(), targetPos.y(), targetPos.z()))
1396                 break;
1397         }
1398 
1399         targetPos.z() += distance / 2.f; // move up a bit to get out from geometry, will snap down later
1400         osg::Vec3f traced = mPhysics->traceDown(actor, targetPos, Constants::CellSizeInUnits);
1401         if (traced != pos)
1402         {
1403             esmPos.pos[0] = traced.x();
1404             esmPos.pos[1] = traced.y();
1405             esmPos.pos[2] = traced.z();
1406             MWWorld::ActionTeleport(actor.getCell()->isExterior() ? "" : actor.getCell()->getCell()->mName, esmPos, false).execute(actor);
1407         }
1408     }
1409 
rotateObject(const Ptr & ptr,float x,float y,float z,MWBase::RotationFlags flags)1410     void World::rotateObject (const Ptr& ptr, float x, float y, float z, MWBase::RotationFlags flags)
1411     {
1412         rotateObjectImp(ptr, osg::Vec3f(x, y, z), flags);
1413     }
1414 
rotateWorldObject(const Ptr & ptr,osg::Quat rotate)1415     void World::rotateWorldObject (const Ptr& ptr, osg::Quat rotate)
1416     {
1417         if(ptr.getRefData().getBaseNode() != nullptr)
1418         {
1419             mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
1420             mWorldScene->removeFromPagedRefs(ptr);
1421 
1422             mRendering->rotateObject(ptr, rotate);
1423             mPhysics->updateRotation(ptr);
1424 
1425             if (const auto object = mPhysics->getObject(ptr))
1426                 updateNavigatorObject(*object);
1427         }
1428     }
1429 
placeObject(const MWWorld::ConstPtr & ptr,MWWorld::CellStore * cell,ESM::Position pos)1430     MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, ESM::Position pos)
1431     {
1432         return copyObjectToCell(ptr,cell,pos,ptr.getRefData().getCount(),false);
1433     }
1434 
safePlaceObject(const ConstPtr & ptr,const ConstPtr & referenceObject,MWWorld::CellStore * referenceCell,int direction,float distance)1435     MWWorld::Ptr World::safePlaceObject(const ConstPtr &ptr, const ConstPtr &referenceObject, MWWorld::CellStore* referenceCell, int direction, float distance)
1436     {
1437         ESM::Position ipos = referenceObject.getRefData().getPosition();
1438         osg::Vec3f pos(ipos.asVec3());
1439         osg::Quat orientation(ipos.rot[2], osg::Vec3f(0,0,-1));
1440 
1441         int fallbackDirections[4] = {direction, (direction+3)%4, (direction+2)%4, (direction+1)%4};
1442 
1443         osg::Vec3f spawnPoint = pos;
1444 
1445         for (int i=0; i<4; ++i)
1446         {
1447             direction = fallbackDirections[i];
1448             if (direction == 0) spawnPoint = pos + (orientation * osg::Vec3f(0,1,0)) * distance;
1449             else if(direction == 1) spawnPoint = pos - (orientation * osg::Vec3f(0,1,0)) * distance;
1450             else if(direction == 2) spawnPoint = pos - (orientation * osg::Vec3f(1,0,0)) * distance;
1451             else if(direction == 3) spawnPoint = pos + (orientation * osg::Vec3f(1,0,0)) * distance;
1452 
1453             if (!ptr.getClass().isActor())
1454                 break;
1455 
1456             // check if spawn point is safe, fall back to another direction if not
1457             spawnPoint.z() += 30; // move up a little to account for slopes, will snap down later
1458 
1459             if (!castRay(spawnPoint.x(), spawnPoint.y(), spawnPoint.z(),
1460                                                                pos.x(), pos.y(), pos.z() + 20))
1461             {
1462                 // safe
1463                 break;
1464             }
1465         }
1466         ipos.pos[0] = spawnPoint.x();
1467         ipos.pos[1] = spawnPoint.y();
1468         ipos.pos[2] = spawnPoint.z();
1469 
1470         if (referenceObject.getClass().isActor())
1471         {
1472             ipos.rot[0] = 0;
1473             ipos.rot[1] = 0;
1474         }
1475 
1476         MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false);
1477         adjustPosition(placed, true); // snap to ground
1478         return placed;
1479     }
1480 
indexToPosition(int cellX,int cellY,float & x,float & y,bool centre) const1481     void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const
1482     {
1483         const int cellSize = Constants::CellSizeInUnits;
1484 
1485         x = static_cast<float>(cellSize * cellX);
1486         y = static_cast<float>(cellSize * cellY);
1487 
1488         if (centre)
1489         {
1490             x += cellSize/2;
1491             y += cellSize/2;
1492         }
1493     }
1494 
positionToIndex(float x,float y,int & cellX,int & cellY) const1495     void World::positionToIndex (float x, float y, int &cellX, int &cellY) const
1496     {
1497         cellX = static_cast<int>(std::floor(x / Constants::CellSizeInUnits));
1498         cellY = static_cast<int>(std::floor(y / Constants::CellSizeInUnits));
1499     }
1500 
queueMovement(const Ptr & ptr,const osg::Vec3f & velocity)1501     void World::queueMovement(const Ptr &ptr, const osg::Vec3f &velocity)
1502     {
1503         mPhysics->queueObjectMovement(ptr, velocity);
1504     }
1505 
updateAnimatedCollisionShape(const Ptr & ptr)1506     void World::updateAnimatedCollisionShape(const Ptr &ptr)
1507     {
1508         mPhysics->updateAnimatedCollisionShape(ptr);
1509     }
1510 
doPhysics(float duration,osg::Timer_t frameStart,unsigned int frameNumber,osg::Stats & stats)1511     void World::doPhysics(float duration, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
1512     {
1513         mPhysics->stepSimulation();
1514         processDoors(duration);
1515 
1516         mProjectileManager->update(duration);
1517 
1518         const auto& results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats);
1519         mProjectileManager->processHits();
1520         mDiscardMovements = false;
1521 
1522         for(const auto& actor : results)
1523         {
1524             // Handle player last, in case a cell transition occurs
1525             if(actor != getPlayerPtr())
1526             {
1527                 auto* physactor = mPhysics->getActor(actor);
1528                 assert(physactor);
1529                 const auto position = physactor->getSimulationPosition();
1530                 moveObject(actor, position.x(), position.y(), position.z(), false, false);
1531             }
1532         }
1533 
1534         const auto player = std::find(results.begin(), results.end(), getPlayerPtr());
1535         if (player != results.end())
1536         {
1537             auto* physactor = mPhysics->getActor(*player);
1538             assert(physactor);
1539             const auto position = physactor->getSimulationPosition();
1540             moveObject(*player, position.x(), position.y(), position.z(), false, false);
1541         }
1542     }
1543 
updateNavigator()1544     void World::updateNavigator()
1545     {
1546         mPhysics->forEachAnimatedObject([&] (const MWPhysics::Object* object) { updateNavigatorObject(*object); });
1547 
1548         for (const auto& door : mDoorStates)
1549             if (const auto object = mPhysics->getObject(door.first))
1550                 updateNavigatorObject(*object);
1551 
1552         if (mShouldUpdateNavigator)
1553         {
1554             mNavigator->update(getPlayerPtr().getRefData().getPosition().asVec3());
1555             mShouldUpdateNavigator = false;
1556         }
1557     }
1558 
updateNavigatorObject(const MWPhysics::Object & object)1559     void World::updateNavigatorObject(const MWPhysics::Object& object)
1560     {
1561         const DetourNavigator::ObjectShapes shapes(object.getShapeInstance());
1562         mShouldUpdateNavigator = mNavigator->updateObject(DetourNavigator::ObjectId(&object), shapes, object.getTransform())
1563             || mShouldUpdateNavigator;
1564     }
1565 
getRayCasting() const1566     const MWPhysics::RayCastingInterface* World::getRayCasting() const
1567     {
1568         return mPhysics.get();
1569     }
1570 
castRay(float x1,float y1,float z1,float x2,float y2,float z2)1571     bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2)
1572     {
1573         int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_Door;
1574         bool result = castRay(x1, y1, z1, x2, y2, z2, mask);
1575         return result;
1576     }
1577 
castRay(float x1,float y1,float z1,float x2,float y2,float z2,int mask)1578     bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2, int mask)
1579     {
1580         osg::Vec3f a(x1,y1,z1);
1581         osg::Vec3f b(x2,y2,z2);
1582 
1583         MWPhysics::RayCastingResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), std::vector<MWWorld::Ptr>(), mask);
1584         return result.mHit;
1585     }
1586 
castRay(const osg::Vec3f & from,const osg::Vec3f & to,int mask,const MWWorld::ConstPtr & ignore)1587     bool World::castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask, const MWWorld::ConstPtr& ignore)
1588     {
1589         return mPhysics->castRay(from, to, ignore, std::vector<MWWorld::Ptr>(), mask).mHit;
1590     }
1591 
rotateDoor(const Ptr door,MWWorld::DoorState state,float duration)1592     bool World::rotateDoor(const Ptr door, MWWorld::DoorState state, float duration)
1593     {
1594         const ESM::Position& objPos = door.getRefData().getPosition();
1595         float oldRot = objPos.rot[2];
1596 
1597         float minRot = door.getCellRef().getPosition().rot[2];
1598         float maxRot = minRot + osg::DegreesToRadians(90.f);
1599 
1600         float diff = duration * osg::DegreesToRadians(90.f) * (state == MWWorld::DoorState::Opening ? 1 : -1);
1601         float targetRot = std::min(std::max(minRot, oldRot + diff), maxRot);
1602         rotateObject(door, objPos.rot[0], objPos.rot[1], targetRot, MWBase::RotationFlag_none);
1603 
1604         bool reached = (targetRot == maxRot && state != MWWorld::DoorState::Idle) || targetRot == minRot;
1605 
1606         /// \todo should use convexSweepTest here
1607         bool collisionWithActor = false;
1608         for (auto& [ptr, point, normal] : mPhysics->getCollisionsPoints(door, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor))
1609         {
1610 
1611             if (ptr.getClass().isActor())
1612             {
1613                 auto localPoint = objPos.asVec3() - point;
1614                 osg::Vec3f direction = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * localPoint - localPoint;
1615                 direction.normalize();
1616                 mPhysics->reportCollision(Misc::Convert::toBullet(point), Misc::Convert::toBullet(normal));
1617                 if (direction * normal < 0) // door is turning away from actor
1618                     continue;
1619 
1620                 collisionWithActor = true;
1621 
1622                 // Collided with actor, ask actor to try to avoid door
1623                 if(ptr != getPlayerPtr() )
1624                 {
1625                     MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence();
1626                     if(seq.getTypeId() != MWMechanics::AiPackageTypeId::AvoidDoor) //Only add it once
1627                         seq.stack(MWMechanics::AiAvoidDoor(door),ptr);
1628                 }
1629 
1630                 // we need to undo the rotation
1631                 reached = false;
1632             }
1633         }
1634 
1635         // Cancel door closing sound if collision with actor is detected
1636         if (collisionWithActor)
1637         {
1638             const ESM::Door* ref = door.get<ESM::Door>()->mBase;
1639 
1640             if (state == MWWorld::DoorState::Opening)
1641             {
1642                 const std::string& openSound = ref->mOpenSound;
1643                 if (!openSound.empty() && MWBase::Environment::get().getSoundManager()->getSoundPlaying(door, openSound))
1644                     MWBase::Environment::get().getSoundManager()->stopSound3D(door, openSound);
1645             }
1646             else if (state == MWWorld::DoorState::Closing)
1647             {
1648                 const std::string& closeSound = ref->mCloseSound;
1649                 if (!closeSound.empty() && MWBase::Environment::get().getSoundManager()->getSoundPlaying(door, closeSound))
1650                     MWBase::Environment::get().getSoundManager()->stopSound3D(door, closeSound);
1651             }
1652 
1653             rotateObject(door, objPos.rot[0], objPos.rot[1], oldRot, MWBase::RotationFlag_none);
1654         }
1655 
1656         return reached;
1657     }
1658 
processDoors(float duration)1659     void World::processDoors(float duration)
1660     {
1661         auto it = mDoorStates.begin();
1662         while (it != mDoorStates.end())
1663         {
1664             if (!mWorldScene->isCellActive(*it->first.getCell()) || !it->first.getRefData().getBaseNode())
1665             {
1666                 // The door is no longer in an active cell, or it was disabled.
1667                 // Erase from mDoorStates, since we no longer need to move it.
1668                 // Once we load the door's cell again (or re-enable the door), Door::insertObject will reinsert to mDoorStates.
1669                 mDoorStates.erase(it++);
1670             }
1671             else
1672             {
1673                 bool reached = rotateDoor(it->first, it->second, duration);
1674 
1675                 if (reached)
1676                 {
1677                     // Mark as non-moving
1678                     it->first.getClass().setDoorState(it->first, MWWorld::DoorState::Idle);
1679                     mDoorStates.erase(it++);
1680                 }
1681                 else
1682                     ++it;
1683             }
1684         }
1685     }
1686 
setActorCollisionMode(const MWWorld::Ptr & ptr,bool internal,bool external)1687     void World::setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external)
1688     {
1689         MWPhysics::Actor *physicActor = mPhysics->getActor(ptr);
1690         if (physicActor && physicActor->getCollisionMode() != internal)
1691         {
1692             physicActor->enableCollisionMode(internal);
1693             physicActor->enableCollisionBody(external);
1694         }
1695     }
1696 
isActorCollisionEnabled(const MWWorld::Ptr & ptr)1697     bool World::isActorCollisionEnabled(const MWWorld::Ptr& ptr)
1698     {
1699         MWPhysics::Actor *physicActor = mPhysics->getActor(ptr);
1700         return physicActor && physicActor->getCollisionMode();
1701     }
1702 
toggleCollisionMode()1703     bool World::toggleCollisionMode()
1704     {
1705         if (mPhysics->toggleCollisionMode())
1706         {
1707             adjustPosition(getPlayerPtr(), true);
1708             return true;
1709         }
1710 
1711         return false;
1712     }
1713 
toggleRenderMode(MWRender::RenderMode mode)1714     bool World::toggleRenderMode (MWRender::RenderMode mode)
1715     {
1716         switch (mode)
1717         {
1718             case MWRender::Render_CollisionDebug:
1719                 return mPhysics->toggleDebugRendering();
1720             default:
1721                 return mRendering->toggleRenderMode(mode);
1722         }
1723     }
1724 
createRecord(const ESM::Potion & record)1725     const ESM::Potion *World::createRecord (const ESM::Potion& record)
1726     {
1727         return mStore.insert(record);
1728     }
1729 
createRecord(const ESM::Class & record)1730     const ESM::Class *World::createRecord (const ESM::Class& record)
1731     {
1732         return mStore.insert(record);
1733     }
1734 
createRecord(const ESM::Spell & record)1735     const ESM::Spell *World::createRecord (const ESM::Spell& record)
1736     {
1737         return mStore.insert(record);
1738     }
1739 
createRecord(const ESM::Cell & record)1740     const ESM::Cell *World::createRecord (const ESM::Cell& record)
1741     {
1742         return mStore.insert(record);
1743     }
1744 
createOverrideRecord(const ESM::CreatureLevList & record)1745     const ESM::CreatureLevList *World::createOverrideRecord(const ESM::CreatureLevList &record)
1746     {
1747         return mStore.overrideRecord(record);
1748     }
1749 
createOverrideRecord(const ESM::ItemLevList & record)1750     const ESM::ItemLevList *World::createOverrideRecord(const ESM::ItemLevList &record)
1751     {
1752         return mStore.overrideRecord(record);
1753     }
1754 
createOverrideRecord(const ESM::Creature & record)1755     const ESM::Creature *World::createOverrideRecord(const ESM::Creature &record)
1756     {
1757         return mStore.overrideRecord(record);
1758     }
1759 
createOverrideRecord(const ESM::NPC & record)1760     const ESM::NPC *World::createOverrideRecord(const ESM::NPC &record)
1761     {
1762         return mStore.overrideRecord(record);
1763     }
1764 
createOverrideRecord(const ESM::Container & record)1765     const ESM::Container *World::createOverrideRecord(const ESM::Container &record)
1766     {
1767         return mStore.overrideRecord(record);
1768     }
1769 
createRecord(const ESM::NPC & record)1770     const ESM::NPC *World::createRecord(const ESM::NPC &record)
1771     {
1772         bool update = false;
1773 
1774         if (Misc::StringUtils::ciEqual(record.mId, "player"))
1775         {
1776             const ESM::NPC *player =
1777                 mPlayer->getPlayer().get<ESM::NPC>()->mBase;
1778 
1779             update = record.isMale() != player->isMale() ||
1780                      !Misc::StringUtils::ciEqual(record.mRace, player->mRace) ||
1781                      !Misc::StringUtils::ciEqual(record.mHead, player->mHead) ||
1782                      !Misc::StringUtils::ciEqual(record.mHair, player->mHair);
1783         }
1784         const ESM::NPC *ret = mStore.insert(record);
1785         if (update) {
1786             renderPlayer();
1787         }
1788         return ret;
1789     }
1790 
createRecord(const ESM::Armor & record)1791     const ESM::Armor *World::createRecord (const ESM::Armor& record)
1792     {
1793         return mStore.insert(record);
1794     }
1795 
createRecord(const ESM::Weapon & record)1796     const ESM::Weapon *World::createRecord (const ESM::Weapon& record)
1797     {
1798         return mStore.insert(record);
1799     }
1800 
createRecord(const ESM::Clothing & record)1801     const ESM::Clothing *World::createRecord (const ESM::Clothing& record)
1802     {
1803         return mStore.insert(record);
1804     }
1805 
createRecord(const ESM::Enchantment & record)1806     const ESM::Enchantment *World::createRecord (const ESM::Enchantment& record)
1807     {
1808         return mStore.insert(record);
1809     }
1810 
createRecord(const ESM::Book & record)1811     const ESM::Book *World::createRecord (const ESM::Book& record)
1812     {
1813         return mStore.insert(record);
1814     }
1815 
update(float duration,bool paused)1816     void World::update (float duration, bool paused)
1817     {
1818         if (mGoToJail && !paused)
1819             goToJail();
1820 
1821         // Reset "traveling" flag - there was a frame to detect traveling.
1822         mPlayerTraveling = false;
1823 
1824         // The same thing for "in jail" flag: reset it if:
1825         // 1. Player was in jail
1826         // 2. Jailing window was closed
1827         if (mPlayerInJail && !mGoToJail && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Jail))
1828             mPlayerInJail = false;
1829 
1830         updateWeather(duration, paused);
1831 
1832         if (!paused)
1833         {
1834             updateNavigator();
1835         }
1836 
1837         updatePlayer();
1838 
1839         mPhysics->debugDraw();
1840 
1841         mWorldScene->update (duration, paused);
1842 
1843         updateSoundListener();
1844 
1845         mSpellPreloadTimer -= duration;
1846         if (mSpellPreloadTimer <= 0.f)
1847         {
1848             mSpellPreloadTimer = 0.1f;
1849             preloadSpells();
1850         }
1851     }
1852 
updatePhysics(float duration,bool paused,osg::Timer_t frameStart,unsigned int frameNumber,osg::Stats & stats)1853     void World::updatePhysics (float duration, bool paused, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
1854     {
1855         if (!paused)
1856         {
1857             doPhysics (duration, frameStart, frameNumber, stats);
1858         }
1859         else
1860         {
1861             // zero the async stats if we are paused
1862             stats.setAttribute(frameNumber, "physicsworker_time_begin", 0);
1863             stats.setAttribute(frameNumber, "physicsworker_time_taken", 0);
1864             stats.setAttribute(frameNumber, "physicsworker_time_end", 0);
1865         }
1866     }
1867 
updatePlayer()1868     void World::updatePlayer()
1869     {
1870         MWWorld::Ptr player = getPlayerPtr();
1871 
1872         // TODO: move to MWWorld::Player
1873 
1874         if (player.getCell()->isExterior())
1875         {
1876             ESM::Position pos = player.getRefData().getPosition();
1877             mPlayer->setLastKnownExteriorPosition(pos.asVec3());
1878         }
1879 
1880         bool isWerewolf = player.getClass().getNpcStats(player).isWerewolf();
1881         bool isFirstPerson = mRendering->getCamera()->isFirstPerson();
1882         if (isWerewolf && isFirstPerson)
1883         {
1884             float werewolfFov = Fallback::Map::getFloat("General_Werewolf_FOV");
1885             if (werewolfFov != 0)
1886                 mRendering->overrideFieldOfView(werewolfFov);
1887             MWBase::Environment::get().getWindowManager()->setWerewolfOverlay(true);
1888         }
1889         else
1890         {
1891             mRendering->resetFieldOfView();
1892             MWBase::Environment::get().getWindowManager()->setWerewolfOverlay(false);
1893         }
1894 
1895         // Sink the camera while sneaking
1896         bool sneaking = player.getClass().getCreatureStats(getPlayerPtr()).getStance(MWMechanics::CreatureStats::Stance_Sneak);
1897         bool swimming = isSwimming(player);
1898         bool flying = isFlying(player);
1899 
1900         static const float i1stPersonSneakDelta = mStore.get<ESM::GameSetting>().find("i1stPersonSneakDelta")->mValue.getFloat();
1901         if (sneaking && !swimming && !flying)
1902             mRendering->getCamera()->setSneakOffset(i1stPersonSneakDelta);
1903         else
1904             mRendering->getCamera()->setSneakOffset(0.f);
1905 
1906         int blind = 0;
1907         auto& magicEffects = player.getClass().getCreatureStats(player).getMagicEffects();
1908         if (!mGodMode)
1909             blind = static_cast<int>(magicEffects.get(ESM::MagicEffect::Blind).getMagnitude());
1910         MWBase::Environment::get().getWindowManager()->setBlindness(std::max(0, std::min(100, blind)));
1911 
1912         int nightEye = static_cast<int>(magicEffects.get(ESM::MagicEffect::NightEye).getMagnitude());
1913         mRendering->setNightEyeFactor(std::min(1.f, (nightEye/100.f)));
1914     }
1915 
preloadSpells()1916     void World::preloadSpells()
1917     {
1918         std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell();
1919         if (!selectedSpell.empty())
1920         {
1921             const ESM::Spell* spell = mStore.get<ESM::Spell>().search(selectedSpell);
1922             if (spell)
1923                 preloadEffects(&spell->mEffects);
1924         }
1925         const MWWorld::Ptr& selectedEnchantItem = MWBase::Environment::get().getWindowManager()->getSelectedEnchantItem();
1926         if (!selectedEnchantItem.isEmpty())
1927         {
1928             std::string enchantId = selectedEnchantItem.getClass().getEnchantment(selectedEnchantItem);
1929             if (!enchantId.empty())
1930             {
1931                 const ESM::Enchantment* ench = mStore.get<ESM::Enchantment>().search(enchantId);
1932                 if (ench)
1933                     preloadEffects(&ench->mEffects);
1934             }
1935         }
1936         const MWWorld::Ptr& selectedWeapon = MWBase::Environment::get().getWindowManager()->getSelectedWeapon();
1937         if (!selectedWeapon.isEmpty())
1938         {
1939             std::string enchantId = selectedWeapon.getClass().getEnchantment(selectedWeapon);
1940             if (!enchantId.empty())
1941             {
1942                 const ESM::Enchantment* ench = mStore.get<ESM::Enchantment>().search(enchantId);
1943                 if (ench && ench->mData.mType == ESM::Enchantment::WhenStrikes)
1944                     preloadEffects(&ench->mEffects);
1945             }
1946         }
1947     }
1948 
updateSoundListener()1949     void World::updateSoundListener()
1950     {
1951         const ESM::Position& refpos = getPlayerPtr().getRefData().getPosition();
1952         osg::Vec3f listenerPos;
1953 
1954         if (isFirstPerson())
1955             listenerPos = mRendering->getCameraPosition();
1956         else
1957             listenerPos = refpos.asVec3() + osg::Vec3f(0, 0, 1.85f * mPhysics->getHalfExtents(getPlayerPtr()).z());
1958 
1959         osg::Quat listenerOrient = osg::Quat(refpos.rot[1], osg::Vec3f(0,-1,0)) *
1960                 osg::Quat(refpos.rot[0], osg::Vec3f(-1,0,0)) *
1961                 osg::Quat(refpos.rot[2], osg::Vec3f(0,0,-1));
1962 
1963         osg::Vec3f forward = listenerOrient * osg::Vec3f(0,1,0);
1964         osg::Vec3f up = listenerOrient * osg::Vec3f(0,0,1);
1965 
1966         bool underwater = isUnderwater(getPlayerPtr().getCell(), mRendering->getCameraPosition());
1967 
1968         MWBase::Environment::get().getSoundManager()->setListenerPosDir(listenerPos, forward, up, underwater);
1969     }
1970 
updateWindowManager()1971     void World::updateWindowManager ()
1972     {
1973         try
1974         {
1975             // inform the GUI about focused object
1976             MWWorld::Ptr object = getFacedObject ();
1977 
1978             // retrieve object dimensions so we know where to place the floating label
1979             if (!object.isEmpty ())
1980             {
1981                 osg::BoundingBox bb = mPhysics->getBoundingBox(object);
1982                 if (!bb.valid() && object.getRefData().getBaseNode())
1983                 {
1984                     osg::ComputeBoundsVisitor computeBoundsVisitor;
1985                     computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect));
1986                     object.getRefData().getBaseNode()->accept(computeBoundsVisitor);
1987                     bb = computeBoundsVisitor.getBoundingBox();
1988                 }
1989                 osg::Vec4f screenBounds = mRendering->getScreenBounds(bb);
1990                 MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords(
1991                     screenBounds.x(), screenBounds.y(), screenBounds.z(), screenBounds.w());
1992             }
1993 
1994             MWBase::Environment::get().getWindowManager()->setFocusObject(object);
1995         }
1996         catch (std::exception& e)
1997         {
1998             Log(Debug::Error) << "Error updating window manager: " << e.what();
1999         }
2000     }
2001 
getFacedObject(float maxDistance,bool ignorePlayer)2002     MWWorld::Ptr World::getFacedObject(float maxDistance, bool ignorePlayer)
2003     {
2004         const float camDist = mRendering->getCamera()->getCameraDistance();
2005         maxDistance += camDist;
2006         MWWorld::Ptr facedObject;
2007         MWRender::RenderingManager::RayResult rayToObject;
2008 
2009         if (MWBase::Environment::get().getWindowManager()->isGuiMode())
2010         {
2011             float x, y;
2012             MWBase::Environment::get().getWindowManager()->getMousePosition(x, y);
2013             rayToObject = mRendering->castCameraToViewportRay(x, y, maxDistance, ignorePlayer);
2014         }
2015         else
2016             rayToObject = mRendering->castCameraToViewportRay(0.5f, 0.5f, maxDistance, ignorePlayer);
2017 
2018         facedObject = rayToObject.mHitObject;
2019         if (facedObject.isEmpty() && rayToObject.mHitRefnum.hasContentFile())
2020         {
2021             for (CellStore* cellstore : mWorldScene->getActiveCells())
2022             {
2023                 facedObject = cellstore->searchViaRefNum(rayToObject.mHitRefnum);
2024                 if (!facedObject.isEmpty()) break;
2025             }
2026         }
2027         if (rayToObject.mHit)
2028             mDistanceToFacedObject = (rayToObject.mRatio * maxDistance) - camDist;
2029         else
2030             mDistanceToFacedObject = -1;
2031         return facedObject;
2032     }
2033 
isCellExterior() const2034     bool World::isCellExterior() const
2035     {
2036         const CellStore *currentCell = mWorldScene->getCurrentCell();
2037         if (currentCell)
2038         {
2039             return currentCell->getCell()->isExterior();
2040         }
2041         return false;
2042     }
2043 
isCellQuasiExterior() const2044     bool World::isCellQuasiExterior() const
2045     {
2046         const CellStore *currentCell = mWorldScene->getCurrentCell();
2047         if (currentCell)
2048         {
2049             if (!(currentCell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
2050                 return false;
2051             else
2052                 return true;
2053         }
2054         return false;
2055     }
2056 
getCurrentWeather() const2057     int World::getCurrentWeather() const
2058     {
2059         return mWeatherManager->getWeatherID();
2060     }
2061 
getNightDayMode() const2062     unsigned int World::getNightDayMode() const
2063     {
2064         return mWeatherManager->getNightDayMode();
2065     }
2066 
changeWeather(const std::string & region,const unsigned int id)2067     void World::changeWeather(const std::string& region, const unsigned int id)
2068     {
2069         mWeatherManager->changeWeather(region, id);
2070     }
2071 
modRegion(const std::string & regionid,const std::vector<char> & chances)2072     void World::modRegion(const std::string &regionid, const std::vector<char> &chances)
2073     {
2074         mWeatherManager->modRegion(regionid, chances);
2075     }
2076 
getNorthVector(const CellStore * cell)2077     osg::Vec2f World::getNorthVector (const CellStore* cell)
2078     {
2079         MWWorld::ConstPtr northmarker = cell->searchConst("northmarker");
2080 
2081         if (northmarker.isEmpty())
2082             return osg::Vec2f(0, 1);
2083 
2084         osg::Quat orient (-northmarker.getRefData().getPosition().rot[2], osg::Vec3f(0,0,1));
2085         osg::Vec3f dir = orient * osg::Vec3f(0,1,0);
2086         osg::Vec2f d (dir.x(), dir.y());
2087         return d;
2088     }
2089 
2090     struct GetDoorMarkerVisitor
2091     {
GetDoorMarkerVisitorMWWorld::GetDoorMarkerVisitor2092         GetDoorMarkerVisitor(std::vector<World::DoorMarker>& out)
2093             : mOut(out)
2094         {
2095         }
2096 
2097         std::vector<World::DoorMarker>& mOut;
2098 
operator ()MWWorld::GetDoorMarkerVisitor2099         bool operator()(const MWWorld::Ptr& ptr)
2100         {
2101             MWWorld::LiveCellRef<ESM::Door>& ref = *static_cast<MWWorld::LiveCellRef<ESM::Door>* >(ptr.getBase());
2102 
2103             if (!ref.mData.isEnabled() || ref.mData.isDeleted())
2104                 return true;
2105 
2106             if (ref.mRef.getTeleport())
2107             {
2108                 World::DoorMarker newMarker;
2109                 newMarker.name = MWClass::Door::getDestination(ref);
2110 
2111                 ESM::CellId cellid;
2112                 if (!ref.mRef.getDestCell().empty())
2113                 {
2114                     cellid.mWorldspace = ref.mRef.getDestCell();
2115                     cellid.mPaged = false;
2116                     cellid.mIndex.mX = 0;
2117                     cellid.mIndex.mY = 0;
2118                 }
2119                 else
2120                 {
2121                     cellid.mPaged = true;
2122                     MWBase::Environment::get().getWorld()->positionToIndex(
2123                                 ref.mRef.getDoorDest().pos[0],
2124                                 ref.mRef.getDoorDest().pos[1],
2125                                 cellid.mIndex.mX,
2126                                 cellid.mIndex.mY);
2127                 }
2128                 newMarker.dest = cellid;
2129 
2130                 ESM::Position pos = ref.mData.getPosition ();
2131 
2132                 newMarker.x = pos.pos[0];
2133                 newMarker.y = pos.pos[1];
2134                 mOut.push_back(newMarker);
2135             }
2136             return true;
2137         }
2138     };
2139 
getDoorMarkers(CellStore * cell,std::vector<World::DoorMarker> & out)2140     void World::getDoorMarkers (CellStore* cell, std::vector<World::DoorMarker>& out)
2141     {
2142         GetDoorMarkerVisitor visitor(out);
2143         cell->forEachType<ESM::Door>(visitor);
2144     }
2145 
setWaterHeight(const float height)2146     void World::setWaterHeight(const float height)
2147     {
2148         mPhysics->setWaterHeight(height);
2149         mRendering->setWaterHeight(height);
2150     }
2151 
toggleWater()2152     bool World::toggleWater()
2153     {
2154         return mRendering->toggleRenderMode(MWRender::Render_Water);
2155     }
2156 
toggleWorld()2157     bool World::toggleWorld()
2158     {
2159         return mRendering->toggleRenderMode(MWRender::Render_Scene);
2160     }
2161 
toggleBorders()2162     bool World::toggleBorders()
2163     {
2164         return mRendering->toggleBorders();
2165     }
2166 
PCDropped(const Ptr & item)2167     void World::PCDropped (const Ptr& item)
2168     {
2169         std::string script = item.getClass().getScript(item);
2170 
2171         // Set OnPCDrop Variable on item's script, if it has a script with that variable declared
2172         if(script != "")
2173             item.getRefData().getLocals().setVarByInt(script, "onpcdrop", 1);
2174     }
2175 
placeObject(const MWWorld::ConstPtr & object,float cursorX,float cursorY,int amount)2176     MWWorld::Ptr World::placeObject (const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount)
2177     {
2178         const float maxDist = 200.f;
2179 
2180         MWRender::RenderingManager::RayResult result = mRendering->castCameraToViewportRay(cursorX, cursorY, maxDist, true, true);
2181 
2182         CellStore* cell = getPlayerPtr().getCell();
2183 
2184         ESM::Position pos = getPlayerPtr().getRefData().getPosition();
2185         if (result.mHit)
2186         {
2187             pos.pos[0] = result.mHitPointWorld.x();
2188             pos.pos[1] = result.mHitPointWorld.y();
2189             pos.pos[2] = result.mHitPointWorld.z();
2190         }
2191         // We want only the Z part of the player's rotation
2192         pos.rot[0] = 0;
2193         pos.rot[1] = 0;
2194 
2195         // copy the object and set its count
2196         Ptr dropped = copyObjectToCell(object, cell, pos, amount, true);
2197 
2198         // only the player place items in the world, so no need to check actor
2199         PCDropped(dropped);
2200 
2201         return dropped;
2202     }
2203 
canPlaceObject(float cursorX,float cursorY)2204     bool World::canPlaceObject(float cursorX, float cursorY)
2205     {
2206         const float maxDist = 200.f;
2207         MWRender::RenderingManager::RayResult result = mRendering->castCameraToViewportRay(cursorX, cursorY, maxDist, true, true);
2208 
2209         if (result.mHit)
2210         {
2211             // check if the wanted position is on a flat surface, and not e.g. against a vertical wall
2212             if (std::acos((result.mHitNormalWorld/result.mHitNormalWorld.length()) * osg::Vec3f(0,0,1)) >= osg::DegreesToRadians(30.f))
2213                 return false;
2214 
2215             return true;
2216         }
2217         else
2218             return false;
2219     }
2220 
2221 
copyObjectToCell(const ConstPtr & object,CellStore * cell,ESM::Position pos,int count,bool adjustPos)2222     Ptr World::copyObjectToCell(const ConstPtr &object, CellStore* cell, ESM::Position pos, int count, bool adjustPos)
2223     {
2224         if (!cell)
2225             throw std::runtime_error("copyObjectToCell(): cannot copy object to null cell");
2226         if (cell->isExterior())
2227         {
2228             int cellX, cellY;
2229             positionToIndex(pos.pos[0], pos.pos[1], cellX, cellY);
2230             cell = mCells.getExterior(cellX, cellY);
2231         }
2232 
2233         MWWorld::Ptr dropped =
2234             object.getClass().copyToCell(object, *cell, pos, count);
2235 
2236         // Reset some position values that could be uninitialized if this item came from a container
2237         dropped.getCellRef().setPosition(pos);
2238         dropped.getCellRef().unsetRefNum();
2239 
2240         if (mWorldScene->isCellActive(*cell)) {
2241             if (dropped.getRefData().isEnabled()) {
2242                 mWorldScene->addObjectToScene(dropped);
2243             }
2244             std::string script = dropped.getClass().getScript(dropped);
2245             if (!script.empty()) {
2246                 mLocalScripts.add(script, dropped);
2247             }
2248             addContainerScripts(dropped, cell);
2249         }
2250 
2251         if (!object.getClass().isActor() && adjustPos && dropped.getRefData().getBaseNode())
2252         {
2253             // Adjust position so the location we wanted ends up in the middle of the object bounding box
2254             osg::ComputeBoundsVisitor computeBounds;
2255             computeBounds.setTraversalMask(~MWRender::Mask_ParticleSystem);
2256             dropped.getRefData().getBaseNode()->accept(computeBounds);
2257             osg::BoundingBox bounds = computeBounds.getBoundingBox();
2258             if (bounds.valid())
2259             {
2260                 bounds.set(bounds._min - pos.asVec3(), bounds._max - pos.asVec3());
2261 
2262                 osg::Vec3f adjust (
2263                             (bounds.xMin() + bounds.xMax()) / 2,
2264                            (bounds.yMin() + bounds.yMax()) / 2,
2265                            bounds.zMin()
2266                            );
2267                 pos.pos[0] -= adjust.x();
2268                 pos.pos[1] -= adjust.y();
2269                 pos.pos[2] -= adjust.z();
2270                 moveObject(dropped, pos.pos[0], pos.pos[1], pos.pos[2]);
2271             }
2272         }
2273 
2274         return dropped;
2275     }
2276 
dropObjectOnGround(const Ptr & actor,const ConstPtr & object,int amount)2277     MWWorld::Ptr World::dropObjectOnGround (const Ptr& actor, const ConstPtr& object, int amount)
2278     {
2279         MWWorld::CellStore* cell = actor.getCell();
2280 
2281         ESM::Position pos =
2282             actor.getRefData().getPosition();
2283         // We want only the Z part of the actor's rotation
2284         pos.rot[0] = 0;
2285         pos.rot[1] = 0;
2286 
2287         osg::Vec3f orig = pos.asVec3();
2288         orig.z() += 20;
2289         osg::Vec3f dir (0, 0, -1);
2290 
2291         float len = 1000000.0;
2292 
2293         MWRender::RenderingManager::RayResult result = mRendering->castRay(orig, orig+dir*len, true, true);
2294         if (result.mHit)
2295             pos.pos[2] = result.mHitPointWorld.z();
2296 
2297         // copy the object and set its count
2298         Ptr dropped = copyObjectToCell(object, cell, pos, amount, true);
2299 
2300         if(actor == mPlayer->getPlayer()) // Only call if dropped by player
2301             PCDropped(dropped);
2302         return dropped;
2303     }
2304 
processChangedSettings(const Settings::CategorySettingVector & settings)2305     void World::processChangedSettings(const Settings::CategorySettingVector& settings)
2306     {
2307         mRendering->processChangedSettings(settings);
2308     }
2309 
isFlying(const MWWorld::Ptr & ptr) const2310     bool World::isFlying(const MWWorld::Ptr &ptr) const
2311     {
2312         if(!ptr.getClass().isActor())
2313             return false;
2314 
2315         const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr);
2316 
2317         if (stats.isDead())
2318             return false;
2319 
2320         const bool isPlayer = ptr == getPlayerConstPtr();
2321         if (!(isPlayer && mGodMode) && stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getModifier() > 0)
2322             return false;
2323 
2324         if (ptr.getClass().canFly(ptr))
2325             return true;
2326 
2327         if(stats.getMagicEffects().get(ESM::MagicEffect::Levitate).getMagnitude() > 0
2328                 && isLevitationEnabled())
2329             return true;
2330 
2331         const MWPhysics::Actor* actor = mPhysics->getActor(ptr);
2332         if(!actor)
2333             return true;
2334 
2335         return false;
2336     }
2337 
isSlowFalling(const MWWorld::Ptr & ptr) const2338     bool World::isSlowFalling(const MWWorld::Ptr &ptr) const
2339     {
2340         if(!ptr.getClass().isActor())
2341             return false;
2342 
2343         const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr);
2344         if(stats.getMagicEffects().get(ESM::MagicEffect::SlowFall).getMagnitude() > 0)
2345             return true;
2346 
2347         return false;
2348     }
2349 
isSubmerged(const MWWorld::ConstPtr & object) const2350     bool World::isSubmerged(const MWWorld::ConstPtr &object) const
2351     {
2352         return isUnderwater(object, 1.0f/mSwimHeightScale);
2353     }
2354 
isSwimming(const MWWorld::ConstPtr & object) const2355     bool World::isSwimming(const MWWorld::ConstPtr &object) const
2356     {
2357         return isUnderwater(object, mSwimHeightScale);
2358     }
2359 
isWading(const MWWorld::ConstPtr & object) const2360     bool World::isWading(const MWWorld::ConstPtr &object) const
2361     {
2362         const float kneeDeep = 0.25f;
2363         return isUnderwater(object, kneeDeep);
2364     }
2365 
isUnderwater(const MWWorld::ConstPtr & object,const float heightRatio) const2366     bool World::isUnderwater(const MWWorld::ConstPtr &object, const float heightRatio) const
2367     {
2368         osg::Vec3f pos (object.getRefData().getPosition().asVec3());
2369 
2370         pos.z() += heightRatio*2*mPhysics->getRenderingHalfExtents(object).z();
2371 
2372         const CellStore *currCell = object.isInCell() ? object.getCell() : nullptr; // currCell == nullptr should only happen for player, during initial startup
2373 
2374         return isUnderwater(currCell, pos);
2375     }
2376 
isUnderwater(const MWWorld::CellStore * cell,const osg::Vec3f & pos) const2377     bool World::isUnderwater(const MWWorld::CellStore* cell, const osg::Vec3f &pos) const
2378     {
2379         if (!cell)
2380             return false;
2381 
2382         if (!(cell->getCell()->hasWater())) {
2383             return false;
2384         }
2385         return pos.z() < cell->getWaterLevel();
2386     }
2387 
isWaterWalkingCastableOnTarget(const MWWorld::ConstPtr & target) const2388     bool World::isWaterWalkingCastableOnTarget(const MWWorld::ConstPtr &target) const
2389     {
2390         const MWWorld::CellStore* cell = target.getCell();
2391         if (!cell->getCell()->hasWater())
2392             return true;
2393 
2394         float waterlevel = cell->getWaterLevel();
2395 
2396         // SwimHeightScale affects the upper z position an actor can swim to
2397         // while in water. Based on observation from the original engine,
2398         // the upper z position you get with a +1 SwimHeightScale is the depth
2399         // limit for being able to cast water walking on an underwater target.
2400         if (isUnderwater(target, mSwimHeightScale + 1) || (isUnderwater(cell, target.getRefData().getPosition().asVec3()) && !mPhysics->canMoveToWaterSurface(target, waterlevel)))
2401             return false; // not castable if too deep or if not enough room to move actor to surface
2402         else
2403             return true;
2404     }
2405 
isOnGround(const MWWorld::Ptr & ptr) const2406     bool World::isOnGround(const MWWorld::Ptr &ptr) const
2407     {
2408         return mPhysics->isOnGround(ptr);
2409     }
2410 
togglePOV(bool force)2411     void World::togglePOV(bool force)
2412     {
2413         mRendering->getCamera()->toggleViewMode(force);
2414     }
2415 
isFirstPerson() const2416     bool World::isFirstPerson() const
2417     {
2418         return mRendering->getCamera()->isFirstPerson();
2419     }
2420 
isPreviewModeEnabled() const2421     bool World::isPreviewModeEnabled() const
2422     {
2423         return mRendering->getCamera()->getMode() == MWRender::Camera::Mode::Preview;
2424     }
2425 
togglePreviewMode(bool enable)2426     void World::togglePreviewMode(bool enable)
2427     {
2428         mRendering->getCamera()->togglePreviewMode(enable);
2429     }
2430 
toggleVanityMode(bool enable)2431     bool World::toggleVanityMode(bool enable)
2432     {
2433         return mRendering->getCamera()->toggleVanityMode(enable);
2434     }
2435 
disableDeferredPreviewRotation()2436     void World::disableDeferredPreviewRotation()
2437     {
2438         mRendering->getCamera()->disableDeferredPreviewRotation();
2439     }
2440 
applyDeferredPreviewRotationToPlayer(float dt)2441     void World::applyDeferredPreviewRotationToPlayer(float dt)
2442     {
2443         mRendering->getCamera()->applyDeferredPreviewRotationToPlayer(dt);
2444     }
2445 
allowVanityMode(bool allow)2446     void World::allowVanityMode(bool allow)
2447     {
2448         mRendering->getCamera()->allowVanityMode(allow);
2449     }
2450 
vanityRotateCamera(float * rot)2451     bool World::vanityRotateCamera(float * rot)
2452     {
2453         if(!mRendering->getCamera()->isVanityOrPreviewModeEnabled())
2454             return false;
2455 
2456         mRendering->getCamera()->rotateCamera(rot[0], rot[2], true);
2457         return true;
2458     }
2459 
adjustCameraDistance(float dist)2460     void World::adjustCameraDistance(float dist)
2461     {
2462         mRendering->getCamera()->adjustCameraDistance(dist);
2463     }
2464 
saveLoaded()2465     void World::saveLoaded()
2466     {
2467         mStore.validateDynamic();
2468     }
2469 
setupPlayer()2470     void World::setupPlayer()
2471     {
2472         const ESM::NPC *player = mStore.get<ESM::NPC>().find("player");
2473         if (!mPlayer)
2474             mPlayer.reset(new MWWorld::Player(player));
2475         else
2476         {
2477             // Remove the old CharacterController
2478             MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr());
2479             mNavigator->removeAgent(getPathfindingHalfExtents(getPlayerConstPtr()));
2480             mPhysics->remove(getPlayerPtr());
2481             mRendering->removePlayer(getPlayerPtr());
2482 
2483             mPlayer->set(player);
2484         }
2485 
2486         Ptr ptr = mPlayer->getPlayer();
2487         mRendering->setupPlayer(ptr);
2488     }
2489 
renderPlayer()2490     void World::renderPlayer()
2491     {
2492         MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr());
2493 
2494         MWWorld::Ptr player = getPlayerPtr();
2495 
2496         mRendering->renderPlayer(player);
2497         MWRender::NpcAnimation* anim = static_cast<MWRender::NpcAnimation*>(mRendering->getAnimation(player));
2498         player.getClass().getInventoryStore(player).setInvListener(anim, player);
2499         player.getClass().getInventoryStore(player).setContListener(anim);
2500 
2501         scaleObject(player, player.getCellRef().getScale()); // apply race height
2502         rotateObject(player, 0.f, 0.f, 0.f, MWBase::RotationFlag_inverseOrder | MWBase::RotationFlag_adjust);
2503 
2504         MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr());
2505         MWBase::Environment::get().getWindowManager()->watchActor(getPlayerPtr());
2506 
2507         std::string model = getPlayerPtr().getClass().getModel(getPlayerPtr());
2508         model = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS());
2509         mPhysics->remove(getPlayerPtr());
2510         mPhysics->addActor(getPlayerPtr(), model);
2511 
2512         applyLoopingParticles(player);
2513 
2514         mDefaultHalfExtents = mPhysics->getOriginalHalfExtents(getPlayerPtr());
2515         mNavigator->addAgent(getPathfindingHalfExtents(getPlayerConstPtr()));
2516     }
2517 
canRest() const2518     World::RestPermitted World::canRest () const
2519     {
2520         CellStore *currentCell = mWorldScene->getCurrentCell();
2521 
2522         Ptr player = mPlayer->getPlayer();
2523         RefData &refdata = player.getRefData();
2524         osg::Vec3f playerPos(refdata.getPosition().asVec3());
2525 
2526         const MWPhysics::Actor* actor = mPhysics->getActor(player);
2527         if (!actor)
2528             throw std::runtime_error("can't find player");
2529 
2530         if(mPlayer->enemiesNearby())
2531             return Rest_EnemiesAreNearby;
2532 
2533         if (isUnderwater(currentCell, playerPos) || isWalkingOnWater(player))
2534             return Rest_PlayerIsUnderwater;
2535 
2536         float fallHeight = player.getClass().getCreatureStats(player).getFallHeight();
2537         float epsilon = 1e-4;
2538         if ((actor->getCollisionMode() && (!mPhysics->isOnSolidGround(player) || fallHeight >= epsilon)) || isFlying(player))
2539             return Rest_PlayerIsInAir;
2540 
2541         if((currentCell->getCell()->mData.mFlags&ESM::Cell::NoSleep) || player.getClass().getNpcStats(player).isWerewolf())
2542             return Rest_OnlyWaiting;
2543 
2544         return Rest_Allowed;
2545     }
2546 
getAnimation(const MWWorld::Ptr & ptr)2547     MWRender::Animation* World::getAnimation(const MWWorld::Ptr &ptr)
2548     {
2549         auto* animation = mRendering->getAnimation(ptr);
2550         if(!animation) {
2551             mWorldScene->removeFromPagedRefs(ptr);
2552             animation = mRendering->getAnimation(ptr);
2553             if(animation)
2554                 mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
2555         }
2556         return animation;
2557     }
2558 
getAnimation(const MWWorld::ConstPtr & ptr) const2559     const MWRender::Animation* World::getAnimation(const MWWorld::ConstPtr &ptr) const
2560     {
2561         return mRendering->getAnimation(ptr);
2562     }
2563 
screenshot(osg::Image * image,int w,int h)2564     void World::screenshot(osg::Image* image, int w, int h)
2565     {
2566         mRendering->screenshot(image, w, h);
2567     }
2568 
screenshot360(osg::Image * image)2569     bool World::screenshot360(osg::Image* image)
2570     {
2571         return mRendering->screenshot360(image);
2572     }
2573 
activateDoor(const MWWorld::Ptr & door)2574     void World::activateDoor(const MWWorld::Ptr& door)
2575     {
2576         auto state = door.getClass().getDoorState(door);
2577         switch (state)
2578         {
2579         case MWWorld::DoorState::Idle:
2580             if (door.getRefData().getPosition().rot[2] == door.getCellRef().getPosition().rot[2])
2581                 state = MWWorld::DoorState::Opening; // if closed, then open
2582             else
2583                 state = MWWorld::DoorState::Closing; // if open, then close
2584             break;
2585         case MWWorld::DoorState::Closing:
2586             state = MWWorld::DoorState::Opening; // if closing, then open
2587             break;
2588         case MWWorld::DoorState::Opening:
2589         default:
2590             state = MWWorld::DoorState::Closing; // if opening, then close
2591             break;
2592         }
2593         door.getClass().setDoorState(door, state);
2594         mDoorStates[door] = state;
2595     }
2596 
activateDoor(const Ptr & door,MWWorld::DoorState state)2597     void World::activateDoor(const Ptr &door, MWWorld::DoorState state)
2598     {
2599         door.getClass().setDoorState(door, state);
2600         mDoorStates[door] = state;
2601         if (state == MWWorld::DoorState::Idle)
2602         {
2603             mDoorStates.erase(door);
2604             rotateDoor(door, state, 1);
2605         }
2606     }
2607 
getPlayerStandingOn(const MWWorld::ConstPtr & object)2608     bool World::getPlayerStandingOn (const MWWorld::ConstPtr& object)
2609     {
2610         MWWorld::Ptr player = getPlayerPtr();
2611         return mPhysics->isActorStandingOn(player, object);
2612     }
2613 
getActorStandingOn(const MWWorld::ConstPtr & object)2614     bool World::getActorStandingOn (const MWWorld::ConstPtr& object)
2615     {
2616         std::vector<MWWorld::Ptr> actors;
2617         mPhysics->getActorsStandingOn(object, actors);
2618         return !actors.empty();
2619     }
2620 
getActorsStandingOn(const MWWorld::ConstPtr & object,std::vector<MWWorld::Ptr> & actors)2621     void World::getActorsStandingOn (const MWWorld::ConstPtr& object, std::vector<MWWorld::Ptr> &actors)
2622     {
2623         mPhysics->getActorsStandingOn(object, actors);
2624     }
2625 
getPlayerCollidingWith(const MWWorld::ConstPtr & object)2626     bool World::getPlayerCollidingWith (const MWWorld::ConstPtr& object)
2627     {
2628         MWWorld::Ptr player = getPlayerPtr();
2629         return mPhysics->isActorCollidingWith(player, object);
2630     }
2631 
getActorCollidingWith(const MWWorld::ConstPtr & object)2632     bool World::getActorCollidingWith (const MWWorld::ConstPtr& object)
2633     {
2634         std::vector<MWWorld::Ptr> actors;
2635         mPhysics->getActorsCollidingWith(object, actors);
2636         return !actors.empty();
2637     }
2638 
hurtStandingActors(const ConstPtr & object,float healthPerSecond)2639     void World::hurtStandingActors(const ConstPtr &object, float healthPerSecond)
2640     {
2641         if (MWBase::Environment::get().getWindowManager()->isGuiMode())
2642             return;
2643 
2644         std::vector<MWWorld::Ptr> actors;
2645         mPhysics->getActorsStandingOn(object, actors);
2646         for (const Ptr &actor : actors)
2647         {
2648             MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
2649             if (stats.isDead())
2650                 continue;
2651 
2652             mPhysics->markAsNonSolid (object);
2653 
2654             if (actor == getPlayerPtr() && mGodMode)
2655                 continue;
2656 
2657             MWMechanics::DynamicStat<float> health = stats.getHealth();
2658             health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration());
2659             stats.setHealth(health);
2660 
2661             if (healthPerSecond > 0.0f)
2662             {
2663                 if (actor == getPlayerPtr())
2664                     MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
2665 
2666                 if (!MWBase::Environment::get().getSoundManager()->getSoundPlaying(actor, "Health Damage"))
2667                     MWBase::Environment::get().getSoundManager()->playSound3D(actor, "Health Damage", 1.0f, 1.0f);
2668             }
2669         }
2670     }
2671 
hurtCollidingActors(const ConstPtr & object,float healthPerSecond)2672     void World::hurtCollidingActors(const ConstPtr &object, float healthPerSecond)
2673     {
2674         if (MWBase::Environment::get().getWindowManager()->isGuiMode())
2675             return;
2676 
2677         std::vector<Ptr> actors;
2678         mPhysics->getActorsCollidingWith(object, actors);
2679         for (const Ptr &actor : actors)
2680         {
2681             MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
2682             if (stats.isDead())
2683                 continue;
2684 
2685             mPhysics->markAsNonSolid (object);
2686 
2687             if (actor == getPlayerPtr() && mGodMode)
2688                 continue;
2689 
2690             MWMechanics::DynamicStat<float> health = stats.getHealth();
2691             health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration());
2692             stats.setHealth(health);
2693 
2694             if (healthPerSecond > 0.0f)
2695             {
2696                 if (actor == getPlayerPtr())
2697                     MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
2698 
2699                 if (!MWBase::Environment::get().getSoundManager()->getSoundPlaying(actor, "Health Damage"))
2700                     MWBase::Environment::get().getSoundManager()->playSound3D(actor, "Health Damage", 1.0f, 1.0f);
2701             }
2702         }
2703     }
2704 
getWindSpeed()2705     float World::getWindSpeed()
2706     {
2707         if (isCellExterior() || isCellQuasiExterior())
2708             return mWeatherManager->getWindSpeed();
2709         else
2710             return 0.f;
2711     }
2712 
isInStorm() const2713     bool World::isInStorm() const
2714     {
2715         if (isCellExterior() || isCellQuasiExterior())
2716             return mWeatherManager->isInStorm();
2717         else
2718             return false;
2719     }
2720 
getStormDirection() const2721     osg::Vec3f World::getStormDirection() const
2722     {
2723         if (isCellExterior() || isCellQuasiExterior())
2724             return mWeatherManager->getStormDirection();
2725         else
2726             return osg::Vec3f(0,1,0);
2727     }
2728 
2729     struct GetContainersOwnedByVisitor
2730     {
GetContainersOwnedByVisitorMWWorld::GetContainersOwnedByVisitor2731         GetContainersOwnedByVisitor(const MWWorld::ConstPtr& owner, std::vector<MWWorld::Ptr>& out)
2732             : mOwner(owner)
2733             , mOut(out)
2734         {
2735         }
2736 
2737         MWWorld::ConstPtr mOwner;
2738         std::vector<MWWorld::Ptr>& mOut;
2739 
operator ()MWWorld::GetContainersOwnedByVisitor2740         bool operator()(const MWWorld::Ptr& ptr)
2741         {
2742             if (ptr.getRefData().isDeleted())
2743                 return true;
2744 
2745             // vanilla Morrowind does not allow to sell items from containers with zero capacity
2746             if (ptr.getClass().getCapacity(ptr) <= 0.f)
2747                 return true;
2748 
2749             if (Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), mOwner.getCellRef().getRefId()))
2750                 mOut.push_back(ptr);
2751 
2752             return true;
2753         }
2754     };
2755 
getContainersOwnedBy(const MWWorld::ConstPtr & owner,std::vector<MWWorld::Ptr> & out)2756     void World::getContainersOwnedBy (const MWWorld::ConstPtr& owner, std::vector<MWWorld::Ptr>& out)
2757     {
2758         for (CellStore* cellstore : mWorldScene->getActiveCells())
2759         {
2760             GetContainersOwnedByVisitor visitor (owner, out);
2761             cellstore->forEachType<ESM::Container>(visitor);
2762         }
2763     }
2764 
getItemsOwnedBy(const MWWorld::ConstPtr & npc,std::vector<MWWorld::Ptr> & out)2765     void World::getItemsOwnedBy (const MWWorld::ConstPtr& npc, std::vector<MWWorld::Ptr>& out)
2766     {
2767         for (CellStore* cellstore : mWorldScene->getActiveCells())
2768         {
2769             cellstore->forEach([&] (const auto& ptr) {
2770                 if (ptr.getRefData().getBaseNode() && Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), npc.getCellRef().getRefId()))
2771                     out.push_back(ptr);
2772                 return true;
2773             });
2774         }
2775     }
2776 
getLOS(const MWWorld::ConstPtr & actor,const MWWorld::ConstPtr & targetActor)2777     bool World::getLOS(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& targetActor)
2778     {
2779         if (!targetActor.getRefData().isEnabled() || !actor.getRefData().isEnabled())
2780             return false; // cannot get LOS unless both NPC's are enabled
2781         if (!targetActor.getRefData().getBaseNode() || !actor.getRefData().getBaseNode())
2782             return false; // not in active cell
2783 
2784         return mPhysics->getLineOfSight(actor, targetActor);
2785     }
2786 
getDistToNearestRayHit(const osg::Vec3f & from,const osg::Vec3f & dir,float maxDist,bool includeWater)2787     float World::getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater)
2788     {
2789         osg::Vec3f to (dir);
2790         to.normalize();
2791         to = from + (to * maxDist);
2792 
2793         int collisionTypes = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door;
2794         if (includeWater) {
2795             collisionTypes |= MWPhysics::CollisionType_Water;
2796         }
2797         MWPhysics::RayCastingResult result = mPhysics->castRay(from, to, MWWorld::Ptr(), std::vector<MWWorld::Ptr>(), collisionTypes);
2798 
2799         if (!result.mHit)
2800             return maxDist;
2801         else
2802             return (result.mHitPos - from).length();
2803     }
2804 
enableActorCollision(const MWWorld::Ptr & actor,bool enable)2805     void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable)
2806     {
2807         MWPhysics::Actor *physicActor = mPhysics->getActor(actor);
2808         if (physicActor)
2809             physicActor->enableCollisionBody(enable);
2810     }
2811 
findInteriorPosition(const std::string & name,ESM::Position & pos)2812     bool World::findInteriorPosition(const std::string &name, ESM::Position &pos)
2813     {
2814         pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
2815         pos.pos[0] = pos.pos[1] = pos.pos[2] = 0;
2816 
2817         MWWorld::CellStore *cellStore = getInterior(name);
2818 
2819         if (!cellStore)
2820             return false;
2821 
2822         std::vector<const MWWorld::CellRef *> sortedDoors;
2823         for (const MWWorld::LiveCellRef<ESM::Door>& door : cellStore->getReadOnlyDoors().mList)
2824         {
2825             if (!door.mRef.getTeleport())
2826                 continue;
2827 
2828             sortedDoors.push_back(&door.mRef);
2829         }
2830 
2831         // Sort teleporting doors alphabetically, first by ID, then by destination cell to make search consistent
2832         std::sort(sortedDoors.begin(), sortedDoors.end(), [] (const MWWorld::CellRef *lhs, const MWWorld::CellRef *rhs)
2833         {
2834             if (lhs->getRefId() != rhs->getRefId())
2835                 return lhs->getRefId() < rhs->getRefId();
2836 
2837             return lhs->getDestCell() < rhs->getDestCell();
2838         });
2839 
2840         for (const MWWorld::CellRef* door : sortedDoors)
2841         {
2842             MWWorld::CellStore *source = nullptr;
2843 
2844             // door to exterior
2845             if (door->getDestCell().empty())
2846             {
2847                 int x, y;
2848                 ESM::Position doorDest = door->getDoorDest();
2849                 positionToIndex(doorDest.pos[0], doorDest.pos[1], x, y);
2850                 source = getExterior(x, y);
2851             }
2852             // door to interior
2853             else
2854             {
2855                 source = getInterior(door->getDestCell());
2856             }
2857             if (source)
2858             {
2859                 // Find door leading to our current teleport door
2860                 // and use its destination to position inside cell.
2861                 for (const MWWorld::LiveCellRef<ESM::Door>& destDoor : source->getReadOnlyDoors().mList)
2862                 {
2863                     if (Misc::StringUtils::ciEqual(name, destDoor.mRef.getDestCell()))
2864                     {
2865                         /// \note Using _any_ door pointed to the interior,
2866                         /// not the one pointed to current door.
2867                         pos = destDoor.mRef.getDoorDest();
2868                         pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
2869                         return true;
2870                     }
2871                 }
2872             }
2873         }
2874         // Fall back to the first static location.
2875         const MWWorld::CellRefList<ESM::Static>::List &statics = cellStore->getReadOnlyStatics().mList;
2876         if (!statics.empty())
2877         {
2878             pos = statics.begin()->mRef.getPosition();
2879             pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
2880             return true;
2881         }
2882 
2883         return false;
2884     }
2885 
findExteriorPosition(const std::string & name,ESM::Position & pos)2886     bool World::findExteriorPosition(const std::string &name, ESM::Position &pos)
2887     {
2888         pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
2889 
2890         const ESM::Cell *ext = getExterior(name);
2891 
2892         if (!ext && name.find(',') != std::string::npos) {
2893             try {
2894                 int x = std::stoi(name.substr(0, name.find(',')));
2895                 int y = std::stoi(name.substr(name.find(',')+1));
2896                 ext = getExterior(x, y)->getCell();
2897             }
2898             catch (const std::invalid_argument&)
2899             {
2900                 // This exception can be ignored, as this means that name probably refers to a interior cell instead of comma separated coordinates
2901             }
2902             catch (const std::out_of_range&)
2903             {
2904                 throw std::runtime_error("Cell coordinates out of range.");
2905             }
2906         }
2907 
2908         if (ext) {
2909             int x = ext->getGridX();
2910             int y = ext->getGridY();
2911             indexToPosition(x, y, pos.pos[0], pos.pos[1], true);
2912 
2913             // Note: Z pos will be adjusted by adjustPosition later
2914             pos.pos[2] = 0;
2915 
2916             return true;
2917         }
2918 
2919         return false;
2920     }
2921 
enableTeleporting(bool enable)2922     void World::enableTeleporting(bool enable)
2923     {
2924         mTeleportEnabled = enable;
2925     }
2926 
isTeleportingEnabled() const2927     bool World::isTeleportingEnabled() const
2928     {
2929         return mTeleportEnabled;
2930     }
2931 
enableLevitation(bool enable)2932     void World::enableLevitation(bool enable)
2933     {
2934         mLevitationEnabled = enable;
2935     }
2936 
isLevitationEnabled() const2937     bool World::isLevitationEnabled() const
2938     {
2939         return mLevitationEnabled;
2940     }
2941 
reattachPlayerCamera()2942     void World::reattachPlayerCamera()
2943     {
2944         mRendering->rebuildPtr(getPlayerPtr());
2945     }
2946 
getGodModeState() const2947     bool World::getGodModeState() const
2948     {
2949         return mGodMode;
2950     }
2951 
toggleGodMode()2952     bool World::toggleGodMode()
2953     {
2954         mGodMode = !mGodMode;
2955 
2956         return mGodMode;
2957     }
2958 
toggleScripts()2959     bool World::toggleScripts()
2960     {
2961         mScriptsEnabled = !mScriptsEnabled;
2962         return mScriptsEnabled;
2963     }
2964 
getScriptsEnabled() const2965     bool World::getScriptsEnabled() const
2966     {
2967         return mScriptsEnabled;
2968     }
2969 
loadContentFiles(const Files::Collections & fileCollections,const std::vector<std::string> & content,const std::vector<std::string> & groundcover,ContentLoader & contentLoader)2970     void World::loadContentFiles(const Files::Collections& fileCollections,
2971         const std::vector<std::string>& content, const std::vector<std::string>& groundcover, ContentLoader& contentLoader)
2972     {
2973         int idx = 0;
2974         for (const std::string &file : content)
2975         {
2976             boost::filesystem::path filename(file);
2977             const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string());
2978             if (col.doesExist(file))
2979             {
2980                 contentLoader.load(col.getPath(file), idx);
2981             }
2982             else
2983             {
2984                 std::string message = "Failed loading " + file + ": the content file does not exist";
2985                 throw std::runtime_error(message);
2986             }
2987             idx++;
2988         }
2989 
2990         ESM::GroundcoverIndex = idx;
2991 
2992         for (const std::string &file : groundcover)
2993         {
2994             boost::filesystem::path filename(file);
2995             const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string());
2996             if (col.doesExist(file))
2997             {
2998                 contentLoader.load(col.getPath(file), idx);
2999             }
3000             else
3001             {
3002                 std::string message = "Failed loading " + file + ": the groundcover file does not exist";
3003                 throw std::runtime_error(message);
3004             }
3005             idx++;
3006         }
3007     }
3008 
startSpellCast(const Ptr & actor)3009     bool World::startSpellCast(const Ptr &actor)
3010     {
3011         MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
3012 
3013         std::string message;
3014         bool fail = false;
3015         bool isPlayer = (actor == getPlayerPtr());
3016 
3017         std::string selectedSpell = stats.getSpells().getSelectedSpell();
3018 
3019         if (!selectedSpell.empty())
3020         {
3021             const ESM::Spell* spell = mStore.get<ESM::Spell>().find(selectedSpell);
3022 
3023             // Check mana
3024             bool godmode = (isPlayer && mGodMode);
3025             MWMechanics::DynamicStat<float> magicka = stats.getMagicka();
3026             if (spell->mData.mCost > 0 && magicka.getCurrent() < spell->mData.mCost && !godmode)
3027             {
3028                 message = "#{sMagicInsufficientSP}";
3029                 fail = true;
3030             }
3031 
3032             // If this is a power, check if it was already used in the last 24h
3033             if (!fail && spell->mData.mType == ESM::Spell::ST_Power && !stats.getSpells().canUsePower(spell))
3034             {
3035                 message = "#{sPowerAlreadyUsed}";
3036                 fail = true;
3037             }
3038 
3039             // Reduce mana
3040             if (!fail && !godmode)
3041             {
3042                 magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost);
3043                 stats.setMagicka(magicka);
3044             }
3045         }
3046 
3047         if (isPlayer && fail)
3048             MWBase::Environment::get().getWindowManager()->messageBox(message);
3049 
3050         return !fail;
3051     }
3052 
castSpell(const Ptr & actor,bool manualSpell)3053     void World::castSpell(const Ptr &actor, bool manualSpell)
3054     {
3055         MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
3056 
3057         // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
3058         std::vector<MWWorld::Ptr> targetActors;
3059         if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell)
3060             stats.getAiSequence().getCombatTargets(targetActors);
3061 
3062         const float fCombatDistance = mStore.get<ESM::GameSetting>().find("fCombatDistance")->mValue.getFloat();
3063 
3064         osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3();
3065 
3066         // for player we can take faced object first
3067         MWWorld::Ptr target;
3068         if (actor == MWMechanics::getPlayer())
3069             target = getFacedObject();
3070 
3071         // if the faced object can not be activated, do not use it
3072         if (!target.isEmpty() && !target.getClass().hasToolTip(target))
3073             target = nullptr;
3074 
3075         if (target.isEmpty())
3076         {
3077             // For scripted spells we should not use hit contact
3078             if (manualSpell)
3079             {
3080                 if (actor != MWMechanics::getPlayer())
3081                 {
3082                     for (const auto& package : stats.getAiSequence())
3083                     {
3084                         if (package->getTypeId() == MWMechanics::AiPackageTypeId::Cast)
3085                         {
3086                             target = package->getTarget();
3087                             break;
3088                         }
3089                     }
3090                 }
3091             }
3092             else
3093             {
3094                 // For actor targets, we want to use hit contact with bounding boxes.
3095                 // This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise.
3096                 // For object targets, we want the detailed shapes (rendering raycast).
3097                 // If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf.
3098                 std::pair<MWWorld::Ptr,osg::Vec3f> result1 = getHitContact(actor, fCombatDistance, targetActors);
3099 
3100                 // Get the target to use for "on touch" effects, using the facing direction from Head node
3101                 osg::Vec3f origin = getActorHeadTransform(actor).getTrans();
3102 
3103                 osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1,0,0))
3104                         * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0,0,-1));
3105 
3106                 osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
3107                 float distance = getMaxActivationDistance();
3108                 osg::Vec3f dest = origin + direction * distance;
3109 
3110                 MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true);
3111 
3112                 float dist1 = std::numeric_limits<float>::max();
3113                 float dist2 = std::numeric_limits<float>::max();
3114 
3115                 if (!result1.first.isEmpty() && result1.first.getClass().isActor())
3116                     dist1 = (origin - result1.second).length();
3117                 if (result2.mHit)
3118                     dist2 = (origin - result2.mHitPointWorld).length();
3119 
3120                 if (!result1.first.isEmpty() && result1.first.getClass().isActor())
3121                 {
3122                     target = result1.first;
3123                     hitPosition = result1.second;
3124                     if (dist1 > getMaxActivationDistance())
3125                         target = nullptr;
3126                 }
3127                 else if (result2.mHit)
3128                 {
3129                     target = result2.mHitObject;
3130                     hitPosition = result2.mHitPointWorld;
3131                     if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().hasToolTip(target))
3132                         target = nullptr;
3133                 }
3134             }
3135         }
3136 
3137         std::string selectedSpell = stats.getSpells().getSelectedSpell();
3138 
3139         MWMechanics::CastSpell cast(actor, target, false, manualSpell);
3140         cast.mHitPosition = hitPosition;
3141 
3142         if (!selectedSpell.empty())
3143         {
3144             const ESM::Spell* spell = mStore.get<ESM::Spell>().find(selectedSpell);
3145             cast.cast(spell);
3146         }
3147         else if (actor.getClass().hasInventoryStore(actor))
3148         {
3149             MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor);
3150             if (inv.getSelectedEnchantItem() != inv.end())
3151                 cast.cast(*inv.getSelectedEnchantItem());
3152         }
3153     }
3154 
launchProjectile(MWWorld::Ptr & actor,MWWorld::Ptr & projectile,const osg::Vec3f & worldPos,const osg::Quat & orient,MWWorld::Ptr & bow,float speed,float attackStrength)3155     void World::launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile,
3156                                    const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength)
3157     {
3158         // An initial position of projectile can be outside shooter's collision box, so any object between shooter and launch position will be ignored.
3159         // To avoid this issue, we should check for impact immediately before launch the projectile.
3160         // So we cast a 1-yard-length ray from shooter to launch position and check if there are collisions in this area.
3161         // TODO: as a better solutuon we should handle projectiles during physics update, not during world update.
3162         const osg::Vec3f sourcePos = worldPos + orient * osg::Vec3f(0,-1,0) * 64.f;
3163 
3164         // Early out if the launch position is underwater
3165         bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), worldPos);
3166         if (underwater)
3167         {
3168             MWMechanics::projectileHit(actor, Ptr(), bow, projectile, worldPos, attackStrength);
3169             mRendering->emitWaterRipple(worldPos);
3170             return;
3171         }
3172 
3173         // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
3174         std::vector<MWWorld::Ptr> targetActors;
3175         if (!actor.isEmpty() && actor.getClass().isActor() && actor != MWMechanics::getPlayer())
3176             actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors);
3177 
3178         // Check for impact, if yes, handle hit, if not, launch projectile
3179         MWPhysics::RayCastingResult result = mPhysics->castRay(sourcePos, worldPos, actor, targetActors, 0xff, MWPhysics::CollisionType_Projectile);
3180         if (result.mHit)
3181             MWMechanics::projectileHit(actor, result.mHitObject, bow, projectile, result.mHitPos, attackStrength);
3182         else
3183             mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength);
3184     }
3185 
launchMagicBolt(const std::string & spellId,const MWWorld::Ptr & caster,const osg::Vec3f & fallbackDirection)3186     void World::launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection)
3187     {
3188         mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection);
3189     }
3190 
updateProjectilesCasters()3191     void World::updateProjectilesCasters()
3192     {
3193         mProjectileManager->updateCasters();
3194     }
3195 
3196     class ApplyLoopingParticlesVisitor : public MWMechanics::EffectSourceVisitor
3197     {
3198     private:
3199         MWWorld::Ptr mActor;
3200 
3201     public:
ApplyLoopingParticlesVisitor(const MWWorld::Ptr & actor)3202         ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor)
3203             : mActor(actor)
3204         {
3205         }
3206 
visit(MWMechanics::EffectKey key,int,const std::string &,const std::string &,int,float,float=-1,float=-1)3207         void visit (MWMechanics::EffectKey key, int /*effectIndex*/,
3208                             const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
3209                             float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1) override
3210         {
3211             const ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
3212             const auto magicEffect = store.get<ESM::MagicEffect>().find(key.mId);
3213             if ((magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) == 0)
3214                 return;
3215             const ESM::Static* castStatic;
3216             if (!magicEffect->mHit.empty())
3217                 castStatic = store.get<ESM::Static>().find (magicEffect->mHit);
3218             else
3219                 castStatic = store.get<ESM::Static>().find ("VFX_DefaultHit");
3220             MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor);
3221             if (anim && !castStatic->mModel.empty())
3222                 anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, /*loop*/true, "", magicEffect->mParticle);
3223         }
3224     };
3225 
applyLoopingParticles(const MWWorld::Ptr & ptr)3226     void World::applyLoopingParticles(const MWWorld::Ptr& ptr)
3227     {
3228         const MWWorld::Class &cls = ptr.getClass();
3229         if (cls.isActor())
3230         {
3231             ApplyLoopingParticlesVisitor visitor(ptr);
3232             cls.getCreatureStats(ptr).getActiveSpells().visitEffectSources(visitor);
3233             cls.getCreatureStats(ptr).getSpells().visitEffectSources(visitor);
3234             if (cls.hasInventoryStore(ptr))
3235                 cls.getInventoryStore(ptr).visitEffectSources(visitor);
3236         }
3237     }
3238 
getContentFiles() const3239     const std::vector<std::string>& World::getContentFiles() const
3240     {
3241         return mContentFiles;
3242     }
3243 
breakInvisibility(const Ptr & actor)3244     void World::breakInvisibility(const Ptr &actor)
3245     {
3246         actor.getClass().getCreatureStats(actor).getSpells().purgeEffect(ESM::MagicEffect::Invisibility);
3247         actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility);
3248         if (actor.getClass().hasInventoryStore(actor))
3249             actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility);
3250 
3251         // Normally updated once per frame, but here it is kinda important to do it right away.
3252         MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor);
3253     }
3254 
useTorches() const3255     bool World::useTorches() const
3256     {
3257         // If we are in exterior, check the weather manager.
3258         // In interiors there are no precipitations and sun, so check the ambient
3259         // Looks like pseudo-exteriors considered as interiors in this case
3260         MWWorld::CellStore* cell = mPlayer->getPlayer().getCell();
3261         if (cell->isExterior())
3262         {
3263             float hour = getTimeStamp().getHour();
3264             return mWeatherManager->useTorches(hour);
3265         }
3266         else
3267         {
3268             uint32_t ambient = cell->getCell()->mAmbi.mAmbient;
3269             int ambientTotal = (ambient & 0xff)
3270                     + ((ambient>>8) & 0xff)
3271                     + ((ambient>>16) & 0xff);
3272             return !(cell->getCell()->mData.mFlags & ESM::Cell::NoSleep) && ambientTotal <= 201;
3273         }
3274     }
3275 
findInteriorPositionInWorldSpace(const MWWorld::CellStore * cell,osg::Vec3f & result)3276     bool World::findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result)
3277     {
3278         if (cell->isExterior())
3279             return false;
3280 
3281         // Search for a 'nearest' exterior, counting each cell between the starting
3282         // cell and the exterior as a distance of 1.  Will fail for isolated interiors.
3283         std::set< std::string >checkedCells;
3284         std::set< std::string >currentCells;
3285         std::set< std::string >nextCells;
3286         nextCells.insert( cell->getCell()->mName );
3287 
3288         while ( !nextCells.empty() ) {
3289             currentCells = nextCells;
3290             nextCells.clear();
3291             for (const std::string &currentCell : currentCells)
3292             {
3293                 MWWorld::CellStore *next = getInterior(currentCell);
3294                 if ( !next ) continue;
3295 
3296                 // Check if any door in the cell leads to an exterior directly
3297                 for (const MWWorld::LiveCellRef<ESM::Door>& ref : next->getReadOnlyDoors().mList)
3298                 {
3299                     if (!ref.mRef.getTeleport()) continue;
3300 
3301                     if (ref.mRef.getDestCell().empty())
3302                     {
3303                         ESM::Position pos = ref.mRef.getDoorDest();
3304                         result = pos.asVec3();
3305                         return true;
3306                     }
3307                     else
3308                     {
3309                         std::string dest = ref.mRef.getDestCell();
3310                         if ( !checkedCells.count(dest) && !currentCells.count(dest) )
3311                             nextCells.insert(dest);
3312                     }
3313                 }
3314 
3315                 checkedCells.insert(currentCell);
3316             }
3317         }
3318 
3319         // No luck :(
3320         return false;
3321     }
3322 
getClosestMarker(const MWWorld::Ptr & ptr,const std::string & id)3323     MWWorld::ConstPtr World::getClosestMarker( const MWWorld::Ptr &ptr, const std::string &id )
3324     {
3325         if ( ptr.getCell()->isExterior() ) {
3326             return getClosestMarkerFromExteriorPosition(mPlayer->getLastKnownExteriorPosition(), id);
3327         }
3328 
3329         // Search for a 'nearest' marker, counting each cell between the starting
3330         // cell and the exterior as a distance of 1.  If an exterior is found, jump
3331         // to the nearest exterior marker, without further interior searching.
3332         std::set< std::string >checkedCells;
3333         std::set< std::string >currentCells;
3334         std::set< std::string >nextCells;
3335         MWWorld::ConstPtr closestMarker;
3336 
3337         nextCells.insert( ptr.getCell()->getCell()->mName );
3338         while ( !nextCells.empty() ) {
3339             currentCells = nextCells;
3340             nextCells.clear();
3341             for (const std::string &cell : currentCells) {
3342                 MWWorld::CellStore *next = getInterior(cell);
3343                 checkedCells.insert(cell);
3344                 if ( !next ) continue;
3345 
3346                 closestMarker = next->searchConst( id );
3347                 if ( !closestMarker.isEmpty() )
3348                 {
3349                     return closestMarker;
3350                 }
3351 
3352                 // Check if any door in the cell leads to an exterior directly
3353                 for (const MWWorld::LiveCellRef<ESM::Door>& ref : next->getReadOnlyDoors().mList)
3354                 {
3355                     if (!ref.mRef.getTeleport()) continue;
3356 
3357                     if (ref.mRef.getDestCell().empty())
3358                     {
3359                         osg::Vec3f worldPos = ref.mRef.getDoorDest().asVec3();
3360                         return getClosestMarkerFromExteriorPosition(worldPos, id);
3361                     }
3362                     else
3363                     {
3364                         std::string dest = ref.mRef.getDestCell();
3365                         if ( !checkedCells.count(dest) && !currentCells.count(dest) )
3366                             nextCells.insert(dest);
3367                     }
3368                 }
3369             }
3370         }
3371         return MWWorld::Ptr();
3372     }
3373 
getClosestMarkerFromExteriorPosition(const osg::Vec3f & worldPos,const std::string & id)3374     MWWorld::ConstPtr World::getClosestMarkerFromExteriorPosition( const osg::Vec3f& worldPos, const std::string &id ) {
3375         MWWorld::ConstPtr closestMarker;
3376         float closestDistance = std::numeric_limits<float>::max();
3377 
3378         std::vector<MWWorld::Ptr> markers;
3379         mCells.getExteriorPtrs(id, markers);
3380         for (const Ptr& marker : markers)
3381         {
3382             osg::Vec3f markerPos = marker.getRefData().getPosition().asVec3();
3383             float distance = (worldPos - markerPos).length2();
3384             if (distance < closestDistance)
3385             {
3386                 closestDistance = distance;
3387                 closestMarker = marker;
3388             }
3389         }
3390 
3391         return closestMarker;
3392     }
3393 
rest(double hours)3394     void World::rest(double hours)
3395     {
3396         mCells.rest(hours);
3397     }
3398 
rechargeItems(double duration,bool activeOnly)3399     void World::rechargeItems(double duration, bool activeOnly)
3400     {
3401         MWWorld::Ptr player = getPlayerPtr();
3402         player.getClass().getInventoryStore(player).rechargeItems(duration);
3403 
3404         if (activeOnly)
3405         {
3406             for (auto &cell : mWorldScene->getActiveCells())
3407             {
3408                 cell->recharge(duration);
3409             }
3410         }
3411         else
3412             mCells.recharge(duration);
3413     }
3414 
teleportToClosestMarker(const MWWorld::Ptr & ptr,const std::string & id)3415     void World::teleportToClosestMarker (const MWWorld::Ptr& ptr,
3416                                           const std::string& id)
3417     {
3418         MWWorld::ConstPtr closestMarker = getClosestMarker( ptr, id );
3419 
3420         if ( closestMarker.isEmpty() )
3421         {
3422             Log(Debug::Warning) << "Failed to teleport: no closest marker found";
3423             return;
3424         }
3425 
3426         std::string cellName;
3427         if ( !closestMarker.mCell->isExterior() )
3428             cellName = closestMarker.mCell->getCell()->mName;
3429 
3430         MWWorld::ActionTeleport action(cellName, closestMarker.getRefData().getPosition(), false);
3431         action.execute(ptr);
3432     }
3433 
updateWeather(float duration,bool paused)3434     void World::updateWeather(float duration, bool paused)
3435     {
3436         bool isExterior = isCellExterior() || isCellQuasiExterior();
3437         if (mPlayer->wasTeleported())
3438         {
3439             mPlayer->setTeleported(false);
3440 
3441             const std::string playerRegion = Misc::StringUtils::lowerCase(getPlayerPtr().getCell()->getCell()->mRegion);
3442             mWeatherManager->playerTeleported(playerRegion, isExterior);
3443         }
3444 
3445         const TimeStamp time = getTimeStamp();
3446         mWeatherManager->update(duration, paused, time, isExterior);
3447     }
3448 
3449     struct AddDetectedReferenceVisitor
3450     {
AddDetectedReferenceVisitorMWWorld::AddDetectedReferenceVisitor3451         AddDetectedReferenceVisitor(std::vector<Ptr>& out, const Ptr& detector, World::DetectionType type, float squaredDist)
3452             : mOut(out), mDetector(detector), mSquaredDist(squaredDist), mType(type)
3453         {
3454         }
3455 
3456         std::vector<Ptr>& mOut;
3457         Ptr mDetector;
3458         float mSquaredDist;
3459         World::DetectionType mType;
operator ()MWWorld::AddDetectedReferenceVisitor3460         bool operator() (const MWWorld::Ptr& ptr)
3461         {
3462             if ((ptr.getRefData().getPosition().asVec3() - mDetector.getRefData().getPosition().asVec3()).length2() >= mSquaredDist)
3463                 return true;
3464 
3465             if (!ptr.getRefData().isEnabled() || ptr.getRefData().isDeleted())
3466                 return true;
3467 
3468             // Consider references inside containers as well (except if we are looking for a Creature, they cannot be in containers)
3469             bool isContainer = ptr.getClass().getTypeName() == typeid(ESM::Container).name();
3470             if (mType != World::Detect_Creature && (ptr.getClass().isActor() || isContainer))
3471             {
3472                 // but ignore containers without resolved content
3473                 if (isContainer && ptr.getRefData().getCustomData() == nullptr)
3474                     return true;
3475 
3476                 MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
3477                 {
3478                     for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
3479                     {
3480                         if (needToAdd(*it, mDetector))
3481                         {
3482                             mOut.push_back(ptr);
3483                             return true;
3484                         }
3485                     }
3486                 }
3487             }
3488 
3489             if (needToAdd(ptr, mDetector))
3490                 mOut.push_back(ptr);
3491 
3492             return true;
3493         }
3494 
needToAddMWWorld::AddDetectedReferenceVisitor3495         bool needToAdd (const MWWorld::Ptr& ptr, const MWWorld::Ptr& detector)
3496         {
3497             if (mType == World::Detect_Creature)
3498             {
3499                 // If in werewolf form, this detects only NPCs, otherwise only creatures
3500                 if (detector.getClass().isNpc() && detector.getClass().getNpcStats(detector).isWerewolf())
3501                 {
3502                     if (ptr.getClass().getTypeName() != typeid(ESM::NPC).name())
3503                         return false;
3504                 }
3505                 else if (ptr.getClass().getTypeName() != typeid(ESM::Creature).name())
3506                     return false;
3507 
3508                 if (ptr.getClass().getCreatureStats(ptr).isDead())
3509                     return false;
3510             }
3511             if (mType == World::Detect_Key && !ptr.getClass().isKey(ptr))
3512                 return false;
3513             if (mType == World::Detect_Enchantment && ptr.getClass().getEnchantment(ptr).empty())
3514                 return false;
3515             return true;
3516         }
3517     };
3518 
listDetectedReferences(const Ptr & ptr,std::vector<Ptr> & out,DetectionType type)3519     void World::listDetectedReferences(const Ptr &ptr, std::vector<Ptr> &out, DetectionType type)
3520     {
3521         const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects();
3522         float dist=0;
3523         if (type == World::Detect_Creature)
3524             dist = effects.get(ESM::MagicEffect::DetectAnimal).getMagnitude();
3525         else if (type == World::Detect_Key)
3526             dist = effects.get(ESM::MagicEffect::DetectKey).getMagnitude();
3527         else if (type == World::Detect_Enchantment)
3528             dist = effects.get(ESM::MagicEffect::DetectEnchantment).getMagnitude();
3529 
3530         if (!dist)
3531             return;
3532 
3533         dist = feetToGameUnits(dist);
3534 
3535         AddDetectedReferenceVisitor visitor (out, ptr, type, dist*dist);
3536 
3537         for (CellStore* cellStore : mWorldScene->getActiveCells())
3538         {
3539             cellStore->forEach(visitor);
3540         }
3541     }
3542 
feetToGameUnits(float feet)3543     float World::feetToGameUnits(float feet)
3544     {
3545         // Original engine rounds size upward
3546         static const int unitsPerFoot = ceil(Constants::UnitsPerFoot);
3547         return feet * unitsPerFoot;
3548     }
3549 
getActivationDistancePlusTelekinesis()3550     float World::getActivationDistancePlusTelekinesis()
3551     {
3552         float telekinesisRangeBonus =
3553                     mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).getMagicEffects()
3554                     .get(ESM::MagicEffect::Telekinesis).getMagnitude();
3555         telekinesisRangeBonus = feetToGameUnits(telekinesisRangeBonus);
3556 
3557         float activationDistance = getMaxActivationDistance() + telekinesisRangeBonus;
3558 
3559         return activationDistance;
3560     }
3561 
getPlayerPtr()3562     MWWorld::Ptr World::getPlayerPtr()
3563     {
3564         return mPlayer->getPlayer();
3565     }
3566 
getPlayerConstPtr() const3567     MWWorld::ConstPtr World::getPlayerConstPtr() const
3568     {
3569         return mPlayer->getConstPlayer();
3570     }
3571 
updateDialogueGlobals()3572     void World::updateDialogueGlobals()
3573     {
3574         MWWorld::Ptr player = getPlayerPtr();
3575         int bounty = player.getClass().getNpcStats(player).getBounty();
3576         int playerGold = player.getClass().getContainerStore(player).count(ContainerStore::sGoldId);
3577 
3578         static float fCrimeGoldDiscountMult = mStore.get<ESM::GameSetting>().find("fCrimeGoldDiscountMult")->mValue.getFloat();
3579         static float fCrimeGoldTurnInMult = mStore.get<ESM::GameSetting>().find("fCrimeGoldTurnInMult")->mValue.getFloat();
3580 
3581         int discount = static_cast<int>(bounty * fCrimeGoldDiscountMult);
3582         int turnIn = static_cast<int>(bounty * fCrimeGoldTurnInMult);
3583 
3584         if (bounty > 0)
3585         {
3586             discount = std::max(1, discount);
3587             turnIn = std::max(1, turnIn);
3588         }
3589 
3590         mGlobalVariables["pchascrimegold"].setInteger((bounty <= playerGold) ? 1 : 0);
3591 
3592         mGlobalVariables["pchasgolddiscount"].setInteger((discount <= playerGold) ? 1 : 0);
3593         mGlobalVariables["crimegolddiscount"].setInteger(discount);
3594 
3595         mGlobalVariables["crimegoldturnin"].setInteger(turnIn);
3596         mGlobalVariables["pchasturnin"].setInteger((turnIn <= playerGold) ? 1 : 0);
3597     }
3598 
confiscateStolenItems(const Ptr & ptr)3599     void World::confiscateStolenItems(const Ptr &ptr)
3600     {
3601         MWWorld::ConstPtr prisonMarker = getClosestMarker( ptr, "prisonmarker" );
3602         if ( prisonMarker.isEmpty() )
3603         {
3604             Log(Debug::Warning) << "Failed to confiscate items: no closest prison marker found.";
3605             return;
3606         }
3607         std::string prisonName = prisonMarker.getCellRef().getDestCell();
3608         if ( prisonName.empty() )
3609         {
3610             Log(Debug::Warning) << "Failed to confiscate items: prison marker not linked to prison interior";
3611             return;
3612         }
3613         MWWorld::CellStore *prison = getInterior( prisonName );
3614         if ( !prison )
3615         {
3616             Log(Debug::Warning) << "Failed to confiscate items: failed to load cell " << prisonName;
3617             return;
3618         }
3619 
3620         MWWorld::Ptr closestChest = prison->search( "stolen_goods" );
3621         if (!closestChest.isEmpty()) //Found a close chest
3622         {
3623             MWBase::Environment::get().getMechanicsManager()->confiscateStolenItems(ptr, closestChest);
3624         }
3625         else
3626            Log(Debug::Warning) << "Failed to confiscate items: no stolen_goods container found";
3627     }
3628 
goToJail()3629     void World::goToJail()
3630     {
3631         if (!mGoToJail)
3632         {
3633             // Reset bounty and forget the crime now, but don't change cell yet (the player should be able to read the dialog text first)
3634             mGoToJail = true;
3635             mPlayerInJail = true;
3636 
3637             MWWorld::Ptr player = getPlayerPtr();
3638 
3639             int bounty = player.getClass().getNpcStats(player).getBounty();
3640             player.getClass().getNpcStats(player).setBounty(0);
3641             mPlayer->recordCrimeId();
3642             confiscateStolenItems(player);
3643 
3644             static int iDaysinPrisonMod = mStore.get<ESM::GameSetting>().find("iDaysinPrisonMod")->mValue.getInteger();
3645             mDaysInPrison = std::max(1, bounty / iDaysinPrisonMod);
3646 
3647             return;
3648         }
3649         else
3650         {
3651             mGoToJail = false;
3652 
3653             MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue);
3654 
3655             MWBase::Environment::get().getWindowManager()->goToJail(mDaysInPrison);
3656         }
3657     }
3658 
isPlayerInJail() const3659     bool World::isPlayerInJail() const
3660     {
3661         return mPlayerInJail;
3662     }
3663 
setPlayerTraveling(bool traveling)3664     void World::setPlayerTraveling(bool traveling)
3665     {
3666         mPlayerTraveling = traveling;
3667     }
3668 
isPlayerTraveling() const3669     bool World::isPlayerTraveling() const
3670     {
3671         return mPlayerTraveling;
3672     }
3673 
getTerrainHeightAt(const osg::Vec3f & worldPos) const3674     float World::getTerrainHeightAt(const osg::Vec3f& worldPos) const
3675     {
3676         return mRendering->getTerrainHeightAt(worldPos);
3677     }
3678 
getHalfExtents(const ConstPtr & object,bool rendering) const3679     osg::Vec3f World::getHalfExtents(const ConstPtr& object, bool rendering) const
3680     {
3681         if (!object.getClass().isActor())
3682             return mRendering->getHalfExtents(object);
3683 
3684         // Handle actors separately because of bodyparts
3685         if (rendering)
3686             return mPhysics->getRenderingHalfExtents(object);
3687         else
3688             return mPhysics->getHalfExtents(object);
3689     }
3690 
exportSceneGraph(const Ptr & ptr)3691     std::string World::exportSceneGraph(const Ptr &ptr)
3692     {
3693         std::string file = mUserDataPath + "/openmw.osgt";
3694         if (!ptr.isEmpty())
3695         {
3696             mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
3697             mWorldScene->removeFromPagedRefs(ptr);
3698         }
3699         mRendering->exportSceneGraph(ptr, file, "Ascii");
3700         return file;
3701     }
3702 
spawnRandomCreature(const std::string & creatureList)3703     void World::spawnRandomCreature(const std::string &creatureList)
3704     {
3705         const ESM::CreatureLevList* list = mStore.get<ESM::CreatureLevList>().find(creatureList);
3706 
3707         static int iNumberCreatures = mStore.get<ESM::GameSetting>().find("iNumberCreatures")->mValue.getInteger();
3708         int numCreatures = 1 + Misc::Rng::rollDice(iNumberCreatures); // [1, iNumberCreatures]
3709 
3710         for (int i=0; i<numCreatures; ++i)
3711         {
3712             std::string selectedCreature = MWMechanics::getLevelledItem(list, true);
3713             if (selectedCreature.empty())
3714                 continue;
3715 
3716             MWWorld::ManualRef ref(mStore, selectedCreature, 1);
3717 
3718             safePlaceObject(ref.getPtr(), getPlayerPtr(), getPlayerPtr().getCell(), 0, 220.f);
3719         }
3720     }
3721 
spawnBloodEffect(const Ptr & ptr,const osg::Vec3f & worldPosition)3722     void World::spawnBloodEffect(const Ptr &ptr, const osg::Vec3f &worldPosition)
3723     {
3724         if (ptr == getPlayerPtr() && Settings::Manager::getBool("hit fader", "GUI"))
3725             return;
3726 
3727         std::string texture = Fallback::Map::getString("Blood_Texture_" + std::to_string(ptr.getClass().getBloodTexture(ptr)));
3728         if (texture.empty())
3729             texture = Fallback::Map::getString("Blood_Texture_0");
3730 
3731         std::string model = "meshes\\" + Fallback::Map::getString("Blood_Model_" + std::to_string(Misc::Rng::rollDice(3))); // [0, 2]
3732 
3733         mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false);
3734     }
3735 
spawnEffect(const std::string & model,const std::string & textureOverride,const osg::Vec3f & worldPos,float scale,bool isMagicVFX)3736     void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos, float scale, bool isMagicVFX)
3737     {
3738         mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX);
3739     }
3740 
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)3741     void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType,
3742                              const std::string& id, const std::string& sourceName, const bool fromProjectile)
3743     {
3744         std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> > toApply;
3745         for (const ESM::ENAMstruct& effectInfo : effects.mList)
3746         {
3747             const ESM::MagicEffect* effect = mStore.get<ESM::MagicEffect>().find(effectInfo.mEffectID);
3748 
3749             if (effectInfo.mRange != rangeType || (effectInfo.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor()))
3750                 continue; // Not right range type, or not area effect and hit an actor
3751 
3752             if (fromProjectile && effectInfo.mArea <= 0)
3753                 continue; // Don't play explosion for projectiles with 0-area effects
3754 
3755             if (!fromProjectile && effectInfo.mRange == ESM::RT_Touch && !ignore.isEmpty() && !ignore.getClass().isActor() && !ignore.getClass().hasToolTip(ignore))
3756                 continue; // Don't play explosion for touch spells on non-activatable objects except when spell is from the projectile enchantment
3757 
3758             // Spawn the explosion orb effect
3759             const ESM::Static* areaStatic;
3760             if (!effect->mArea.empty())
3761                 areaStatic = mStore.get<ESM::Static>().find (effect->mArea);
3762             else
3763                 areaStatic = mStore.get<ESM::Static>().find ("VFX_DefaultArea");
3764 
3765             std::string texture = effect->mParticle;
3766 
3767             if (effectInfo.mArea <= 0)
3768             {
3769                 if (effectInfo.mRange == ESM::RT_Target)
3770                     mRendering->spawnEffect("meshes\\" + areaStatic->mModel, texture, origin, 1.0f);
3771                 continue;
3772             }
3773             else
3774                 mRendering->spawnEffect("meshes\\" + areaStatic->mModel, texture, origin, static_cast<float>(effectInfo.mArea * 2));
3775 
3776             // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now)
3777             static const std::string schools[] = {
3778                 "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
3779             };
3780             {
3781                 MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
3782                 if(!effect->mAreaSound.empty())
3783                     sndMgr->playSound3D(origin, effect->mAreaSound, 1.0f, 1.0f);
3784                 else
3785                     sndMgr->playSound3D(origin, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f);
3786             }
3787             // Get the actors in range of the effect
3788             std::vector<MWWorld::Ptr> objects;
3789             MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(
3790                         origin, feetToGameUnits(static_cast<float>(effectInfo.mArea)), objects);
3791             for (const Ptr& affected : objects)
3792             {
3793                 // Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing range.
3794                 if (affected.getClass().isActor() && !isActorCollisionEnabled(affected))
3795                     continue;
3796 
3797                 toApply[affected].push_back(effectInfo);
3798             }
3799         }
3800 
3801         // Now apply the appropriate effects to each actor in range
3802         for (auto& applyPair : toApply)
3803         {
3804             MWWorld::Ptr source = caster;
3805             // Vanilla-compatible behaviour of never applying the spell to the caster
3806             // (could be changed by mods later)
3807             if (applyPair.first == caster)
3808                 continue;
3809 
3810             if (applyPair.first == ignore)
3811                 continue;
3812 
3813             if (source.isEmpty())
3814                 source = applyPair.first;
3815 
3816             MWMechanics::CastSpell cast(source, applyPair.first);
3817             cast.mHitPosition = origin;
3818             cast.mId = id;
3819             cast.mSourceName = sourceName;
3820             cast.mStack = false;
3821             ESM::EffectList effectsToApply;
3822             effectsToApply.mList = applyPair.second;
3823             cast.inflict(applyPair.first, caster, effectsToApply, rangeType, false, true);
3824         }
3825     }
3826 
activate(const Ptr & object,const Ptr & actor)3827     void World::activate(const Ptr &object, const Ptr &actor)
3828     {
3829         breakInvisibility(actor);
3830 
3831         if (object.getRefData().activate())
3832         {
3833             std::shared_ptr<MWWorld::Action> action = (object.getClass().activate(object, actor));
3834             action->execute (actor);
3835         }
3836     }
3837 
3838     struct ResetActorsVisitor
3839     {
operator ()MWWorld::ResetActorsVisitor3840         bool operator() (Ptr ptr)
3841         {
3842             if (ptr.getClass().isActor() && ptr.getCellRef().hasContentFile())
3843             {
3844                 if (ptr.getCell()->movedHere(ptr))
3845                     return true;
3846 
3847                 const ESM::Position& origPos = ptr.getCellRef().getPosition();
3848                 MWBase::Environment::get().getWorld()->moveObject(ptr, origPos.pos[0], origPos.pos[1], origPos.pos[2]);
3849                 MWBase::Environment::get().getWorld()->rotateObject(ptr, origPos.rot[0], origPos.rot[1], origPos.rot[2]);
3850                 ptr.getClass().adjustPosition(ptr, true);
3851             }
3852             return true;
3853         }
3854     };
resetActors()3855     void World::resetActors()
3856     {
3857         for (CellStore* cellstore : mWorldScene->getActiveCells())
3858         {
3859             ResetActorsVisitor visitor;
3860             cellstore->forEach(visitor);
3861         }
3862     }
3863 
isWalkingOnWater(const ConstPtr & actor) const3864     bool World::isWalkingOnWater(const ConstPtr &actor) const
3865     {
3866         const MWPhysics::Actor* physicActor = mPhysics->getActor(actor);
3867         if (physicActor && physicActor->isWalkingOnWater())
3868             return true;
3869         return false;
3870     }
3871 
aimToTarget(const ConstPtr & actor,const ConstPtr & target,bool isRangedCombat)3872     osg::Vec3f World::aimToTarget(const ConstPtr &actor, const ConstPtr &target, bool isRangedCombat)
3873     {
3874         osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3();
3875         float heightRatio = isRangedCombat ? 2.f * Constants::TorsoHeight : 1.f;
3876         weaponPos.z() += mPhysics->getHalfExtents(actor).z() * heightRatio;
3877         osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target);
3878         return (targetPos - weaponPos);
3879     }
3880 
getHitDistance(const ConstPtr & actor,const ConstPtr & target)3881     float World::getHitDistance(const ConstPtr &actor, const ConstPtr &target)
3882     {
3883         osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3();
3884         osg::Vec3f halfExtents = mPhysics->getHalfExtents(actor);
3885         weaponPos.z() += halfExtents.z();
3886 
3887         return mPhysics->getHitDistance(weaponPos, target) - halfExtents.y();
3888     }
3889 
preload(MWWorld::Scene * scene,const ESMStore & store,const std::string & obj)3890     void preload(MWWorld::Scene* scene, const ESMStore& store, const std::string& obj)
3891     {
3892         if (obj.empty())
3893             return;
3894         try
3895         {
3896             MWWorld::ManualRef ref(store, obj);
3897             std::string model = ref.getPtr().getClass().getModel(ref.getPtr());
3898             if (!model.empty())
3899                 scene->preload(model, ref.getPtr().getClass().useAnim());
3900         }
3901         catch(std::exception&)
3902         {
3903         }
3904     }
3905 
preloadEffects(const ESM::EffectList * effectList)3906     void World::preloadEffects(const ESM::EffectList *effectList)
3907     {
3908         for (const ESM::ENAMstruct& effectInfo : effectList->mList)
3909         {
3910             const ESM::MagicEffect *effect = mStore.get<ESM::MagicEffect>().find(effectInfo.mEffectID);
3911 
3912             if (MWMechanics::isSummoningEffect(effectInfo.mEffectID))
3913             {
3914                 preload(mWorldScene.get(), mStore, "VFX_Summon_Start");
3915                 preload(mWorldScene.get(), mStore, MWMechanics::getSummonedCreature(effectInfo.mEffectID));
3916             }
3917 
3918             preload(mWorldScene.get(), mStore, effect->mCasting);
3919             preload(mWorldScene.get(), mStore, effect->mHit);
3920 
3921             if (effectInfo.mArea > 0)
3922                 preload(mWorldScene.get(), mStore, effect->mArea);
3923             if (effectInfo.mRange == ESM::RT_Target)
3924                 preload(mWorldScene.get(), mStore, effect->mBolt);
3925         }
3926     }
3927 
getNavigator() const3928     DetourNavigator::Navigator* World::getNavigator() const
3929     {
3930         return mNavigator.get();
3931     }
3932 
updateActorPath(const MWWorld::ConstPtr & actor,const std::deque<osg::Vec3f> & path,const osg::Vec3f & halfExtents,const osg::Vec3f & start,const osg::Vec3f & end) const3933     void World::updateActorPath(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
3934             const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const
3935     {
3936         mRendering->updateActorPath(actor, path, halfExtents, start, end);
3937     }
3938 
removeActorPath(const MWWorld::ConstPtr & actor) const3939     void World::removeActorPath(const MWWorld::ConstPtr& actor) const
3940     {
3941         mRendering->removeActorPath(actor);
3942     }
3943 
setNavMeshNumberToRender(const std::size_t value)3944     void World::setNavMeshNumberToRender(const std::size_t value)
3945     {
3946         mRendering->setNavMeshNumber(value);
3947     }
3948 
getPathfindingHalfExtents(const MWWorld::ConstPtr & actor) const3949     osg::Vec3f World::getPathfindingHalfExtents(const MWWorld::ConstPtr& actor) const
3950     {
3951         if (actor.isInCell() && actor.getCell()->isExterior())
3952             return mDefaultHalfExtents; // Using default half extents for better performance
3953         else
3954             return getHalfExtents(actor);
3955     }
3956 
hasCollisionWithDoor(const MWWorld::ConstPtr & door,const osg::Vec3f & position,const osg::Vec3f & destination) const3957     bool World::hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const
3958     {
3959         const auto object = mPhysics->getObject(door);
3960 
3961         if (!object)
3962             return false;
3963 
3964         btVector3 aabbMin;
3965         btVector3 aabbMax;
3966         object->getShapeInstance()->getCollisionShape()->getAabb(btTransform::getIdentity(), aabbMin, aabbMax);
3967 
3968         const auto toLocal = object->getTransform().inverse();
3969         const auto localFrom = toLocal(Misc::Convert::toBullet(position));
3970         const auto localTo = toLocal(Misc::Convert::toBullet(destination));
3971 
3972         btScalar hitDistance = 1;
3973         btVector3 hitNormal;
3974         return btRayAabb(localFrom, localTo, aabbMin, aabbMax, hitDistance, hitNormal);
3975     }
3976 
isAreaOccupiedByOtherActor(const osg::Vec3f & position,const float radius,const MWWorld::ConstPtr & ignore) const3977     bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const
3978     {
3979         return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore);
3980     }
3981 
reportStats(unsigned int frameNumber,osg::Stats & stats) const3982     void World::reportStats(unsigned int frameNumber, osg::Stats& stats) const
3983     {
3984         mNavigator->reportStats(frameNumber, stats);
3985         mPhysics->reportStats(frameNumber, stats);
3986     }
3987 
updateSkyDate()3988     void World::updateSkyDate()
3989     {
3990         ESM::EpochTimeStamp currentDate = mCurrentDate->getEpochTimeStamp();
3991         mRendering->skySetDate(currentDate.mDay, currentDate.mMonth);
3992     }
3993 
getAll(const std::string & id)3994     std::vector<MWWorld::Ptr> World::getAll(const std::string& id)
3995     {
3996         return mCells.getAll(id);
3997     }
3998 }
3999