Major refactoring - internal server state machine implemented

This commit is contained in:
Vladislav Khorev committed 2026-10-29 00:09:16 +06:00
1 parent 71c654e5b6
commit 66e93d46f4
48 files changed
+3948 -3696

No files matched your search

+116 -450
View File
@@ -9,10 +9,11 @@
#include "render/TextureManager.h"
#include "utils/Utils.h"
#include "TextModel.h"
#include "game_server/IGameServer.h"
#include "navigation/PathFinder.h"
#include "Location.h"
namespace FRG {
const float ATTACK_COOLDOWN_TIME = 1.6f;
extern float x;
extern float y;
@@ -26,6 +27,8 @@ void Character::loadAnimation(AnimationState animState, const std::string& filen
} else {
data.totalFrames = 1;
}
gameServer_.submit(SetCharacterAnimationMetadataCommand{
locationId_, entityId_, animState, data.model.startingFrame, data.totalFrames});
}
void Character::loadBinaryAnimation(AnimationState animState, const std::string& filename, const std::string& zipFile) {
@@ -36,11 +39,14 @@ void Character::loadBinaryAnimation(AnimationState animState, const std::string&
} else {
data.totalFrames = 1;
}
gameServer_.submit(SetCharacterAnimationMetadataCommand{
locationId_, entityId_, animState, data.model.startingFrame, data.totalFrames});
}
std::unique_ptr<Character> Character::createFromState(CharacterState st, Renderer& renderer, const char* zipFile) {
auto c = std::make_unique<Character>();
c->state = std::move(st);
std::unique_ptr<Character> Character::createFromState(const CharacterState& st, IGameServer& server,
const std::string& locationId, const std::string& entityId,
Renderer& renderer, const char* zipFile) {
auto c = std::make_unique<Character>(st, server, locationId, entityId);
const std::string zip = zipFile ? zipFile : "";
@@ -56,7 +62,7 @@ std::unique_ptr<Character> Character::createFromState(CharacterState st, Rendere
}
};
for (const auto& [animState, path] : c->state.creationInfo.animationPaths)
for (const auto& [animState, path] : c->characterState.creationInfo.animationPaths)
loadAnim(animState, path);
auto loadTex = [&](const std::string& path) -> std::shared_ptr<Texture> {
@@ -70,27 +76,27 @@ std::unique_ptr<Character> Character::createFromState(CharacterState st, Rendere
}
};
if (!c->state.creationInfo.singleTexturePath.empty()) {
auto tex = loadTex(c->state.creationInfo.singleTexturePath);
if (!c->characterState.creationInfo.singleTexturePath.empty()) {
auto tex = loadTex(c->characterState.creationInfo.singleTexturePath);
if (tex) c->setTexture(tex);
}
for (const auto& [meshName, texPath] : c->state.creationInfo.meshTexturePaths) {
for (const auto& [meshName, texPath] : c->characterState.creationInfo.meshTexturePaths) {
auto tex = loadTex(texPath);
if (tex) c->setTexture(meshName, tex);
}
if (!c->state.creationInfo.weaponMeshPath.empty()) {
const std::string& wp = c->state.creationInfo.weaponMeshPath;
if (!c->characterState.creationInfo.weaponMeshPath.empty()) {
const std::string& wp = c->characterState.creationInfo.weaponMeshPath;
try {
if (wp.size() >= 4 && wp.substr(wp.size() - 4) == ".bin")
c->weaponMesh.data = LoadModelFromBinFile(wp, zip);
else
c->weaponMesh.data = LoadFromTextFile02(wp, zip);
if (c->state.creationInfo.weaponMeshMove != Eigen::Vector3f::Zero())
c->weaponMesh.data.Move(c->state.creationInfo.weaponMeshMove);
if (c->state.creationInfo.weaponMeshScale != 1.0f)
c->weaponMesh.data.Scale(c->state.creationInfo.weaponMeshScale);
if (c->characterState.creationInfo.weaponMeshMove != Eigen::Vector3f::Zero())
c->weaponMesh.data.Move(c->characterState.creationInfo.weaponMeshMove);
if (c->characterState.creationInfo.weaponMeshScale != 1.0f)
c->weaponMesh.data.Scale(c->characterState.creationInfo.weaponMeshScale);
c->weaponMesh.RefreshVBO();
} catch (const std::exception& e) {
@@ -98,100 +104,14 @@ std::unique_ptr<Character> Character::createFromState(CharacterState st, Rendere
}
}
c->weaponTexture = loadTex(c->state.creationInfo.weaponTexturePath);
c->weaponAttachBoneName = c->state.creationInfo.weaponAttachBoneName;
c->weaponInitialPosition = c->state.creationInfo.weaponInitialPosition;
c->weaponInitialRotation = c->state.creationInfo.weaponInitialRotation.toRotationMatrix();
c->weaponTexture = loadTex(c->characterState.creationInfo.weaponTexturePath);
c->weaponAttachBoneName = c->characterState.creationInfo.weaponAttachBoneName;
c->weaponInitialPosition = c->characterState.creationInfo.weaponInitialPosition;
c->weaponInitialRotation = c->characterState.creationInfo.weaponInitialRotation.toRotationMatrix();
c->setTarget(c->state.position);
return c;
}
void Character::setTarget(const Eigen::Vector3f& target, std::function<void()> onArrived, std::string callbackName) {
Eigen::Vector3f normalizedTarget(target.x(), 0.f, target.z());
const bool sameRequestedTarget =
(normalizedTarget - state.requestedWalkTarget).norm() <= TARGET_REPLAN_THRESHOLD;
const bool alreadyMovingToTarget =
!state.pathWaypoints.empty() || (state.walkTarget - normalizedTarget).norm() <= TARGET_REPLAN_THRESHOLD;
const bool stoppedAfterFailedPath =
state.pathWaypoints.empty() && (state.walkTarget - state.position).norm() <= TARGET_REPLAN_THRESHOLD;
if (!onArrived && sameRequestedTarget && (alreadyMovingToTarget || stoppedAfterFailedPath)) {
return;
}
state.requestedWalkTarget = normalizedTarget;
state.onArrivedCallback = std::move(onArrived);
state.onArrivedCallbackName = std::move(callbackName);
if (pathPlanner) {
state.pathWaypoints = pathPlanner(state.position, normalizedTarget);
state.currentWaypointIndex = 0;
if (!state.pathWaypoints.empty()) {
for (Eigen::Vector3f& waypoint : state.pathWaypoints) {
waypoint.y() = 0.f;
}
state.walkTarget = state.pathWaypoints.back();
return;
}
state.walkTarget = Eigen::Vector3f(state.position.x(), 0.f, state.position.z());
state.onArrivedCallback = nullptr;
state.onArrivedCallbackName.clear();
return;
}
state.pathWaypoints.clear();
state.currentWaypointIndex = 0;
state.walkTarget = normalizedTarget;
}
void Character::stopAndRotateToTarget(int npcId)
{
state.onArrivedCallback = nullptr;
state.onArrivedCallbackName = "";
state.walkTarget = state.position;
state.requestedWalkTarget = state.position;
state.faceTargetIndex = npcId;
state.pathWaypoints.clear();
state.currentWaypointIndex = 0;
}
void Character::setPathPlanner(PathPlanner planner) {
pathPlanner = std::move(planner);
}
void Character::resetPlayerAfterDeath() {
if (!state.isPlayer) return;
state.currentState = AnimationState::STAND;
state.resetAnim = true;
state.showWeapon = false;
state.battle_state = 0;
state.attack = 0;
}
void Character::forceReplan() {
if (!pathPlanner) return;
const Eigen::Vector3f normalizedTarget(state.requestedWalkTarget.x(), 0.f, state.requestedWalkTarget.z());
state.pathWaypoints = pathPlanner(state.position, normalizedTarget);
state.currentWaypointIndex = 0;
if (!state.pathWaypoints.empty()) {
for (Eigen::Vector3f& waypoint : state.pathWaypoints) {
waypoint.y() = 0.f;
}
state.walkTarget = state.pathWaypoints.back();
return;
}
state.walkTarget = Eigen::Vector3f(state.position.x(), 0.f, state.position.z());
state.onArrivedCallback = nullptr;
state.onArrivedCallbackName.clear();
}
void Character::setTexture(std::shared_ptr<FRG::Texture> texture) {
for (auto& animEntry : animations) {
for (const auto& name : animEntry.second.model.meshNamesOrdered) {
@@ -205,275 +125,38 @@ void Character::setTexture(const std::string& meshName, std::shared_ptr<Texture>
}
AnimationState Character::resolveActiveState() const {
if (animations.count(state.currentState)) return state.currentState;
if (animations.count(characterState.currentState)) return characterState.currentState;
return AnimationState::STAND;
}
float Character::getSpeedMultiplier(float transitionProgress) const {
if (state.isPlayer) {
return 1.0f + (1.5f - 1.0f) * transitionProgress;
} else {
return 1.0f - (1.0f - 0.1f) * transitionProgress;
}
}
void Character::update(int64_t deltaMs, const CharacterResolver& resolver) {
if (!state.enabled) return;
if (state.slowMoTimeRemaining > 0.0f) {
state.slowMoTimeRemaining -= static_cast<float>(deltaMs) / 1000.0f;
float transitionProgress;
if (state.slowMoTimeRemaining > state.slowMoActiveTime - state.slowMoTransitionTime) {
float timeInTransition = state.slowMoActiveTime - state.slowMoTimeRemaining;
transitionProgress = (std::min)(1.0f, timeInTransition / state.slowMoTransitionTime);
} else if (state.slowMoTimeRemaining < state.slowMoTransitionTime) {
transitionProgress = (std::max)(0.0f, state.slowMoTimeRemaining / state.slowMoTransitionTime);
} else {
transitionProgress = 1.0f;
}
float speedMultiplier = getSpeedMultiplier(transitionProgress);
deltaMs = static_cast<int64_t>(static_cast<float>(deltaMs) * speedMultiplier);
}
Character* attackTarget = resolver ? resolver(state.attackTargetIndex) : nullptr;
Character* faceTarget = resolver ? resolver(state.faceTargetIndex) : nullptr;
Eigen::Vector3f activeTarget;
Eigen::Vector3f lookTarget;
if (attackTarget == nullptr) {
state.battle_state = 0;
state.attack = 0;
}
if (attackTarget != nullptr) {
float distToGhost = (attackTarget->state.position - state.position).norm();
if (distToGhost >= state.visibleRange) {
if (state.isPlayer) {
setTarget(attackTarget->state.position);
}
state.battle_state = 0;
} else if (distToGhost < state.visibleRange && distToGhost >= kAttackRange) {
setTarget(attackTarget->state.position);
state.battle_state = 0;
} else {
state.battle_state = 1;
}
}
if (!state.pathWaypoints.empty() && state.currentWaypointIndex < state.pathWaypoints.size()) {
activeTarget = state.pathWaypoints[state.currentWaypointIndex];
} else {
activeTarget = state.walkTarget;
}
if (state.battle_state == 0) {
lookTarget = activeTarget;
} else {
lookTarget = attackTarget->state.position;
}
if (state.currentState == AnimationState::WALK || state.currentState == AnimationState::STAND) {
Eigen::Vector3f toTarget = activeTarget - state.position;
toTarget.y() = 0.f;
float dist = toTarget.norm();
if (dist > CharacterState::kWalkThreshold) {
Eigen::Vector3f dir = toTarget / dist;
float moveAmount = state.walkSpeed * static_cast<float>(deltaMs) / 1000.f;
if (moveAmount >= dist) {
state.position = activeTarget;
state.position.y() = 0.f;
} else {
state.position += dir * moveAmount;
}
state.targetFacingAngle = atan2(dir.x(), -dir.z());
state.currentState = AnimationState::WALK;
} else {
state.currentState = AnimationState::STAND;
const bool hasNextWaypoint = !state.pathWaypoints.empty() &&
state.currentWaypointIndex + 1 < state.pathWaypoints.size();
if (hasNextWaypoint) {
++state.currentWaypointIndex;
} else {
state.pathWaypoints.clear();
state.currentWaypointIndex = 0;
}
if (!hasNextWaypoint && state.onArrivedCallback) {
auto cb = std::move(state.onArrivedCallback);
state.onArrivedCallback = nullptr;
state.onArrivedCallbackName.clear();
cb();
}
if (faceTarget) {
Eigen::Vector3f toFace = faceTarget->state.position - state.position;
toFace.y() = 0.f;
float d = toFace.norm();
if (d > 1e-3f) {
state.targetFacingAngle = atan2(toFace.x() / d, -toFace.z() / d);
} else {
state.targetFacingAngle = state.facingAngle;
}
}
}
}
if (state.hp <= 0) {
if (state.currentState != AnimationState::ACTION_TO_DEATH &&
state.currentState != AnimationState::DEATH_IDLE) {
state.currentState = AnimationState::ACTION_TO_DEATH;
state.resetAnim = true;
state.attackTargetIndex = CharacterState::kNoTarget;
}
} else {
if (state.battle_state == 1) {
state.targetFacingAngle = atan2(lookTarget.x() - state.position.x(),
-(lookTarget.z() - state.position.z()));
if (state.currentState == AnimationState::STAND || state.currentState == AnimationState::WALK) {
state.currentState = AnimationState::STAND_TO_ACTION;
state.resetAnim = true;
}
if (state.canAttack && state.attack == 0 && state.attack_cooldown < 0.f &&
state.currentState == AnimationState::ACTION_IDLE) {
state.attack = 1;
state.attack_cooldown = ATTACK_COOLDOWN_TIME;
}
if (state.canAttack && state.attack_cooldown >= 0.f) {
state.attack_cooldown -= deltaMs / 1000.f;
}
if (state.attack == 1 && state.currentState == AnimationState::ACTION_IDLE) {
if (attackTarget != nullptr && attackTarget->state.hp > 0) {
state.currentState = AnimationState::ACTION_ATTACK;
state.resetAnim = true;
}
}
} else {
if (state.currentState == AnimationState::STAND_TO_ACTION ||
state.currentState == AnimationState::ACTION_IDLE ||
state.currentState == AnimationState::ACTION_ATTACK) {
state.currentState = AnimationState::ACTION_TO_STAND;
state.resetAnim = true;
}
}
}
// Rotate toward target facing angle at constant angular speed
float angleDiff = state.targetFacingAngle - state.facingAngle;
while (angleDiff > static_cast<float>(M_PI)) angleDiff -= 2.f * static_cast<float>(M_PI);
while (angleDiff < -static_cast<float>(M_PI)) angleDiff += 2.f * static_cast<float>(M_PI);
float rotStep = state.rotationSpeed * static_cast<float>(deltaMs) / 1000.f;
if (std::fabs(angleDiff) <= rotStep) {
state.facingAngle = state.targetFacingAngle;
// One-shot: once aligned, clear the face target so the NPC stops tracking.
state.faceTargetIndex = CharacterState::kNoTarget;
} else {
state.facingAngle += (angleDiff > 0.f ? rotStep : -rotStep);
}
if (!state.isPlayer && state.hp > 0.f && state.battle_state == 0 && state.maxHomeDrift > 0.f) {
state.homeDriftCheckTimer -= static_cast<float>(deltaMs) / 1000.f;
if (state.homeDriftCheckTimer <= 0.f) {
state.homeDriftCheckTimer = HOME_DRIFT_CHECK_INTERVAL;
Eigen::Vector3f toHome = state.homePosition - state.position;
toHome.y() = 0.f;
if (!isMoving() && toHome.norm() > state.maxHomeDrift) {
setTarget(state.homePosition);
}
}
}
void Character::update(int64_t deltaMs, const CharacterUpdateEvents* events) {
if (hitSparkEmitter.isConfigured()) {
hitSparkEmitter.update(static_cast<float>(deltaMs));
}
auto it = animations.find(state.currentState);
if (events) {
if (events->deathAnimationComplete && onDeathAnimComplete)
onDeathAnimComplete();
if (events->attackImpact && audioPlayer) {
audioPlayer->playSoundAsync(characterState.isPlayer
? "resources/audio/547041__cogfirestudios__hit-swing-sword-small-3.ogg"
: "resources/audio/13925__adcbicycle__21.ogg");
}
}
if (!characterState.enabled) return;
auto it = animations.find(characterState.currentState);
if (it == animations.end()) return;
auto& anim = it->second;
if (state.resetAnim) {
state.resetAnim = false;
anim.currentFrame = 0;
}
int prevFrame = anim.currentFrame;
anim.currentFrame += static_cast<float>(deltaMs) / 24.f;
if (static_cast<int>(anim.currentFrame) >= 20 && state.currentState == AnimationState::STAND_TO_ACTION) {
state.showWeapon = true;
} else if (static_cast<int>(anim.currentFrame) <= 32 && state.currentState == AnimationState::ACTION_TO_STAND) {
state.showWeapon = true;
} else if (state.currentState == AnimationState::ACTION_ATTACK_2 ||
state.currentState == AnimationState::ACTION_ATTACK ||
state.currentState == AnimationState::ACTION_IDLE) {
state.showWeapon = true;
} else {
state.showWeapon = false;
}
auto attackDirectionToTarget = [&]() {
Eigen::Vector3f dir = attackTarget->state.position - state.position;
dir.y() = 0.f;
float n = dir.norm();
if (n > 1e-3f) dir /= n;
else dir = Eigen::Vector3f::Zero();
return dir;
};
if (prevFrame < 19 && static_cast<int>(anim.currentFrame) >= 19 &&
(state.currentState == AnimationState::ACTION_ATTACK ||
state.currentState == AnimationState::ACTION_ATTACK_2)) {
if (attackTarget != nullptr) {
attackTarget->applyDamage(10.f, attackDirectionToTarget(), index);
if (audioPlayer) {
if (state.isPlayer)
{
audioPlayer->playSoundAsync("resources/audio/547041__cogfirestudios__hit-swing-sword-small-3.ogg");
}
else
{
audioPlayer->playSoundAsync("resources/audio/13925__adcbicycle__21.ogg");
}
}
}
}
int frms = anim.model.animations[0].keyFrames[anim.model.animations[0].keyFrames.size() - 1].frame;
if (static_cast<int>(anim.currentFrame) >= frms) {
anim.currentFrame = anim.model.startingFrame;
if (state.currentState == AnimationState::STAND_TO_ACTION) {
state.currentState = AnimationState::ACTION_IDLE;
}
if (state.currentState == AnimationState::ACTION_TO_STAND) {
state.currentState = AnimationState::STAND;
}
if (state.currentState == AnimationState::ACTION_ATTACK) {
state.currentState = AnimationState::ACTION_IDLE;
state.attack = 0;
}
if (state.currentState == AnimationState::ACTION_TO_DEATH) {
state.currentState = AnimationState::DEATH_IDLE;
if (state.onDeathAnimComplete) {
state.onDeathAnimComplete();
}
}
}
anim.currentFrame = (std::min)(
characterState.animationFrame, static_cast<float>((std::max)(0, anim.totalFrames - 1)));
if (static_cast<int>(anim.currentFrame) != anim.lastFrame) {
anim.gpuSkinningShaderData.ComputeSkinningMatrices(
anim.model.startBones, anim.model.animations[0].keyFrames,
static_cast<int>(anim.currentFrame));
if (!state.useGpuSkinning) {
if (!characterState.useGpuSkinning) {
anim.model.Interpolate(static_cast<int>(anim.currentFrame));
}
anim.lastFrame = static_cast<int>(anim.currentFrame);
@@ -488,9 +171,9 @@ void Character::draw(Renderer& renderer) {
renderer.RenderUniform1f("uAlpha", 1.0f);
glDisable(GL_BLEND);
if (!state.enabled) return;
if (!characterState.enabled) return;
if (state.useGpuSkinning) {
if (characterState.useGpuSkinning) {
drawGpuSkinning(renderer);
return;
}
@@ -509,10 +192,10 @@ void Character::draw(Renderer& renderer) {
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(state.modelScale);
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
renderer.TranslateMatrix({ characterState.position.x(), characterState.position.y(), characterState.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-characterState.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(characterState.modelScale);
renderer.RotateMatrix(characterState.modelCorrectionRotation.toRotationMatrix());
auto& anim = it->second;
for (const auto& name : anim.model.meshNamesOrdered) {
@@ -563,10 +246,10 @@ void Character::drawGpuSkinning(Renderer& renderer) {
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(state.modelScale);
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
renderer.TranslateMatrix({ characterState.position.x(), characterState.position.y(), characterState.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-characterState.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(characterState.modelScale);
renderer.RotateMatrix(characterState.modelCorrectionRotation.toRotationMatrix());
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
static_cast<int>(anim.gpuSkinningShaderData.skinningMatrices.size()), false,
@@ -610,24 +293,8 @@ bool Character::prepareGpuSkinning() {
return true;
}
void Character::tryAttackCombo()
{
logger() << "Try Attack Combo" << std::endl;
auto it = animations.find(state.currentState);
if (it == animations.end()) return;
auto& anim = it->second;
if (static_cast<int>(anim.currentFrame) >= 20 &&
(state.currentState == AnimationState::ACTION_ATTACK ||
state.currentState == AnimationState::ACTION_ATTACK_2)) {
logger() << "Try Attack Combo Success" << std::endl;
anim.currentFrame = anim.model.startingFrame;
}
}
void Character::drawAttachedWeapon(Renderer& renderer) {
if (!state.showWeapon) return;
if (!characterState.showWeapon) return;
if (weaponMesh.data.PositionData.size() == 0 || !weaponTexture) return;
auto it = animations.find(resolveActiveState());
@@ -643,10 +310,10 @@ void Character::drawAttachedWeapon(Renderer& renderer) {
Eigen::Matrix4f currentBone = anim.gpuSkinningShaderData.skinningMatrices[boneIdx] * bindBone;
renderer.PushMatrix();
renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(state.modelScale);
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
renderer.TranslateMatrix({ characterState.position.x(), characterState.position.y(), characterState.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-characterState.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(characterState.modelScale);
renderer.RotateMatrix(characterState.modelCorrectionRotation.toRotationMatrix());
renderer.TranslateMatrix(Eigen::Vector3f(currentBone.block<3, 1>(0, 3)));
renderer.RotateMatrix(Eigen::Matrix3f(currentBone.block<3, 3>(0, 0)));
renderer.TranslateMatrix(weaponInitialPosition);
@@ -660,8 +327,8 @@ void Character::drawAttachedWeapon(Renderer& renderer) {
// ==================== Shadow depth pass ====================
void Character::drawShadowDepth(Renderer& renderer) {
if (!state.enabled) return;
if (state.useGpuSkinning) {
if (!characterState.enabled) return;
if (characterState.useGpuSkinning) {
drawShadowDepthGpuSkinning(renderer);
} else {
drawShadowDepthCpu(renderer);
@@ -674,10 +341,10 @@ void Character::drawShadowDepthCpu(Renderer& renderer) {
if (it == animations.end()) return;
renderer.PushMatrix();
renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(state.modelScale);
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
renderer.TranslateMatrix({ characterState.position.x(), characterState.position.y(), characterState.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-characterState.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(characterState.modelScale);
renderer.RotateMatrix(characterState.modelCorrectionRotation.toRotationMatrix());
auto& anim = it->second;
for (const auto& name : anim.model.meshNamesOrdered) {
@@ -710,10 +377,10 @@ void Character::drawShadowDepthGpuSkinning(Renderer& renderer) {
renderer.shaderManager.PushShader(shadowSkinningShader);
renderer.PushMatrix();
renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(state.modelScale);
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
renderer.TranslateMatrix({ characterState.position.x(), characterState.position.y(), characterState.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-characterState.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(characterState.modelScale);
renderer.RotateMatrix(characterState.modelCorrectionRotation.toRotationMatrix());
CheckGlError(__FILE__, __LINE__);
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
@@ -740,7 +407,7 @@ void Character::drawShadowDepthGpuSkinning(Renderer& renderer) {
// ==================== Main pass with shadows ====================
void Character::drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera) {
if (!state.enabled) return;
if (!characterState.enabled) return;
if (hitSparkEmitter.isConfigured() && hitSparkEmitter.getActiveParticleCount() > 0) {
hitSparkEmitter.draw(renderer, 1.0f, Environment::width, Environment::height, false);
}
@@ -748,7 +415,7 @@ void Character::drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightF
renderer.RenderUniform1f("uAlpha", 1.0f);
glDisable(GL_BLEND);
if (state.useGpuSkinning) {
if (characterState.useGpuSkinning) {
drawGpuSkinningWithShadow(renderer, lightFromCamera, shadowMapTex, lightDirCamera);
} else {
drawCpuWithShadow(renderer, lightFromCamera, shadowMapTex, lightDirCamera);
@@ -779,10 +446,10 @@ void Character::drawCpuWithShadow(Renderer& renderer, const Eigen::Matrix4f& lig
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(state.modelScale);
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
renderer.TranslateMatrix({ characterState.position.x(), characterState.position.y(), characterState.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-characterState.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(characterState.modelScale);
renderer.RotateMatrix(characterState.modelCorrectionRotation.toRotationMatrix());
auto& anim = it->second;
for (const auto& name : anim.model.meshNamesOrdered) {
@@ -841,10 +508,10 @@ void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matri
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(state.modelScale);
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
renderer.TranslateMatrix({ characterState.position.x(), characterState.position.y(), characterState.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-characterState.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(characterState.modelScale);
renderer.RotateMatrix(characterState.modelCorrectionRotation.toRotationMatrix());
CheckGlError(__FILE__, __LINE__);
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
@@ -887,7 +554,7 @@ void Character::drawNight(Renderer& renderer,
const Eigen::Matrix4f& cameraViewInverse,
const float* ambientColor, const float* fogColor)
{
if (!state.enabled) return;
if (!characterState.enabled) return;
if (hitSparkEmitter.isConfigured() && hitSparkEmitter.getActiveParticleCount() > 0) {
hitSparkEmitter.draw(renderer, 1.0f, Environment::width, Environment::height, false);
}
@@ -895,7 +562,7 @@ void Character::drawNight(Renderer& renderer,
renderer.RenderUniform1f("uAlpha", 1.0f);
glDisable(GL_BLEND);
if (state.useGpuSkinning) {
if (characterState.useGpuSkinning) {
drawNightGpuSkinning(renderer, pointLightPosEye, pointLightDirEye, pointLightColors, pointLightCount,
pointLightWorldXZ, pointLightLimitXZ, cameraViewInverse, ambientColor, fogColor);
} else {
@@ -939,10 +606,10 @@ void Character::drawNightGpuSkinning(Renderer& renderer,
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(state.modelScale);
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
renderer.TranslateMatrix({ characterState.position.x(), characterState.position.y(), characterState.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-characterState.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(characterState.modelScale);
renderer.RotateMatrix(characterState.modelCorrectionRotation.toRotationMatrix());
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false,
@@ -1010,10 +677,10 @@ void Character::drawNightCpu(Renderer& renderer,
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(state.modelScale);
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
renderer.TranslateMatrix({ characterState.position.x(), characterState.position.y(), characterState.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-characterState.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(characterState.modelScale);
renderer.RotateMatrix(characterState.modelCorrectionRotation.toRotationMatrix());
auto& anim = it->second;
for (const auto& name : anim.model.meshNamesOrdered) {
@@ -1045,14 +712,14 @@ void Character::drawNightWithShadow(Renderer& renderer,
const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex,
const float* ambientColor, const float* fogColor)
{
if (!state.enabled) return;
if (!characterState.enabled) return;
if (hitSparkEmitter.isConfigured() && hitSparkEmitter.getActiveParticleCount() > 0) {
hitSparkEmitter.draw(renderer, 1.0f, Environment::width, Environment::height, false);
}
renderer.RenderUniform1f("uAlpha", 1.0f);
glDisable(GL_BLEND);
if (state.useGpuSkinning) {
if (characterState.useGpuSkinning) {
drawNightGpuSkinningWithShadow(renderer, plPosEye, plDirEye, plColors, plCount,
plWorldXZ, plLimitXZ, cameraViewInverse,
lightFromCamera, shadowMapTex, ambientColor, fogColor);
@@ -1107,10 +774,10 @@ void Character::drawNightGpuSkinningWithShadow(Renderer& renderer,
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(state.modelScale);
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
renderer.TranslateMatrix({ characterState.position.x(), characterState.position.y(), characterState.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-characterState.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(characterState.modelScale);
renderer.RotateMatrix(characterState.modelCorrectionRotation.toRotationMatrix());
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false,
@@ -1191,10 +858,10 @@ void Character::drawNightCpuWithShadow(Renderer& renderer,
Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
renderer.PushMatrix();
renderer.TranslateMatrix({ state.position.x(), state.position.y(), state.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-state.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(state.modelScale);
renderer.RotateMatrix(state.modelCorrectionRotation.toRotationMatrix());
renderer.TranslateMatrix({ characterState.position.x(), characterState.position.y(), characterState.position.z() });
renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-characterState.facingAngle, Eigen::Vector3f::UnitY())).toRotationMatrix());
renderer.ScaleMatrix(characterState.modelScale);
renderer.RotateMatrix(characterState.modelCorrectionRotation.toRotationMatrix());
auto& anim = it->second;
for (const auto& name : anim.model.meshNamesOrdered) {
@@ -1232,16 +899,15 @@ void Character::setupHitSparks(std::shared_ptr<Texture> sparkTexture) {
hitSparkEmitter.markConfigured();
}
void Character::applyDamage(float damageAmount, const Eigen::Vector3f& attackDirection, int attackerIndex) {
logger() << "Character::applyDamage" << std::endl;
state.hp -= damageAmount;
if (state.hp < 0) state.hp = 0;
if (state.onHpChanged) state.onHpChanged(state.hp, state.initialHp, attackerIndex);
void Character::presentDamage(float hp, float maxHp,
const Eigen::Vector3f& attackDirection,
const std::string& attackerId) {
if (onHpChanged)
onHpChanged(hp, maxHp, attackerId);
if (!hitSparkEmitter.isConfigured()) return;
Eigen::Vector3f emitPos = state.position + Eigen::Vector3f(0.f, 1.f, 0.f);
Eigen::Vector3f emitPos = characterState.position + Eigen::Vector3f(0.f, 1.f, 0.f);
hitSparkEmitter.resetEmissionPoints({ emitPos });
hitSparkEmitter.setEmissionDirection(attackDirection);
@@ -1262,12 +928,12 @@ void Character::drawName(TextRenderer& textRenderer,
const Eigen::Matrix4f& cameraViewMatrix,
const Eigen::Matrix4f& projectionMatrix)
{
if (state.isPlayer) return;
if (!state.enabled) return;
if (state.hp <= 0.f) return;
if (state.npcName.empty()) return;
if (characterState.isPlayer) return;
if (!characterState.enabled) return;
if (characterState.hp <= 0.f) return;
if (characterState.npcName.empty()) return;
Eigen::Vector4f worldPos(state.position.x(), state.position.y() + 1.83f, state.position.z(), 1.f);
Eigen::Vector4f worldPos(characterState.position.x(), characterState.position.y() + 1.83f, characterState.position.z(), 1.f);
Eigen::Vector4f clip = projectionMatrix * cameraViewMatrix * worldPos;
if (clip.w() <= 0.f) return;
@@ -1278,11 +944,11 @@ void Character::drawName(TextRenderer& textRenderer,
float screenX = (ndcX * 0.5f + 0.5f) * Environment::projectionWidth;
float screenY = (ndcY * 0.5f + 0.5f) * Environment::projectionHeight;
std::array<float, 4> color = state.canAttack
std::array<float, 4> color = characterState.canAttack
? std::array<float, 4>{ 1.f, 0.f, 0.f, 1.f }
: std::array<float, 4>{ 0.f, 1.f, 0.f, 1.f };
const std::string& translatedName = Character::nameTranslations.translateRef(state.npcName, g_currentLanguage);
const std::string& translatedName = Character::nameTranslations.translateRef(characterState.npcName, g_currentLanguage);
textRenderer.drawText(translatedName, screenX, screenY, 1.0f, true, color);
}
@@ -1291,12 +957,12 @@ void Character::drawHealthBar(Renderer& renderer,
const Eigen::Matrix4f& cameraViewMatrix,
const Eigen::Matrix4f& projectionMatrix)
{
if (state.isPlayer || !state.canAttack) return;
if (!state.enabled) return;
if (state.hp <= 0.f) return;
if (state.initialHp <= 0.f) return;
if (characterState.isPlayer || !characterState.canAttack) return;
if (!characterState.enabled) return;
if (characterState.hp <= 0.f) return;
if (characterState.initialHp <= 0.f) return;
Eigen::Vector4f worldPos(state.position.x(), state.position.y() + 1.75f, state.position.z(), 1.f);
Eigen::Vector4f worldPos(characterState.position.x(), characterState.position.y() + 1.75f, characterState.position.z(), 1.f);
Eigen::Vector4f clip = projectionMatrix * cameraViewMatrix * worldPos;
if (clip.w() <= 0.f) return;
float ndcX = clip.x() / clip.w();
@@ -1314,7 +980,7 @@ void Character::drawHealthBar(Renderer& renderer,
const float bottom = sy - barH * 0.5f;
const float top = sy + barH * 0.5f;
float frac = state.hp / state.initialHp;
float frac = characterState.hp / characterState.initialHp;
if (frac < 0.f) frac = 0.f;
if (frac > 1.f) frac = 1.f;
const float split = left + barW * frac;