481 lines
13 KiB
C++
481 lines
13 KiB
C++
#include "precompile.h"
|
|
|
|
#include <float.h>
|
|
|
|
#include "hero.ai.h"
|
|
#include "hero.h"
|
|
#include "room.h"
|
|
#include "metamgr.h"
|
|
#include "player.h"
|
|
|
|
enum HeroState_e : int
|
|
{
|
|
HSE_Idle = 0,
|
|
HSE_Thinking = 1,
|
|
HSE_Attack = 2,
|
|
HSE_RandomWalk = 3,
|
|
HSE_Pursuit = 4,
|
|
HSE_FollowMaster = 5
|
|
};
|
|
|
|
class HeroAINode
|
|
{
|
|
public:
|
|
HeroState_e main_state = HSE_Idle;
|
|
long long frameno = 0;
|
|
long long exec_frame_num = 0;
|
|
long long start_shot_frameno = 0;
|
|
long long next_random_move_frameno = 0;
|
|
int shot_times = 0;
|
|
int total_shot_times = 0;
|
|
int next_total_shot_times = 0;
|
|
|
|
long long param1 = 0;
|
|
CreatureWeakPtr target;
|
|
CreatureWeakPtr nearest_human;
|
|
long long last_check_nearest_human_frameno = 0;
|
|
a8::Vec2 shot_dir;
|
|
a8::Vec2 target_pos;
|
|
};
|
|
|
|
HeroAI::HeroAI()
|
|
{
|
|
node_ = new HeroAINode();
|
|
}
|
|
|
|
HeroAI::~HeroAI()
|
|
{
|
|
A8_SAFE_DELETE(node_);
|
|
}
|
|
|
|
void HeroAI::Update(int delta_time)
|
|
{
|
|
Hero* hero = (Hero*)owner;
|
|
if (hero->poisoning) {
|
|
hero->poisoning_time += delta_time;
|
|
}
|
|
if (hero->poisoning) {
|
|
hero->UpdatePoisoning();
|
|
}
|
|
if (hero->dead) {
|
|
return;
|
|
}
|
|
UpdateAI();
|
|
}
|
|
|
|
float HeroAI::GetAttackRate()
|
|
{
|
|
if (!ai_meta) {
|
|
return 1;
|
|
} else {
|
|
return ai_meta->i->attack_rate();
|
|
}
|
|
}
|
|
|
|
void HeroAI::UpdateAI()
|
|
{
|
|
Hero* hero = (Hero*)owner;
|
|
if (!ai_meta && GetAiLevel() != 0) {
|
|
ai_meta = MetaMgr::Instance()->GetAndroidAI(GetAiLevel(), 0);
|
|
if (!ai_meta) {
|
|
abort();
|
|
}
|
|
}
|
|
++node_->exec_frame_num;
|
|
hero->shot_hold = false;
|
|
switch (node_->main_state) {
|
|
case HSE_Idle:
|
|
{
|
|
UpdateIdle();
|
|
}
|
|
break;
|
|
case HSE_Thinking:
|
|
{
|
|
UpdateThinking();
|
|
}
|
|
break;
|
|
case HSE_Attack:
|
|
{
|
|
UpdateAttack();
|
|
}
|
|
break;
|
|
case HSE_RandomWalk:
|
|
{
|
|
UpdateRandomWalk();
|
|
}
|
|
break;
|
|
case HSE_Pursuit:
|
|
{
|
|
UpdatePursuit();
|
|
}
|
|
break;
|
|
case HSE_FollowMaster:
|
|
{
|
|
UpdateFollowMaster();
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
abort();
|
|
}
|
|
break;
|
|
}
|
|
if (moving_) {
|
|
DoMoveAI();
|
|
}
|
|
}
|
|
|
|
void HeroAI::UpdateIdle()
|
|
{
|
|
Hero* hero = (Hero*)owner;
|
|
if (hero->room->GetFrameNo() > node_->frameno + node_->param1) {
|
|
ChangeToStateAI(HSE_Thinking);
|
|
}
|
|
}
|
|
|
|
void HeroAI::UpdateThinking()
|
|
{
|
|
Hero* hero = (Hero*)owner;
|
|
if (hero->room->GetGasData().gas_mode == GasInactive ||
|
|
hero->room->IsWaitingStart() ||
|
|
hero->HasBuffEffect(kBET_Jump)
|
|
) {
|
|
if (hero->room->IsWaitingStart()) {
|
|
ChangeToStateAI(HSE_Idle);
|
|
} else {
|
|
ChangeToStateAI(HSE_RandomWalk);
|
|
}
|
|
} else {
|
|
if (hero->HasBuffEffect(kBET_FollowMaster) &&
|
|
hero->master.Get() &&
|
|
hero->master.Get()->GetPos().ManhattanDistance(hero->GetPos()) > 200) {
|
|
ChangeToStateAI(HSE_FollowMaster);
|
|
return;
|
|
}
|
|
Creature* target = GetTarget();
|
|
if (target) {
|
|
node_->target.Attach(target);
|
|
ChangeToStateAI(HSE_Attack);
|
|
} else {
|
|
if (hero->room->GetFrameNo() >= node_->next_random_move_frameno) {
|
|
if ((rand() % 7) < 4) {
|
|
ChangeToStateAI(HSE_Idle);
|
|
} else {
|
|
ChangeToStateAI(HSE_RandomWalk);
|
|
}
|
|
} else {
|
|
ChangeToStateAI(HSE_Idle);
|
|
node_->param1 = node_->next_random_move_frameno - hero->room->GetFrameNo();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HeroAI::UpdateAttack()
|
|
{
|
|
Hero* myself = (Hero*)owner;
|
|
if (!node_->target.Get() || node_->target.Get()->dead) {
|
|
ChangeToStateAI(HSE_Thinking);
|
|
return;
|
|
}
|
|
if (node_->exec_frame_num > SERVER_FRAME_RATE * 8) {
|
|
ChangeToStateAI(HSE_Thinking);
|
|
return;
|
|
}
|
|
if (myself->HasBuffEffect(kBET_Vertigo)) {
|
|
return;
|
|
}
|
|
float distance = myself->GetPos().Distance(node_->target.Get()->GetPos());
|
|
if (distance > GetAttackRange()) {
|
|
if (ai_meta->i->pursuit_radius() <= 0) {
|
|
//站桩
|
|
ChangeToStateAI(HSE_Thinking);
|
|
} else {
|
|
if (distance < ai_meta->i->pursuit_radius()) {
|
|
//追击
|
|
ChangeToStateAI(HSE_Pursuit);
|
|
} else {
|
|
ChangeToStateAI(HSE_Thinking);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
//攻击逻辑
|
|
switch (ai_meta->i->attack_type()) {
|
|
case kShotClick:
|
|
{
|
|
if (ai_meta->i->attack_interval() > 0) {
|
|
if (node_->shot_times < GetAttackTimes()) {
|
|
DoShotAI();
|
|
} else {
|
|
ChangeToStateAI(HSE_Idle);
|
|
node_->next_total_shot_times = node_->total_shot_times;
|
|
node_->param1 = ai_meta->i->attack_interval() / 1000 * SERVER_FRAME_RATE;
|
|
}
|
|
} else {
|
|
myself->shot_hold = true;
|
|
DoShotAI();
|
|
}
|
|
}
|
|
break;
|
|
case kShotHold:
|
|
{
|
|
myself->shot_hold = true;
|
|
DoShotAI();
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
ChangeToStateAI(HSE_Idle);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void HeroAI::UpdateRandomWalk()
|
|
{
|
|
Hero* hero = (Hero*)owner;
|
|
if (hero->room->GetFrameNo() > node_->frameno + node_->param1) {
|
|
ChangeToStateAI(HSE_Thinking);
|
|
}
|
|
}
|
|
|
|
void HeroAI::UpdatePursuit()
|
|
{
|
|
Hero* myself = (Hero*)owner;
|
|
float distance = myself->GetPos().Distance(node_->target.Get()->GetPos());
|
|
if (!myself->HasBuffEffect(kBET_Jump) &&
|
|
distance < GetAttackRange()) {
|
|
ChangeToStateAI(HSE_Attack);
|
|
} else {
|
|
if (node_->exec_frame_num > 100 * 2) {
|
|
ChangeToStateAI(HSE_RandomWalk);
|
|
}
|
|
}
|
|
}
|
|
|
|
void HeroAI::UpdateFollowMaster()
|
|
{
|
|
Hero* myself = (Hero*)owner;
|
|
if (myself->master.Get()) {
|
|
float mdistance = myself->GetPos().ManhattanDistance(node_->target_pos);
|
|
if (mdistance < 10) {
|
|
ChangeToStateAI(HSE_Thinking);
|
|
}
|
|
}
|
|
}
|
|
|
|
void HeroAI::DoMoveAI()
|
|
{
|
|
Hero* hero = (Hero*)owner;
|
|
if (hero->HasBuffEffect(kBET_Vertigo)) {
|
|
return;
|
|
}
|
|
if (std::abs(hero->GetMoveDir().x) > FLT_EPSILON ||
|
|
std::abs(hero->GetMoveDir().y) > FLT_EPSILON) {
|
|
auto old_on_move_collision_func = hero->on_move_collision;
|
|
hero->on_move_collision =
|
|
[this] () {
|
|
if (node_->main_state == HSE_FollowMaster) {
|
|
ChangeToStateAI(HSE_FollowMaster);
|
|
} else {
|
|
ChangeToStateAI(HSE_RandomWalk);
|
|
}
|
|
return false;
|
|
};
|
|
int speed = std::max(1, (int)hero->GetSpeed()) * 1;
|
|
hero->_UpdateMove(speed);
|
|
hero->on_move_collision = old_on_move_collision_func;
|
|
}
|
|
}
|
|
|
|
void HeroAI::ChangeToStateAI(HeroState_e to_state)
|
|
{
|
|
Hero* hero = (Hero*)owner;
|
|
switch (to_state) {
|
|
case HSE_Idle:
|
|
{
|
|
node_->target.Reset();
|
|
node_->param1 = 0;
|
|
node_->start_shot_frameno = 0;
|
|
node_->shot_times = 0;
|
|
moving_ = false;
|
|
if (hero->room->GetGasData().gas_mode == GasInactive ||
|
|
hero->room->IsWaitingStart() ||
|
|
hero->HasBuffEffect(kBET_Jump)) {
|
|
node_->param1 = rand() % (3 * SERVER_FRAME_RATE);
|
|
} else {
|
|
node_->param1 = rand() % (2 * SERVER_FRAME_RATE);
|
|
}
|
|
}
|
|
break;
|
|
case HSE_Thinking:
|
|
{
|
|
node_->target.Reset();
|
|
node_->param1 = 0;
|
|
node_->start_shot_frameno = 0;
|
|
node_->shot_times = 0;
|
|
moving_ = false;
|
|
}
|
|
break;
|
|
case HSE_Attack:
|
|
{
|
|
node_->param1 = 0;
|
|
node_->start_shot_frameno = 0;
|
|
node_->shot_times = 0;
|
|
moving_ = false;
|
|
node_->shot_times = 0;
|
|
}
|
|
break;
|
|
case HSE_RandomWalk:
|
|
{
|
|
moving_ = true;
|
|
node_->target.Reset();
|
|
#if 1
|
|
node_->param1 = SERVER_FRAME_RATE * ai_meta->GetMoveTime();
|
|
#else
|
|
node_->param1 = SERVER_FRAME_RATE * 5 + rand() % (SERVER_FRAME_RATE * 3);
|
|
#endif
|
|
node_->start_shot_frameno = 0;
|
|
node_->shot_times = 0;
|
|
node_->next_random_move_frameno = hero->room->GetFrameNo() +
|
|
SERVER_FRAME_RATE * ai_meta->GetMoveIdleTime();
|
|
a8::Vec2 move_dir = a8::Vec2(1.0f, 0);
|
|
move_dir.Rotate(a8::RandAngle());
|
|
move_dir.Normalize();
|
|
hero->SetMoveDir(move_dir);
|
|
hero->SetAttackDir(hero->GetMoveDir());
|
|
if (node_->param1 <= 1) {
|
|
moving_ = false;
|
|
}
|
|
}
|
|
break;
|
|
case HSE_Pursuit:
|
|
{
|
|
moving_ = true;
|
|
if (node_->target.Get()) {
|
|
a8::Vec2 move_dir = node_->target.Get()->GetPos() - hero->GetPos();
|
|
move_dir.Normalize();
|
|
hero->SetMoveDir(move_dir);
|
|
hero->SetAttackDir(hero->GetMoveDir());
|
|
}
|
|
}
|
|
break;
|
|
case HSE_FollowMaster:
|
|
{
|
|
moving_ = true;
|
|
if (hero->master.Get()) {
|
|
a8::Vec2 target_pos = hero->master.Get()->GetPos();
|
|
a8::Vec2 target_dir = a8::Vec2::UP;
|
|
target_dir.Rotate(a8::RandAngle());
|
|
target_pos = target_pos + target_dir * 80;
|
|
a8::Vec2 move_dir = target_pos - hero->GetPos();
|
|
move_dir.Normalize();
|
|
hero->SetMoveDir(move_dir);
|
|
hero->SetAttackDir(hero->GetMoveDir());
|
|
node_->target_pos = target_pos;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
node_->main_state = to_state;
|
|
node_->frameno = hero->room->GetFrameNo();
|
|
node_->exec_frame_num = 0;
|
|
}
|
|
|
|
Creature* HeroAI::GetTarget()
|
|
{
|
|
if (GetAiLevel() <= 1) {
|
|
return nullptr;
|
|
}
|
|
Hero* myself = (Hero*)owner;
|
|
if (myself->room->GetGasData().gas_mode == GasInactive) {
|
|
return nullptr;
|
|
}
|
|
|
|
Creature* target = nullptr;
|
|
myself->TraverseProperTargets
|
|
(
|
|
[myself, &target] (Creature* hum, bool& stop)
|
|
{
|
|
if (target) {
|
|
if (myself->GetPos().ManhattanDistance(target->GetPos()) >
|
|
myself->GetPos().ManhattanDistance(hum->GetPos())) {
|
|
target = hum;
|
|
}
|
|
} else {
|
|
target = hum;
|
|
}
|
|
});
|
|
if (target) {
|
|
node_->nearest_human.Attach(target);
|
|
node_->last_check_nearest_human_frameno = myself->room->GetFrameNo();
|
|
float distance = myself->GetPos().Distance(target->GetPos());
|
|
if (distance > GetAttackRange()) {
|
|
target = nullptr;
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
|
|
float HeroAI::GetAttackRange()
|
|
{
|
|
float attack_range = 0;
|
|
Hero* myself = (Hero*)owner;
|
|
if (myself->GetCurrWeapon() && myself->GetCurrWeapon()->meta) {
|
|
attack_range = myself->GetCurrWeapon()->meta->i->range();
|
|
}
|
|
attack_range = std::min(ai_meta->i->attack_range(), (int)attack_range);
|
|
return attack_range;
|
|
}
|
|
|
|
void HeroAI::DoShotAI()
|
|
{
|
|
Hero* myself = (Hero*)owner;
|
|
if (!node_->target.Get()) {
|
|
return;
|
|
}
|
|
|
|
bool shot_ok = false;
|
|
a8::Vec2 shot_dir = myself->GetAttackDir();
|
|
if (node_->total_shot_times >= node_->next_total_shot_times) {
|
|
shot_dir = node_->target.Get()->GetPos() - myself->GetPos();
|
|
node_->next_total_shot_times += 7 + (rand() % 6);
|
|
myself->SetAttackDir(shot_dir);
|
|
}
|
|
if (std::abs(shot_dir.x) > FLT_EPSILON ||
|
|
std::abs(shot_dir.y) > FLT_EPSILON) {
|
|
shot_dir.Normalize();
|
|
if (ai_meta->i->shot_offset_angle() > 0) {
|
|
int shot_offset_angle = a8::RandEx(ai_meta->i->shot_offset_angle(),
|
|
1);
|
|
if (rand() % 10 < 3) {
|
|
shot_dir.Rotate(shot_offset_angle / 180.0f);
|
|
} else {
|
|
shot_dir.Rotate(shot_offset_angle / -180.0f);
|
|
}
|
|
}
|
|
a8::Vec2 old_attack_dir = myself->GetAttackDir();
|
|
myself->SetAttackDir(shot_dir);
|
|
myself->Shot(shot_dir, shot_ok, DEFAULT_FLY_DISTANCE);
|
|
myself->SetAttackDir(old_attack_dir);
|
|
if (shot_ok) {
|
|
if (node_->shot_times <= 0) {
|
|
node_->start_shot_frameno = myself->room->GetFrameNo();
|
|
}
|
|
++node_->shot_times;
|
|
++node_->total_shot_times;
|
|
}
|
|
}
|
|
}
|
|
|
|
int HeroAI::GetAttackTimes()
|
|
{
|
|
Hero* myself = (Hero*)owner;
|
|
if (myself->GetCurrWeapon()) {
|
|
return std::min(ai_meta->i->attack_times(), myself->GetCurrWeapon()->GetClipVolume());
|
|
} else {
|
|
return ai_meta->i->attack_times();
|
|
}
|
|
}
|