#include "Character.h" #include #include namespace ZL { const float ATTACK_COOLDOWN_TIME = 3.0f; void Character::loadAnimation(AnimationState state, const std::string& filename, const std::string& zipFile) { auto& data = animations[state]; data.model.LoadFromFile(filename, zipFile); if (!data.model.animations.empty() && !data.model.animations[0].keyFrames.empty()) { data.totalFrames = data.model.animations[0].keyFrames.back().frame + 1; } else { data.totalFrames = 1; } } /*void Character::setTexture(std::shared_ptr tex) { texture = tex; }*/ void Character::setTarget(const Eigen::Vector3f& target, std::function onArrived) { Eigen::Vector3f normalizedTarget(target.x(), 0.f, target.z()); const bool sameRequestedTarget = (normalizedTarget - requestedWalkTarget).norm() <= TARGET_REPLAN_THRESHOLD; const bool alreadyMovingToTarget = !pathWaypoints.empty() || (walkTarget - normalizedTarget).norm() <= TARGET_REPLAN_THRESHOLD; const bool stoppedAfterFailedPath = pathWaypoints.empty() && (walkTarget - position).norm() <= TARGET_REPLAN_THRESHOLD; if (!onArrived && sameRequestedTarget && (alreadyMovingToTarget || stoppedAfterFailedPath)) { return; } requestedWalkTarget = normalizedTarget; onArrivedCallback = std::move(onArrived); if (pathPlanner) { pathWaypoints = pathPlanner(position, normalizedTarget); currentWaypointIndex = 0; if (!pathWaypoints.empty()) { for (Eigen::Vector3f& waypoint : pathWaypoints) { waypoint.y() = 0.f; } walkTarget = pathWaypoints.back(); return; } walkTarget = Eigen::Vector3f(position.x(), 0.f, position.z()); onArrivedCallback = nullptr; return; } pathWaypoints.clear(); currentWaypointIndex = 0; walkTarget = normalizedTarget; } void Character::setPathPlanner(PathPlanner planner) { pathPlanner = std::move(planner); } AnimationState Character::resolveActiveState() const { if (animations.count(currentState)) return currentState; return AnimationState::STAND; } void Character::update(int64_t deltaMs) { //std::cout << "update called with deltaMs: " << deltaMs << std::endl; // Move toward walk target on the XZ plane Eigen::Vector3f activeTarget = walkTarget; if (!pathWaypoints.empty() && currentWaypointIndex < pathWaypoints.size()) { activeTarget = pathWaypoints[currentWaypointIndex]; } Eigen::Vector3f toTarget = activeTarget - position; toTarget.y() = 0.f; float dist = toTarget.norm(); if (dist > WALK_THRESHOLD) { Eigen::Vector3f dir = toTarget / dist; float moveAmount = walkSpeed * static_cast(deltaMs) / 1000.f; if (moveAmount >= dist) { position = activeTarget; position.y() = 0.f; } else { position += dir * moveAmount; } targetFacingAngle = atan2(dir.x(), -dir.z()); if (battle_state == 0 && currentState == AnimationState::STAND) { currentState = AnimationState::WALK; } } else { if (battle_state == 0 && currentState == AnimationState::WALK ) { currentState = AnimationState::STAND; } const bool hasNextWaypoint = !pathWaypoints.empty() && currentWaypointIndex + 1 < pathWaypoints.size(); if (hasNextWaypoint) { ++currentWaypointIndex; } else { pathWaypoints.clear(); currentWaypointIndex = 0; } if (!hasNextWaypoint && onArrivedCallback) { auto cb = std::move(onArrivedCallback); onArrivedCallback = nullptr; cb(); } } if (battle_state == 1 && attack == 1 && attackTarget != nullptr) { targetFacingAngle = atan2(attackTarget->position.x() - position.x(), -(attackTarget->position.z() - position.z())); } // Rotate toward target facing angle at constant angular speed float angleDiff = targetFacingAngle - facingAngle; while (angleDiff > static_cast(M_PI)) angleDiff -= 2.f * static_cast(M_PI); while (angleDiff < -static_cast(M_PI)) angleDiff += 2.f * static_cast(M_PI); float rotStep = rotationSpeed * static_cast(deltaMs) / 1000.f; if (std::fabs(angleDiff) <= rotStep) { facingAngle = targetFacingAngle; } else { facingAngle += (angleDiff > 0.f ? rotStep : -rotStep); } if (canAttack && attackTarget != nullptr) { float distToGhost = (attackTarget->position - position).norm(); if (distToGhost >= 10.f) { if (isPlayer) { setTarget(attackTarget->position); } if (battle_state != 0) { battle_state = 0; } } else if (distToGhost < 10.0f && distToGhost >= 1.f) { setTarget(attackTarget->position); if (battle_state != 0) { battle_state = 0; } } else { setTarget(position); if (battle_state != 1) { battle_state = 1; } } } if (attackTarget == nullptr) { if (battle_state != 0) { battle_state = 0; attack = 0; } } /* if (isPlayer) //Player should decide only by himself { if (attackTarget != nullptr) { auto pos = attackTarget->position; float distToTarget = (position - pos).norm(); if (distToTarget > 1.0) { setTarget(Eigen::Vector3f(pos.x(), 0.f, pos.z())); } else { if (battle_state != 1) { setTarget(position); battle_state = 1; //player->attack = 1; } } } else { if (battle_state != 0) { battle_state = 0; attack = 0; } } }*/ if (battle_state == 1) { if (currentState == AnimationState::STAND || currentState == AnimationState::WALK) { currentState = AnimationState::STAND_TO_ACTION; resetAnim = true; } if (canAttack && attack == 0 && attack_cooldown < 0.f && currentState == AnimationState::ACTION_IDLE) { attack = 1; attack_cooldown = ATTACK_COOLDOWN_TIME; } if (canAttack && attack_cooldown >= 0.f) { attack_cooldown = attack_cooldown - deltaMs / 1000.f; } if (attack == 1 && currentState == AnimationState::ACTION_IDLE) { currentState = AnimationState::ACTION_ATTACK; resetAnim = true; } } else { if (currentState == AnimationState::STAND_TO_ACTION || currentState == AnimationState::ACTION_IDLE || currentState == AnimationState::ACTION_ATTACK ) { currentState = AnimationState::ACTION_TO_STAND; resetAnim = true; } } auto it = animations.find(currentState); if (it == animations.end()) return; auto& anim = it->second; if (resetAnim) { resetAnim = false; anim.currentFrame = 0; } anim.currentFrame += static_cast(deltaMs) / 24.f; //std::cout << "Current animation frame: " << anim.currentFrame << " / " << anim.totalFrames << " -- " << anim.lastFrame << std::endl; int frms = anim.model.animations[0].keyFrames[anim.model.animations[0].keyFrames.size() - 1].frame; if (static_cast(anim.currentFrame) >= frms) { anim.currentFrame = anim.model.startingFrame; if (currentState == AnimationState::STAND_TO_ACTION) { currentState = AnimationState::ACTION_IDLE; resetAnim = true; } if (currentState == AnimationState::ACTION_TO_STAND) { currentState = AnimationState::STAND; resetAnim = true; } if (currentState == AnimationState::ACTION_ATTACK) { currentState = AnimationState::ACTION_IDLE; resetAnim = true; attack = 0; } } if (static_cast(anim.currentFrame) != anim.lastFrame) { anim.model.Interpolate(static_cast(anim.currentFrame)); anim.lastFrame = static_cast(anim.currentFrame); } } void Character::draw(Renderer& renderer) { //std::cout << "draw called for Character at position: " << position.transpose() << std::endl; AnimationState drawState = resolveActiveState(); auto it = animations.find(drawState); if (it == animations.end() || !texture) return; renderer.PushMatrix(); renderer.TranslateMatrix({ position.x(), position.y(), position.z() }); renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix()); renderer.ScaleMatrix(modelScale); renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix()); auto& anim = it->second; anim.modelMutable.AssignFrom(anim.model.mesh); anim.modelMutable.RefreshVBO(); glBindTexture(GL_TEXTURE_2D, texture->getTexID()); renderer.DrawVertexRenderStruct(anim.modelMutable); renderer.PopMatrix(); } } // namespace ZL