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