Animal AI
This commit is contained in:
parent
9a9c182347
commit
030539f8f0
@ -2,6 +2,7 @@
|
||||
|
||||
#include "player_character.hpp"
|
||||
#include "input_mapping.hpp"
|
||||
#include "utils/random.hpp"
|
||||
|
||||
game::Animal::Animal(World& world, const CharacterTuning& tuning, const glm::vec3& position, float yaw)
|
||||
: Character(world, tuning), Usable(root_.matrix), Rideable(*this, RIDEABLE_ANIMAL)
|
||||
@ -14,6 +15,13 @@ game::Animal::Animal(World& world, const CharacterTuning& tuning, const glm::vec
|
||||
collision::AddObjectFlags(&GetController()->GetBtGhost(), collision::OF_USABLE);
|
||||
}
|
||||
|
||||
void game::Animal::Update()
|
||||
{
|
||||
Think();
|
||||
just_hit_ = false;
|
||||
Super::Update();
|
||||
}
|
||||
|
||||
bool game::Animal::QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res)
|
||||
{
|
||||
if (character.GetRideable())
|
||||
@ -38,12 +46,25 @@ void game::Animal::Use(PlayerCharacter& character, uint32_t target_id)
|
||||
|
||||
void game::Animal::SetRideableInput(PlayerInputFlags in)
|
||||
{
|
||||
SetInputs(MapPlayerInputToCharacterInput(in));
|
||||
if (think_state_ == ANIMAL_THINKSTATE_MOUNTED)
|
||||
{
|
||||
SetInputs(MapPlayerInputToCharacterInput(in));
|
||||
}
|
||||
}
|
||||
|
||||
void game::Animal::SetRideableViewAngles(float yaw, float pitch)
|
||||
{
|
||||
SetViewAngles(yaw, pitch);
|
||||
if (think_state_ == ANIMAL_THINKSTATE_MOUNTED)
|
||||
{
|
||||
SetViewAngles(yaw, pitch);
|
||||
}
|
||||
}
|
||||
|
||||
void game::Animal::OnBulletHit(const game::BulletInfo& bullet, const std::string_view hit_bone)
|
||||
{
|
||||
just_hit_ = true;
|
||||
// attacker_ = bullet.shooter->GetEntNum();
|
||||
hit_from_ = bullet.start;
|
||||
}
|
||||
|
||||
void game::Animal::OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger)
|
||||
@ -65,3 +86,170 @@ void game::Animal::AddAnimalSeat(const glm::vec3& offset)
|
||||
use_targets_.emplace_back(this, static_cast<uint32_t>(seat_idx), offset + glm::vec3(0.0f, 0.0f, 1.0f),
|
||||
use_message_ + " (místo " + std::to_string(seat_idx + 1) + ")");
|
||||
}
|
||||
|
||||
bool game::Animal::IsMounted() const
|
||||
{
|
||||
return GetPassenger(0) != nullptr;
|
||||
}
|
||||
|
||||
void game::Animal::ChangeDirection()
|
||||
{
|
||||
auto yaw = GetViewYaw();
|
||||
yaw += RandomFloat(-1.0f, 1.0f) * glm::half_pi<float>() * 0.5f;
|
||||
yaw = glm::mod(yaw, glm::two_pi<float>());
|
||||
SetViewAngles(yaw, 0.0f);
|
||||
}
|
||||
|
||||
void game::Animal::TryMakeSound()
|
||||
{
|
||||
auto time = GetWorld().GetTime();
|
||||
if (time - last_sound_time_ < 3000)
|
||||
return;
|
||||
|
||||
last_sound_time_ = time;
|
||||
MakeSound();
|
||||
}
|
||||
|
||||
void game::Animal::Think()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
auto new_state = CheckThinkStateTransition();
|
||||
if (new_state == think_state_)
|
||||
break;
|
||||
|
||||
EnterThinkState(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
static game::CharacterInputFlags GetRandomRoamInput()
|
||||
{
|
||||
game::CharacterInputFlags in = 0;
|
||||
auto dir_choice = RandomFloat(0.0f, 1.0f);
|
||||
|
||||
if (dir_choice < 0.4f)
|
||||
in |= 1 << game::CIN_FORWARD;
|
||||
else if (dir_choice < 0.6f)
|
||||
in |= (1 << game::CIN_FORWARD) | (1 << game::CIN_RIGHT);
|
||||
else if (dir_choice < 0.8f)
|
||||
in |= (1 << game::CIN_FORWARD) | (1 << game::CIN_RIGHT);
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
static float GetAwayYaw(const glm::vec3& my_pos, const glm::vec3& enemy)
|
||||
{
|
||||
auto away_dir = glm::normalize(glm::vec2(my_pos - enemy));
|
||||
auto yaw = glm::atan(-away_dir.x, away_dir.y);
|
||||
return yaw;
|
||||
}
|
||||
|
||||
void game::Animal::EnterThinkState(AnimalThinkState state)
|
||||
{
|
||||
think_state_ = state;
|
||||
think_state_start_ = GetWorld().GetTime();
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case ANIMAL_THINKSTATE_IDLE:
|
||||
SetInputs(0);
|
||||
break;
|
||||
|
||||
case ANIMAL_THINKSTATE_ROAM:
|
||||
SetViewAngles(RandomFloat(0.0f, glm::two_pi<float>()), 0.0f);
|
||||
SetInputs(GetRandomRoamInput());
|
||||
SetWeightSpeedMult(0.3f);
|
||||
break;
|
||||
|
||||
case ANIMAL_THINKSTATE_MOUNTED:
|
||||
SetInputs(0);
|
||||
SetWeightSpeedMult(1.0f);
|
||||
break;
|
||||
|
||||
case ANIMAL_THINKSTATE_HURT:
|
||||
// SetInput(CIN_JUMP, true);
|
||||
SetWeightSpeedMult(1.0f);
|
||||
MakeHurtSound();
|
||||
break;
|
||||
|
||||
case ANIMAL_THINKSTATE_RUN_AWAY:
|
||||
SetWeightSpeedMult(1.0f);
|
||||
SetInputs((1 << CIN_FORWARD) | (1 << CIN_SPRINT));
|
||||
SetViewAngles(GetAwayYaw(root_.GetGlobalPosition(), hit_from_), 0.0f);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
game::AnimalThinkState game::Animal::CheckThinkStateTransition()
|
||||
{
|
||||
switch (think_state_)
|
||||
{
|
||||
case ANIMAL_THINKSTATE_IDLE:
|
||||
if (IsMounted())
|
||||
return ANIMAL_THINKSTATE_MOUNTED;
|
||||
|
||||
if (just_hit_)
|
||||
return ANIMAL_THINKSTATE_HURT;
|
||||
|
||||
if (ChanceAvgTime(5.0f) || GetCurrentThinkStateDuration() > 10000)
|
||||
return ANIMAL_THINKSTATE_ROAM;
|
||||
|
||||
if (ChanceAvgTime(15.0f))
|
||||
TryMakeSound();
|
||||
|
||||
return ANIMAL_THINKSTATE_IDLE;
|
||||
|
||||
case ANIMAL_THINKSTATE_ROAM:
|
||||
if (just_hit_)
|
||||
return ANIMAL_THINKSTATE_HURT;
|
||||
|
||||
if (ChanceAvgTime(5.0f) || GetCurrentThinkStateDuration() > 15000)
|
||||
return ANIMAL_THINKSTATE_IDLE;
|
||||
|
||||
if (ChanceAvgTime(1.0f))
|
||||
ChangeDirection();
|
||||
|
||||
return ANIMAL_THINKSTATE_ROAM;
|
||||
|
||||
case ANIMAL_THINKSTATE_MOUNTED:
|
||||
if (!IsMounted())
|
||||
return ANIMAL_THINKSTATE_IDLE;
|
||||
|
||||
if (just_hit_ && Chance(0.07f))
|
||||
return ANIMAL_THINKSTATE_HURT;
|
||||
|
||||
return ANIMAL_THINKSTATE_MOUNTED;
|
||||
|
||||
case ANIMAL_THINKSTATE_HURT:
|
||||
if (GetCurrentThinkStateDuration() > 0)
|
||||
return ANIMAL_THINKSTATE_RUN_AWAY;
|
||||
|
||||
return ANIMAL_THINKSTATE_HURT;
|
||||
|
||||
case ANIMAL_THINKSTATE_RUN_AWAY:
|
||||
if (!IsMounted() && GetCurrentThinkStateDuration() > 4000 &&
|
||||
(ChanceAvgTime(7.0f) || GetCurrentThinkStateDuration() > 9000))
|
||||
return ANIMAL_THINKSTATE_ROAM;
|
||||
|
||||
if (IsMounted() && (ChanceAvgTime(1.0f) || GetCurrentThinkStateDuration() > 2000))
|
||||
return ANIMAL_THINKSTATE_IDLE;
|
||||
|
||||
if (ChanceAvgTime(1.0f))
|
||||
ChangeDirection();
|
||||
|
||||
return ANIMAL_THINKSTATE_RUN_AWAY;
|
||||
|
||||
|
||||
default:
|
||||
return ANIMAL_THINKSTATE_IDLE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int64_t game::Animal::GetCurrentThinkStateDuration() const
|
||||
{
|
||||
return GetWorld().GetTime() - think_state_start_;
|
||||
}
|
||||
|
||||
@ -7,11 +7,24 @@
|
||||
namespace game
|
||||
{
|
||||
|
||||
enum AnimalThinkState
|
||||
{
|
||||
ANIMAL_THINKSTATE_IDLE,
|
||||
ANIMAL_THINKSTATE_ROAM,
|
||||
ANIMAL_THINKSTATE_MOUNTED,
|
||||
ANIMAL_THINKSTATE_HURT,
|
||||
ANIMAL_THINKSTATE_RUN_AWAY,
|
||||
};
|
||||
|
||||
class Animal : public Character, public Usable, public Rideable
|
||||
{
|
||||
public:
|
||||
using Super = Character;
|
||||
|
||||
Animal(World& world, const CharacterTuning& tuning, const glm::vec3& position, float yaw);
|
||||
|
||||
virtual void Update() override;
|
||||
|
||||
virtual bool QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res) override;
|
||||
virtual void Use(PlayerCharacter& character, uint32_t target_id) override;
|
||||
|
||||
@ -19,13 +32,36 @@ public:
|
||||
virtual void SetRideableViewAngles(float yaw, float pitch) override;
|
||||
|
||||
protected:
|
||||
virtual void OnBulletHit(const game::BulletInfo& bullet, const std::string_view hit_bone) override;
|
||||
|
||||
virtual void OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger) override;
|
||||
void SetUseMessage(const std::string& message);
|
||||
void AddAnimalSeat(const glm::vec3& offset);
|
||||
|
||||
virtual void MakeSound() {}
|
||||
virtual void MakeHurtSound() {}
|
||||
|
||||
private:
|
||||
bool IsMounted() const;
|
||||
void ChangeDirection();
|
||||
void TryMakeSound();
|
||||
|
||||
void Think();
|
||||
void EnterThinkState(AnimalThinkState state);
|
||||
AnimalThinkState CheckThinkStateTransition();
|
||||
int64_t GetCurrentThinkStateDuration() const;
|
||||
|
||||
private:
|
||||
std::string use_message_;
|
||||
|
||||
AnimalThinkState think_state_ = ANIMAL_THINKSTATE_IDLE;
|
||||
int64_t think_state_start_ = 0;
|
||||
int64_t last_sound_time_ = 0;
|
||||
|
||||
bool just_hit_ = false;
|
||||
// net::EntNum attacker_ = 0;
|
||||
glm::vec3 hit_from_{};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@ -89,6 +89,8 @@ void game::Character::OnBulletHit(const game::BulletInfo& bullet, const btCollis
|
||||
|
||||
std::string text = "au! " + std::string(hit_name);
|
||||
GetWorld().SendChat(text);
|
||||
|
||||
OnBulletHit(bullet, hit_name);
|
||||
}
|
||||
|
||||
void game::Character::Attach(net::EntNum parentnum)
|
||||
|
||||
@ -116,6 +116,8 @@ protected:
|
||||
void SetViewItem(const std::string& item_name);
|
||||
void SendFire();
|
||||
|
||||
virtual void OnBulletHit(const game::BulletInfo& bullet, const std::string_view hit_bone) {}
|
||||
|
||||
private:
|
||||
void SyncControllerTransform();
|
||||
void SyncTransformFromController();
|
||||
|
||||
@ -22,7 +22,6 @@ game::Cow::Cow(World& world, const glm::vec3& position, float yaw) : Animal(worl
|
||||
SetIdleAnim("idle");
|
||||
SetWalkAnim("walk");
|
||||
|
||||
ScheduleRandomMoo();
|
||||
}
|
||||
|
||||
void game::Cow::OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger)
|
||||
@ -35,12 +34,14 @@ void game::Cow::OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger)
|
||||
}
|
||||
}
|
||||
|
||||
void game::Cow::ScheduleRandomMoo()
|
||||
void game::Cow::MakeSound()
|
||||
{
|
||||
Schedule(rand() % 15000 + 5000, [this]() {
|
||||
PlayRandomMoo();
|
||||
ScheduleRandomMoo();
|
||||
});
|
||||
PlayRandomMoo();
|
||||
}
|
||||
|
||||
void game::Cow::MakeHurtSound()
|
||||
{
|
||||
PlayUseSound();
|
||||
}
|
||||
|
||||
void game::Cow::PlayRandomMoo()
|
||||
|
||||
@ -15,8 +15,10 @@ public:
|
||||
protected:
|
||||
virtual void OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger) override;
|
||||
|
||||
virtual void MakeSound() override;
|
||||
virtual void MakeHurtSound() override;
|
||||
|
||||
private:
|
||||
void ScheduleRandomMoo();
|
||||
void PlayRandomMoo();
|
||||
void PlayUseSound();
|
||||
|
||||
|
||||
@ -31,7 +31,7 @@ void game::Rideable::SetPassenger(size_t seat_idx, HumanCharacter* passenger)
|
||||
OnPassengerChanged(seat_idx, passenger);
|
||||
}
|
||||
|
||||
game::HumanCharacter* game::Rideable::GetPassenger(size_t seat_idx)
|
||||
game::HumanCharacter* game::Rideable::GetPassenger(size_t seat_idx) const
|
||||
{
|
||||
if (seat_idx >= seats_.size())
|
||||
return nullptr;
|
||||
@ -39,7 +39,7 @@ game::HumanCharacter* game::Rideable::GetPassenger(size_t seat_idx)
|
||||
return seats_[seat_idx].passenger;
|
||||
}
|
||||
|
||||
const glm::vec3& game::Rideable::GetSeatOffset(size_t seat_idx)
|
||||
const glm::vec3& game::Rideable::GetSeatOffset(size_t seat_idx) const
|
||||
{
|
||||
if (seat_idx >= seats_.size())
|
||||
throw std::runtime_error("Invalid seat index");
|
||||
|
||||
@ -25,8 +25,8 @@ public:
|
||||
Rideable(Entity& entity, RideableType type);
|
||||
|
||||
void SetPassenger(size_t seat_idx, HumanCharacter* passenger);
|
||||
HumanCharacter* GetPassenger(size_t seat_idx);
|
||||
const glm::vec3& GetSeatOffset(size_t seat_idx);
|
||||
HumanCharacter* GetPassenger(size_t seat_idx) const;
|
||||
const glm::vec3& GetSeatOffset(size_t seat_idx) const;
|
||||
size_t GetNumSeats() const { return seats_.size(); }
|
||||
void KickAll();
|
||||
|
||||
|
||||
@ -21,4 +21,14 @@ inline glm::vec3 ApplyRandomDispersion(const glm::vec3& dir, float dispersion)
|
||||
auto up = glm::normalize(glm::cross(right, dir));
|
||||
|
||||
return dir + (right * glm::sin(rand_rotation) + up * glm::cos(rand_rotation)) * rand_dispersion;
|
||||
}
|
||||
|
||||
inline bool Chance(float probability)
|
||||
{
|
||||
return RandomFloat(0.0f, 1.0f) < probability;
|
||||
}
|
||||
|
||||
inline bool ChanceAvgTime(float avg_time)
|
||||
{
|
||||
return Chance((1.0f / 25.0f) / avg_time);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user