diff --git a/server/gameserver/android.h b/server/gameserver/android.h index 1a5a3ea..3cd1df6 100644 --- a/server/gameserver/android.h +++ b/server/gameserver/android.h @@ -11,7 +11,6 @@ class AIComponent; class Android : public Human { public: - AIComponent* ai = nullptr; MetaData::Robot* robot_meta = nullptr; virtual ~Android() override; diff --git a/server/gameserver/hero.ai.cc b/server/gameserver/hero.ai.cc new file mode 100644 index 0000000..0396ea9 --- /dev/null +++ b/server/gameserver/hero.ai.cc @@ -0,0 +1,437 @@ +#include "precompile.h" + +#include + +#include "hero.ai.h" +#include "hero.h" +#include "room.h" +#include "metamgr.h" +#include "player.h" + +const int SHUA_RANGE = 580; + +enum ShotType_e +{ + kShotNone = 0, + kShotClick = 1, + kShotHold = 2, +}; + +/* +nn目标:ai可切换,降级/升级(根据系统当前负载) + +ai级别 +1: 木桩(不射击) +2: 站桩间隔开枪 +3: 站桩描边射击 +4: 站桩扫射(连续射击) +5: 跑动间隔开枪 +6: 跑动描边射击 +7: 跑动扫射 +8: 跑动射击 +*/ + +HeroAI::~HeroAI() +{ +} + +void HeroAI::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(); +} + +float HeroAI::GetAttackRate() +{ + if (!ai_meta) { + return 1; + } else { + return ai_meta->i->attack_rate(); + } +} + +void HeroAI::UpdateAI() +{ + Human* hum = (Human*)owner; + if (a8::HasBitFlag(hum->status, HS_Disable)) { + return; + } + if (!ai_meta && GetAiLevel() != 0) { + ai_meta = MetaMgr::Instance()->GetAI(GetAiLevel(), 0); + if (!ai_meta) { + abort(); + } + } + ++node_.exec_frame_num; + hum->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; + default: + { + abort(); + } + break; + } + if (moving_) { + DoMoveAI(); + } +} + +void HeroAI::UpdateIdle() +{ + Human* hum = (Human*)owner; + if (hum->room->GetFrameNo() > node_.frameno + node_.param1) { + ChangeToStateAI(HSE_Thinking); + } +} + +void HeroAI::UpdateThinking() +{ + Human* hum = (Human*)owner; + if (hum->room->GetGasData().gas_mode == GasInactive || + hum->room->IsWaitingStart() || + hum->HasBuffEffect(kBET_Jump) || + a8::HasBitFlag(hum->status, HS_DisableAttack)) { + if (hum->room->IsWaitingStart()) { + ChangeToStateAI(HSE_Idle); + } else { + ChangeToStateAI(HSE_RandomWalk); + } + } else { + Human* target = GetTarget(); + if (target) { + node_.target = target; + ChangeToStateAI(HSE_Attack); + } else { + if (hum->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 - hum->room->GetFrameNo(); + } + } + } +} + +void HeroAI::UpdateAttack() +{ + Human* myself = (Human*)owner; + if (!node_.target || node_.target->dead) { + ChangeToStateAI(HSE_Thinking); + return; + } + if (node_.exec_frame_num > SERVER_FRAME_RATE * 8) { + ChangeToStateAI(HSE_Thinking); + return; + } + float distance = myself->GetPos().Distance(node_.target->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() +{ + Human* hum = (Human*)owner; + if (hum->room->GetFrameNo() > node_.frameno + node_.param1) { + ChangeToStateAI(HSE_Thinking); + } +} + +void HeroAI::UpdatePursuit() +{ + Human* myself = (Human*)owner; + float distance = myself->GetPos().Distance(node_.target->GetPos()); + if (!myself->HasBuffEffect(kBET_Jump) && + !a8::HasBitFlag(myself->status, HS_DisableAttack) && + distance < GetAttackRange()) { + ChangeToStateAI(HSE_Attack); + } else { + if (node_.exec_frame_num > 100 * 2) { + ChangeToStateAI(HSE_RandomWalk); + } + } +} + +void HeroAI::DoMoveAI() +{ + Human* hum = (Human*)owner; + if (std::abs(hum->move_dir.x) > FLT_EPSILON || + std::abs(hum->move_dir.y) > FLT_EPSILON) { + hum->on_move_collision = + [this] () { + ChangeToStateAI(HSE_RandomWalk); + return false; + }; + int speed = std::max(1, (int)hum->GetSpeed()) * 1; + hum->_UpdateMove(speed); + hum->on_move_collision = nullptr; + if (node_.nearest_human) { + if (node_.main_state != HSE_Pursuit && + hum->GetPos().ManhattanDistance(node_.nearest_human->GetPos()) < 200) { + ChangeToStateAI(HSE_Thinking); + } else if (hum->GetPos().ManhattanDistance(node_.nearest_human->GetPos()) > 800) { + GetTarget(); + } + } + } +} + +void HeroAI::ChangeToStateAI(HeroState_e to_state) +{ + Human* hum = (Human*)owner; + switch (to_state) { + case HSE_Idle: + { + node_.target = nullptr; + 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 HSE_Thinking: + { + node_.target = nullptr; + 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 = nullptr; + #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(); + hum->move_dir = a8::Vec2(1.0f, 0); + hum->move_dir.Rotate(a8::RandAngle()); + hum->move_dir.Normalize(); + hum->attack_dir = hum->move_dir; + if (node_.param1 <= 1) { + moving_ = false; + } + } + break; + case HSE_Pursuit: + { + moving_ = true; + if (node_.target) { + hum->move_dir = node_.target->GetPos() - hum->GetPos(); + hum->move_dir.Normalize(); + hum->attack_dir = hum->move_dir; + } + } + break; + } + node_.main_state = to_state; + node_.frameno = hum->room->GetFrameNo(); + node_.exec_frame_num = 0; +} + +Human* HeroAI::GetTarget() +{ + if (GetAiLevel() <= 1) { + return nullptr; + } + Human* myself = (Human*)owner; + if (myself->room->GetGasData().gas_mode == GasInactive) { + return nullptr; + } + + Human* target = nullptr; + myself->TouchAllLayerHumanList + ( + [myself, &target] (Human* hum, bool& stop) + { + if (hum->dead) { + return; + } + if (a8::HasBitFlag(hum->status, HS_Disable)) { + return; + } + if (myself->team_id == hum->team_id) { + return; + } + if (target) { + if (myself->GetPos().ManhattanDistance(target->GetPos()) > + myself->GetPos().ManhattanDistance(hum->GetPos())) { + target = hum; + } + } else { + target = hum; + } + }); + if (target) { + node_.nearest_human = 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; + Human* myself = (Human*)owner; + if (myself->curr_weapon && myself->curr_weapon->meta) { + attack_range = myself->curr_weapon->meta->i->range(); + } + attack_range = std::min(ai_meta->i->attack_range(), (int)attack_range); + return attack_range; +} + +void HeroAI::DoShotAI() +{ + Human* myself = (Human*)owner; + if (!node_.target) { + 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->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 (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->attack_dir; + myself->attack_dir = shot_dir; + myself->Shot(shot_dir, shot_ok); + 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; + } + } +} + +int HeroAI::GetAttackTimes() +{ + Human* myself = (Human*)owner; + if (myself->curr_weapon) { + return std::min(ai_meta->i->attack_times(), myself->curr_weapon->GetClipVolume()); + } else { + return ai_meta->i->attack_times(); + } +} diff --git a/server/gameserver/hero.ai.h b/server/gameserver/hero.ai.h new file mode 100644 index 0000000..4442c50 --- /dev/null +++ b/server/gameserver/hero.ai.h @@ -0,0 +1,66 @@ +#pragma once + +#include "aicomponent.h" + +namespace MetaData +{ + class AI; +} + +enum HeroState_e +{ + HSE_Idle = 0, + HSE_Thinking = 1, + HSE_Attack = 2, + HSE_RandomWalk = 3, + HSE_Pursuit = 4 +}; + +class Human; +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; + Human* target = nullptr; + Human* nearest_human = nullptr; + long long last_check_nearest_human_frameno = 0; + a8::Vec2 shot_dir; +}; + +class HeroAI : public AIComponent +{ +public: + + virtual ~HeroAI() override; + virtual void Update(int delta_time) override; + float GetAttackRate(); + +private: + void UpdateAI(); + void UpdateIdle(); + void UpdateThinking(); + void UpdateAttack(); + void UpdateRandomWalk(); + void UpdatePursuit(); + void DoMoveAI(); + void ChangeToStateAI(HeroState_e to_state); + void DoShotAI(); + + Human* GetTarget(); + float GetAttackRange(); + int GetAttackTimes(); + +private: + MetaData::AI* ai_meta = nullptr; + HeroAINode node_; + bool moving_ = false; +}; diff --git a/server/gameserver/moveableentity.h b/server/gameserver/moveableentity.h index 3377365..f5c73db 100644 --- a/server/gameserver/moveableentity.h +++ b/server/gameserver/moveableentity.h @@ -3,10 +3,12 @@ #include "gridservice.h" #include "roomentity.h" +class AIComponent; class MoveableEntity : public RoomEntity { public: a8::Vec2 move_dir; + AIComponent* ai = nullptr; virtual void Update(int delta_time) {}; int UpdatedTimes() { return updated_times_;}