#include "precompile.h" #include #include "zombiemode.ai.h" #include "android.h" #include "room.h" #include "metamgr.h" #include "player.h" #include "skill.h" const int SHUA_RANGE = 580; enum ShotType_e { kShotNone = 0, kShotClick = 1, kShotHold = 2, }; class ZombieAINode { public: ZombieState_e main_state = ZSE_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; bool moving = false; MetaData::AI* ai_meta = nullptr; }; ZombieModeAI::ZombieModeAI() { node_ = new ZombieAINode(); } ZombieModeAI::~ZombieModeAI() { A8_SAFE_DELETE(node_); } void ZombieModeAI::Update(int delta_time) { Human* hum = (Human*)owner; if (hum->poisoning) { hum->poisoning_time += delta_time; } if (hum->poisoning) { hum->UpdatePoisoning(); } if (hum->dead) { return; } UpdateAI(); } void ZombieModeAI::Reset() { ChangeToState(ZSE_Idle); node_->param1 = 2; node_->ai_meta = nullptr; } float ZombieModeAI::GetAttackRate() { if (!node_->ai_meta) { return 1; } else { return node_->ai_meta->i->attack_rate(); } } void ZombieModeAI::UpdateAI() { Human* hum = (Human*)owner; if (a8::HasBitFlag(hum->status, HS_Disable)) { return; } if (!node_->ai_meta && GetAiLevel() != 0) { node_->ai_meta = MetaMgr::Instance()->GetAI(GetAiLevel(), GetAiMode()); if (!node_->ai_meta) { abort(); } } ++node_->exec_frame_num; hum->shot_hold = false; switch (node_->main_state) { case ZSE_Idle: { UpdateIdle(); } break; case ZSE_Thinking: { UpdateThinking(); } break; case ZSE_Attack: { UpdateAttack(); } break; case ZSE_RandomWalk: { UpdateRandomWalk(); } break; case ZSE_Pursuit: { UpdatePursuit(); } break; default: { abort(); } break; } if (hum->HasSpecMove()) { hum->_UpdateSpecMove(); } else { if (node_->moving) { DoMove(); } } } void ZombieModeAI::UpdateIdle() { Human* hum = (Human*)owner; if (hum->room->GetFrameNo() > node_->frameno + node_->param1) { ChangeToState(ZSE_Thinking); } } void ZombieModeAI::UpdateThinking() { Human* hum = (Human*)owner; if (hum->room->GetGasData().gas_mode == GasInactive || hum->room->IsWaitingStart()) { if (hum->room->IsWaitingStart()) { ChangeToState(ZSE_Idle); } else { ChangeToState(ZSE_RandomWalk); } } else { Creature* target = GetTarget(); if (target) { node_->target.Attach(target); ChangeToState(ZSE_Attack); } else { if (hum->room->GetFrameNo() >= node_->next_random_move_frameno) { if ((rand() % 7) < 4) { ChangeToState(ZSE_Idle); } else { ChangeToState(ZSE_RandomWalk); } } else { ChangeToState(ZSE_Idle); node_->param1 = node_->next_random_move_frameno - hum->room->GetFrameNo(); } } } } void ZombieModeAI::UpdateAttack() { Human* myself = (Human*)owner; if (myself->HasBuffEffect(kBET_Vertigo)) { return; } if (!node_->target.Get() || node_->target.Get()->dead) { ChangeToState(ZSE_Thinking); return; } if (node_->exec_frame_num > SERVER_FRAME_RATE * 8 || myself->GetRace() == node_->target.Get()->GetRace()) { ChangeToState(ZSE_Thinking); return; } float distance = myself->GetPos().Distance(node_->target.Get()->GetPos()); if (distance > GetAttackRange()) { Skill* skill = myself->SelectSkill(); if (myself->CanUseSkill(skill->meta->i->skill_id()) && skill->meta->i->skill_id() != TURN_OVER_SKILL_ID && distance < skill->meta->i->skill_distance()) { DoSkill(skill->meta->i->skill_id()); } else if (node_->ai_meta->i->pursuit_radius() <= 0) { //站桩 ChangeToState(ZSE_Thinking); } else { #if 1 ChangeToState(ZSE_Pursuit); #else if (distance < node_->ai_meta->i->pursuit_radius()) { //追击 ChangeToState(ZSE_Pursuit); } else { ChangeToState(ZSE_Thinking); } #endif } return; } #if 0 if (myself->GetRace() == kHumanRace) { return; } #endif //攻击逻辑 switch (node_->ai_meta->i->attack_type()) { case kShotClick: { if (node_->ai_meta->i->attack_interval() > 0) { if (node_->shot_times < GetAttackTimes()) { DoShot(); } else { ChangeToState(ZSE_Idle); node_->next_total_shot_times = node_->total_shot_times; node_->param1 = node_->ai_meta->i->attack_interval() / 1000 * SERVER_FRAME_RATE; } } else { myself->shot_hold = true; DoShot(); } } break; case kShotHold: { myself->shot_hold = true; DoShot(); } break; default: { ChangeToState(ZSE_Idle); } break; } } void ZombieModeAI::UpdateRandomWalk() { Human* hum = (Human*)owner; if (hum->room->GetFrameNo() > node_->frameno + node_->param1) { ChangeToState(ZSE_Thinking); } } void ZombieModeAI::UpdatePursuit() { Human* myself = (Human*)owner; float distance = myself->GetPos().Distance(node_->target.Get()->GetPos()); if (distance < GetAttackRange()) { ChangeToState(ZSE_Attack); } else { if (node_->exec_frame_num > SERVER_FRAME_RATE * 3) { ChangeToState(ZSE_RandomWalk); } } } void ZombieModeAI::DoMove() { 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] () { ChangeToState(ZSE_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 != ZSE_Pursuit && hum->GetPos().ManhattanDistance(node_->nearest_human.Get()->GetPos()) < 200) { ChangeToState(ZSE_Thinking); } else if (hum->GetPos().ManhattanDistance(node_->nearest_human.Get()->GetPos()) > 800) { GetTarget(); } } } } void ZombieModeAI::ChangeToState(ZombieState_e to_state) { Human* hum = (Human*)owner; switch (to_state) { case ZSE_Idle: { node_->target.Reset(); node_->param1 = 0; node_->start_shot_frameno = 0; node_->shot_times = 0; node_->moving = false; if (hum->room->GetGasData().gas_mode == GasInactive || hum->room->IsWaitingStart()) { node_->param1 = rand() % (3 * SERVER_FRAME_RATE); } else { node_->param1 = rand() % (2 * SERVER_FRAME_RATE); } } break; case ZSE_Thinking: { node_->target.Reset(); node_->param1 = 0; node_->start_shot_frameno = 0; node_->shot_times = 0; node_->moving = false; } break; case ZSE_Attack: { node_->param1 = 0; node_->start_shot_frameno = 0; node_->shot_times = 0; node_->moving = false; node_->shot_times = 0; } break; case ZSE_RandomWalk: { node_->moving = true; node_->target.Reset(); node_->param1 = SERVER_FRAME_RATE * node_->ai_meta->GetMoveTime(); node_->start_shot_frameno = 0; node_->shot_times = 0; node_->next_random_move_frameno = hum->room->GetFrameNo() + SERVER_FRAME_RATE * node_->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->attack_dir = hum->GetMoveDir(); if (node_->param1 <= 1) { node_->moving = false; } } break; case ZSE_Pursuit: { node_->moving = true; if (node_->target.Get()) { a8::Vec2 move_dir = node_->target.Get()->GetPos() - hum->GetPos(); move_dir.Normalize(); hum->SetMoveDir(move_dir); hum->attack_dir = hum->GetMoveDir(); } } break; } node_->main_state = to_state; node_->frameno = hum->room->GetFrameNo(); node_->exec_frame_num = 0; } Creature* ZombieModeAI::GetTarget() { if (GetAiLevel() <= 1) { return nullptr; } Human* myself = (Human*)owner; if (myself->room->GetGasData().gas_mode == GasInactive) { return nullptr; } Creature* target = nullptr; myself->TouchProperTargets ( [myself, &target] (Creature* c, bool& stop) { if (target) { if (myself->GetPos().ManhattanDistance(target->GetPos()) > myself->GetPos().ManhattanDistance(c->GetPos())) { target = c; } } else { target = c; } }); if (target) { node_->nearest_human.Attach(target); node_->last_check_nearest_human_frameno = myself->room->GetFrameNo(); float distance = myself->GetPos().Distance(target->GetPos()); #if 0 if (distance > GetAttackRange()) { target = nullptr; } #endif #if 0 if (target->IsPlayer()) { target = nullptr; } #endif } return target; } float ZombieModeAI::GetAttackRange() { float attack_range = 0; Human* myself = (Human*)owner; if (myself->curr_weapon && myself->curr_weapon->meta) { attack_range = myself->curr_weapon->meta->i->range() + 10; } else { abort(); } attack_range = std::min(node_->ai_meta->i->attack_range(), (int)attack_range); return attack_range; } void ZombieModeAI::DoShot() { Human* myself = (Human*)owner; if (!node_->target.Get()) { return; } bool shot_ok = false; a8::Vec2 shot_dir = myself->attack_dir; 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->attack_dir = shot_dir; } if (std::abs(shot_dir.x) > FLT_EPSILON || std::abs(shot_dir.y) > FLT_EPSILON) { shot_dir.Normalize(); if (node_->ai_meta->i->shot_offset_angle() > 0) { int shot_offset_angle = a8::RandEx(node_->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->attack_dir; myself->attack_dir = shot_dir; myself->Shot(shot_dir, shot_ok, DEFAULT_FLY_DISTANCE); myself->attack_dir = 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; } } } void ZombieModeAI::DoSkill(int skill_id) { Human* myself = (Human*)owner; a8::Vec2 skill_dir; float skill_distance = node_->target.Get()->GetPos().Distance(myself->GetPos()); if (fabs(skill_distance) > 0.00001f) { skill_dir = node_->target.Get()->GetPos() - myself->GetPos(); skill_dir.Normalize(); } myself->DoSkill(skill_id, node_->target.Get()->GetEntityUniId(), a8::Vec2(), skill_distance, node_->target.Get()->GetPos()); } int ZombieModeAI::GetAttackTimes() { Human* myself = (Human*)owner; int attack_times = 0; if (myself->curr_weapon) { attack_times = std::min(node_->ai_meta->i->attack_times(), myself->curr_weapon->GetClipVolume()); } else { attack_times = node_->ai_meta->i->attack_times(); } if (attack_times <= 0) { return node_->ai_meta->i->attack_times(); } else { return 0; } }