Animal AI
This commit is contained in:
parent
9a9c182347
commit
030539f8f0
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "player_character.hpp"
|
#include "player_character.hpp"
|
||||||
#include "input_mapping.hpp"
|
#include "input_mapping.hpp"
|
||||||
|
#include "utils/random.hpp"
|
||||||
|
|
||||||
game::Animal::Animal(World& world, const CharacterTuning& tuning, const glm::vec3& position, float yaw)
|
game::Animal::Animal(World& world, const CharacterTuning& tuning, const glm::vec3& position, float yaw)
|
||||||
: Character(world, tuning), Usable(root_.matrix), Rideable(*this, RIDEABLE_ANIMAL)
|
: 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);
|
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)
|
bool game::Animal::QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res)
|
||||||
{
|
{
|
||||||
if (character.GetRideable())
|
if (character.GetRideable())
|
||||||
@ -38,12 +46,25 @@ void game::Animal::Use(PlayerCharacter& character, uint32_t target_id)
|
|||||||
|
|
||||||
void game::Animal::SetRideableInput(PlayerInputFlags in)
|
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)
|
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)
|
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_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) + ")");
|
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
|
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
|
class Animal : public Character, public Usable, public Rideable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using Super = Character;
|
||||||
|
|
||||||
Animal(World& world, const CharacterTuning& tuning, const glm::vec3& position, float yaw);
|
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 bool QueryUseTarget(PlayerCharacter& character, uint32_t target_id, UseTargetQueryResult& res) override;
|
||||||
virtual void Use(PlayerCharacter& character, uint32_t target_id) override;
|
virtual void Use(PlayerCharacter& character, uint32_t target_id) override;
|
||||||
|
|
||||||
@ -19,13 +32,36 @@ public:
|
|||||||
virtual void SetRideableViewAngles(float yaw, float pitch) override;
|
virtual void SetRideableViewAngles(float yaw, float pitch) override;
|
||||||
|
|
||||||
protected:
|
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;
|
virtual void OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger) override;
|
||||||
void SetUseMessage(const std::string& message);
|
void SetUseMessage(const std::string& message);
|
||||||
void AddAnimalSeat(const glm::vec3& offset);
|
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:
|
private:
|
||||||
std::string use_message_;
|
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);
|
std::string text = "au! " + std::string(hit_name);
|
||||||
GetWorld().SendChat(text);
|
GetWorld().SendChat(text);
|
||||||
|
|
||||||
|
OnBulletHit(bullet, hit_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Character::Attach(net::EntNum parentnum)
|
void game::Character::Attach(net::EntNum parentnum)
|
||||||
|
|||||||
@ -116,6 +116,8 @@ protected:
|
|||||||
void SetViewItem(const std::string& item_name);
|
void SetViewItem(const std::string& item_name);
|
||||||
void SendFire();
|
void SendFire();
|
||||||
|
|
||||||
|
virtual void OnBulletHit(const game::BulletInfo& bullet, const std::string_view hit_bone) {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SyncControllerTransform();
|
void SyncControllerTransform();
|
||||||
void SyncTransformFromController();
|
void SyncTransformFromController();
|
||||||
|
|||||||
@ -22,7 +22,6 @@ game::Cow::Cow(World& world, const glm::vec3& position, float yaw) : Animal(worl
|
|||||||
SetIdleAnim("idle");
|
SetIdleAnim("idle");
|
||||||
SetWalkAnim("walk");
|
SetWalkAnim("walk");
|
||||||
|
|
||||||
ScheduleRandomMoo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Cow::OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger)
|
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();
|
||||||
PlayRandomMoo();
|
}
|
||||||
ScheduleRandomMoo();
|
|
||||||
});
|
void game::Cow::MakeHurtSound()
|
||||||
|
{
|
||||||
|
PlayUseSound();
|
||||||
}
|
}
|
||||||
|
|
||||||
void game::Cow::PlayRandomMoo()
|
void game::Cow::PlayRandomMoo()
|
||||||
|
|||||||
@ -15,8 +15,10 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
virtual void OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger) override;
|
virtual void OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger) override;
|
||||||
|
|
||||||
|
virtual void MakeSound() override;
|
||||||
|
virtual void MakeHurtSound() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ScheduleRandomMoo();
|
|
||||||
void PlayRandomMoo();
|
void PlayRandomMoo();
|
||||||
void PlayUseSound();
|
void PlayUseSound();
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,7 @@ void game::Rideable::SetPassenger(size_t seat_idx, HumanCharacter* passenger)
|
|||||||
OnPassengerChanged(seat_idx, 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())
|
if (seat_idx >= seats_.size())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -39,7 +39,7 @@ game::HumanCharacter* game::Rideable::GetPassenger(size_t seat_idx)
|
|||||||
return seats_[seat_idx].passenger;
|
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())
|
if (seat_idx >= seats_.size())
|
||||||
throw std::runtime_error("Invalid seat index");
|
throw std::runtime_error("Invalid seat index");
|
||||||
|
|||||||
@ -25,8 +25,8 @@ public:
|
|||||||
Rideable(Entity& entity, RideableType type);
|
Rideable(Entity& entity, RideableType type);
|
||||||
|
|
||||||
void SetPassenger(size_t seat_idx, HumanCharacter* passenger);
|
void SetPassenger(size_t seat_idx, HumanCharacter* passenger);
|
||||||
HumanCharacter* GetPassenger(size_t seat_idx);
|
HumanCharacter* GetPassenger(size_t seat_idx) const;
|
||||||
const glm::vec3& GetSeatOffset(size_t seat_idx);
|
const glm::vec3& GetSeatOffset(size_t seat_idx) const;
|
||||||
size_t GetNumSeats() const { return seats_.size(); }
|
size_t GetNumSeats() const { return seats_.size(); }
|
||||||
void KickAll();
|
void KickAll();
|
||||||
|
|
||||||
|
|||||||
@ -21,4 +21,14 @@ inline glm::vec3 ApplyRandomDispersion(const glm::vec3& dir, float dispersion)
|
|||||||
auto up = glm::normalize(glm::cross(right, dir));
|
auto up = glm::normalize(glm::cross(right, dir));
|
||||||
|
|
||||||
return dir + (right * glm::sin(rand_rotation) + up * glm::cos(rand_rotation)) * rand_dispersion;
|
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