mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-26 18:35:20 +00:00
ce263af393
Remove WindowManager wrappers. It's not safe to use WindowManager in all places and it's not required. Environment stores resource system providing VFS required to call these functions. In the case of ObjectPaging it's available from the member variable. Also ObjectPaging::createChunk may access WindowManager when it's already destructed when exiting the game because it's destructed before CellPreloader finishes all background jobs. Engine::mResourceSystem is destructed after all other systems so it's safe to use it.
372 lines
13 KiB
C++
372 lines
13 KiB
C++
#include "door.hpp"
|
|
|
|
#include <MyGUI_TextIterator.h>
|
|
|
|
#include <components/esm3/loaddoor.hpp>
|
|
#include <components/esm3/doorstate.hpp>
|
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
#include "../mwbase/soundmanager.hpp"
|
|
|
|
#include "../mwworld/ptr.hpp"
|
|
#include "../mwworld/failedaction.hpp"
|
|
#include "../mwworld/actionteleport.hpp"
|
|
#include "../mwworld/actiondoor.hpp"
|
|
#include "../mwworld/cellstore.hpp"
|
|
#include "../mwworld/esmstore.hpp"
|
|
#include "../mwphysics/physicssystem.hpp"
|
|
#include "../mwworld/inventorystore.hpp"
|
|
#include "../mwworld/actiontrap.hpp"
|
|
#include "../mwworld/customdata.hpp"
|
|
#include "../mwworld/cellutils.hpp"
|
|
|
|
#include "../mwgui/tooltips.hpp"
|
|
|
|
#include "../mwrender/objects.hpp"
|
|
#include "../mwrender/renderinginterface.hpp"
|
|
#include "../mwrender/animation.hpp"
|
|
#include "../mwrender/vismask.hpp"
|
|
|
|
#include "../mwmechanics/actorutil.hpp"
|
|
|
|
#include "classmodel.hpp"
|
|
|
|
namespace MWClass
|
|
{
|
|
class DoorCustomData : public MWWorld::TypedCustomData<DoorCustomData>
|
|
{
|
|
public:
|
|
MWWorld::DoorState mDoorState = MWWorld::DoorState::Idle;
|
|
|
|
DoorCustomData& asDoorCustomData() override
|
|
{
|
|
return *this;
|
|
}
|
|
const DoorCustomData& asDoorCustomData() const override
|
|
{
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
Door::Door()
|
|
: MWWorld::RegisteredClass<Door>(ESM::Door::sRecordId)
|
|
{
|
|
}
|
|
|
|
void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
|
|
{
|
|
if (!model.empty())
|
|
{
|
|
renderingInterface.getObjects().insertModel(ptr, model, true);
|
|
ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static);
|
|
}
|
|
}
|
|
|
|
void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const
|
|
{
|
|
insertObjectPhysics(ptr, model, rotation, physics);
|
|
|
|
// Resume the door's opening/closing animation if it wasn't finished
|
|
if (ptr.getRefData().getCustomData())
|
|
{
|
|
const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
|
|
if (customData.mDoorState != MWWorld::DoorState::Idle)
|
|
{
|
|
MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Door::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const
|
|
{
|
|
physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door);
|
|
}
|
|
|
|
bool Door::isDoor() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool Door::useAnim() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
std::string Door::getModel(const MWWorld::ConstPtr &ptr) const
|
|
{
|
|
return getClassModel<ESM::Door>(ptr);
|
|
}
|
|
|
|
std::string Door::getName (const MWWorld::ConstPtr& ptr) const
|
|
{
|
|
const MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
|
|
const std::string& name = ref->mBase->mName;
|
|
|
|
return !name.empty() ? name : ref->mBase->mId;
|
|
}
|
|
|
|
std::unique_ptr<MWWorld::Action> Door::activate (const MWWorld::Ptr& ptr,
|
|
const MWWorld::Ptr& actor) const
|
|
{
|
|
MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
|
|
|
|
const std::string &openSound = ref->mBase->mOpenSound;
|
|
const std::string &closeSound = ref->mBase->mCloseSound;
|
|
const std::string lockedSound = "LockedDoor";
|
|
const std::string trapActivationSound = "Disarm Trap Fail";
|
|
|
|
MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor);
|
|
|
|
bool isLocked = ptr.getCellRef().getLockLevel() > 0;
|
|
bool isTrapped = !ptr.getCellRef().getTrap().empty();
|
|
bool hasKey = false;
|
|
std::string keyName;
|
|
|
|
// FIXME: If NPC activate teleporting door, it can lead to crash due to iterator invalidation in the Actors update.
|
|
// Make such activation a no-op for now, like how it is in the vanilla game.
|
|
if (actor != MWMechanics::getPlayer() && ptr.getCellRef().getTeleport())
|
|
{
|
|
std::unique_ptr<MWWorld::Action> action = std::make_unique<MWWorld::FailedAction>(std::string(), ptr);
|
|
action->setSound(lockedSound);
|
|
return action;
|
|
}
|
|
|
|
// make door glow if player activates it with telekinesis
|
|
if (actor == MWMechanics::getPlayer() &&
|
|
MWBase::Environment::get().getWorld()->getDistanceToFacedObject() >
|
|
MWBase::Environment::get().getWorld()->getMaxActivationDistance())
|
|
{
|
|
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
|
|
if(animation)
|
|
{
|
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
|
int index = ESM::MagicEffect::effectStringToId("sEffectTelekinesis");
|
|
const ESM::MagicEffect *effect = store.get<ESM::MagicEffect>().find(index);
|
|
|
|
animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing
|
|
}
|
|
}
|
|
|
|
const std::string keyId = ptr.getCellRef().getKey();
|
|
if (!keyId.empty())
|
|
{
|
|
MWWorld::Ptr keyPtr = invStore.search(keyId);
|
|
if (!keyPtr.isEmpty())
|
|
{
|
|
hasKey = true;
|
|
keyName = keyPtr.getClass().getName(keyPtr);
|
|
}
|
|
}
|
|
|
|
if (isLocked && hasKey)
|
|
{
|
|
if(actor == MWMechanics::getPlayer())
|
|
MWBase::Environment::get().getWindowManager()->messageBox(keyName + " #{sKeyUsed}");
|
|
ptr.getCellRef().unlock(); //Call the function here. because that makes sense.
|
|
// using a key disarms the trap
|
|
if(isTrapped)
|
|
{
|
|
ptr.getCellRef().setTrap("");
|
|
MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Disarm Trap", 1.0f, 1.0f);
|
|
isTrapped = false;
|
|
}
|
|
}
|
|
|
|
if (!isLocked || hasKey)
|
|
{
|
|
if(isTrapped)
|
|
{
|
|
// Trap activation
|
|
std::unique_ptr<MWWorld::Action> action = std::make_unique<MWWorld::ActionTrap>(ptr.getCellRef().getTrap(), ptr);
|
|
action->setSound(trapActivationSound);
|
|
return action;
|
|
}
|
|
|
|
if (ptr.getCellRef().getTeleport())
|
|
{
|
|
if (actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getDistanceToFacedObject() > MWBase::Environment::get().getWorld()->getMaxActivationDistance())
|
|
{
|
|
// player activated teleport door with telekinesis
|
|
return std::make_unique<MWWorld::FailedAction>();
|
|
}
|
|
else
|
|
{
|
|
std::unique_ptr<MWWorld::Action> action = std::make_unique<MWWorld::ActionTeleport>(ptr.getCellRef().getDestCell(), ptr.getCellRef().getDoorDest(), true);
|
|
action->setSound(openSound);
|
|
return action;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// animated door
|
|
std::unique_ptr<MWWorld::Action> action = std::make_unique<MWWorld::ActionDoor>(ptr);
|
|
const auto doorState = getDoorState(ptr);
|
|
bool opening = true;
|
|
float doorRot = ptr.getRefData().getPosition().rot[2] - ptr.getCellRef().getPosition().rot[2];
|
|
if (doorState == MWWorld::DoorState::Opening)
|
|
opening = false;
|
|
if (doorState == MWWorld::DoorState::Idle && doorRot != 0)
|
|
opening = false;
|
|
|
|
if (opening)
|
|
{
|
|
MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr,
|
|
closeSound, 0.5f);
|
|
// Doors rotate at 90 degrees per second, so start the sound at
|
|
// where it would be at the current rotation.
|
|
float offset = doorRot/(osg::PI * 0.5f);
|
|
action->setSoundOffset(offset);
|
|
action->setSound(openSound);
|
|
}
|
|
else
|
|
{
|
|
MWBase::Environment::get().getSoundManager()->fadeOutSound3D(ptr,
|
|
openSound, 0.5f);
|
|
float offset = 1.0f - doorRot/(osg::PI * 0.5f);
|
|
action->setSoundOffset(std::max(offset, 0.0f));
|
|
action->setSound(closeSound);
|
|
}
|
|
|
|
return action;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// locked, and we can't open.
|
|
std::unique_ptr<MWWorld::Action> action = std::make_unique<MWWorld::FailedAction>(std::string(), ptr);
|
|
action->setSound(lockedSound);
|
|
return action;
|
|
}
|
|
}
|
|
|
|
bool Door::canLock(const MWWorld::ConstPtr &ptr) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool Door::allowTelekinesis(const MWWorld::ConstPtr &ptr) const
|
|
{
|
|
if (ptr.getCellRef().getTeleport() && ptr.getCellRef().getLockLevel() <= 0 && ptr.getCellRef().getTrap().empty())
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
std::string Door::getScript (const MWWorld::ConstPtr& ptr) const
|
|
{
|
|
const MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
|
|
|
|
return ref->mBase->mScript;
|
|
}
|
|
|
|
MWGui::ToolTipInfo Door::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const
|
|
{
|
|
const MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
|
|
|
|
MWGui::ToolTipInfo info;
|
|
info.caption = MyGUI::TextIterator::toTagsString(getName(ptr));
|
|
|
|
std::string text;
|
|
|
|
if (ptr.getCellRef().getTeleport())
|
|
{
|
|
text += "\n#{sTo}";
|
|
text += "\n" + getDestination(*ref);
|
|
}
|
|
|
|
int lockLevel = ptr.getCellRef().getLockLevel();
|
|
if (lockLevel > 0 && lockLevel != ESM::UnbreakableLock)
|
|
text += "\n#{sLockLevel}: " + MWGui::ToolTips::toString(ptr.getCellRef().getLockLevel());
|
|
else if (ptr.getCellRef().getLockLevel() < 0)
|
|
text += "\n#{sUnlocked}";
|
|
if (ptr.getCellRef().getTrap() != "")
|
|
text += "\n#{sTrapped}";
|
|
|
|
if (MWBase::Environment::get().getWindowManager()->getFullHelp())
|
|
{
|
|
text += MWGui::ToolTips::getCellRefString(ptr.getCellRef());
|
|
text += MWGui::ToolTips::getMiscString(ref->mBase->mScript, "Script");
|
|
}
|
|
info.text = text;
|
|
|
|
return info;
|
|
}
|
|
|
|
std::string Door::getDestination (const MWWorld::LiveCellRef<ESM::Door>& door)
|
|
{
|
|
std::string dest = door.mRef.getDestCell();
|
|
if (dest.empty())
|
|
{
|
|
// door leads to exterior, use cell name (if any), otherwise translated region name
|
|
auto world = MWBase::Environment::get().getWorld();
|
|
const osg::Vec2i index = MWWorld::positionToCellIndex(door.mRef.getDoorDest().pos[0],
|
|
door.mRef.getDoorDest().pos[1]);
|
|
const ESM::Cell* cell = world->getStore().get<ESM::Cell>().search(index.x(), index.y());
|
|
dest = world->getCellName(cell);
|
|
}
|
|
|
|
return "#{sCell=" + dest + "}";
|
|
}
|
|
|
|
MWWorld::Ptr Door::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const
|
|
{
|
|
const MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
|
|
|
|
return MWWorld::Ptr(cell.insert(ref), &cell);
|
|
}
|
|
|
|
void Door::ensureCustomData(const MWWorld::Ptr &ptr) const
|
|
{
|
|
if (!ptr.getRefData().getCustomData())
|
|
{
|
|
ptr.getRefData().setCustomData(std::make_unique<DoorCustomData>());
|
|
}
|
|
}
|
|
|
|
MWWorld::DoorState Door::getDoorState (const MWWorld::ConstPtr &ptr) const
|
|
{
|
|
if (!ptr.getRefData().getCustomData())
|
|
return MWWorld::DoorState::Idle;
|
|
const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
|
|
return customData.mDoorState;
|
|
}
|
|
|
|
void Door::setDoorState (const MWWorld::Ptr &ptr, MWWorld::DoorState state) const
|
|
{
|
|
if (ptr.getCellRef().getTeleport())
|
|
throw std::runtime_error("load doors can't be moved");
|
|
|
|
ensureCustomData(ptr);
|
|
DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
|
|
customData.mDoorState = state;
|
|
}
|
|
|
|
void Door::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const
|
|
{
|
|
if (!state.mHasCustomState)
|
|
return;
|
|
|
|
ensureCustomData(ptr);
|
|
DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
|
|
const ESM::DoorState& doorState = state.asDoorState();
|
|
customData.mDoorState = MWWorld::DoorState(doorState.mDoorState);
|
|
}
|
|
|
|
void Door::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
|
|
{
|
|
if (!ptr.getRefData().getCustomData())
|
|
{
|
|
state.mHasCustomState = false;
|
|
return;
|
|
}
|
|
|
|
const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
|
|
ESM::DoorState& doorState = state.asDoorState();
|
|
doorState.mDoorState = int(customData.mDoorState);
|
|
}
|
|
|
|
}
|