From 1079daa49be74699bd9c2977b3e190547e158919 Mon Sep 17 00:00:00 2001 From: tovjemam Date: Thu, 11 Jun 2026 14:51:31 +0200 Subject: [PATCH] Character states, aiming and stuff --- src/assets/animation.cpp | 8 + src/assets/animation.hpp | 5 +- src/assets/skeleton.cpp | 23 +++ src/assets/skeleton.hpp | 14 ++ src/client/main.cpp | 24 +-- src/game/animal.cpp | 5 +- src/game/animal.hpp | 2 +- src/game/character.cpp | 157 +++++++++++++++--- src/game/character.hpp | 32 +++- src/game/character_anim_state.cpp | 13 +- src/game/character_anim_state.hpp | 8 +- src/game/character_sync.hpp | 22 ++- src/game/enterable_world.cpp | 2 +- src/game/human_character.cpp | 263 ++++++++++++++++++++++++++++-- src/game/human_character.hpp | 67 ++++++++ src/game/player_character.cpp | 4 +- src/game/player_input.hpp | 3 +- src/game/rideable.hpp | 2 +- src/game/skeletoninstance.cpp | 14 ++ src/game/skeletoninstance.hpp | 2 + src/gameview/characterview.cpp | 60 ++++++- src/gameview/characterview.hpp | 6 + src/net/defs.hpp | 2 + src/net/utils.hpp | 10 +- 24 files changed, 669 insertions(+), 79 deletions(-) diff --git a/src/assets/animation.cpp b/src/assets/animation.cpp index 425ea77..e6594fc 100644 --- a/src/assets/animation.cpp +++ b/src/assets/animation.cpp @@ -97,6 +97,10 @@ std::shared_ptr assets::Animation::LoadFromFile(const s { iss >> anim->tps_; } + else if (command == "cyclic") + { + anim->cyclic_ = true; + } }); if (anim->channels_.empty()) @@ -120,5 +124,9 @@ std::shared_ptr assets::Animation::LoadFromFile(const s channel.frames = &anim->frame_refs_[i * anim->num_frames_]; } + // calc duration + auto frame_range = anim->cyclic_ ? anim->num_frames_ : anim->num_frames_ - 1; + anim->duration_ = static_cast(frame_range) / anim->tps_; + return anim; } diff --git a/src/assets/animation.hpp b/src/assets/animation.hpp index d3d55ea..c721de6 100644 --- a/src/assets/animation.hpp +++ b/src/assets/animation.hpp @@ -24,7 +24,8 @@ public: size_t GetNumFrames() const { return num_frames_; } float GetTPS() const { return tps_; } - float GetDuration() const { return static_cast(num_frames_) / tps_; } + float GetDuration() const { return duration_; } + bool IsCyclic() const { return cyclic_; } size_t GetNumChannels() const { return channels_.size(); } const AnimationChannel& GetChannel(int index) const { return channels_[index]; } @@ -32,6 +33,8 @@ public: private: size_t num_frames_ = 0; float tps_ = 24.0f; + bool cyclic_ = false; + float duration_ = 0.0f; std::vector channels_; std::vector frame_refs_; diff --git a/src/assets/skeleton.cpp b/src/assets/skeleton.cpp index ea8f316..7f77f7c 100644 --- a/src/assets/skeleton.cpp +++ b/src/assets/skeleton.cpp @@ -40,6 +40,8 @@ std::shared_ptr assets::Skeleton::LoadFromFile(const std } }); + skeleton->AddAimBones(); + return skeleton; } @@ -95,3 +97,24 @@ void assets::Skeleton::AddAnimation(const std::string& name, const std::shared_p anim_idxs_[name] = anims_.size(); anims_.push_back(anim); } + +void assets::Skeleton::AddAimBones() +{ + AddAimBone("DEF-spine.002", 0.5f); + AddAimBone("MCH-spine.002", 0.5f); + AddAimBone("DEF-spine.003", 0.5f); + AddAimBone("MCH-spine.003", 0.5f); + +} + +void assets::Skeleton::AddAimBone(const std::string& name, float weight) +{ + auto idx = GetBoneIndex(name); + if (idx < 0) + return; + + AimBone aimbone{}; + aimbone.idx = idx; + aimbone.weight = weight; + aim_bones_.emplace_back(aimbone); +} diff --git a/src/assets/skeleton.hpp b/src/assets/skeleton.hpp index e4e3d4e..c1ce447 100644 --- a/src/assets/skeleton.hpp +++ b/src/assets/skeleton.hpp @@ -22,6 +22,12 @@ struct Bone using AnimIdx = uint8_t; constexpr AnimIdx NO_ANIM = 255; +struct AimBone +{ + size_t idx; + float weight; +}; + class Skeleton { public: @@ -37,16 +43,24 @@ public: const Animation* GetAnimation(AnimIdx idx) const; const Animation* GetAnimation(const std::string& name) const; + const std::vector GetAimBones() const { return aim_bones_; } + private: void AddBone(const std::string& name, const std::string& parent_name, const Transform& transform); void AddAnimation(const std::string& name, const std::shared_ptr& anim); + void AddAimBones(); + void AddAimBone(const std::string& name, float weight); + private: + std::string name_; std::vector bones_; std::map bone_map_; std::vector> anims_; std::map anim_idxs_; + + std::vector aim_bones_; }; } // namespace assets \ No newline at end of file diff --git a/src/client/main.cpp b/src/client/main.cpp index fff9a4a..f70b84a 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -196,6 +196,20 @@ static void PollEvents() } break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + if (event.button.button == SDL_BUTTON_LEFT) + { + s_app->Input(game::IN_ATTACK_PRIMARY, event.button.state == SDL_PRESSED, event.button.clicks > 1); + } + else if (event.button.button == SDL_BUTTON_RIGHT) + { + s_app->Input(game::IN_ATTACK_SECONDARY, event.button.state == SDL_PRESSED, event.button.clicks > 1); + } + } + break; + } } @@ -362,16 +376,6 @@ static void Frame() int width, height; SDL_GetWindowSize(s_window, &width, &height); s_app->SetViewportSize(width, height); - - game::PlayerInputFlags input = 0; - const uint8_t* kbd_state = SDL_GetKeyboardState(nullptr); - - - - int mouse_state = SDL_GetMouseState(nullptr, nullptr); - - if (mouse_state & SDL_BUTTON(SDL_BUTTON_LEFT)) - input |= (1 << game::IN_ATTACK); s_app->Frame(); diff --git a/src/game/animal.cpp b/src/game/animal.cpp index cc42bc8..32f9af0 100644 --- a/src/game/animal.cpp +++ b/src/game/animal.cpp @@ -9,6 +9,7 @@ game::Animal::Animal(World& world, const CharacterTuning& tuning, const glm::vec SetPosition(position); SetYaw(yaw); EnablePhysics(true); + SetMovementType(CMT_TURN); collision::AddObjectFlags(&GetController()->GetBtGhost(), collision::OF_USABLE); } @@ -40,9 +41,9 @@ void game::Animal::SetRideableInput(PlayerInputFlags in) SetInputs(MapPlayerInputToCharacterInput(in)); } -void game::Animal::SetRideableYaw(float yaw) +void game::Animal::SetRideableViewAngles(float yaw, float pitch) { - SetForwardYaw(yaw); + SetViewAngles(yaw, pitch); } void game::Animal::OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger) diff --git a/src/game/animal.hpp b/src/game/animal.hpp index dc0a286..34dc20d 100644 --- a/src/game/animal.hpp +++ b/src/game/animal.hpp @@ -16,7 +16,7 @@ public: virtual void Use(PlayerCharacter& character, uint32_t target_id) override; virtual void SetRideableInput(PlayerInputFlags in) override; - virtual void SetRideableYaw(float yaw) override; + virtual void SetRideableViewAngles(float yaw, float pitch) override; protected: virtual void OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger) override; diff --git a/src/game/character.cpp b/src/game/character.cpp index b4d1d9e..ac77ee4 100644 --- a/src/game/character.cpp +++ b/src/game/character.cpp @@ -38,6 +38,7 @@ void game::Character::Update() SyncTransformFromController(); UpdateMovement(); + UpdateActionAnim(); root_.UpdateMatrix(); sync_current_ = 1 - sync_current_; @@ -100,6 +101,17 @@ void game::Character::SetInput(CharacterInputType type, bool enable) in_ &= ~(1 << type); } +void game::Character::SetMovementType(CharacterMovementType type) +{ + movement_ = type; +} + +void game::Character::SetViewAngles(float yaw, float pitch) +{ + view_yaw_ = yaw; + view_pitch_ = pitch; +} + void game::Character::SetPosition(const glm::vec3& position) { root_.local.position = position; @@ -121,6 +133,30 @@ void game::Character::SetRunAnim(const std::string& anim_name) animstate_.run_anim_idx = GetAnim(anim_name); } +void game::Character::PlayActionAnim(assets::AnimIdx anim_idx, float speed) +{ + action_anim_end_ = (anim_idx != assets::NO_ANIM) ? sk_.GetSkeleton()->GetAnimation(anim_idx)->GetDuration() : 0.0f; + + if (animstate_.action_anim_idx != anim_idx) + { + // continue from current time if same anim + animstate_.action_phase = (speed > 0.0f) ? 0.0f : action_anim_end_; + } + animstate_.action_anim_idx = anim_idx; + action_anim_playback_speed_ = speed; + action_anim_done_ = anim_idx == assets::NO_ANIM; +} + +void game::Character::PlayActionAnim(const std::string& anim_name, float speed) +{ + PlayActionAnim(GetAnim(anim_name), speed); +} + +void game::Character::ClearActionAnim() +{ + PlayActionAnim(assets::NO_ANIM, 0.0f); +} + void game::Character::SyncControllerTransform() { if (!controller_) @@ -143,42 +179,58 @@ void game::Character::SyncTransformFromController() root_.local.position.z -= z_offset_; // foot pos } +static glm::vec2 GetInputDir(game::CharacterInputFlags in) +{ + glm::vec2 dir(0.0f); + + if (in & (1 << game::CIN_FORWARD)) + dir.y += 1.0f; + + if (in & (1 << game::CIN_BACKWARD)) + dir.y -= 1.0f; + + if (in & (1 << game::CIN_RIGHT)) + dir.x -= 1.0f; + + if (in & (1 << game::CIN_LEFT)) + dir.x += 1.0f; + + return dir; +} + void game::Character::UpdateMovement() { + if (movement_ == CMT_DISABLED) + { + animstate_.loco_blend = 0.0f; + return; + } + constexpr float dt = 1.0f / 25.0f; bool walking = false; bool running = false; - glm::vec2 movedir(0.0f); - if (in_ & (1 << CIN_FORWARD)) - movedir.y += 1.0f; + glm::vec3 move_dir(0.0f); - if (in_ & (1 << CIN_BACKWARD)) - movedir.y -= 1.0f; - - if (in_ & (1 << CIN_RIGHT)) - movedir.x -= 1.0f; - - if (in_ & (1 << CIN_LEFT)) - movedir.x += 1.0f; - - glm::vec3 walkdir(0.0f); - - if (movedir.x != 0.0f || movedir.y != 0.0f) + auto input_dir = GetInputDir(in_); + if (input_dir.x != 0.0f || input_dir.y != 0.0f) { walking = true; if (in_ & (1 << CIN_SPRINT)) running = true; - float target_yaw = forward_yaw_ + std::atan2(movedir.x, movedir.y); - Turn(yaw_, target_yaw, turn_speed_ * dt); - - glm::vec3 forward_dir(-glm::sin(yaw_), glm::cos(yaw_), 0.0f); - walkdir = forward_dir * walk_speed_ * dt; + const bool directional = (movement_ == CMT_DIRECTIONAL); + float relative_yaw = std::atan2(input_dir.x, input_dir.y); + float turn_yaw = directional ? view_yaw_ : view_yaw_ + relative_yaw; + Turn(yaw_, turn_yaw, turn_speed_ * dt); + float move_yaw = directional ? yaw_ + relative_yaw : yaw_; + + move_dir = glm::vec3(-glm::sin(move_yaw), glm::cos(move_yaw), 0.0f) * walk_speed_ * dt; + if (running) - walkdir *= run_speed_mult_; + move_dir *= run_speed_mult_; } @@ -187,7 +239,7 @@ void game::Character::UpdateMovement() if (controller_) { auto& bt_character = controller_->GetBtController(); - bt_character.setWalkDirection(btVector3(walkdir.x, walkdir.y, walkdir.z)); + bt_character.setWalkDirection(btVector3(move_dir.x, move_dir.y, move_dir.z)); if (in_ & (1 << CIN_JUMP) && bt_character.canJump()) { @@ -202,6 +254,9 @@ void game::Character::UpdateMovement() if (running) anim_speed *= run_speed_mult_; animstate_.loco_phase = glm::mod(animstate_.loco_phase + anim_speed * dt, 1.0f); + + animstate_.pitch = view_pitch_; + } void game::Character::UpdateSyncState() @@ -220,6 +275,14 @@ void game::Character::UpdateSyncState() state.run_anim = animstate_.run_anim_idx; state.loco_phase.Encode(animstate_.loco_phase); state.loco_blend.Encode(animstate_.loco_blend); + + // action + state.action_anim = animstate_.action_anim_idx; + state.action_phase.Encode(animstate_.action_phase); + + // aim + state.aim_yaw.Encode(animstate_.yaw); + state.aim_pitch.Encode(animstate_.pitch); } void game::Character::SendUpdateMsg() @@ -282,6 +345,31 @@ game::CharacterSyncFieldFlags game::Character::WriteState(net::OutMessage& msg, net::WriteDelta(msg, curr.loco_phase, base.loco_phase); } + // action anim + if (curr.action_anim != base.action_anim) + { + fields |= CSF_ACTION_ANIM; + + msg.Write(curr.action_anim); + } + + // action phase + if (curr.action_phase.value != base.action_phase.value) + { + fields |= CSF_ACTION_PHASE; + + net::WriteDelta(msg, curr.action_phase, base.action_phase); + } + + // aim + if (curr.aim_yaw.value != base.aim_yaw.value || curr.aim_pitch.value != base.aim_pitch.value) + { + fields |= CSF_AIM; + + net::WriteDelta(msg, curr.aim_yaw.value, base.aim_yaw.value); + net::WriteDelta(msg, curr.aim_pitch.value, base.aim_pitch.value); + } + return fields; } @@ -290,6 +378,31 @@ assets::AnimIdx game::Character::GetAnim(const std::string& name) const return sk_.GetSkeleton()->GetAnimationIdx(name); } +void game::Character::UpdateActionAnim() +{ + if (action_anim_done_) + return; + + animstate_.action_phase += action_anim_playback_speed_ * (1.0f / 25.0f); + + if (action_anim_playback_speed_ > 0.0f) + { + if (animstate_.action_phase >= action_anim_end_) + { + animstate_.action_phase = action_anim_end_; + action_anim_done_ = true; + } + } + else + { + if (animstate_.action_phase <= 0.0f) + { + animstate_.action_phase = 0.0f; + action_anim_done_ = true; + } + } +} + game::CharacterPhysicsController::CharacterPhysicsController(Character& character, btDynamicsWorld& bt_world, btCapsuleShapeZ& bt_shape) : character_(character), bt_world_(bt_world), bt_character_(&bt_ghost_, &bt_shape, 0.3f, btVector3(0, 0, 1)) { diff --git a/src/game/character.hpp b/src/game/character.hpp index 8277d1d..f442196 100644 --- a/src/game/character.hpp +++ b/src/game/character.hpp @@ -45,6 +45,13 @@ private: btKinematicCharacterController bt_character_; }; +enum CharacterMovementType +{ + CMT_DISABLED, + CMT_TURN, + CMT_DIRECTIONAL, +}; + class Character : public Entity { public: @@ -64,13 +71,17 @@ public: void SetInput(CharacterInputType type, bool enable); void SetInputs(CharacterInputFlags inputs) { in_ = inputs; } + CharacterInputFlags GetInputs() const { return in_; } + + void SetMovementType(CharacterMovementType type); + + void SetViewAngles(float yaw, float pitch); + float GetViewYaw() const { return view_yaw_; } + float GetViewPitch() const { return view_pitch_; } - void SetForwardYaw(float yaw) { forward_yaw_ = yaw; } - float GetForwardYaw() const { return forward_yaw_; } void SetYaw(float yaw) { yaw_ = yaw; } void SetPosition(const glm::vec3& position); - ~Character() override = default; @@ -78,6 +89,10 @@ protected: void SetIdleAnim(const std::string& anim_name); void SetWalkAnim(const std::string& anim_name); void SetRunAnim(const std::string& anim_name); + void PlayActionAnim(assets::AnimIdx anim_idx, float speed); + void PlayActionAnim(const std::string& anim_name, float speed = 1.0f); + void ClearActionAnim(); + bool IsActionAnimDone() { return action_anim_done_; } private: void SyncControllerTransform(); @@ -90,6 +105,8 @@ private: assets::AnimIdx GetAnim(const std::string& name) const; + void UpdateActionAnim(); + protected: float turn_speed_ = 8.0f; float walk_speed_ = 2.0f; @@ -108,13 +125,20 @@ private: std::unique_ptr controller_; float yaw_ = 0.0f; - float forward_yaw_ = 0.0f; + float view_yaw_ = 0.0f; + float view_pitch_ = 0.0f; SkeletonInstance sk_; CharacterAnimState animstate_; CharacterSyncState sync_[2]; size_t sync_current_ = 0; + + CharacterMovementType movement_ = CMT_DISABLED; + + float action_anim_playback_speed_ = 0.0f; + float action_anim_end_ = 0.0f; + bool action_anim_done_ = true; }; } // namespace game \ No newline at end of file diff --git a/src/game/character_anim_state.cpp b/src/game/character_anim_state.cpp index 5227b26..09c7732 100644 --- a/src/game/character_anim_state.cpp +++ b/src/game/character_anim_state.cpp @@ -26,7 +26,6 @@ void game::CharacterAnimState::ApplyToSkeleton(SkeletonInstance& sk) const sk.ApplySkelAnim(*idle_anim, loco_phase, 1.0f); sk.ApplySkelAnim(*walk_anim, loco_phase, UnMix(0.0f, 0.5f, loco_blend)); } - else if (loco_blend == 0.5f) // walk { sk.ApplySkelAnim(*walk_anim, loco_phase, 1.0f); @@ -41,4 +40,16 @@ void game::CharacterAnimState::ApplyToSkeleton(SkeletonInstance& sk) const sk.ApplySkelAnim(*run_anim, loco_phase, 1.0f); } + // action + auto action_anim = skeleton->GetAnimation(action_anim_idx); + if (action_anim) + { + sk.ApplySkelAnim(*action_anim, action_phase, 1.0f); + } + + if (glm::abs(yaw) > 0.01f || glm::abs(pitch) > 0.01f) + { + sk.ApplyAim(yaw, pitch); + } + } diff --git a/src/game/character_anim_state.hpp b/src/game/character_anim_state.hpp index 3e16e0b..ada390f 100644 --- a/src/game/character_anim_state.hpp +++ b/src/game/character_anim_state.hpp @@ -8,12 +8,18 @@ namespace game struct CharacterAnimState { assets::AnimIdx idle_anim_idx = assets::NO_ANIM; + assets::AnimIdx walk_anim_idx = assets::NO_ANIM; assets::AnimIdx run_anim_idx = assets::NO_ANIM; - float loco_blend = 0.0f; float loco_phase = 0.0f; + assets::AnimIdx action_anim_idx = assets::NO_ANIM; + float action_phase = 0.0f; + + float yaw = 0.0f; + float pitch = 0.0f; + void ApplyToSkeleton(SkeletonInstance& sk) const; diff --git a/src/game/character_sync.hpp b/src/game/character_sync.hpp index d49f779..af3ae92 100644 --- a/src/game/character_sync.hpp +++ b/src/game/character_sync.hpp @@ -22,20 +22,30 @@ struct CharacterSyncState assets::AnimIdx run_anim = assets::NO_ANIM; net::AnimBlendQ loco_blend; net::AnimTimeQ loco_phase; + + // action anim + assets::AnimIdx action_anim = assets::NO_ANIM; + net::AnimTimeQ action_phase; + + // aim + net::AnimAimAngleQ aim_yaw; + net::AnimAimAngleQ aim_pitch; + //assets::AnimIdx strafe_left_anim = assets::NO_ANIM; //assets::AnimIdx strafe_right_anim = assets::NO_ANIM; - - // TODO: action }; using CharacterSyncFieldFlags = uint8_t; enum CharacterSyncFieldFlag { - CSF_TRANSFORM = 0x01, - CSF_IDLE_ANIM = 0x02, - CSF_LOCO_ANIMS = 0x04, - CSF_LOCO_VALS = 0x08, + CSF_TRANSFORM = 1, + CSF_IDLE_ANIM = 2, + CSF_LOCO_ANIMS = 4, + CSF_LOCO_VALS = 8, + CSF_ACTION_ANIM = 16, + CSF_ACTION_PHASE = 32, + CSF_AIM = 64, }; } // namespace game \ No newline at end of file diff --git a/src/game/enterable_world.cpp b/src/game/enterable_world.cpp index 70956a2..2e4b00d 100644 --- a/src/game/enterable_world.cpp +++ b/src/game/enterable_world.cpp @@ -45,7 +45,7 @@ void game::EnterableWorld::PlayerViewAnglesChanged(Player& player, float yaw, fl auto character = it->second; - character->SetForwardYaw(yaw); + character->SetViewAngles(yaw, pitch); } void game::EnterableWorld::RemovePlayer(Player& player) diff --git a/src/game/human_character.cpp b/src/game/human_character.cpp index c195137..de51b06 100644 --- a/src/game/human_character.cpp +++ b/src/game/human_character.cpp @@ -17,33 +17,27 @@ game::HumanCharacter::HumanCharacter(World& world, const HumanCharacterTuning& t SetWalkAnim("walk"); } +void game::HumanCharacter::Update() +{ + UpdateState(); + UpdateActionState(); + Super::Update(); +} + void game::HumanCharacter::SetRideable(Rideable* rideable, size_t seat_idx) { if (rideable == rideable_ && seat_idx == seat_idx_) return; - - if (rideable) + + if (rideable_) { - SetPosition(rideable->GetSeatOffset(seat_idx)); - EnablePhysics(false); - - Attach(rideable->GetEntity().GetEntNum()); - SetIdleAnim((rideable->GetRideableType() == RIDEABLE_VEHICLE && seat_idx == 0) ? "vehicle_drive" : "vehicle_passenger"); - SetYaw(0.0f); - } - else - { - EnablePhysics(true); - glm::vec3 seat_loc = rideable_->GetSeatOffset(seat_idx_); seat_loc.x += glm::sign(seat_loc.x) * 0.5f; // to the side glm::vec3 pos = rideable_->GetEntity().GetRoot().matrix * glm::vec4(seat_loc, 1.0f); pos.z += 0.5f; - SetPosition(pos); - Attach(0); - SetIdleAnim("idle"); + rideable_exit_pos_ = pos; } rideable_ = rideable; @@ -51,8 +45,10 @@ void game::HumanCharacter::SetRideable(Rideable* rideable, size_t seat_idx) seat_idx_ = seat_idx; is_driver_ = rideable && seat_idx_ == 0; + SetSignal(HSS_RIDEABLE_CHANGED); OnRideableChanged(); + } void game::HumanCharacter::Ride(Rideable* rideable, size_t seat_idx) @@ -72,3 +68,238 @@ game::HumanCharacter::~HumanCharacter() { Ride(nullptr, 0); // exit rideable } + +void game::HumanCharacter::UpdateState() +{ + struct HumanCharacterStateTableEntry + { + void (HumanCharacter::*enter)(); + HumanCharacterState (HumanCharacter::*update)(); + void (HumanCharacter::*exit)(); + }; + + static const HumanCharacterStateTableEntry state_table[] = + { + // HS_INIT + { + nullptr, + &HumanCharacter::StateInitUpdate, + nullptr, + }, + + // HS_ON_FOOT + { + &HumanCharacter::StateOnFootEnter, + &HumanCharacter::StateOnFootUpdate, + nullptr, + }, + + // HS_RIDING + { + &HumanCharacter::StateRidingEnter, + &HumanCharacter::StateRidingUpdate, + &HumanCharacter::StateRidingExit, + }, + + // HS_KNOCKED_DOWN + { + nullptr, + &HumanCharacter::StateKnockedDownUpdate, + nullptr, + }, + }; + + while (true) + { + auto new_state = (this->*state_table[state_].update)(); + + if (new_state == state_) + break; + + if (auto exit_fun = state_table[state_].exit) + (this->*exit_fun)(); + + state_ = new_state; + + if (auto enter_fun = state_table[state_].enter) + (this->*enter_fun)(); + } + + signals_ = 0; +} + +void game::HumanCharacter::SetSignal(HumanCharacterStateSignal signal) +{ + signals_ |= signal; +} + +bool game::HumanCharacter::PopSignal(HumanCharacterStateSignal signal) +{ + if (signals_ & signal) + { + signals_ &= ~signal; + return true; + } + + return false; +} + +game::HumanCharacterState game::HumanCharacter::StateInitUpdate() +{ + if (GetRideable()) + return HS_RIDING; + + return HS_ON_FOOT; +} + +void game::HumanCharacter::StateOnFootEnter() +{ + SetIdleAnim("idle"); + SetWalkAnim("walk"); + SetMovementType(CMT_TURN); + EnablePhysics(true); +} + +game::HumanCharacterState game::HumanCharacter::StateOnFootUpdate() +{ + if (PopSignal(HSS_RIDEABLE_CHANGED)) + return HS_INIT; + + if (PopSignal(HSS_KNOCK_DOWN)) + return HS_KNOCKED_DOWN; + + SetMovementType(aiming_ ? CMT_DIRECTIONAL : CMT_TURN); + + return HS_ON_FOOT; +} + +void game::HumanCharacter::StateRidingEnter() +{ + auto rideable = GetRideable(); + SetPosition(rideable->GetSeatOffset(seat_idx_)); + EnablePhysics(false); + + Attach(rideable->GetEntity().GetEntNum()); + SetIdleAnim((rideable->GetRideableType() == RIDEABLE_VEHICLE && seat_idx_ == 0) ? "vehicle_drive" : "vehicle_passenger"); + SetYaw(0.0f); + SetMovementType(CMT_DISABLED); +} + +game::HumanCharacterState game::HumanCharacter::StateRidingUpdate() +{ + if (!GetRideable()) + return HS_INIT; + + if (PopSignal(HSS_RIDEABLE_CHANGED)) + return HS_INIT; + + return HS_RIDING; +} + +void game::HumanCharacter::StateRidingExit() +{ + EnablePhysics(true); + SetPosition(rideable_exit_pos_); + Attach(0); +} + +game::HumanCharacterState game::HumanCharacter::StateKnockedDownUpdate() +{ + return HS_INIT; +} + +void game::HumanCharacter::UpdateActionState() +{ + while (true) + { + auto new_state = CheckActionStateTransition(); + + if (new_state == actionstate_) + break; + + ExitActionState(); + actionstate_ = new_state; + EnterActionState(); + } +} + +void game::HumanCharacter::EnterActionState() +{ + switch (actionstate_) + { + case ACTION_IDLE: + ClearActionAnim(); + break; + + case ACTION_AIM: + PlayActionAnim("rifle_aim", 3.0f); + break; + + case ACTION_AIMING: + break; + + case ACTION_FIRE: + PlayActionAnim("rifle_fire"); + break; + + case ACTION_UNAIM: + PlayActionAnim("rifle_aim", -3.0f); + break; + + default: + break; + } +} + +game::ActionState game::HumanCharacter::CheckActionStateTransition() +{ + switch (actionstate_) + { + case ACTION_IDLE: + if (aiming_) // want aim + return ACTION_AIM; + + return ACTION_IDLE; + + case ACTION_AIM: + if (IsActionAnimDone()) + return ACTION_AIMING; + + if (!aiming_) // stop aiming immediately + return ACTION_UNAIM; + + return ACTION_AIM; + + case ACTION_AIMING: + if (!aiming_) + return ACTION_UNAIM; // wants aim no more + + // TODO: check fire + + return ACTION_AIMING; + + case ACTION_FIRE: + return ACTION_FIRE; + + case ACTION_UNAIM: + if (IsActionAnimDone()) + return ACTION_IDLE; + + if (aiming_) // start aiming again + return ACTION_AIM; + + return ACTION_UNAIM; + + default: + return actionstate_; + } +} + +void game::HumanCharacter::ExitActionState() +{ + switch (actionstate_) + { + default: + break; + } +} diff --git a/src/game/human_character.hpp b/src/game/human_character.hpp index efb2c42..f18b166 100644 --- a/src/game/human_character.hpp +++ b/src/game/human_character.hpp @@ -13,6 +13,31 @@ struct HumanCharacterTuning class Rideable; class DrivableVehicle; +using HumanCharacterStateSignals = uint32_t; + +enum HumanCharacterStateSignal : HumanCharacterStateSignals +{ + HSS_KNOCK_DOWN = 1, + HSS_RIDEABLE_CHANGED = 2, +}; + +enum HumanCharacterState +{ + HS_INIT, + HS_ON_FOOT, + HS_RIDING, + HS_KNOCKED_DOWN, +}; + +enum ActionState +{ + ACTION_IDLE, + ACTION_AIM, + ACTION_AIMING, + ACTION_FIRE, + ACTION_UNAIM, +}; + class HumanCharacter : public Character { public: @@ -20,6 +45,8 @@ public: HumanCharacter(World& world, const HumanCharacterTuning& tuning); + virtual void Update() override; + const HumanCharacterTuning& GetHumanTuning() const { return human_tuning_; } void SetRideable(Rideable* rideable, size_t seat_idx); // called by Rideable!! @@ -31,11 +58,41 @@ public: size_t GeatSeatIdx() const { return seat_idx_; } bool IsDriver() const { return is_driver_; } + void SetAiming(bool aiming) { aiming_ = aiming; } + virtual ~HumanCharacter() override; protected: virtual void OnRideableChanged() {} +private: + void UpdateState(); + void SetSignal(HumanCharacterStateSignal signal); + bool PopSignal(HumanCharacterStateSignal signal); + + //void StateInitEnter(); + HumanCharacterState StateInitUpdate(); + //void StateInitExit(); + + void StateOnFootEnter(); + HumanCharacterState StateOnFootUpdate(); + //void StateOnFootExit(); + + void StateRidingEnter(); + HumanCharacterState StateRidingUpdate(); + void StateRidingExit(); + + // void StateKnockedDownEnter(); + HumanCharacterState StateKnockedDownUpdate(); + // void StateKnockedDownExit(); + + + void UpdateActionState(); + + void EnterActionState(); + ActionState CheckActionStateTransition(); + void ExitActionState(); + private: HumanCharacterTuning human_tuning_; @@ -43,6 +100,16 @@ private: DrivableVehicle* vehicle_ = nullptr; size_t seat_idx_ = 0; bool is_driver_ = false; + + HumanCharacterState state_ = HS_INIT; + HumanCharacterStateSignals signals_ = 0; + + glm::vec3 rideable_exit_pos_ = glm::vec3(0.0f); + + bool aiming_ = false; + + ActionState actionstate_ = ACTION_IDLE; + }; } diff --git a/src/game/player_character.cpp b/src/game/player_character.cpp index 0d58541..9edf39d 100644 --- a/src/game/player_character.cpp +++ b/src/game/player_character.cpp @@ -17,7 +17,7 @@ void game::PlayerCharacter::Update() if (GetRideable() && IsDriver()) { - GetRideable()->SetRideableYaw(GetForwardYaw()); + GetRideable()->SetRideableViewAngles(GetViewYaw(), GetViewPitch()); } } @@ -78,6 +78,8 @@ void game::PlayerCharacter::UpdateInputs() { SetInputs(MapPlayerInputToCharacterInput(in)); } + + SetAiming(in & (1 << IN_ATTACK_SECONDARY)); } void game::PlayerCharacter::UpdateUseTarget() diff --git a/src/game/player_input.hpp b/src/game/player_input.hpp index 0380d75..db11fcc 100644 --- a/src/game/player_input.hpp +++ b/src/game/player_input.hpp @@ -16,7 +16,8 @@ namespace game IN_CROUCH, IN_SPRINT, IN_USE, - IN_ATTACK, + IN_ATTACK_PRIMARY, + IN_ATTACK_SECONDARY, IN_DEBUG1, IN_DEBUG2, IN_DEBUG3, diff --git a/src/game/rideable.hpp b/src/game/rideable.hpp index 7196f17..2deddf5 100644 --- a/src/game/rideable.hpp +++ b/src/game/rideable.hpp @@ -31,7 +31,7 @@ public: void KickAll(); virtual void SetRideableInput(PlayerInputFlags in) {} - virtual void SetRideableYaw(float yaw) {} + virtual void SetRideableViewAngles(float yaw, float pitch) {} RideableType GetRideableType() const { return type_; } diff --git a/src/game/skeletoninstance.cpp b/src/game/skeletoninstance.cpp index 3b837ed..04cae0e 100644 --- a/src/game/skeletoninstance.cpp +++ b/src/game/skeletoninstance.cpp @@ -52,6 +52,20 @@ void game::SkeletonInstance::ApplySkelAnim(const assets::Animation& anim, float } } +void game::SkeletonInstance::ApplyAim(float yaw, float pitch) +{ + const auto& aim_bones = skeleton_->GetAimBones(); + if (aim_bones.empty()) + return; + + for (const auto& aim_bone : aim_bones) + { + auto& bone_transform = bone_nodes_[aim_bone.idx].local; + auto rotation = glm::angleAxis(-pitch * aim_bone.weight, glm::vec3(1.0f, 0.0f, 0.0f)); + bone_transform.rotation = rotation * bone_transform.rotation; + } +} + void game::SkeletonInstance::UpdateBoneMatrices() { for (TransformNode& node : bone_nodes_) diff --git a/src/game/skeletoninstance.hpp b/src/game/skeletoninstance.hpp index 95b10b9..1f07214 100644 --- a/src/game/skeletoninstance.hpp +++ b/src/game/skeletoninstance.hpp @@ -17,6 +17,8 @@ public: const TransformNode& GetBoneNode(size_t index) const { return bone_nodes_[index]; } void ApplySkelAnim(const assets::Animation& anim, float time, float weight); + void ApplyAim(float yaw, float pitch); + void UpdateBoneMatrices(); const std::vector GetBoneNodes() const { return bone_nodes_; } diff --git a/src/gameview/characterview.cpp b/src/gameview/characterview.cpp index 5c2a558..b37fb53 100644 --- a/src/gameview/characterview.cpp +++ b/src/gameview/characterview.cpp @@ -63,10 +63,13 @@ void game::view::CharacterView::Update(const UpdateInfo& info) // interpolate states float tps = 25.0f; - float t = (info.time - update_time_) * tps * 0.8f; // assume some jitter, interpolate for longer + float t = (info.time - update_time_) * tps * 0.8f; // assume some jitter, interpolate for longer; t = glm::clamp(t, 0.0f, 2.0f); + float t_sane = glm::clamp(t, 0.0f, 1.0f); root_.local = Transform::Lerp(states_[0].trans, states_[1].trans, t); + + // loco animstate_.loco_blend = glm::mix(states_[0].loco_blend, states_[1].loco_blend, t); float loco_phase0 = states_[0].loco_phase; @@ -75,6 +78,13 @@ void game::view::CharacterView::Update(const UpdateInfo& info) loco_phase0 -= 1.0f; animstate_.loco_phase = glm::mod(glm::mix(loco_phase0, loco_phase1, t), 1.0f); + // action + animstate_.action_phase = glm::mix(states_[0].action_phase, states_[1].action_phase, t_sane); + + // aim + animstate_.yaw = glm::mix(states_[0].aim_yaw, states_[1].aim_yaw, t_sane); + animstate_.pitch = glm::mix(states_[0].aim_pitch, states_[1].aim_pitch, t_sane); + animstate_.ApplyToSkeleton(sk_); root_.UpdateMatrix(); @@ -157,13 +167,17 @@ void game::view::CharacterView::OnAttach() bool game::view::CharacterView::ReadState(net::InMessage* msg) { update_time_ = world_.GetTime(); + + auto& old_state = states_[0]; + auto& new_state = states_[1]; // init lerp start state - states_[0].trans = root_.local; - states_[0].loco_blend = animstate_.loco_blend; - states_[0].loco_phase = animstate_.loco_phase; - - auto& new_state = states_[1]; + old_state.trans = root_.local; + old_state.loco_blend = animstate_.loco_blend; + old_state.loco_phase = animstate_.loco_phase; + old_state.action_phase = animstate_.action_phase; + old_state.aim_yaw = animstate_.yaw; + old_state.aim_pitch = animstate_.pitch; if (msg) { @@ -209,6 +223,40 @@ bool game::view::CharacterView::ReadState(net::InMessage* msg) new_state.loco_blend = sync_.loco_blend.Decode(); new_state.loco_phase = sync_.loco_phase.Decode(); } + + // action anim + if (fields & CSF_ACTION_ANIM) + { + if (!msg->Read(sync_.action_anim)) + return false; + + animstate_.action_anim_idx = sync_.action_anim; + } + + // action phase + if (fields & CSF_ACTION_PHASE) + { + if (!net::ReadDelta(*msg, sync_.action_phase)) + return false; + + new_state.action_phase = sync_.action_phase.Decode(); + + if (fields & CSF_ACTION_ANIM) + { + // anim just changed, dont blend phase + old_state.action_phase = new_state.action_phase; + } + } + + // aim + if (fields & CSF_AIM) + { + if (!net::ReadDelta(*msg, sync_.aim_yaw) || !net::ReadDelta(*msg, sync_.aim_pitch)) + return false; + + new_state.aim_yaw = sync_.aim_yaw.Decode(); + new_state.aim_pitch = sync_.aim_pitch.Decode(); + } } return true; diff --git a/src/gameview/characterview.hpp b/src/gameview/characterview.hpp index e9e1371..16bd655 100644 --- a/src/gameview/characterview.hpp +++ b/src/gameview/characterview.hpp @@ -13,8 +13,14 @@ namespace game::view struct CharacterViewState { Transform trans; + float loco_blend = 0.0f; float loco_phase = 0.0f; + + float action_phase = 0.0f; + + float aim_yaw = 0.0f; + float aim_pitch = 0.0f; }; struct CharacterViewClothes diff --git a/src/net/defs.hpp b/src/net/defs.hpp index 93767d3..caee998 100644 --- a/src/net/defs.hpp +++ b/src/net/defs.hpp @@ -139,6 +139,8 @@ using SoundPitchQ = Quantized; using AnimBlendQ = Quantized; using AnimTimeQ = Quantized; +using AnimAimAngleQ = Quantized; + using NumClothes = uint8_t; using ClothesName = FixedStr<32>; diff --git a/src/net/utils.hpp b/src/net/utils.hpp index d884922..ea8efca 100644 --- a/src/net/utils.hpp +++ b/src/net/utils.hpp @@ -147,14 +147,14 @@ inline bool ReadRGB(InMessage& msg, uint32_t& color) // DELTA template requires (sizeof(T) == 1) -inline void WriteDelta(OutMessage& msg, T previous, T current) +inline void WriteDelta(OutMessage& msg, T current, T previous) { // 1 byte => just write current msg.Write(current); } template requires (sizeof(T) > 1) -inline void WriteDelta(OutMessage& msg, T previous, T current) +inline void WriteDelta(OutMessage& msg, T current, T previous) { static_assert(sizeof(T) <= 4); @@ -169,18 +169,18 @@ inline void WriteDelta(OutMessage& msg, T previous, T current) template inline void WriteDelta(OutMessage& msg, T current, T previous) { - WriteDelta(msg, previous.value, current.value); + WriteDelta(msg, current.value, previous.value); } template requires (sizeof(T) == 1) -inline bool ReadDelta(InMessage& msg, T previous, T& current) +inline bool ReadDelta(InMessage& msg, T& current, T previous) { // 1 byte => just read current return msg.Read(current); } template requires (sizeof(T) > 1) -inline bool ReadDelta(InMessage& msg, T previous, T& current) +inline bool ReadDelta(InMessage& msg, T& current, T previous) { static_assert(sizeof(T) <= 4);