text: //
  //  Character.cpp
  //  ZombieShooter
  //
  //  Created by Gilbert Gwizdała on 18.07.2018.
  //
  
  #include <Urho3D/Core/Context.h>
  #include <Urho3D/Graphics/AnimationController.h>
  #include <Urho3D/Physics/RigidBody.h>
  #include <Urho3D/IO/MemoryBuffer.h>
  #include <Urho3D/Physics/PhysicsEvents.h>
  #include <Urho3D/Physics/PhysicsWorld.h>
  #include <Urho3D/Scene/Scene.h>
  #include <Urho3D/Scene/SceneEvents.h>
  
  #include "Character.hpp"
  #include "CharacterAnimationController.hpp"
  #include "ZombieShooter.hpp"
  
  Character::Character(Context* context) :
  LogicComponent(context),
  onGround_(false),
  okToJump_(true),
  inAirTimer_(0.0f)
  {
      // Only the physics update event is needed: unsubscribe from the rest for optimization
      SetUpdateEventMask(USE_FIXEDUPDATE);
  }
  
  void Character::RegisterObject(Context* context)
  {
      context->RegisterFactory<Character>();
      
      URHO3D_ATTRIBUTE("Controls Yaw", float, controls_.yaw_, 0.0f, AM_DEFAULT);
      URHO3D_ATTRIBUTE("Controls Pitch", float, controls_.pitch_, 0.0f, AM_DEFAULT);
      URHO3D_ATTRIBUTE("On Ground", bool, onGround_, false, AM_DEFAULT);
      URHO3D_ATTRIBUTE("OK To Jump", bool, okToJump_, true, AM_DEFAULT);
      URHO3D_ATTRIBUTE("In Air Timer", float, inAirTimer_, 0.0f, AM_DEFAULT);
  }
  
  void Character::Start()
  {
      SubscribeToEvent(GetNode(), E_NODECOLLISION, URHO3D_HANDLER(Character, HandleNodeCollision));
      body = GetComponent<RigidBody>();
      animCtrl = node_->GetComponent<CharacterAnimationController>(true);
      animCtrl->setIsEmpty(true);
  }
  
  void Character::FixedUpdate(float timeStep)
  {
      
      if (!onGround_)
          inAirTimer_ += timeStep;
      else
          inAirTimer_ = 0.0f;
      
      bool softGrounded = inAirTimer_ < INAIR_THRESHOLD_TIME;
      
      const Quaternion& rot = node_->GetRotation();
      Vector3 moveDir = Vector3::ZERO;
      const Vector3& velocity = body->GetLinearVelocity();
      
      Vector3 planeVelocity(velocity.x_, 0.0f, velocity.z_);
      
      if (controls_.IsDown(CTRL_FORWARD))
          moveDir += Vector3::FORWARD;
      if (controls_.IsDown(CTRL_BACK))
          moveDir += Vector3::BACK;
      if (controls_.IsDown(CTRL_LEFT))
          moveDir += Vector3::LEFT;
      if (controls_.IsDown(CTRL_RIGHT))
          moveDir += Vector3::RIGHT;
      
      // Normalize move vector so that diagonal strafing is not faster
      if (moveDir.LengthSquared() > 0.0f)
          moveDir.Normalize();
      
      // If in air, allow control, but slower than when on ground
      body->ApplyImpulse(rot * moveDir * (softGrounded ? MOVE_FORCE : INAIR_MOVE_FORCE));
      
      if (softGrounded)
      {
          // When on ground, apply a braking force to limit maximum ground velocity
          Vector3 brakeForce = -planeVelocity * BRAKE_FORCE;
          body->ApplyImpulse(brakeForce);
          
          // Jump. Must release jump control between jumps
          if (controls_.IsDown(CTRL_JUMP))
          {
              if (okToJump_)
              {
                  body->ApplyImpulse(Vector3::UP * JUMP_FORCE);
                  okToJump_ = false;
                  animCtrl->PlayJump();
              }
          }
          else
              okToJump_ = true;
      }
      
      if ( !onGround_ && !softGrounded)
      {
          animCtrl->PlayJump();
      }
      else
      {
          // Play walk animation if moving on ground, otherwise fade it out
          if (softGrounded && !moveDir.Equals(Vector3::ZERO)) {
              if (controls_.IsDown(CTRL_FORWARD))
                  animCtrl->PlayRun();
              if (controls_.IsDown(CTRL_BACK))
                  animCtrl->PlayWalkBack();
              if (controls_.IsDown(CTRL_LEFT))
                  animCtrl->PlayWalkLeft();
              if (controls_.IsDown(CTRL_RIGHT))
                  animCtrl->PlayWalkRight();
          } else {
              animCtrl->PlayIdle();
          }
          
          
          // Set walk animations speed proportional to velocity
          animCtrl->SetSpeed("Models/Character/run.ani", planeVelocity.Length() * 0.3f);
          animCtrl->SetSpeed("Models/Character/walkBack.ani", planeVelocity.Length() * 0.3f);
          animCtrl->SetSpeed("Models/Character/leftWalk.ani", planeVelocity.Length() * 0.3f);
          animCtrl->SetSpeed("Models/Character/rightWalk.ani", planeVelocity.Length() * 0.3f);
      }
      
      // Reset grounded flag for next frame
      onGround_ = false;
  }
  
  void Character::HandleNodeCollision(StringHash eventType, VariantMap& eventData)
  {
      // Check collision contacts and see if character is standing on ground (look for a contact that has near vertical normal)
      using namespace NodeCollision;
      
      MemoryBuffer contacts(eventData[P_CONTACTS].GetBuffer());
      
      while (!contacts.IsEof())
      {
          Vector3 contactPosition = contacts.ReadVector3();
          Vector3 contactNormal = contacts.ReadVector3();
          /*float contactDistance = */contacts.ReadFloat();
          /*float contactImpulse = */contacts.ReadFloat();
  
          // If contact is below node center and pointing up, assume it's a ground contact
          if (contactPosition.y_ < (node_->GetPosition().y_ + 1.0f))
          {
              float level = contactNormal.y_;
              if (level > 0.75) {
                  onGround_ = true;
              }
          }
      }
  }
  