634 lines
17 KiB
C++
634 lines
17 KiB
C++
#include "precompile.h"
|
||
|
||
#include <float.h>
|
||
|
||
#include "android.ai.h"
|
||
#include "android.h"
|
||
#include "room.h"
|
||
#include "metamgr.h"
|
||
#include "player.h"
|
||
#include "app.h"
|
||
|
||
const int SHUA_RANGE = 580;
|
||
|
||
enum AndroidState_e : int
|
||
{
|
||
AS_thinking,
|
||
AS_moving,
|
||
AS_attack
|
||
};
|
||
|
||
enum AndroidStateEx_e : int
|
||
{
|
||
ASE_Idle = 0,
|
||
ASE_Thinking = 1,
|
||
ASE_Attack = 2,
|
||
ASE_RandomWalk = 3,
|
||
ASE_Pursuit = 4
|
||
};
|
||
|
||
class Human;
|
||
class AINode
|
||
{
|
||
public:
|
||
AndroidStateEx_e main_state = ASE_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;
|
||
};
|
||
|
||
struct OldAiData
|
||
{
|
||
AndroidState_e state = AS_thinking;
|
||
int state_elapsed_time = 0;
|
||
CreatureWeakPtr last_target;
|
||
long long last_attack_frameno = 0;
|
||
long long last_findenemy_frameno = 0;
|
||
long long series_attack_frames = 0;
|
||
};
|
||
|
||
/*
|
||
nn目标:ai可切换,降级/升级(根据系统当前负载)
|
||
|
||
ai级别
|
||
1: 木桩(不射击)
|
||
2: 站桩间隔开枪
|
||
3: 站桩描边射击
|
||
4: 站桩扫射(连续射击)
|
||
5: 跑动间隔开枪
|
||
6: 跑动描边射击
|
||
7: 跑动扫射
|
||
8: 跑动射击
|
||
*/
|
||
|
||
AndroidAI::AndroidAI()
|
||
{
|
||
old_ai_data_ = new OldAiData();
|
||
node_ = new AINode();
|
||
}
|
||
|
||
AndroidAI::~AndroidAI()
|
||
{
|
||
A8_SAFE_DELETE(old_ai_data_);
|
||
A8_SAFE_DELETE(node_);
|
||
}
|
||
|
||
void AndroidAI::Update(int delta_time)
|
||
{
|
||
old_ai_data_->state_elapsed_time += delta_time;
|
||
Human* hum = (Human*)owner;
|
||
if (hum->poisoning) {
|
||
hum->poisoning_time += delta_time;
|
||
}
|
||
if (hum->poisoning) {
|
||
hum->UpdatePoisoning();
|
||
}
|
||
if (hum->dead) {
|
||
return;
|
||
}
|
||
if (hum->room->GetGasData().gas_mode == GasInactive) {
|
||
DefaultAi();
|
||
return;
|
||
}
|
||
UpdateNewAI();
|
||
}
|
||
|
||
float AndroidAI::GetAttackRate()
|
||
{
|
||
if (!ai_meta) {
|
||
return 1;
|
||
} else {
|
||
return ai_meta->i->attack_rate();
|
||
}
|
||
}
|
||
|
||
void AndroidAI::DefaultAi()
|
||
{
|
||
switch (old_ai_data_->state) {
|
||
case AS_thinking:
|
||
{
|
||
if (old_ai_data_->state_elapsed_time > 1500 + rand() % 3000) {
|
||
int rnd = rand();
|
||
if (rnd % 100 < 30) {
|
||
ChangeToStateOldAI(AS_moving);
|
||
} else if (rnd % 100 < 50) {
|
||
ChangeToStateOldAI(AS_attack);
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case AS_moving:
|
||
{
|
||
if (old_ai_data_->state_elapsed_time < 1000 + rand() % 2000) {
|
||
DoMoveOldAI();
|
||
} else {
|
||
int rnd = rand();
|
||
if (rnd % 100 < 30) {
|
||
ChangeToStateOldAI(AS_thinking);
|
||
} else if (rnd % 100 < 50) {
|
||
ChangeToStateOldAI(AS_attack);
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case AS_attack:
|
||
{
|
||
if ((old_ai_data_->state_elapsed_time < 3000 && old_ai_data_->last_target.Get()) ||
|
||
(old_ai_data_->state_elapsed_time < 1100)) {
|
||
DoAttackOldAI();
|
||
} else {
|
||
int rnd = rand();
|
||
if (rnd % 100 < 30) {
|
||
ChangeToStateOldAI(AS_moving);
|
||
} else if (rnd % 100 < 50) {
|
||
ChangeToStateOldAI(AS_thinking);
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
void AndroidAI::ChangeToStateOldAI(AndroidState_e to_state)
|
||
{
|
||
old_ai_data_->state = to_state;
|
||
old_ai_data_->state_elapsed_time = 0;
|
||
switch (old_ai_data_->state) {
|
||
case AS_moving:
|
||
{
|
||
Human* hum = (Human*)owner;
|
||
a8::Vec2 move_dir = a8::Vec2(1.0f, 0);
|
||
move_dir.Rotate(a8::RandAngle());
|
||
move_dir.Normalize();
|
||
hum->SetMoveDir(move_dir);
|
||
hum->SetAttackDir(hum->GetMoveDir());
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void AndroidAI::DoMoveOldAI()
|
||
{
|
||
Human* hum = (Human*)owner;
|
||
if (hum->room->IsWaitingStart()) {
|
||
return;
|
||
}
|
||
#if 0
|
||
if (hum->HasBuffEffect(kBET_Jump)) {
|
||
return;
|
||
}
|
||
#endif
|
||
if (owner->UpdatedTimes() % 2 == 0) {
|
||
Human* hum = (Human*)owner;
|
||
int speed = std::max(1, (int)hum->GetSpeed());
|
||
for (int i = 0; i < speed; ++i) {
|
||
a8::Vec2 old_pos = hum->GetPos();
|
||
hum->SetPos(hum->GetPos() + hum->GetMoveDir());
|
||
if (hum->IsCollisionInMapService()) {
|
||
hum->SetPos(old_pos);
|
||
if (i == 0) {
|
||
hum->FindPathInMapService();
|
||
}
|
||
break;
|
||
}
|
||
hum->room->grid_service->MoveCreature(hum);
|
||
}
|
||
}
|
||
}
|
||
|
||
void AndroidAI::DoAttackOldAI()
|
||
{
|
||
Human* hum = (Human*)owner;
|
||
if (hum->room->IsWaitingStart()) {
|
||
return;
|
||
}
|
||
if (hum->HasBuffEffect(kBET_Jump)) {
|
||
return;
|
||
}
|
||
if (hum->room->GetGasData().gas_mode == GasInactive) {
|
||
return;
|
||
}
|
||
if (owner->UpdatedTimes() % 10 == 0) {
|
||
Human* enemy = owner->room->FindEnemy((Human*)owner);
|
||
if (enemy) {
|
||
Human* sender = (Human*)owner;
|
||
a8::Vec2 shot_dir = enemy->GetPos() - sender->GetPos();
|
||
if (std::abs(shot_dir.x) > FLT_EPSILON ||
|
||
std::abs(shot_dir.y) > FLT_EPSILON) {
|
||
shot_dir.Normalize();
|
||
shot_dir.Rotate((rand() % 10) / 180.0f);
|
||
sender->SetAttackDir(shot_dir);
|
||
bool shot_ok = false;
|
||
sender->Shot(shot_dir, shot_ok, DEFAULT_FLY_DISTANCE);
|
||
}
|
||
old_ai_data_->last_target.Attach(enemy);
|
||
}
|
||
}
|
||
}
|
||
|
||
void AndroidAI::UpdateNewAI()
|
||
{
|
||
Human* hum = (Human*)owner;
|
||
if (a8::HasBitFlag(hum->status, CS_Disable)) {
|
||
return;
|
||
}
|
||
if (!ai_meta && GetAiLevel() != 0) {
|
||
ai_meta = MetaMgr::Instance()->GetAI(GetAiLevel(), 0);
|
||
if (!ai_meta) {
|
||
abort();
|
||
}
|
||
}
|
||
if (hum->playing_skill) {
|
||
hum->UpdateSkill();
|
||
}
|
||
++node_->exec_frame_num;
|
||
hum->shot_hold = false;
|
||
switch (node_->main_state) {
|
||
case ASE_Idle:
|
||
{
|
||
UpdateIdle();
|
||
}
|
||
break;
|
||
case ASE_Thinking:
|
||
{
|
||
UpdateThinking();
|
||
}
|
||
break;
|
||
case ASE_Attack:
|
||
{
|
||
UpdateAttack();
|
||
}
|
||
break;
|
||
case ASE_RandomWalk:
|
||
{
|
||
UpdateRandomWalk();
|
||
}
|
||
break;
|
||
case ASE_Pursuit:
|
||
{
|
||
UpdatePursuit();
|
||
}
|
||
break;
|
||
default:
|
||
{
|
||
abort();
|
||
}
|
||
break;
|
||
}
|
||
if (moving_) {
|
||
DoMoveNewAI();
|
||
}
|
||
}
|
||
|
||
void AndroidAI::UpdateIdle()
|
||
{
|
||
Human* hum = (Human*)owner;
|
||
if (hum->room->GetFrameNo() > node_->frameno + node_->param1) {
|
||
ChangeToStateNewAI(ASE_Thinking);
|
||
}
|
||
}
|
||
|
||
void AndroidAI::UpdateThinking()
|
||
{
|
||
Human* hum = (Human*)owner;
|
||
if (hum->room->GetGasData().gas_mode == GasInactive ||
|
||
hum->room->IsWaitingStart() ||
|
||
hum->HasBuffEffect(kBET_Jump) ||
|
||
a8::HasBitFlag(hum->status, CS_DisableAttack)) {
|
||
if (hum->room->IsWaitingStart()) {
|
||
ChangeToStateNewAI(ASE_Idle);
|
||
} else {
|
||
ChangeToStateNewAI(ASE_RandomWalk);
|
||
}
|
||
} else {
|
||
Creature* target = GetTarget();
|
||
if (target) {
|
||
node_->target.Attach(target);
|
||
ChangeToStateNewAI(ASE_Attack);
|
||
} else {
|
||
if (hum->room->GetFrameNo() >= node_->next_random_move_frameno) {
|
||
if ((rand() % 7) < 4) {
|
||
ChangeToStateNewAI(ASE_Idle);
|
||
} else {
|
||
ChangeToStateNewAI(ASE_RandomWalk);
|
||
}
|
||
} else {
|
||
ChangeToStateNewAI(ASE_Idle);
|
||
node_->param1 = node_->next_random_move_frameno - hum->room->GetFrameNo();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void AndroidAI::UpdateAttack()
|
||
{
|
||
Human* myself = (Human*)owner;
|
||
if (!node_->target.Get() || node_->target.Get()->dead) {
|
||
ChangeToStateNewAI(ASE_Thinking);
|
||
return;
|
||
}
|
||
if (node_->exec_frame_num > SERVER_FRAME_RATE * 8) {
|
||
ChangeToStateNewAI(ASE_Thinking);
|
||
return;
|
||
}
|
||
#ifdef DEBUG
|
||
if (App::Instance()->HasFlag(20)) {
|
||
ChangeToStateNewAI(ASE_Thinking);
|
||
return;
|
||
}
|
||
#endif
|
||
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) {
|
||
//站桩
|
||
ChangeToStateNewAI(ASE_Thinking);
|
||
} else {
|
||
if (distance < ai_meta->i->pursuit_radius()) {
|
||
//追击
|
||
ChangeToStateNewAI(ASE_Pursuit);
|
||
} else {
|
||
ChangeToStateNewAI(ASE_Thinking);
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
//攻击逻辑
|
||
switch (ai_meta->i->attack_type()) {
|
||
case kShotClick:
|
||
{
|
||
if (ai_meta->i->attack_interval() > 0) {
|
||
if (node_->shot_times < GetAttackTimes()) {
|
||
DoShotNewAI();
|
||
} else {
|
||
ChangeToStateNewAI(ASE_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;
|
||
DoShotNewAI();
|
||
}
|
||
}
|
||
break;
|
||
case kShotHold:
|
||
{
|
||
myself->shot_hold = true;
|
||
DoShotNewAI();
|
||
}
|
||
break;
|
||
default:
|
||
{
|
||
ChangeToStateNewAI(ASE_Idle);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
void AndroidAI::UpdateRandomWalk()
|
||
{
|
||
Human* hum = (Human*)owner;
|
||
if (hum->room->GetFrameNo() > node_->frameno + node_->param1) {
|
||
ChangeToStateNewAI(ASE_Thinking);
|
||
}
|
||
}
|
||
|
||
void AndroidAI::UpdatePursuit()
|
||
{
|
||
Human* myself = (Human*)owner;
|
||
if (node_->target.Get()) {
|
||
float distance = myself->GetPos().Distance(node_->target.Get()->GetPos());
|
||
if (!myself->HasBuffEffect(kBET_Jump) &&
|
||
!a8::HasBitFlag(myself->status, CS_DisableAttack) &&
|
||
distance < GetAttackRange()) {
|
||
ChangeToStateNewAI(ASE_Attack);
|
||
} else {
|
||
if (node_->exec_frame_num > 100 * 2) {
|
||
ChangeToStateNewAI(ASE_RandomWalk);
|
||
}
|
||
}
|
||
} else {
|
||
if (node_->exec_frame_num > 100 * 2) {
|
||
ChangeToStateNewAI(ASE_RandomWalk);
|
||
}
|
||
}
|
||
}
|
||
|
||
void AndroidAI::DoMoveNewAI()
|
||
{
|
||
Human* hum = (Human*)owner;
|
||
if (hum->HasBuffEffect(kBET_Vertigo)) {
|
||
return;
|
||
}
|
||
if (std::abs(hum->GetMoveDir().x) > FLT_EPSILON ||
|
||
std::abs(hum->GetMoveDir().y) > FLT_EPSILON) {
|
||
hum->on_move_collision =
|
||
[this] () {
|
||
ChangeToStateNewAI(ASE_RandomWalk);
|
||
return false;
|
||
};
|
||
int speed = std::max(1, (int)hum->GetSpeed()) * 1;
|
||
hum->_UpdateMove(speed);
|
||
hum->on_move_collision = nullptr;
|
||
if (node_->nearest_human.Get()) {
|
||
if (node_->main_state != ASE_Pursuit &&
|
||
hum->GetPos().ManhattanDistance(node_->nearest_human.Get()->GetPos()) < 200) {
|
||
ChangeToStateNewAI(ASE_Thinking);
|
||
} else if (hum->GetPos().ManhattanDistance(node_->nearest_human.Get()->GetPos()) > 800) {
|
||
GetTarget();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void AndroidAI::ChangeToStateNewAI(AndroidStateEx_e to_state)
|
||
{
|
||
Human* hum = (Human*)owner;
|
||
switch (to_state) {
|
||
case ASE_Idle:
|
||
{
|
||
node_->target.Reset();
|
||
node_->param1 = 0;
|
||
node_->start_shot_frameno = 0;
|
||
node_->shot_times = 0;
|
||
moving_ = false;
|
||
if (hum->room->GetGasData().gas_mode == GasInactive ||
|
||
hum->room->IsWaitingStart() ||
|
||
hum->HasBuffEffect(kBET_Jump)) {
|
||
node_->param1 = rand() % (3 * SERVER_FRAME_RATE);
|
||
} else {
|
||
node_->param1 = rand() % (2 * SERVER_FRAME_RATE);
|
||
}
|
||
}
|
||
break;
|
||
case ASE_Thinking:
|
||
{
|
||
node_->target.Reset();
|
||
node_->param1 = 0;
|
||
node_->start_shot_frameno = 0;
|
||
node_->shot_times = 0;
|
||
moving_ = false;
|
||
}
|
||
break;
|
||
case ASE_Attack:
|
||
{
|
||
node_->param1 = 0;
|
||
node_->start_shot_frameno = 0;
|
||
node_->shot_times = 0;
|
||
moving_ = false;
|
||
node_->shot_times = 0;
|
||
}
|
||
break;
|
||
case ASE_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 = hum->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();
|
||
hum->SetMoveDir(move_dir);
|
||
hum->SetAttackDir(hum->GetMoveDir());
|
||
if (node_->param1 <= 1) {
|
||
moving_ = false;
|
||
}
|
||
}
|
||
break;
|
||
case ASE_Pursuit:
|
||
{
|
||
moving_ = true;
|
||
if (node_->target.Get()) {
|
||
a8::Vec2 move_dir = node_->target.Get()->GetPos() - hum->GetPos();
|
||
move_dir.Normalize();
|
||
hum->SetMoveDir(move_dir);
|
||
hum->SetAttackDir(hum->GetMoveDir());
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
node_->main_state = to_state;
|
||
node_->frameno = hum->room->GetFrameNo();
|
||
node_->exec_frame_num = 0;
|
||
}
|
||
|
||
Creature* AndroidAI::GetTarget()
|
||
{
|
||
if (GetAiLevel() <= 1) {
|
||
return nullptr;
|
||
}
|
||
Human* myself = (Human*)owner;
|
||
if (myself->room->GetGasData().gas_mode == GasInactive) {
|
||
return nullptr;
|
||
}
|
||
|
||
Creature* target = nullptr;
|
||
myself->TraverseProperTargets
|
||
(
|
||
[myself, &target] (Creature* hum, bool& stop)
|
||
{
|
||
if (target->HasBuffEffect(kBET_Camouflage)) {
|
||
return;
|
||
}
|
||
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 AndroidAI::GetAttackRange()
|
||
{
|
||
float attack_range = 0;
|
||
Human* myself = (Human*)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 AndroidAI::DoShotNewAI()
|
||
{
|
||
Human* myself = (Human*)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 AndroidAI::GetAttackTimes()
|
||
{
|
||
Human* myself = (Human*)owner;
|
||
if (myself->GetCurrWeapon()) {
|
||
return std::min(ai_meta->i->attack_times(), myself->GetCurrWeapon()->GetClipVolume());
|
||
} else {
|
||
return ai_meta->i->attack_times();
|
||
}
|
||
}
|