#include "precompile.h" #include #include "bullet.h" #include "room.h" #include "collider.h" #include "obstacle.h" #include "player.h" #include "android.h" #include "app.h" #include "perfmonitor.h" #include "smoke_mitask.h" #include "frag_mitask.h" #include "creature.h" #include "roomobstacle.h" #include "car.h" #include "creature.h" #include "skillhelper.h" #include "trigger.h" #include "ability.h" #include "buff.h" #include "mt/Param.h" #include "mt/Equip.h" #include "mt/Skill.h" #include "mt/SkillNumber.h" #include "mt/Buff.h" #include "mt/Hero.h" #include "mt/MapThing.h" Bullet::Bullet():MoveableEntity() { ++PerfMonitor::Instance()->entity_num[ET_Bullet]; } Bullet::~Bullet() { --PerfMonitor::Instance()->entity_num[ET_Bullet]; } void Bullet::Initialize() { MoveableEntity::Initialize(); RecalcSelfCollider(); ability_ = sender.Get()->GetAbility(); is_curr_weapon = sender.Get()->GetCurrWeapon()->meta == gun_meta; create_frameno_ = room->GetFrameNo(); if (IsFlyHook()) { int buff_uniid = sender.Get()->TryAddBuff(sender.Get(), kVertigoBuffId); if (buff_uniid) { Buff* buff = sender.Get()->GetBuffByUniId(buff_uniid); if (buff && buff->remover_timer.expired()) { buff_list_.push_back(buff_uniid); sender.Get()->room->xtimer.ModifyTime(buff->remover_timer, SERVER_FRAME_RATE * 10); } } if (sender.Get()) { sender.Get()->GetTrigger()->FlyHookCreate(this); } } } void Bullet::Update(int delta_time) { if (shot_animi_time <= (room->GetFrameNo() - create_frameno_) * FRAME_RATE_MS) { if (!trace_target_id) { MapServiceUpdate(); ++updated_times_; } } } void Bullet::RecalcSelfCollider() { if (!self_collider_) { self_collider_ = new CircleCollider(); self_collider_->owner = this; AddEntityCollider(self_collider_); } self_collider_->pos = a8::Vec2(); self_collider_->rad = gun_meta->bullet_rad(); } void Bullet::OnHit(std::set& objects) { std::shared_ptr old_context_ability = sender.Get()->context_ability; glm::vec3 old_context_dir = sender.Get()->context_dir; Position old_context_pos = sender.Get()->context_pos; sender.Get()->context_dir = dir; sender.Get()->context_pos = GetPos(); if (IsFlyHook()) { if (sender.Get() && !sender.Get()->dead) { for (auto& target : objects) { Creature* c = target->IsCreature(room) ? (Creature*)target : nullptr; ProcFlyHook(target); if (!c || (c->team_id != sender.Get()->team_id)) { if (!c || !c->IsCar()) { target->OnBulletHit(this); } } break; } } } else { for (auto& target : objects) { bool old_is_dead = target->IsDead(room); TriggerHitBuff(target); target->OnBulletHit(this); if (target->IsDead(room) && !old_is_dead) { OnKillTarget(target); } } } sender.Get()->context_dir = old_context_dir; sender.Get()->context_pos = old_context_pos; sender.Get()->context_ability = old_context_ability; } void Bullet::ProcBomb() { float old_collider_rad = self_collider_->rad; if (meta->_inventory_slot() != IS_C4) { self_collider_->rad = GetExplosionRange(); } if (IsCurrWeapon()) { } std::set objects; Car* c4_target = nullptr; TraverseCreatures ( [this, &objects, &c4_target] (Creature* c, bool& stop) { if (c->dead) { return; } if (sender.Get()->team_id != c->team_id) { //友军火箭筒伤害取消 if ((meta->_inventory_slot() == 4 || meta->_inventory_slot() == 5) && sender.Get()->team_id == c->team_id) { return; } AabbCollider aabb_box; c->GetHitAabbBox(aabb_box); if (meta->_inventory_slot() == IS_C4) { if (!objects.empty()) { stop = true; return; } if (!c->IsCar()) { return; } if (TestCollision(room, &aabb_box)) { c4_target = c->AsCar(); stop = true; objects.insert(c); return; } } else { if (TestCollision(room, &aabb_box)) { objects.insert(c); } } } }); TraverseAllLayerEntityList ( [this, &objects, c4_target] (Entity* entity, bool& stop) { if (meta->_inventory_slot() == IS_C4) { if (c4_target) { stop = true; return; } } switch (entity->GetEntityType()) { case ET_Obstacle: case ET_Building: case ET_Dummy: { if (TestCollision(room, entity)) { objects.insert(entity); } } break; default: { } break; } }); bool block = false; if (objects.empty()) { float distance = GetPos().Distance2D2(born_pos); if (distance < fly_distance) { block = true; } } int delay_time = 0; if (!block) { delay_time = gun_meta->missiles_time(); } if (IsCurrWeapon() && sender.Get() && sender.Get()->GetAbility()->GetAttrAbs(kHAT_WeaponExplosionDealyTime) > 0) { delay_time = delay_time - sender.Get()->GetAbility()->GetAttrAbs(kHAT_WeaponExplosionDealyTime); delay_time = std::max(0, delay_time); } { Position old_pos = GetPos(); room->xtimer.SetTimeoutEx (delay_time / FRAME_RATE_MS, [this, old_pos] (int event, const a8::Args* args) { if (a8::TIMER_EXEC_EVENT == event) { } }, &xtimer_attacher ); } switch (meta->_inventory_slot()) { case IS_RPG: { //榴弹炮 if (sender.Get() && sender.Get()->IsHuman()) { sender.Get()->AsHuman()->stats.IncWeaponUseTimes(gun_meta->id(), 1); } Position bomb_pos = GetPos(); room->frame_event.AddExplosionEx(sender, meta->id(), bomb_pos, gun_meta->explosion_effect()); OnHit(objects); } break; case IS_FRAG: { //手雷 if (sender.Get() && sender.Get()->IsHuman()) { sender.Get()->AsHuman()->stats.IncWeaponUseTimes(gun_meta->id(), 1); } ProcFragBomb(delay_time); } break; case IS_SMOKE: { //烟雾弹 if (sender.Get() && sender.Get()->IsHuman()) { sender.Get()->AsHuman()->stats.IncWeaponUseTimes(gun_meta->id(), 1); } #if 1 AddGunBuff(); ProcSmokeBomb(); #else a8::Vec2 bomb_pos = GetPos(); float time_addition = 0; if (IsCurrWeapon() && sender.Get()) { time_addition += sender.Get()->GetAbility()->GetAttrAbs(kHAT_WeaponExplosionContinueTime); } room->frame_event.AddSmoke(this, meta->id(), bomb_pos, time_addition); #endif } break; case IS_POSION_GAS_BOMB: { //毒气弹 if (sender.Get() && sender.Get()->IsHuman()) { sender.Get()->AsHuman()->stats.IncWeaponUseTimes(gun_meta->id(), 1); } ProcPosionGasBomb(delay_time); } break; case IS_MOLOTOR_COCKTAIL: { //燃烧瓶 if (sender.Get() && sender.Get()->IsHuman()) { sender.Get()->AsHuman()->stats.IncWeaponUseTimes(gun_meta->id(), 1); } ProcMolotorCocktailBomb(delay_time); } break; case IS_C4: { //c4 if (sender.Get() && sender.Get()->IsHuman()) { sender.Get()->AsHuman()->stats.IncWeaponUseTimes(gun_meta->id(), 1); } if (block) { delay_time = gun_meta->missiles_time(); } ProcC4Bomb(c4_target, delay_time); } break; case IS_SINGAL_GUN: { if (sender.Get() && sender.Get()->IsHuman()) { sender.Get()->AsHuman()->stats.IncWeaponUseTimes(gun_meta->id(), 1); } ProcSignalGunBomb(delay_time); } break; case IS_SHIELD_WALL: { if (sender.Get() && sender.Get()->IsHuman()) { sender.Get()->AsHuman()->stats.IncWeaponUseTimes(gun_meta->id(), 1); } ProcShieldWallBomb(delay_time); } break; case IS_OIL_BUCKET: { if (sender.Get() && sender.Get()->IsHuman()) { sender.Get()->AsHuman()->stats.IncWeaponUseTimes(gun_meta->id(), 1); } ProcOilBucketBomb(delay_time); } break; default: { } break; } self_collider_->rad = old_collider_rad; ForceRemove(); } void Bullet::ProcSmokeBomb() { const mt::Buff* buff_meta = mt::Buff::GetById(HUNLUAN_BUFFID); if (buff_meta) { std::shared_ptr task = std::make_shared(); task->room = room; task->bomb_pos = GetPos(); task->buff_meta = buff_meta; task->gun_meta = gun_meta; auto task_wp = std::weak_ptr(task); room->xtimer.SetIntervalEx (SERVER_FRAME_RATE / 2, [task_wp] (int event, const a8::Args* args) { if (a8::TIMER_EXEC_EVENT == event) { if (!task_wp.expired()) { task_wp.lock()->Check(); } } }, &task->xtimer_attacher); room->xtimer.SetTimeoutEx (SERVER_FRAME_RATE * mt::Param::GetIntParam("smoke_duration", 10), [task] (int event, const a8::Args* args) { if (a8::TIMER_DELETE_EVENT == event) { task->Done(); } }, &room->xtimer_attacher_); } } bool Bullet::IsBomb() { if (!meta) { return false; } return meta->_inventory_slot() == IS_RPG || meta->_inventory_slot() == IS_FRAG || meta->_inventory_slot() == IS_SMOKE || meta->_inventory_slot() == IS_POSION_GAS_BOMB || meta->_inventory_slot() == IS_MOLOTOR_COCKTAIL || meta->_inventory_slot() == IS_C4 || meta->_inventory_slot() == IS_SINGAL_GUN || meta->_inventory_slot() == IS_SHIELD_WALL || meta->_inventory_slot() == IS_OIL_BUCKET; } void Bullet::MapServiceUpdate() { if (later_removed_) { return; } if (sender.Get()) { float move_length = gun_meta->bullet_speed() / (float)SERVER_FRAME_RATE; do { float step_len = move_length - mt::Param::s().bullet_planck_step_length; if (step_len <= 0.001f) { if (move_length > 0.1f) { step_len = move_length; } else { break; } } move_length -= step_len; // 999 #if 1 #else SetPos(GetPos() + dir * step_len); #endif float distance = GetPos().Distance2D2(born_pos); if (room->OverBorder(GetPos(), gun_meta->bullet_rad())) { if (IsBomb()) { ProcBomb(); } else { Check(distance); if (!later_removed_) { ForceRemove(); } } } else { room->grid_service->MoveBullet(this); Check(distance); } } while(!later_removed_ && move_length >= 0.0001f); #ifdef DEBUG1 { if (sender.Get()->IsPlayer()) { if ((room->GetFrameNo() - create_frameno_) % 2 == 0) { float distance = (GetPos() - born_pos).Norm(); a8::XPrintf("bullet_id:%d frame_no:%d speed:%f range:%f fly_distance:%f born_pos:%f,%f pos:%f,%f src_fly_distance:%f\n", { gun_meta->id(), (room->GetFrameNo() - create_frameno_) / 2, gun_meta->bullet_speed(), gun_meta->range(), distance, born_pos.x, born_pos.y, GetPos().x, GetPos().y, fly_distance }); } } } #endif } else { ForceRemove(); } } float Bullet::GetAtk() { float atk = gun_meta->atk(); if (sender.Get()->IsAndroid()) { Android* android = (Android*)sender.Get(); #if 0 atk *= android->ai->GetAttackRate(); #endif } float attr_rate = 1 + ability_->GetAttrRate(kHAT_Atk); if (IsCurrWeapon()) { attr_rate += ability_->GetAttrRate(kHAT_WeaponDmg); } return atk * attr_rate; } float Bullet::GetExplosionRange() { float e_range = gun_meta->explosion_range(); if (IsCurrWeapon()) { e_range *= (1 + ability_->GetAttrRate(kHAT_WeaponExplosionRange)); } return e_range; } void Bullet::Check(float distance) { int c_hit_num = 0; int t_hit_num = 0; int o_hit_num = 0; std::set objects; { std::set colliders; room->map_service->GetColliders(room, GetPos().GetX(), GetPos().GetY(), colliders); for (ColliderComponent* collider : colliders) { if (collider->owner->IsEntityType(ET_Dummy)) { if (a8::HasBitFlag(collider->tag, kHalfWallTag)) { continue; } if (TestCollision(room, collider)) { ++o_hit_num; objects.insert(collider->owner); } } else if (collider->owner->IsEntityType(ET_Obstacle)) { Obstacle* obstacle = (Obstacle*)collider->owner; if (gun_meta->is_penetrate_thing() && hit_objects_.find(obstacle->GetUniId()) != hit_objects_.end()) { //穿物件 continue; } if (!obstacle->CanThroughable(this)) { if (TestCollision(room, collider)) { objects.insert(collider->owner); if (gun_meta->is_penetrate_thing()) { ++t_hit_num; ++o_hit_num; hit_objects_.insert(collider->owner->GetUniId()); } } } else if (obstacle->meta->thing_type() == kObstacleStrengthenWall) { if (!strengthened_ && sender.Get() && sender.Get()->team_id == obstacle->GetTeamId(room)) { bool ret = Check2dRotationRectangle (GetPos().x, GetPos().y, gun_meta->bullet_rad(), obstacle->GetPos().x, obstacle->GetPos().y, obstacle->meta->width(), obstacle->meta->height(), obstacle->GetRotate() * 180.0f ); if (ret) { strengthened_ = true; OnStrengthen(obstacle); #ifdef DEBUG a8::XPrintf("命中能量墙\n", {}); #endif } } } } } } bool eat = false; if (o_hit_num <= 0) { room->grid_service->TraverseCreatures (room->GetRoomIdx(), GetGridList(), [this, &objects, &c_hit_num, &eat] (Creature* c, bool& stop) { bool no_teammate = IsFlyHook(); if (sender.Get()->IsProperTarget(c, no_teammate)) { if (gun_meta->ispenetrate() && hit_objects_.find(c->GetUniId()) != hit_objects_.end()) { //穿人 return; } if (c->HasBuffEffect(kBET_BulletThrough)) { return; } { Buff* hold_shield_buff = c->GetBuffByEffectId(kBET_HoldShield); if (hold_shield_buff && !IsBomb() && !c->dead && c != sender.Get()) { // 999 #if 0 //param2是距离 param4是宽度 a8::Vec2 shield_pos = c->GetPos() + c->GetAttackDir() * hold_shield_buff->meta->param2; bool ret = Check2dRotationRectangle(GetPos().x, GetPos().y, //10, gun_meta->bullet_rad(), shield_pos.x, shield_pos.y, hold_shield_buff->meta->param4, MetaMgr::Instance()->bullet_planck_step_length, c->GetAttackDirRotate() * 180.0f ); if (ret) { float finaly_dmg = c->GetBattleContext()->CalcDmg(c, this); c->shield_hp_ = std::max(0.0f, c->shield_hp_ - finaly_dmg); #ifdef DEBUG a8::XPrintf("命中盾牌 finally_dmg:%f shield_hp:%f\n", { finaly_dmg, c->shield_hp_ }); #endif room->frame_event.AddPropChg(c->GetWeakPtrRef(), kPropShieldHp, c->shield_max_hp_, c->shield_hp_); if (c->shield_hp_ <= 0) { #ifdef DEBUG a8::XPrintf("shiled destory\n", {}); #endif c->GetTrigger()->ShieldDestory(); c->RemoveBuffByUniId(hold_shield_buff->buff_uniid); } eat = true; stop = true; return; } #endif } } AabbCollider aabb_box; c->GetHitAabbBox(aabb_box); if (c != sender.Get() && !c->dead && TestCollision(room, &aabb_box)) { if (meta->_inventory_slot() == IS_C4) { if (!c->IsHuman()) { objects.insert(c); if (gun_meta->ispenetrate()) { ++c_hit_num; hit_objects_.insert(c->GetUniId()); } } } else { objects.insert(c); if (gun_meta->ispenetrate()) { ++c_hit_num; hit_objects_.insert(c->GetUniId()); } } } } }); } float bullet_range = gun_meta->range(); if (!objects.empty() || (!IsBomb() && distance > bullet_range) || eat || (gun_meta->id() == 30918 && distance >= fly_distance) || (IsBomb() && meta->_inventory_slot() != IS_RPG && distance >= fly_distance) ) { if (IsBomb()) { ProcBomb(); } else { bool hited = false; if (!eat && !objects.empty()) { hited = true; OnHit(objects); } bool need_remove = true; if (distance < bullet_range) { if (!gun_meta->is_penetrate_thing() && !gun_meta->ispenetrate()) { } else { if ((!gun_meta->is_penetrate_thing() && (t_hit_num > 0)) || (!gun_meta->ispenetrate() && (c_hit_num > 0))) { } else { need_remove = false; } } } if (need_remove) { if (IsFlyHook()) { if (!hited) { sender.Get()->IncDisableMoveTimes(); sender.Get()->IncDisableAttackDirTimes(); auto sender_p = sender; sender.Get()->room->xtimer.SetTimeoutEx ( std::ceil((distance / gun_meta->bullet_speed() / 2) * SERVER_FRAME_RATE), [sender_p] (int event, const a8::Args* args) mutable { if (a8::TIMER_EXEC_EVENT == event) { sender_p.Get()->RemoveBuffById(kKeepShotAnimiBuffId); } }, &sender.Get()->xtimer_attacher ); sender.Get()->room->xtimer.SetTimeoutEx ( (0.75 + distance / gun_meta->bullet_speed() / 2) * SERVER_FRAME_RATE, [sender_p] (int event, const a8::Args* args) mutable { if (a8::TIMER_EXEC_EVENT == event) { sender_p.Get()->DecDisableMoveTimes(); sender_p.Get()->DecDisableAttackDirTimes(); sender_p.Get()->GetTrigger()->FlyHookDestory(); } }, &sender.Get()->xtimer_attacher ); sender.Get()->TryAddBuff(sender.Get(), gun_meta->_int_param2); } } ForceRemove(); } } } } void Bullet::ProcFragBomb(int delay_time) { if (sender.Get()) { std::shared_ptr task = std::shared_ptr(); task->room = room; task->sender.Attach(sender.Get()); task->bomb_pos = GetPos(); task->gun_meta = gun_meta; task->meta = meta; task->atk = GetAtk(); task->explosion_range = GetExplosionRange(); room->xtimer.SetTimeoutEx (std::max(1, (int)(delay_time / FRAME_RATE_MS)), [task] (int event, const a8::Args* args) { if (a8::TIMER_EXEC_EVENT == event) { task->Done(); } }, &task->xtimer_attacher); } } void Bullet::ProcPosionGasBomb(int delay_time) { AddGunBuff(); } void Bullet::ProcMolotorCocktailBomb(int delay_time) { AddGunBuff(); } void Bullet::ProcC4Bomb(Car* target, int delay_time) { if (sender.Get()) { std::shared_ptr task = std::shared_ptr(); task->room = room; task->sender.Attach(sender.Get()); task->bomb_pos = GetPos(); if (target) { task->follow_target.Attach(target); task->force_target.Attach(target); if (gun_meta->_int_param1 > 0) { int buff_uniid = target->TryAddBuff(sender.Get(), gun_meta->_int_param1); if (buff_uniid != 0) { Buff* buff = target->GetBuffByUniId(buff_uniid); if (buff && !buff->remover_timer.expired()) { target->room->xtimer.ModifyTime(buff->remover_timer, std::max(1, (int)(delay_time / FRAME_RATE_MS))); } } } } task->explosion_range = GetExplosionRange(); task->gun_meta = gun_meta; task->meta = meta; task->atk = GetAtk(); room->xtimer.SetTimeoutEx (std::max(1, (int)(delay_time / FRAME_RATE_MS)), [task] (int event, const a8::Args* args) { if (a8::TIMER_EXEC_EVENT == event) { task->Done(); } }, &task->xtimer_attacher); } } void Bullet::ProcSignalGunBomb(int delay_time) { AddGunBuff(); } void Bullet::ProcShieldWallBomb(int delay_time) { AddGunBuff(); } void Bullet::ProcOilBucketBomb(int delay_time) { // 999 #if 1 #else if (sender.Get()) { room->CreateLoot(gun_meta->id(), GetPos(), 1, 1); } #endif } bool Bullet::IsCurrWeapon() { return is_curr_weapon; } void Bullet::AddGunBuff() { if (sender.Get()) { std::shared_ptr old_context_ability = sender.Get()->context_ability; glm::vec3 old_context_dir = sender.Get()->context_dir; Position old_context_pos = sender.Get()->context_pos; sender.Get()->context_dir = dir; sender.Get()->context_pos = GetPos(); if (IsCurrWeapon()) { sender.Get()->context_ability = ability_; } else { sender.Get()->context_ability = nullptr; } const mt::Buff * buff_meta = mt::Buff::GetById(gun_meta->buffid()); if (buff_meta) { sender.Get()->AddBuff(sender.Get(), buff_meta ); } sender.Get()->context_dir = old_context_dir; sender.Get()->context_pos = old_context_pos; sender.Get()->context_ability = old_context_ability; } } bool Bullet::IsPreBattleBullet() { if (room->IsPveRoom()) { return false; } return create_frameno_ <= room->GetBattleStartFrameNo() || room->GetBattleStartFrameNo() == 0; } void Bullet::OnKillTarget(Entity* target) { if (target->IsCreature(room)) { Creature* c = (Creature*)target; if (c->IsHuman() && sender.Get() && sender.Get()->IsHuman()) { sender.Get()->AsHuman()->stats.IncWeaponKills(gun_meta->id(), 1); } } } void Bullet::OnStrengthen(Obstacle* ob) { if (ob->IsRoomObstacle()) { RoomObstacle* room_ob = ob->AsRoomObstacle(); if (room_ob->skill_meta) { const mt::Skill* skill_meta = room_ob->skill_meta; if (skill_meta && skill_meta->_number_meta) { switch (skill_meta->GetMagicId()) { case MAGIC_WLFB: { strengthen_wall = skill_meta->_number_meta->_float_ratio2; } break; default: { } break; } } } } } void Bullet::ClearBuffList() { for (auto& buff_uniid : buff_list_) { if (sender.Get()) { sender.Get()->RemoveBuffByUniId(buff_uniid); } } buff_list_.clear(); } void Bullet::ProcFlyHook(Entity* target) { ClearBuffList(); float distance = born_pos.Distance2D2(GetPos()); if (distance < 0.001f) { return; } Creature* c = (Creature*)target; if (target->IsCreature(room) && !c->IsCar()) { room->frame_event.AddPropChg(c->GetWeakPtrRef(), kPropBeHook, 0, sender.Get()->GetUniId()); std::vector buff_uniids; for (int buff_id : gun_meta->_hit_buff_list) { int buff_uniid = c->TryAddBuff(c, buff_id); if (buff_uniid) { buff_uniids.push_back(buff_uniid); } } CreatureWeakPtr sender_bk = sender; int ok_buff_id = gun_meta->_int_param1; c->AutoNavigation(born_pos, gun_meta->bullet_speed() * 2, [buff_uniids, ok_buff_id, sender_bk] (Creature* c) mutable { for (int buff_uniid : buff_uniids) { c->RemoveBuffByUniId(buff_uniid); } if (sender_bk.Get()) { int buff_uniid = sender_bk.Get()->TryAddBuff(sender_bk.Get(), ok_buff_id); } } ); } else { sender.Get()->AutoNavigation(GetPos(), gun_meta->bullet_speed() * 2, [] (Creature* c) { }); } sender.Get()->IncDisableMoveTimes(); auto sender_p = sender; sender.Get()->room->xtimer.SetTimeoutEx ( (distance / gun_meta->bullet_speed() / 2 + 0.75) * SERVER_FRAME_RATE, [sender_p] (int event, const a8::Args* args) mutable { if (a8::TIMER_EXEC_EVENT == event) { sender_p.Get()->DecDisableMoveTimes(); sender_p.Get()->GetTrigger()->FlyHookDestory(); } }, &sender.Get()->xtimer_attacher ); sender.Get()->RemoveBuffById(kKeepShotAnimiBuffId); sender.Get()->TryAddBuff(sender.Get(), gun_meta->_int_param2); } void Bullet::ForceRemove() { if (!later_removed_) { ClearBuffList(); room->RemoveObjectLater(this); later_removed_ = true; if (!keep_shot_animi_timer_ptr.expired()) { room->xtimer.Delete(keep_shot_animi_timer_ptr); } } } void Bullet::TriggerHitBuff(Entity* e) { if (!e->IsDead(room) && e->IsCreature(room)) { Creature* c = (Creature*)e; for (int buff_id : gun_meta->_hit_buff_list) { int buff_uniid = c->TryAddBuff( sender.Get(), buff_id, skill_meta ); if (skill_meta && buff_uniid) { SkillHelper::ProcBulletHitBuff(this, c, buff_uniid); } } c->GetTrigger()->BulletHitBuff(this); } } bool Bullet::IsFlyHook() { return gun_meta->equip_subtype() == GUN_SUB_EQUIP_TYPE_FLY_HOOk; }