#include #include #include "GameData.h" #include "WaterModule.h" #include "TerrainModule.h" #include "Components.h" #include "PhysicsModule.h" #include "physics.h" #include "CharacterAnimationModule.h" #include "CharacterManagerModule.h" #include "CharacterModule.h" #include "goap.h" namespace ECS { CharacterModule::CharacterModule(flecs::world &ecs) { struct TriggerPhysicsChange {}; ecs.module(); ecs.component(); ecs.component(); ecs.component(); ecs.component(); ecs.component(); ecs.component(); ecs.component(); ecs.component(); ecs.component(); ecs.component(); ecs.component(); ecs.component(); ecs.component(); ecs.component(); ecs.component(); ecs.component().add(flecs::Singleton); ecs.import (); ecs.import (); ecs.import (); ecs.system("UpdateTimer") .kind(flecs::OnUpdate) .each([this](EngineData &eng, CharacterBase &ch) { ch.mTimer += eng.delta; }); ecs.system("HandleInput") .kind(flecs::OnUpdate) .each([this](Input &input, Camera &camera) { flecs::entity player = ECS::get().getPlayer(); if (!player.is_valid()) return; /* handle input */ // if (input.control == input.control_prev) // return; uint32_t pressed = input.control & ~input.control_prev; uint32_t released = input.control_prev & ~input.control; uint32_t active = input.control; float zaxis = input.motion.z; zaxis *= 0.9f; if (!camera.mCameraPivot || !camera.mCameraGoal) return; if (pressed & 1) std::cout << "W pressed\n"; if (released & 1) std::cout << "W released\n"; if (active & 1) zaxis -= 1.0f; if (active & 4) zaxis += 1.0f; if (zaxis > -1.0f && zaxis < 1.0f) zaxis = 0.0f; else zaxis = Ogre::Math::Sign(zaxis); if (active & 32) input.act = true; else input.act = false; if (pressed & 32) input.act_pressed = true; else input.act_pressed = false; if (active & 64) input.act2 = true; else input.act2 = false; if (pressed & 64) input.act2_pressed = true; else input.act2_pressed = false; input.motion.z = zaxis; float xaxis = input.motion.x; xaxis *= 0.9f; if (active & 2) xaxis = -1.0f; if (active & 8) xaxis += 1.0f; if (xaxis > -1.0f && xaxis < 1.0f) xaxis = 0.0f; else xaxis = Ogre::Math::Sign(xaxis); input.motion.x = xaxis; if (active & 16) input.fast = true; else input.fast = false; input.control_prev = input.control; if (input.mouse_moved) { updateCameraGoal(camera, -0.18f * input.mouse.x, -0.12f * input.mouse.y, 0); input.mouse_moved = false; input.mouse.x = 0; input.mouse.y = 0; } if (input.wheel_moved) { updateCameraGoal(camera, 0, 0, -0.15f * input.wheel_y); input.wheel_moved = false; input.wheel_y = 0; } ECS::get().modified(); }); ecs.system() .kind(flecs::OnUpdate) .with() .with() .with() .each([this](flecs::entity e, CharacterBase &ch) { float full_subm = 2.0f; Ogre::Vector3 pos = ch.mBodyNode->getPosition(); float current_subm = -Ogre::Math::Clamp( pos.y + Ogre::Math::Sin(ch.mTimer * 0.13f + 130.0f) * 0.07f, -full_subm, 0.0f); if (current_subm > 0.9f) ch.is_submerged = true; else if (current_subm < 0.8f) ch.is_submerged = false; }); #if 0 ecs.system( "HandleGravityBouyanceWater") .kind(flecs::OnUpdate) .with() .with() .with() .without() .without() .each([this](flecs::entity e, const EngineData &eng, const CharacterBase &ch, CharacterVelocity &gr) { Ogre::Vector3 gravity(0, -9.8f, 0); Ogre::Vector3 pos = ch.mBodyNode->getPosition(); Ogre::Vector3 v(0, 0, 0); if (e.has()) v += gravity; if (e.has()) { float volume = 2.0f * 0.5f * 0.5f; float density = 900.0f; float full_subm = 2.0f; float mass = 80.0f; float multiplier = 0.25f; float current_subm = -Ogre::Math::Clamp( pos.y + Ogre::Math::Sin(ch.mTimer * 0.13f + 130.0f) * 0.07f, -full_subm, 0.0f); Ogre::Vector3 b = -gravity * density * volume * multiplier * current_subm / full_subm / mass; v += b; } gr.gvelocity += v * eng.delta; gr.gvelocity.y = Ogre::Math::Clamp(gr.gvelocity.y, -2.5f, 1.5f); gr.gvelocity *= (1.0 - eng.delta); gr.velocity.y *= (1.0 - eng.delta); }); #endif #if 0 ecs.system( "HandleGravityNoWater") .kind(flecs::OnUpdate) .with() .with() .without() .with() .without() .each([this](flecs::entity e, const EngineData &eng, const CharacterBase &ch, CharacterVelocity &gr) { Ogre::Vector3 gravity(0, -9.8f, 0); Ogre::Vector3 pos = ch.mBodyNode->getPosition(); gr.gvelocity += gravity * eng.delta; if (pos.y < -1.2) { gr.gvelocity.y = 0.0f; } gr.gvelocity *= (1.0 - eng.delta); gr.velocity.y *= (1.0 - eng.delta); }); #endif #define TURN_SPEED 500.0f // character turning in degrees per second ecs.system("UpdateBody") .kind(flecs::OnUpdate) .with() .with() .without() .each([](flecs::entity e, const Input &input, const Camera &camera, CharacterBase &ch) { ch.mGoalDirection = Ogre::Vector3::ZERO; float delta = e.world().delta_time(); if (!input.motion.zeroLength()) { // calculate actually goal direction in world based on player's key directions ch.mGoalDirection += input.motion.z * camera.mCameraNode->getOrientation() .zAxis(); ch.mGoalDirection += input.motion.x * camera.mCameraNode->getOrientation() .xAxis(); ch.mGoalDirection.y = 0; ch.mGoalDirection.normalise(); Ogre::Quaternion toGoal = ch.mBodyNode->getOrientation() .zAxis() .getRotationTo( ch.mGoalDirection); // calculate how much the character has to turn to face goal direction Ogre::Real yawToGoal = toGoal.getYaw().valueDegrees(); // this is how much the character CAN turn this frame Ogre::Real yawAtSpeed = yawToGoal / Ogre::Math::Abs(yawToGoal) * delta * TURN_SPEED; // reduce "turnability" if we're in midair // if (mBaseAnimID == ANIM_JUMP_LOOP) yawAtSpeed *= 0.2f; if (yawToGoal < 0) yawToGoal = std::min( 0, std::max( yawToGoal, yawAtSpeed)); //yawToGoal = Math::Clamp(yawToGoal, yawAtSpeed, 0); else if (yawToGoal > 0) yawToGoal = std::max( 0, std::min( yawToGoal, yawAtSpeed)); //yawToGoal = Math::Clamp(yawToGoal, 0, yawAtSpeed); ch.mBodyNode->yaw(Ogre::Degree(yawToGoal)); } }); #if 0 ecs.system("UpdateCharacterBase") .kind(flecs::OnUpdate) .with() .with() .with() .each([](const EngineData &eng, CharacterLocation &loc, CharacterBase &ch, CharacterBody &body) { if (!ch.mBodyNode) { } else { loc.orientation = ch.mBodyNode->_getDerivedOrientation(); loc.position = ch.mBodyNode->_getDerivedPosition(); } }); #endif static int characterCount = 0; ecs.observer( "SetupCharacterBaseObs") .event(flecs::OnAdd) .each([&](flecs::entity e, const EngineData &eng, const CharacterLocation &loc, const CharacterConf &conf, CharacterBase &ch) { ch.mBodyEnt = eng.mScnMgr->createEntity(conf.type); ch.mBodyNode = eng.mScnMgr->getRootSceneNode() ->createChildSceneNode(); ch.mBodyNode->setOrientation(loc.orientation); ch.mBodyNode->setPosition(loc.position); ch.mBodyNode->attachObject(ch.mBodyEnt); ch.mSkeleton = ch.mBodyEnt->getSkeleton(); OgreAssert(ch.mBodyEnt->getSkeleton()->hasBone("Root"), "No root bone"); OgreAssert(ch.mSkeleton->hasBone("Root"), "No root bone"); ch.mRootBone = ch.mSkeleton->getBone("Root"); OgreAssert(ch.mRootBone, "No root bone"); ch.mBoneMotion = Ogre::Vector3::ZERO; ch.mBonePrevMotion = Ogre::Vector3::ZERO; }); ecs.observer("SetupAnimationControlObs") .event(flecs::OnAdd) .each([](flecs::entity e, AnimationControl &anim) { anim.configured = false; }); ecs.observer( "SetupCharacterObs") .event(flecs::OnSet) .with() .without() .without() .write() .write() .each([&](flecs::entity e, const CharacterLocation &loc, const CharacterConf &conf) { std::cout << "OBSERVER!!!" << " " << e.id() << std::endl; if (e.has() || e.has()) return; e.world().defer_begin(); e.add(); e.add(); e.set( { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } }); e.add(); e.add(); e.world().defer_end(); }); #if 0 ecs.system("SetupCharacter") .kind(flecs::OnUpdate) .with() .without() .without() .each([](flecs::entity e, const EngineData &eng, const CharacterLocation &loc, const CharacterConf &conf, Body2Entity &b2e) { CharacterBase &ch = e.ensure(); CharacterBody &body = e.ensure(); AnimationControl &anim = e.ensure(); ch.mBodyEnt = eng.mScnMgr->createEntity(conf.type); ch.mBodyNode = eng.mScnMgr->getRootSceneNode() ->createChildSceneNode(); ch.mBodyNode->setOrientation(loc.orientation); ch.mBodyNode->setPosition(loc.position); ch.mBodyNode->attachObject(ch.mBodyEnt); ch.mSkeleton = ch.mBodyEnt->getSkeleton(); OgreAssert(ch.mBodyEnt->getSkeleton()->hasBone("Root"), "No root bone"); OgreAssert(ch.mSkeleton->hasBone("Root"), "No root bone"); ch.mRootBone = ch.mSkeleton->getBone("Root"); OgreAssert(ch.mRootBone, "No root bone"); // body.mController = nullptr; ch.mBoneMotion = Ogre::Vector3::ZERO; ch.mBonePrevMotion = Ogre::Vector3::ZERO; e.set( { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } }); body.checkGround = false; body.checkGroundResult = false; #if 0 body.mCollisionShape = nullptr; body.mGhostObject = nullptr; body.mController = nullptr; body.mGhostObject = new btPairCachingGhostObject(); b2e.entities[body.mGhostObject] = e; body.mCollisionShape = new btCompoundShape(false); body.mGhostObject->setCollisionShape( body.mCollisionShape); { btVector3 inertia(0, 0, 0); // mCollisionShape = new btCompoundShape(); btScalar height = 1.0f; btScalar radius = 0.3f; btCapsuleShape *shape = new btCapsuleShape( radius, 2 * height - 2 * radius); btTransform transform; transform.setIdentity(); transform.setOrigin(btVector3(0, 1, 0)); static_cast( body.mCollisionShape) ->addChildShape(transform, shape); btScalar masses[1] = { 0 }; btTransform principal; static_cast( body.mCollisionShape) ->calculatePrincipalAxisTransform( masses, principal, inertia); } body.mGhostObject->setCollisionFlags(body.mGhostObject->getCollisionFlags() | btCollisionObject::CF_CHARACTER_OBJECT | btCollisionObject::CF_KINEMATIC_OBJECT /*btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_NO_CONTACT_RESPONSE */); body.mGhostObject->setActivationState( DISABLE_DEACTIVATION); eng.mWorld->attachCollisionObject( body.mGhostObject, ch.mBodyEnt, 1, 0x7FFFFFFF); OgreAssert(body.mGhostObject, "Need GhostObject"); OgreAssert(body.mCollisionShape, "No collision shape"); #endif e.add(); e.add(); anim.configured = false; // OgreAssert(body.mGhostObject->hasContactResponse(), // "need contact response"); }); #endif #if 0 ecs.system( "UpdateCharacterPhysics") .kind(flecs::OnUpdate) .with() .with() .with() .each([](const EngineData &eng, CharacterBase &ch, CharacterBody &body) { #if 0 if (ch.mBodyNode && !body.mController && eng.startupDelay < 0.0f) { body.mController = new Ogre::Bullet::KinematicMotionSimple( body.mGhostObject, ch.mBodyNode); body.mController->enableManualNarrowPhase(true); eng.mWorld->getBtWorld()->addAction( body.mController); OgreAssert(body.mController, "Need controller"); } #endif }); #endif #define CAM_HEIGHT 1.6f // height of camera above character's center of mass ecs.system( "UpdateCamera") .kind(flecs::OnUpdate) .with() .with() .each([](const EngineData &eng, Camera &camera, const CharacterBase &ch) { float delta = eng.delta; if (!camera.configured) { // create a pivot at roughly the character's shoulder camera.mCameraPivot = eng.mScnMgr->getRootSceneNode() ->createChildSceneNode(); camera.mCameraGoal = camera.mCameraPivot ->createChildSceneNode( Ogre::Vector3(0, 2, 3)); camera.mCameraNode->setPosition( camera.mCameraPivot->getPosition() + camera.mCameraGoal->getPosition()); camera.mCameraPivot->setFixedYawAxis(true); camera.mCameraGoal->setFixedYawAxis(true); camera.mCameraNode->setFixedYawAxis(true); // our model is quite small, so reduce the clipping planes camera.mCamera->setNearClipDistance(0.1f); camera.mCamera->setFarClipDistance(700); camera.mPivotPitch = 0; camera.configured = true; } else { // place the camera pivot roughly at the character's shoulder camera.mCameraPivot->setPosition( ch.mBodyNode->getPosition() + Ogre::Vector3::UNIT_Y * CAM_HEIGHT); // move the camera smoothly to the goal Ogre::Vector3 goalOffset = camera.mCameraGoal ->_getDerivedPosition() - camera.mCameraNode->getPosition(); camera.mCameraNode->translate(goalOffset * delta * 9.0f); // always look at the pivot camera.mCameraNode->lookAt( camera.mCameraPivot ->_getDerivedPosition(), Ogre::Node::TS_PARENT); } }); #if 0 class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { btCollisionObject *mMe; public: ClosestNotMeRayResultCallback(btCollisionObject *me, const btVector3 &from, const btVector3 &to) : btCollisionWorld::ClosestRayResultCallback(from, to) { mMe = me; } virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult &rayResult, bool normalInWorldSpace) { if (rayResult.m_collisionObject == mMe) return 1.0f; return ClosestRayResultCallback::addSingleResult( rayResult, normalInWorldSpace); } }; #endif ecs.system("CheckGround") .kind(flecs::OnUpdate) .with() .with() .without() .each([](const EngineData &eng, CharacterBase &ch) { #if 0 if (body.mGhostObject) { btVector3 from = body.mGhostObject->getWorldTransform() .getOrigin() + btVector3(0, 0.2f, 0); btVector3 to = from + btVector3(0, -3000.0f, 0); ClosestNotMeRayResultCallback resultCallback( body.mGhostObject, from, to); body.mGhostObject->rayTest(from, to, resultCallback); body.checkGroundResult = resultCallback.hasHit(); if (resultCallback.hasHit()) ECS::get().add(); } #endif ECS::get().add(); }); #if 0 ecs.system( "CharacterWater1") .kind(flecs::OnUpdate) .with() .without() .each([](flecs::entity e, const WaterBody &waterb, const CharacterBase &ch, CharacterBody &body) { #if 0 if (waterb.isInWater(body.mGhostObject) && ch.mBodyNode->_getDerivedPosition().y < -0.05f) { e.add(); std::cout << "Big Splash\n"; } #endif #if 0 if (waterb.mInWater.find(body.mGhostObject) == waterb.mInWater.end()) e.add(); std::cout << waterb.mInWater.size() << " InWater\n"; #endif }); #endif #if 0 ecs.system( "CharacterWater2") .kind(flecs::OnUpdate) .with() .with() .each([](flecs::entity e, const WaterBody &waterb, const CharacterBase &ch, CharacterBody &body) { float h = ch.mBodyNode->_getDerivedPosition().y; #if 0 if (waterb.isInWater(body.mGhostObject) && h > 0.05f) e.remove(); else if (!waterb.isInWater(body.mGhostObject) && h > 0.05f) e.remove(); #endif }); #endif #if 0 ecs.system( "DisplayPlayerPos") .kind(flecs::OnUpdate) .with() .with() .each([](const EngineData &eng, CharacterBase &ch, CharacterBody &body) { std::cout << "player: " << ch.mBodyNode->getPosition() << "\n"; }); #endif #if 0 #endif } void CharacterModule::updateCameraGoal(Camera &camera, Ogre::Real deltaYaw, Ogre::Real deltaPitch, Ogre::Real deltaZoom) { static float canonDist = 0; camera.mCameraPivot->yaw(Ogre::Degree(deltaYaw), Ogre::Node::TS_PARENT); if (!(camera.mPivotPitch + deltaPitch > 25 && deltaPitch > 0) && !(camera.mPivotPitch + deltaPitch < -60 && deltaPitch < 0)) { camera.mCameraPivot->pitch(Ogre::Degree(deltaPitch), Ogre::Node::TS_LOCAL); camera.mPivotPitch += deltaPitch; } Ogre::Real dist = camera.mCameraGoal->_getDerivedPosition().distance( camera.mCameraPivot->_getDerivedPosition()); Ogre::Real distChange = deltaZoom * dist; // bound the zoom if (!(dist + distChange < 1.5f && distChange < 0) && !(dist + distChange > 10 && distChange > 0)) { camera.mCameraGoal->translate(0, 0, distChange, Ogre::Node::TS_LOCAL); canonDist += distChange; } JPH::BodyID id; Ogre::Vector3 position; Ogre::Vector3 d = (camera.mCameraPivot->_getDerivedPosition() - camera.mCameraGoal->_getDerivedPosition()) .normalisedCopy(); if (JoltPhysicsWrapper::getSingleton().raycastQuery( camera.mCameraPivot->_getDerivedPosition(), camera.mCameraGoal->_getDerivedPosition() - d * 0.6, position, id)) { float l = camera.mCameraPivot->_getDerivedPosition() .squaredDistance( camera.mCameraGoal ->_getDerivedPosition()); float m = camera.mCameraPivot->_getDerivedPosition() .squaredDistance(position); if (m < l) camera.mCameraGoal->_setDerivedPosition(position + d * 0.6f); } else { Ogre::Real dist2 = camera.mCameraGoal->_getDerivedPosition().distance( camera.mCameraPivot->_getDerivedPosition()); if (deltaZoom < 0.0f || deltaZoom > 0.0f) canonDist = dist2; else { if (canonDist < dist2) canonDist = dist2; if (dist2 < canonDist) camera.mCameraGoal->translate( 0, 0, 0.08f, Ogre::Node::TS_LOCAL); } } } CharacterAIModule::CharacterAIModule(flecs::world &ecs) { ecs.module(); ecs.system("UpdateCharacters") .kind(flecs::OnUpdate) .each([&](flecs::entity e, Blackboard &bb) { bb.flags &= ~(Blackboard::LOW_HEALTH | Blackboard::FULL_HEALTH | Blackboard::LOW_STAMINA | Blackboard::FULL_STAMINA | Blackboard::LOW_LUST | Blackboard::HIGH_LUST | Blackboard::FULL_LUST); if (bb.health < 5) bb.flags |= Blackboard::LOW_HEALTH; else if (bb.health >= 100) bb.flags |= Blackboard::FULL_HEALTH; if (bb.stamina < 5) bb.flags |= Blackboard::LOW_STAMINA; if (bb.stamina >= 100) bb.flags |= Blackboard::FULL_STAMINA; if (bb.lust >= 100) bb.flags |= Blackboard::FULL_LUST; if (bb.lust > 10) bb.flags |= Blackboard::HIGH_LUST; if (bb.lust < 5) bb.flags |= Blackboard::LOW_LUST; if (bb.stamina < 0) bb.stamina = 0; else if (bb.stamina > 100) bb.stamina = 100; if (bb.lust < 0) bb.lust = 0; else if (bb.lust > 100) bb.lust = 100; }); ecs.system("UpdateCharactersPlan2") .kind(flecs::OnUpdate) .each([&](flecs::entity e, Blackboard &bb, Plan &p) { int i; bool ok_plan = true; for (i = p.position; i < p.length; i++) { if (!p.actions[i]->can_run(bb, true)) { ok_plan = false; break; } int ret = p.actions[i]->execute(bb); p.position = i; if (ret == BaseAction::BUSY) break; else if (ret == BaseAction::ABORT) { ok_plan = false; break; } if (ret == BaseAction::OK && i == p.length - 1) ok_plan = false; // stop_this = true; } if (!ok_plan) { std::cout << e.name() << ": invalidated plan" << " step: " << i << std::endl; for (i = 0; i < p.length; i++) p.actions[i]->stop(bb); e.remove(); } }); ecs.system("UpdateCharacters2") .kind(flecs::OnUpdate) .without() .each([&](flecs::entity e, Blackboard &bb, Planner &planner) { int i; std::vector actions; actions.insert(actions.end(), planner.actions.begin(), planner.actions.end()); e.world() .query_builder() .build() .each([&](flecs::entity me, const ActionTarget &t) { actions.insert(actions.end(), t.actions.begin(), t.actions.end()); #if 0 auto it = t.actions.begin(); while (it != t.actions.end()) { if (me != bb.me) actions.push_back(*it); // std::cout << (*it)->get_name() // << std::endl; it++; } #endif }); #if 0 for (i = 0; i < actions.size(); i++) std::cout << "action: " << i << " " << actions[i]->get_name() << std::endl; #endif if (actions.size() == 0) return; int len = -1; OgreAssert( bb.stamina < 100 || bb.stamina >= 100 && !bb.get_flag( Blackboard::LOW_STAMINA), "bad thing"); for (i = 0; i < planner.goals.size(); i++) { if (planner.goals[i]->distance_to(bb) > 1000000) continue; len = planner.planner.plan( bb, *planner.goals[i], actions.data(), actions.size(), planner.path.data(), 100); std::cout << bb.me.name() << ": goal: " << len << " " << planner.goals[i]->get_name() << std::endl; if (len > 0) break; } // std::cout << "plan length: " << len << std::endl; #if 0 if (len > 0) stop_this = true; if (len < 0) stop_this = true; #endif if (len > 0) { Plan &p = e.ensure(); p.actions = planner.path; p.position = 0; p.length = len; for (i = 0; i < len; i++) std::cout << i << " " << planner.path[i]->get_name() << " " << planner.path[i]->get_cost(bb) << std::endl; bool ok_plan = true; for (i = 0; i < len; i++) { if (!planner.path[i]->can_run(bb, true)) { ok_plan = false; break; } int ret = planner.path[i]->execute(bb); p.position = i; std::cout << "exec: " << i << " " << planner.path[i]->get_name() << std::endl; if (ret == BaseAction::BUSY) break; else if (ret == BaseAction::ABORT) { ok_plan = false; } else if (ret == BaseAction::OK) std::cout << "exec: complete " << i << " " << planner.path[i] ->get_name() << std::endl; } e.modified(); if (!ok_plan) { std::cout << e.name() << ": invalidate plan" << " step: " << i << std::endl; for (i = 0; i < len; i++) planner.path[i]->stop(bb); e.remove(); } } }); } }