From 543bb22e8f4b8a0c31c1920b610c7a0206916f74 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 29 Jul 2014 19:01:40 +0200 Subject: [PATCH] Implement collision script instructions (Fixes #1111) --- apps/openmw/mwbase/world.hpp | 9 +++ apps/openmw/mwscript/docs/vmformat.txt | 10 +++- apps/openmw/mwscript/miscextensions.cpp | 62 ++++++++++++++++++++ apps/openmw/mwworld/physicssystem.cpp | 76 ++++++++++++++++++++++++- apps/openmw/mwworld/physicssystem.hpp | 22 +++++++ apps/openmw/mwworld/worldimp.cpp | 60 ++++++++++++++++--- apps/openmw/mwworld/worldimp.hpp | 9 +++ components/compiler/extensions0.cpp | 4 ++ components/compiler/opcodes.hpp | 8 +++ libs/openengine/bullet/physic.cpp | 16 ------ libs/openengine/bullet/physic.hpp | 2 - libs/openengine/bullet/trace.cpp | 2 + libs/openengine/bullet/trace.h | 1 + 13 files changed, 252 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 29f326a1b..62da2682d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -404,6 +404,15 @@ namespace MWBase virtual bool getPlayerStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if any actor is standing on \a object + virtual bool getPlayerCollidingWith(const MWWorld::Ptr& object) = 0; ///< @return true if the player is colliding with \a object + virtual bool getActorCollidingWith (const MWWorld::Ptr& object) = 0; ///< @return true if any actor is colliding with \a object + virtual void hurtStandingActors (const MWWorld::Ptr& object, float dmgPerSecond) = 0; + ///< Apply a health difference to any actors standing on \a object. + /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. + virtual void hurtCollidingActors (const MWWorld::Ptr& object, float dmgPerSecond) = 0; + ///< Apply a health difference to any actors colliding with \a object. + /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. + virtual float getWindSpeed() = 0; virtual void getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out) = 0; diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 8c9d6e480..4b501b20f 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -405,5 +405,13 @@ op 0x200024c: Face op 0x200024d: Face, explicit op 0x200024e: GetStat (dummy function) op 0x200024f: GetStat (dummy function), explicit +op 0x2000250: GetCollidingPC +op 0x2000251: GetCollidingPC, explicit +op 0x2000252: GetCollidingActor +op 0x2000253: GetCollidingActor, explicit +op 0x2000254: HurtStandingActor +op 0x2000255: HurtStandingActor, explicit +op 0x2000256: HurtCollidingActor +op 0x2000257: HurtCollidingActor, explicit -opcodes 0x2000250-0x3ffffff unused +opcodes 0x2000258-0x3ffffff unused diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 4e0257d82..36848d44c 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -611,6 +611,60 @@ namespace MWScript } }; + template + class OpGetCollidingPc : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push (MWBase::Environment::get().getWorld()->getPlayerStandingOn(ptr)); + } + }; + + template + class OpGetCollidingActor : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + runtime.push (MWBase::Environment::get().getWorld()->getActorStandingOn(ptr)); + } + }; + + template + class OpHurtStandingActor : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + float healthDiffPerSecond = runtime[0].mFloat; + runtime.pop(); + + MWBase::Environment::get().getWorld()->hurtStandingActors(ptr, healthDiffPerSecond); + } + }; + + template + class OpHurtCollidingActor : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + float healthDiffPerSecond = runtime[0].mFloat; + runtime.pop(); + + MWBase::Environment::get().getWorld()->hurtCollidingActors(ptr, healthDiffPerSecond); + } + }; + class OpGetWindSpeed : public Interpreter::Opcode0 { public: @@ -967,6 +1021,14 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingPcExplicit, new OpGetStandingPc); interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingActor, new OpGetStandingActor); interpreter.installSegment5 (Compiler::Misc::opcodeGetStandingActorExplicit, new OpGetStandingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingPc, new OpGetCollidingPc); + interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingPcExplicit, new OpGetCollidingPc); + interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingActor, new OpGetCollidingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeGetCollidingActorExplicit, new OpGetCollidingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeHurtStandingActor, new OpHurtStandingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeHurtStandingActorExplicit, new OpHurtStandingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeHurtCollidingActor, new OpHurtCollidingActor); + interpreter.installSegment5 (Compiler::Misc::opcodeHurtCollidingActorExplicit, new OpHurtCollidingActor); interpreter.installSegment5 (Compiler::Misc::opcodeGetWindSpeed, new OpGetWindSpeed); interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMe, new OpHitOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMeExplicit, new OpHitOnMe); diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index daad5b0e6..02a198f3d 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -241,7 +241,9 @@ namespace MWWorld } static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, - bool isFlying, float waterlevel, float slowFall, OEngine::Physic::PhysicEngine *engine) + bool isFlying, float waterlevel, float slowFall, OEngine::Physic::PhysicEngine *engine + , std::map& collisionTracker + , std::map& standingCollisionTracker) { const ESM::Position &refpos = ptr.getRefData().getPosition(); Ogre::Vector3 position(refpos.pos); @@ -318,6 +320,11 @@ namespace MWWorld tracer.doTrace(colobj, position, position - Ogre::Vector3(0,0,2), engine); // check if down 2 possible if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) { + const btCollisionObject* standingOn = tracer.mHitObject; + if (const OEngine::Physic::RigidBody* body = dynamic_cast(standingOn)) + { + standingCollisionTracker[ptr.getRefData().getHandle()] = body->mName; + } isOnGround = true; // if we're on the ground, don't try to fall any more velocity.z = std::max(0.0f, velocity.z); @@ -376,6 +383,14 @@ namespace MWWorld remainingTime *= (1.0f-tracer.mFraction); // FIXME: remainingTime is no longer used so don't set it? break; } + else + { + const btCollisionObject* standingOn = tracer.mHitObject; + if (const OEngine::Physic::RigidBody* body = dynamic_cast(standingOn)) + { + collisionTracker[ptr.getRefData().getHandle()] = body->mName; + } + } } else { @@ -771,6 +786,10 @@ namespace MWWorld const PtrVelocityList& PhysicsSystem::applyQueuedMovement(float dt) { + // Collision events are only tracked for a single frame, so reset first + mCollisions.clear(); + mStandingCollisions.clear(); + mMovementResults.clear(); mTimeAccum += dt; @@ -810,7 +829,7 @@ namespace MWWorld Ogre::Vector3 newpos = MovementSolver::move(iter->first, iter->second, mTimeAccum, world->isFlying(iter->first), - waterlevel, slowFall, mEngine); + waterlevel, slowFall, mEngine, mCollisions, mStandingCollisions); if (waterCollision) mEngine->mDynamicsWorld->removeCollisionObject(&object); @@ -837,4 +856,57 @@ namespace MWWorld mEngine->stepSimulation(dt); } + + bool PhysicsSystem::isActorStandingOn(const Ptr &actor, const Ptr &object) const + { + const std::string& actorHandle = actor.getRefData().getHandle(); + const std::string& objectHandle = object.getRefData().getHandle(); + + for (std::map::const_iterator it = mStandingCollisions.begin(); + it != mStandingCollisions.end(); ++it) + { + if (it->first == actorHandle && it->second == objectHandle) + return true; + } + return false; + } + + void PhysicsSystem::getActorsStandingOn(const Ptr &object, std::vector &out) const + { + const std::string& objectHandle = object.getRefData().getHandle(); + + for (std::map::const_iterator it = mStandingCollisions.begin(); + it != mStandingCollisions.end(); ++it) + { + if (it->second == objectHandle) + out.push_back(it->first); + } + } + + bool PhysicsSystem::isActorCollidingWith(const Ptr &actor, const Ptr &object) const + { + const std::string& actorHandle = actor.getRefData().getHandle(); + const std::string& objectHandle = object.getRefData().getHandle(); + + for (std::map::const_iterator it = mCollisions.begin(); + it != mCollisions.end(); ++it) + { + if (it->first == actorHandle && it->second == objectHandle) + return true; + } + return false; + } + + void PhysicsSystem::getActorsCollidingWith(const Ptr &object, std::vector &out) const + { + const std::string& objectHandle = object.getRefData().getHandle(); + + for (std::map::const_iterator it = mCollisions.begin(); + it != mCollisions.end(); ++it) + { + if (it->second == objectHandle) + out.push_back(it->first); + } + } + } diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 8e0be95d5..ac60a80b5 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -87,12 +87,34 @@ namespace MWWorld const PtrVelocityList& applyQueuedMovement(float dt); + /// Return true if \a actor has been standing on \a object in this frame + /// This will trigger whenever the object is directly below the actor. + /// It doesn't matter if the actor is stationary or moving. + bool isActorStandingOn(const MWWorld::Ptr& actor, const MWWorld::Ptr& object) const; + + /// Get the handle of all actors standing on \a object in this frame. + void getActorsStandingOn(const MWWorld::Ptr& object, std::vector& out) const; + + /// Return true if \a actor has collided with \a object in this frame. + /// This will detect running into objects, but will not detect climbing stairs, stepping up a small object, etc. + bool isActorCollidingWith(const MWWorld::Ptr& actor, const MWWorld::Ptr& object) const; + + /// Get the handle of all actors colliding with \a object in this frame. + void getActorsCollidingWith(const MWWorld::Ptr& object, std::vector& out) const; + private: OEngine::Render::OgreRenderer &mRender; OEngine::Physic::PhysicEngine* mEngine; std::map handleToMesh; + // Tracks all movement collisions happening during a single frame. + // This will detect e.g. running against a vertical wall. It will not detect climbing up stairs, + // stepping up small objects, etc. + std::map mCollisions; + + std::map mStandingCollisions; + PtrVelocityList mMovementQueue; PtrVelocityList mMovementResults; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d1207dd8b..1c9faa4a7 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1995,18 +1995,62 @@ namespace MWWorld bool World::getPlayerStandingOn (const MWWorld::Ptr& object) { - MWWorld::Ptr player = mPlayer->getPlayer(); - if (!mPhysEngine->getCharacter("player")->getOnGround()) - return false; - btVector3 from (player.getRefData().getPosition().pos[0], player.getRefData().getPosition().pos[1], player.getRefData().getPosition().pos[2]); - btVector3 to = from - btVector3(0,0,5); - std::pair result = mPhysEngine->rayTest(from, to); - return result.first == object.getRefData().getBaseNode()->getName(); + MWWorld::Ptr player = getPlayerPtr(); + return mPhysics->isActorStandingOn(player, object); } bool World::getActorStandingOn (const MWWorld::Ptr& object) { - return mPhysEngine->isAnyActorStandingOn(object.getRefData().getBaseNode()->getName()); + std::vector actors; + mPhysics->getActorsStandingOn(object, actors); + return !actors.empty(); + } + + bool World::getPlayerCollidingWith (const MWWorld::Ptr& object) + { + MWWorld::Ptr player = getPlayerPtr(); + return mPhysics->isActorCollidingWith(player, object); + } + + bool World::getActorCollidingWith (const MWWorld::Ptr& object) + { + std::vector actors; + mPhysics->getActorsCollidingWith(object, actors); + return !actors.empty(); + } + + void World::hurtStandingActors(const Ptr &object, float healthPerSecond) + { + std::vector actors; + mPhysics->getActorsStandingOn(object, actors); + for (std::vector::iterator it = actors.begin(); it != actors.end(); ++it) + { + MWWorld::Ptr actor = searchPtrViaHandle(*it); // Collision events are from the last frame, actor might no longer exist + if (actor.isEmpty()) + continue; + + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + MWMechanics::DynamicStat health = stats.getHealth(); + health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); + stats.setHealth(health); + } + } + + void World::hurtCollidingActors(const Ptr &object, float healthPerSecond) + { + std::vector actors; + mPhysics->getActorsCollidingWith(object, actors); + for (std::vector::iterator it = actors.begin(); it != actors.end(); ++it) + { + MWWorld::Ptr actor = searchPtrViaHandle(*it); // Collision events are from the last frame, actor might no longer exist + if (actor.isEmpty()) + continue; + + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + MWMechanics::DynamicStat health = stats.getHealth(); + health.setCurrent(health.getCurrent()-healthPerSecond*MWBase::Environment::get().getFrameDuration()); + stats.setHealth(health); + } } float World::getWindSpeed() diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 5c44388ca..c4c8b588e 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -479,6 +479,15 @@ namespace MWWorld virtual bool getPlayerStandingOn (const MWWorld::Ptr& object); ///< @return true if the player is standing on \a object virtual bool getActorStandingOn (const MWWorld::Ptr& object); ///< @return true if any actor is standing on \a object + virtual bool getPlayerCollidingWith(const MWWorld::Ptr& object); ///< @return true if the player is colliding with \a object + virtual bool getActorCollidingWith (const MWWorld::Ptr& object); ///< @return true if any actor is colliding with \a object + virtual void hurtStandingActors (const MWWorld::Ptr& object, float dmgPerSecond); + ///< Apply a health difference to any actors standing on \a object. + /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. + virtual void hurtCollidingActors (const MWWorld::Ptr& object, float dmgPerSecond); + ///< Apply a health difference to any actors colliding with \a object. + /// To hurt actors, healthPerSecond should be a positive value. For a negative value, actors will be healed. + virtual float getWindSpeed(); virtual void getContainersOwnedBy (const MWWorld::Ptr& npc, std::vector& out); diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index ef4fe4fbd..40246d1dc 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -272,6 +272,10 @@ namespace Compiler extensions.registerInstruction ("fall", "", opcodeFall, opcodeFallExplicit); extensions.registerFunction ("getstandingpc", 'l', "", opcodeGetStandingPc, opcodeGetStandingPcExplicit); extensions.registerFunction ("getstandingactor", 'l', "", opcodeGetStandingActor, opcodeGetStandingActorExplicit); + extensions.registerFunction ("getcollidingpc", 'l', "", opcodeGetCollidingPc, opcodeGetCollidingPcExplicit); + extensions.registerFunction ("getcollidingactor", 'l', "", opcodeGetCollidingActor, opcodeGetCollidingActorExplicit); + extensions.registerInstruction ("hurtstandingactor", "f", opcodeHurtStandingActor, opcodeHurtStandingActorExplicit); + extensions.registerInstruction ("hurtcollidingactor", "f", opcodeHurtCollidingActor, opcodeHurtCollidingActorExplicit); extensions.registerFunction ("getwindspeed", 'f', "", opcodeGetWindSpeed); extensions.registerFunction ("hitonme", 'l', "S", opcodeHitOnMe, opcodeHitOnMeExplicit); extensions.registerInstruction ("disableteleporting", "", opcodeDisableTeleporting); diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 9f3ed3574..828d736c4 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -238,6 +238,14 @@ namespace Compiler const int opcodeGetStandingPcExplicit = 0x200020d; const int opcodeGetStandingActor = 0x200020e; const int opcodeGetStandingActorExplicit = 0x200020f; + const int opcodeGetCollidingPc = 0x2000250; + const int opcodeGetCollidingPcExplicit = 0x2000251; + const int opcodeGetCollidingActor = 0x2000252; + const int opcodeGetCollidingActorExplicit = 0x2000253; + const int opcodeHurtStandingActor = 0x2000254; + const int opcodeHurtStandingActorExplicit = 0x2000255; + const int opcodeHurtCollidingActor = 0x2000256; + const int opcodeHurtCollidingActorExplicit = 0x2000257; const int opcodeGetWindSpeed = 0x2000212; const int opcodePlayBink = 0x20001f7; const int opcodeGoToJail = 0x2000235; diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 954d7c283..c95552cb7 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -846,21 +846,5 @@ namespace Physic } } - bool PhysicEngine::isAnyActorStandingOn (const std::string& objectName) - { - for (PhysicActorContainer::iterator it = mActorMap.begin(); it != mActorMap.end(); ++it) - { - if (!it->second->getOnGround()) - continue; - Ogre::Vector3 pos = it->second->getPosition(); - btVector3 from (pos.x, pos.y, pos.z); - btVector3 to = from - btVector3(0,0,5); - std::pair result = rayTest(from, to); - if (result.first == objectName) - return true; - } - return false; - } - } } diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 09bff4b04..590b56c01 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -296,8 +296,6 @@ namespace Physic void setSceneManager(Ogre::SceneManager* sceneMgr); - bool isAnyActorStandingOn (const std::string& objectName); - /** * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). * If \a normal is non-NULL, the hit normal will be written there (if there is a hit) diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp index ad140f1f7..de5fecfca 100644 --- a/libs/openengine/bullet/trace.cpp +++ b/libs/openengine/bullet/trace.cpp @@ -81,12 +81,14 @@ void ActorTracer::doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, mFraction = newTraceCallback.m_closestHitFraction; mPlaneNormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); mEndPos = (end-start)*mFraction + start; + mHitObject = newTraceCallback.m_hitCollisionObject; } else { mEndPos = end; mPlaneNormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); mFraction = 1.0f; + mHitObject = NULL; } } diff --git a/libs/openengine/bullet/trace.h b/libs/openengine/bullet/trace.h index b9fbce64d..f499f4a27 100644 --- a/libs/openengine/bullet/trace.h +++ b/libs/openengine/bullet/trace.h @@ -18,6 +18,7 @@ namespace Physic { Ogre::Vector3 mEndPos; Ogre::Vector3 mPlaneNormal; + const btCollisionObject* mHitObject; float mFraction;