1 #include "door.hpp"
2 
3 #include <components/esm/loaddoor.hpp>
4 #include <components/esm/doorstate.hpp>
5 #include <components/sceneutil/positionattitudetransform.hpp>
6 
7 #include "../mwbase/environment.hpp"
8 #include "../mwbase/world.hpp"
9 #include "../mwbase/windowmanager.hpp"
10 #include "../mwbase/soundmanager.hpp"
11 
12 #include "../mwworld/ptr.hpp"
13 #include "../mwworld/failedaction.hpp"
14 #include "../mwworld/actionteleport.hpp"
15 #include "../mwworld/actiondoor.hpp"
16 #include "../mwworld/cellstore.hpp"
17 #include "../mwworld/esmstore.hpp"
18 #include "../mwphysics/physicssystem.hpp"
19 #include "../mwworld/inventorystore.hpp"
20 #include "../mwworld/actiontrap.hpp"
21 #include "../mwworld/customdata.hpp"
22 
23 #include "../mwgui/tooltips.hpp"
24 
25 #include "../mwrender/objects.hpp"
26 #include "../mwrender/renderinginterface.hpp"
27 #include "../mwrender/animation.hpp"
28 #include "../mwrender/vismask.hpp"
29 
30 #include "../mwmechanics/actorutil.hpp"
31 
32 namespace MWClass
33 {
34     class DoorCustomData : public MWWorld::TypedCustomData<DoorCustomData>
35     {
36     public:
37         MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle;
38 
asDoorCustomData()39         DoorCustomData& asDoorCustomData() override
40         {
41             return *this;
42         }
asDoorCustomData() const43         const DoorCustomData& asDoorCustomData() const override
44         {
45             return *this;
46         }
47     };
48 
insertObjectRendering(const MWWorld::Ptr & ptr,const std::string & model,MWRender::RenderingInterface & renderingInterface) const49     void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
50     {
51         if (!model.empty())
52         {
53             renderingInterface.getObjects().insertModel(ptr, model, true);
54             ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static);
55         }
56     }
57 
insertObject(const MWWorld::Ptr & ptr,const std::string & model,MWPhysics::PhysicsSystem & physics) const58     void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const
59     {
60         if(!model.empty())
61             physics.addObject(ptr, model, MWPhysics::CollisionType_Door);
62 
63         // Resume the door's opening/closing animation if it wasn't finished
64         if (ptr.getRefData().getCustomData())
65         {
66             const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
67             if (customData.mDoorState != MWWorld::DoorState::Idle)
68             {
69                 MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState);
70             }
71         }
72     }
73 
isDoor() const74     bool Door::isDoor() const
75     {
76         return true;
77     }
78 
useAnim() const79     bool Door::useAnim() const
80     {
81         return true;
82     }
83 
getModel(const MWWorld::ConstPtr & ptr) const84     std::string Door::getModel(const MWWorld::ConstPtr &ptr) const
85     {
86         const MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
87 
88         const std::string &model = ref->mBase->mModel;
89         if (!model.empty()) {
90             return "meshes\\" + model;
91         }
92         return "";
93     }
94 
getName(const MWWorld::ConstPtr & ptr) const95     std::string Door::getName (const MWWorld::ConstPtr& ptr) const
96     {
97         const MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
98         const std::string& name = ref->mBase->mName;
99 
100         return !name.empty() ? name : ref->mBase->mId;
101     }
102 
activate(const MWWorld::Ptr & ptr,const MWWorld::Ptr & actor) const103     std::shared_ptr<MWWorld::Action> Door::activate (const MWWorld::Ptr& ptr,
104         const MWWorld::Ptr& actor) const
105     {
106         MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
107 
108         const std::string &openSound = ref->mBase->mOpenSound;
109         const std::string &closeSound = ref->mBase->mCloseSound;
110         const std::string lockedSound = "LockedDoor";
111         const std::string trapActivationSound = "Disarm Trap Fail";
112 
113         MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor);
114 
115         bool isLocked = ptr.getCellRef().getLockLevel() > 0;
116         bool isTrapped = !ptr.getCellRef().getTrap().empty();
117         bool hasKey = false;
118         std::string keyName;
119 
120         // FIXME: If NPC activate teleporting door, it can lead to crash due to iterator invalidation in the Actors update.
121         // Make such activation a no-op for now, like how it is in the vanilla game.
122         if (actor != MWMechanics::getPlayer() && ptr.getCellRef().getTeleport())
123         {
124             std::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction(std::string(), ptr));
125             action->setSound(lockedSound);
126             return action;
127         }
128 
129         // make door glow if player activates it with telekinesis
130         if (actor == MWMechanics::getPlayer() &&
131             MWBase::Environment::get().getWorld()->getDistanceToFacedObject() >
132             MWBase::Environment::get().getWorld()->getMaxActivationDistance())
133         {
134             MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
135             if(animation)
136             {
137                 const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
138                 int index = ESM::MagicEffect::effectStringToId("sEffectTelekinesis");
139                 const ESM::MagicEffect *effect = store.get<ESM::MagicEffect>().find(index);
140 
141                 animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing
142             }
143         }
144 
145         const std::string keyId = ptr.getCellRef().getKey();
146         if (!keyId.empty())
147         {
148             MWWorld::Ptr keyPtr = invStore.search(keyId);
149             if (!keyPtr.isEmpty())
150             {
151                 hasKey = true;
152                 keyName = keyPtr.getClass().getName(keyPtr);
153             }
154         }
155 
156         if (isLocked && hasKey)
157         {
158             if(actor == MWMechanics::getPlayer())
159                 MWBase::Environment::get().getWindowManager()->messageBox(keyName + " #{sKeyUsed}");
160             ptr.getCellRef().unlock(); //Call the function here. because that makes sense.
161             // using a key disarms the trap
162             if(isTrapped)
163             {
164                 ptr.getCellRef().setTrap("");
165                 MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Disarm Trap", 1.0f, 1.0f);
166                 isTrapped = false;
167             }
168         }
169 
170         if (!isLocked || hasKey)
171         {
172             if(isTrapped)
173             {
174                 // Trap activation
175                 std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionTrap(ptr.getCellRef().getTrap(), ptr));
176                 action->setSound(trapActivationSound);
177                 return action;
178             }
179 
180             if (ptr.getCellRef().getTeleport())
181             {
182                 if (actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > MWBase::Environment::get().getWorld()->getMaxActivationDistance())
183                 {
184                     // player activated teleport door with telekinesis
185                     std::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction);
186                     return action;
187                 }
188                 else
189                 {
190                     std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionTeleport (ptr.getCellRef().getDestCell(), ptr.getCellRef().getDoorDest(), true));
191                     action->setSound(openSound);
192                     return action;
193                 }
194             }
195             else
196             {
197                 // animated door
198                 std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionDoor(ptr));
199                 const auto doorState = getDoorState(ptr);
200                 bool opening = true;
201                 float doorRot = ptr.getRefData().getPosition().rot[2] - ptr.getCellRef().getPosition().rot[2];
202                 if (doorState == MWWorld::DoorState::Opening)
203                     opening = false;
204                 if (doorState == MWWorld::DoorState::Idle && doorRot != 0)
205                     opening = false;
206 
207                 if (opening)
208                 {
209                     MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr,
210                             closeSound, 0.5f);
211                     // Doors rotate at 90 degrees per second, so start the sound at
212                     // where it would be at the current rotation.
213                     float offset = doorRot/(osg::PI * 0.5f);
214                     action->setSoundOffset(offset);
215                     action->setSound(openSound);
216                 }
217                 else
218                 {
219                     MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr,
220                                                 openSound, 0.5f);
221                     float offset = 1.0f - doorRot/(osg::PI * 0.5f);
222                     action->setSoundOffset(std::max(offset, 0.0f));
223                     action->setSound(closeSound);
224                 }
225 
226                 return action;
227             }
228         }
229         else
230         {
231             // locked, and we can't open.
232             std::shared_ptr<MWWorld::Action> action(new MWWorld::FailedAction(std::string(), ptr));
233             action->setSound(lockedSound);
234             return action;
235         }
236     }
237 
canLock(const MWWorld::ConstPtr & ptr) const238     bool Door::canLock(const MWWorld::ConstPtr &ptr) const
239     {
240         return true;
241     }
242 
allowTelekinesis(const MWWorld::ConstPtr & ptr) const243     bool Door::allowTelekinesis(const MWWorld::ConstPtr &ptr) const
244     {
245         if (ptr.getCellRef().getTeleport() && ptr.getCellRef().getLockLevel() <= 0 && ptr.getCellRef().getTrap().empty())
246             return false;
247         else
248             return true;
249     }
250 
getScript(const MWWorld::ConstPtr & ptr) const251     std::string Door::getScript (const MWWorld::ConstPtr& ptr) const
252     {
253         const MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
254 
255         return ref->mBase->mScript;
256     }
257 
registerSelf()258     void Door::registerSelf()
259     {
260         std::shared_ptr<Class> instance (new Door);
261 
262         registerClass (typeid (ESM::Door).name(), instance);
263     }
264 
getToolTipInfo(const MWWorld::ConstPtr & ptr,int count) const265     MWGui::ToolTipInfo Door::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const
266     {
267         const MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
268 
269         MWGui::ToolTipInfo info;
270         info.caption = MyGUI::TextIterator::toTagsString(getName(ptr));
271 
272         std::string text;
273 
274         if (ptr.getCellRef().getTeleport())
275         {
276             text += "\n#{sTo}";
277             text += "\n" + getDestination(*ref);
278         }
279 
280         int lockLevel = ptr.getCellRef().getLockLevel();
281         if (lockLevel > 0 && lockLevel != ESM::UnbreakableLock)
282             text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ptr.getCellRef().getLockLevel());
283         else if (ptr.getCellRef().getLockLevel() < 0)
284             text += "\n#{sUnlocked}";
285         if (ptr.getCellRef().getTrap() != "")
286             text += "\n#{sTrapped}";
287 
288         if (MWBase::Environment::get().getWindowManager()->getFullHelp())
289         {
290             text += MWGui::ToolTips::getCellRefString(ptr.getCellRef());
291             text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
292         }
293         info.text = text;
294 
295         return info;
296     }
297 
getDestination(const MWWorld::LiveCellRef<ESM::Door> & door)298     std::string Door::getDestination (const MWWorld::LiveCellRef<ESM::Door>& door)
299     {
300         std::string dest = door.mRef.getDestCell();
301         if (dest.empty())
302         {
303             // door leads to exterior, use cell name (if any), otherwise translated region name
304             int x,y;
305             auto world = MWBase::Environment::get().getWorld();
306             world->positionToIndex (door.mRef.getDoorDest().pos[0], door.mRef.getDoorDest().pos[1], x, y);
307             const ESM::Cell* cell = world->getStore().get<ESM::Cell>().search(x,y);
308             dest = world->getCellName(cell);
309         }
310 
311         return "#{sCell=" + dest + "}";
312     }
313 
copyToCellImpl(const MWWorld::ConstPtr & ptr,MWWorld::CellStore & cell) const314     MWWorld::Ptr Door::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const
315     {
316         const MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
317 
318         return MWWorld::Ptr(cell.insert(ref), &cell);
319     }
320 
ensureCustomData(const MWWorld::Ptr & ptr) const321     void Door::ensureCustomData(const MWWorld::Ptr &ptr) const
322     {
323         if (!ptr.getRefData().getCustomData())
324         {
325             ptr.getRefData().setCustomData(std::make_unique<DoorCustomData>());
326         }
327     }
328 
getDoorState(const MWWorld::ConstPtr & ptr) const329     MWWorld::DoorState Door::getDoorState (const MWWorld::ConstPtr &ptr) const
330     {
331         if (!ptr.getRefData().getCustomData())
332             return MWWorld::DoorState::Idle;
333         const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
334         return customData.mDoorState;
335     }
336 
setDoorState(const MWWorld::Ptr & ptr,MWWorld::DoorState state) const337     void Door::setDoorState (const MWWorld::Ptr &ptr, MWWorld::DoorState state) const
338     {
339         if (ptr.getCellRef().getTeleport())
340             throw std::runtime_error("load doors can't be moved");
341 
342         ensureCustomData(ptr);
343         DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
344         customData.mDoorState = state;
345     }
346 
readAdditionalState(const MWWorld::Ptr & ptr,const ESM::ObjectState & state) const347     void Door::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const
348     {
349         if (!state.mHasCustomState)
350             return;
351 
352         ensureCustomData(ptr);
353         DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
354         const ESM::DoorState& doorState = state.asDoorState();
355         customData.mDoorState = MWWorld::DoorState(doorState.mDoorState);
356     }
357 
writeAdditionalState(const MWWorld::ConstPtr & ptr,ESM::ObjectState & state) const358     void Door::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
359     {
360         if (!ptr.getRefData().getCustomData())
361         {
362             state.mHasCustomState = false;
363             return;
364         }
365 
366         const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
367         ESM::DoorState& doorState = state.asDoorState();
368         doorState.mDoorState = int(customData.mDoorState);
369     }
370 
371 }
372