#include "precompile.h" #include #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(); } }