693 lines
17 KiB
C++
693 lines
17 KiB
C++
#include "precompile.h"
|
|
|
|
#include <f8/btmgr.h>
|
|
|
|
#include "new_hero_agent.h"
|
|
|
|
#include "hero.h"
|
|
#include "room.h"
|
|
#include "movement.h"
|
|
#include "netdata.h"
|
|
#include "glmhelper.h"
|
|
#include "target_agent.h"
|
|
#include "weapon.h"
|
|
#include "mapinstance.h"
|
|
#include "collision.h"
|
|
#include "human.h"
|
|
#include "btcoroutine.h"
|
|
#include "btcontext.h"
|
|
#include "team.h"
|
|
#include "master_agent.h"
|
|
#include "team_agent.h"
|
|
|
|
#include "mt/Hero.h"
|
|
#include "mt/Equip.h"
|
|
|
|
HeroAgent::HeroAgent():BaseAgent()
|
|
{
|
|
current_target_agent = behaviac::Agent::Create<TargetAgent>();
|
|
master_agent = behaviac::Agent::Create<MasterAgent>();
|
|
team_agent = behaviac::Agent::Create<TeamAgent>();
|
|
}
|
|
|
|
HeroAgent::~HeroAgent()
|
|
{
|
|
{
|
|
f8::BtMgr::Instance()->BtDestory(current_target_agent);
|
|
current_target_agent = nullptr;
|
|
}
|
|
{
|
|
f8::BtMgr::Instance()->BtDestory(master_agent);
|
|
master_agent = nullptr;
|
|
}
|
|
{
|
|
f8::BtMgr::Instance()->BtDestory(team_agent);
|
|
team_agent = nullptr;
|
|
}
|
|
}
|
|
|
|
void HeroAgent::Exec()
|
|
{
|
|
behaviac::EBTStatus status = f8::BtMgr::Instance()->BtExec(this);
|
|
if (status == behaviac::BT_RUNNING &&
|
|
coroutine_ &&
|
|
coroutine_->GetContext()->HasEvent()) {
|
|
status_= behaviac::BT_INVALID;
|
|
auto old_coroutine = coroutine_;
|
|
coroutine_ = nullptr;
|
|
owner_->shot_hold = false;
|
|
old_coroutine->GetContext()->FireEvent(this);
|
|
old_coroutine = nullptr;
|
|
}
|
|
}
|
|
|
|
void HeroAgent::SetOwner(Creature* owner)
|
|
{
|
|
owner_ = owner;
|
|
current_target_agent->SetOwner(owner_);
|
|
room_agent = owner_->room->GetRoomAgent();
|
|
}
|
|
|
|
int HeroAgent::GetUniId()
|
|
{
|
|
return owner_->GetUniId();
|
|
}
|
|
|
|
int HeroAgent::GetAgentType()
|
|
{
|
|
abort();
|
|
}
|
|
|
|
bool HeroAgent::IsMoving()
|
|
{
|
|
return owner_->GetMovement()->GetPathSize() > 0;
|
|
}
|
|
|
|
glm::vec3 HeroAgent::GetPos()
|
|
{
|
|
return owner_->GetPos().ToGlmVec3();
|
|
}
|
|
|
|
bool HeroAgent::IsDead()
|
|
{
|
|
return owner_->dead;
|
|
}
|
|
|
|
glm::vec3 HeroAgent::GetSafeAreaCenter()
|
|
{
|
|
return glm::vec3(
|
|
owner_->room->GetGasData().pos_new.x,
|
|
0.0f,
|
|
owner_->room->GetGasData().pos_new.y
|
|
);
|
|
}
|
|
|
|
float HeroAgent::GetSafeAreaRadius()
|
|
{
|
|
return owner_->room->GetGasData().gas_progress;
|
|
}
|
|
|
|
float HeroAgent::GetHp()
|
|
{
|
|
return owner_->GetHP();
|
|
}
|
|
|
|
float HeroAgent::GetMaxHp()
|
|
{
|
|
return owner_->GetMaxHP();
|
|
}
|
|
|
|
void HeroAgent::OpenBulletTraceMode()
|
|
{
|
|
bullet_trace_mode_ = true;
|
|
}
|
|
|
|
void HeroAgent::CloseBulletTraceMode()
|
|
{
|
|
bullet_trace_mode_ = false;
|
|
}
|
|
|
|
float HeroAgent::CalcDistance(const glm::vec3& target_pos)
|
|
{
|
|
return owner_->GetPos().DistanceGlmVec3(target_pos);
|
|
}
|
|
|
|
int HeroAgent::GetHeroId()
|
|
{
|
|
return owner_->GetHeroMeta()->id();
|
|
}
|
|
|
|
int HeroAgent::GetLevel()
|
|
{
|
|
return owner_->level;
|
|
}
|
|
|
|
bool HeroAgent::CanShot()
|
|
{
|
|
return owner_->CanShot(false);
|
|
}
|
|
|
|
bool HeroAgent::CanUseSkill()
|
|
{
|
|
abort();
|
|
}
|
|
|
|
void HeroAgent::UseSkill(int skill_id)
|
|
{
|
|
abort();
|
|
}
|
|
|
|
void HeroAgent::SendEmote(int emote)
|
|
{
|
|
owner_->room->frame_event.AddEmote(owner_->GetWeakPtrRef(), emote);
|
|
}
|
|
|
|
int HeroAgent::GetBattleTimes()
|
|
{
|
|
return owner_->GetBattleContext()->GetBattleTimes();
|
|
}
|
|
|
|
void HeroAgent::SetMoveDir(const glm::vec3& dir)
|
|
{
|
|
owner_->SetMoveDir(dir);
|
|
}
|
|
|
|
void HeroAgent::SetAttackDir(const glm::vec3& dir)
|
|
{
|
|
owner_->SetAttackDir(dir);
|
|
}
|
|
|
|
void HeroAgent::ShotNormal(const glm::vec3& dir)
|
|
{
|
|
if (owner_->dead) {
|
|
return;
|
|
}
|
|
if (owner_->CanShot(true)) {
|
|
bool shot_ok = false;
|
|
glm::vec3 shot_dir = owner_->GetAttackDir();
|
|
if (bullet_trace_mode_) {
|
|
owner_->Shot(shot_dir, shot_ok, 0, 0);
|
|
} else {
|
|
owner_->Shot(shot_dir, shot_ok, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void HeroAgent::ShotTrace()
|
|
{
|
|
abort();
|
|
}
|
|
|
|
glm::vec3 HeroAgent::GetRandomDir()
|
|
{
|
|
glm::vec3 dir = owner_->GetAttackDir();
|
|
GlmHelper::RotateY(dir, (10 + rand() % 360)/ 180.0f);
|
|
return dir;
|
|
}
|
|
|
|
glm::vec3 HeroAgent::GetTargetDir()
|
|
{
|
|
if (current_target_agent->IsValid()) {
|
|
return owner_->GetMoveDir();
|
|
}
|
|
glm::vec3 dir = current_target_agent->GetPos() - owner_->GetPos().ToGlmVec3();
|
|
GlmHelper::Normalize(dir);
|
|
return dir;
|
|
}
|
|
|
|
glm::vec3 HeroAgent::RandomPoint(const glm::vec3& center, float range)
|
|
{
|
|
glm::vec3 hit_point;
|
|
bool hit_result = false;
|
|
|
|
glm::vec3 start = center;
|
|
glm::vec3 dir = GlmHelper::UP;
|
|
GlmHelper::RotateY(dir, (10 + rand() % 360)/ 180.0f);
|
|
GlmHelper::Normalize(dir);
|
|
glm::vec3 end = center + dir * range;
|
|
|
|
owner_->room->map_instance->Scale(start);
|
|
owner_->room->map_instance->Scale(end);
|
|
if (owner_->room->map_instance->Raycast
|
|
(
|
|
start,
|
|
end,
|
|
hit_point,
|
|
hit_result
|
|
)) {
|
|
owner_->room->map_instance->Scale(hit_point);
|
|
}
|
|
return hit_point;
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::RandomSafeZonePoint(int try_count, int step_len)
|
|
{
|
|
if (status_ == behaviac::BT_RUNNING) {
|
|
return DoRunningCb();
|
|
}
|
|
auto context = MAKE_BTCONTEXT
|
|
(
|
|
);
|
|
auto co = std::make_shared<BtCoroutine>(context, "RandomSafeZonePoint");
|
|
co->runing_cb =
|
|
[this, context, try_count, step_len] ()
|
|
{
|
|
if (owner_->dead) {
|
|
return behaviac::BT_FAILURE;
|
|
}
|
|
glm::vec3 gas_center = GlmHelper::Vec2ToVec3(owner_->room->GetGasData().pos_new);
|
|
gas_center.y = owner_->GetPos().ToGlmVec3().y;
|
|
if (GlmHelper::IsEqual2D(owner_->GetPos().ToGlmVec3(),
|
|
gas_center)) {
|
|
out_point0 = gas_center;
|
|
return behaviac::BT_SUCCESS;
|
|
}
|
|
|
|
glm::vec3 dir = gas_center - owner_->GetPos().ToGlmVec3();
|
|
GlmHelper::Normalize(dir);
|
|
|
|
glm::vec3 center = owner_->GetPos().ToGlmVec3() +
|
|
dir * (50.0f + (float)(try_count + 1) * step_len);
|
|
owner_->room->map_instance->Scale(center);
|
|
glm::vec3 point;
|
|
bool ok = owner_->room->map_instance->FindConnectableNearestPoint(center, 50, point);
|
|
if (ok) {
|
|
owner_->room->map_instance->UnScale(point);
|
|
out_point0 = point;
|
|
return behaviac::BT_SUCCESS;
|
|
}
|
|
return behaviac::BT_FAILURE;
|
|
};
|
|
return StartCoroutine(co);
|
|
}
|
|
|
|
float HeroAgent::GetShotRange()
|
|
{
|
|
if (owner_->GetCurrWeapon()) {
|
|
return owner_->GetCurrWeapon()->meta->range();
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
void HeroAgent::SetV(int id, int val)
|
|
{
|
|
auto itr = dyn_hash_.find(id);
|
|
if (itr != dyn_hash_.end()) {
|
|
itr->second = val;
|
|
} else {
|
|
dyn_hash_[id] = val;
|
|
}
|
|
}
|
|
|
|
int HeroAgent::GetV(int id)
|
|
{
|
|
auto itr = dyn_hash_.find(id);
|
|
if (itr != dyn_hash_.end()) {
|
|
return itr->second;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int HeroAgent::IncV(int id, int val)
|
|
{
|
|
auto itr = dyn_hash_.find(id);
|
|
if (itr != dyn_hash_.end()) {
|
|
itr->second += val;
|
|
return itr->second;
|
|
} else {
|
|
dyn_hash_[id] = val;
|
|
return val;
|
|
}
|
|
}
|
|
|
|
int HeroAgent::DecV(int id, int val)
|
|
{
|
|
return IncV(id, -val);
|
|
}
|
|
|
|
bool HeroAgent::HasBuffEffect(int effect_id)
|
|
{
|
|
return owner_->HasBuffEffect(effect_id);
|
|
}
|
|
|
|
bool HeroAgent::IsNearGas(float anti_range)
|
|
{
|
|
return !Collision::InSquare
|
|
(GlmHelper::Vec2ToVec3(owner_->room->GetGasData().pos_old),
|
|
owner_->GetPos().ToGlmVec3(),
|
|
std::max(owner_->room->GetGasData().rad_new,
|
|
owner_->room->GetGasData().gas_progress) * 0.7 -
|
|
anti_range);
|
|
}
|
|
|
|
bool HeroAgent::MasterInRange(float range)
|
|
{
|
|
if (!owner_->IsHero() ||
|
|
!owner_->AsHero()->master.Get()) {
|
|
return false;
|
|
}
|
|
return Collision::InSquare
|
|
(owner_->AsHero()->master.Get()->GetPos().ToGlmVec3(),
|
|
owner_->GetPos().ToGlmVec3(),
|
|
range);
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::SearchEnemy(float range)
|
|
{
|
|
Creature* myself = owner_;
|
|
Human* target = nullptr;
|
|
float last_distance = range + 1;
|
|
owner_->room->TraverseHumanList
|
|
(
|
|
[myself, &last_distance, &target, range] (Human* hum)
|
|
{
|
|
if (!hum->dead &&
|
|
!a8::HasBitFlag(hum->status, CS_Disable) &&
|
|
!hum->HasBuffEffect(kBET_Hide) &&
|
|
!hum->HasBuffEffect(kBET_Invincible) &&
|
|
hum->team_id != myself->team_id) {
|
|
if (a8::HasBitFlag(myself->status, CS_DisableAttackAndroid) &&
|
|
hum->IsAndroid()) {
|
|
} else {
|
|
float distance = hum->GetPos().Distance2D2(myself->GetPos());
|
|
if (distance <= range) {
|
|
if (distance < last_distance) {
|
|
target = hum;
|
|
last_distance = distance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
return behaviac::BT_FAILURE;
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::CoIdle(int min_val, int max_val)
|
|
{
|
|
if (status_ == behaviac::BT_RUNNING) {
|
|
return DoRunningCb();
|
|
}
|
|
auto context = MAKE_BTCONTEXT
|
|
(
|
|
int time = 0;
|
|
);
|
|
context->time = a8::RandEx(min_val, max_val);
|
|
auto co = std::make_shared<BtCoroutine>(context, "CoIdle");
|
|
co->runing_cb =
|
|
[this, context] ()
|
|
{
|
|
if ((GetRoom()->GetFrameNo() - context->frameno) * FRAME_RATE_MS <
|
|
context->time) {
|
|
return behaviac::BT_RUNNING;
|
|
} else {
|
|
return behaviac::BT_SUCCESS;
|
|
}
|
|
};
|
|
return StartCoroutine(co);
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::CoMoveCurrentTargetRaycast()
|
|
{
|
|
if (status_ == behaviac::BT_RUNNING) {
|
|
return DoRunningCb();
|
|
}
|
|
auto context = MAKE_BTCONTEXT
|
|
(
|
|
);
|
|
auto co = std::make_shared<BtCoroutine>(context, "CoMoveCurrentTargetRaycast");
|
|
co->runing_cb =
|
|
[this, context] ()
|
|
{
|
|
return behaviac::BT_SUCCESS;
|
|
};
|
|
return StartCoroutine(co);
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::CoShotCurrentTargetRaycast()
|
|
{
|
|
if (status_ == behaviac::BT_RUNNING) {
|
|
return DoRunningCb();
|
|
}
|
|
auto context = MAKE_BTCONTEXT
|
|
(
|
|
a8::XTimerWp timer_ptr;
|
|
);
|
|
context->timer_ptr = owner_->room->xtimer.SetTimeoutWpEx
|
|
(
|
|
1000 / FRAME_RATE_MS,
|
|
[] (int event, const a8::Args* args)
|
|
{
|
|
},
|
|
&owner_->xtimer_attacher);
|
|
auto co = std::make_shared<BtCoroutine>(context, "CoShotCurrentTargetRaycast");
|
|
co->runing_cb =
|
|
[this, context] ()
|
|
{
|
|
if (!context->timer_ptr.expired()) {
|
|
return behaviac::BT_RUNNING;
|
|
} else {
|
|
return behaviac::BT_SUCCESS;
|
|
}
|
|
};
|
|
return StartCoroutine(co);
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::CoMoveMasterRaycast()
|
|
{
|
|
if (status_ == behaviac::BT_RUNNING) {
|
|
return DoRunningCb();
|
|
}
|
|
auto context = MAKE_BTCONTEXT
|
|
(
|
|
a8::XTimerWp timer_ptr;
|
|
);
|
|
context->timer_ptr = owner_->room->xtimer.SetTimeoutWpEx
|
|
(
|
|
1000 / FRAME_RATE_MS,
|
|
[] (int event, const a8::Args* args)
|
|
{
|
|
},
|
|
&owner_->xtimer_attacher);
|
|
auto co = std::make_shared<BtCoroutine>(context, "CoMoveMasterRaycast");
|
|
co->runing_cb =
|
|
[this, context] ()
|
|
{
|
|
if (!context->timer_ptr.expired()) {
|
|
return behaviac::BT_RUNNING;
|
|
} else {
|
|
return behaviac::BT_SUCCESS;
|
|
}
|
|
};
|
|
return StartCoroutine(co);
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::CoFindPath(const glm::vec3& pos)
|
|
{
|
|
if (status_ == behaviac::BT_RUNNING) {
|
|
return DoRunningCb();
|
|
}
|
|
auto context = MAKE_BTCONTEXT
|
|
(
|
|
bool find_ok = false;
|
|
glm::vec3 target_pos;
|
|
);
|
|
context->target_pos = pos;
|
|
auto co = std::make_shared<BtCoroutine>(context, "CoFindPath");
|
|
co->runing_cb =
|
|
[this, context, pos] ()
|
|
{
|
|
if (owner_->dead) {
|
|
return behaviac::BT_FAILURE;
|
|
}
|
|
if (context->find_ok) {
|
|
if (owner_->GetMovement()->GetPathSize() <= 0) {
|
|
return behaviac::BT_SUCCESS;
|
|
} else {
|
|
return behaviac::BT_RUNNING;
|
|
}
|
|
}
|
|
bool ret = owner_->GetMovement()->FindPath(context->target_pos, 0);
|
|
if (ret) {
|
|
context->find_ok = true;
|
|
} else {
|
|
return behaviac::BT_FAILURE;
|
|
}
|
|
return behaviac::BT_RUNNING;
|
|
};
|
|
return StartCoroutine(co);
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::CoFindPathEx(const glm::vec3& pos, float distance)
|
|
{
|
|
if (status_ == behaviac::BT_RUNNING) {
|
|
return DoRunningCb();
|
|
}
|
|
auto context = MAKE_BTCONTEXT
|
|
(
|
|
a8::XTimerWp timer_ptr;
|
|
);
|
|
context->timer_ptr = owner_->room->xtimer.SetTimeoutWpEx
|
|
(
|
|
1000 / FRAME_RATE_MS,
|
|
[] (int event, const a8::Args* args)
|
|
{
|
|
},
|
|
&owner_->xtimer_attacher);
|
|
auto co = std::make_shared<BtCoroutine>(context, "CoFindPathEx");
|
|
co->runing_cb =
|
|
[this, context] ()
|
|
{
|
|
if (!context->timer_ptr.expired()) {
|
|
return behaviac::BT_RUNNING;
|
|
} else {
|
|
return behaviac::BT_SUCCESS;
|
|
}
|
|
};
|
|
return StartCoroutine(co);
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::CoStartMove(int min_val, int max_val)
|
|
{
|
|
if (status_ == behaviac::BT_RUNNING) {
|
|
return DoRunningCb();
|
|
}
|
|
float distance = a8::RandEx(min_val, max_val);
|
|
auto context = MAKE_BTCONTEXT
|
|
(
|
|
);
|
|
auto co = std::make_shared<BtCoroutine>(context, "CoStartMove");
|
|
co->runing_cb =
|
|
[this, context, distance] ()
|
|
{
|
|
if (!owner_->dead) {
|
|
owner_->GetMovement()->CalcTargetPos(distance);
|
|
}
|
|
return behaviac::BT_SUCCESS;
|
|
};
|
|
return StartCoroutine(co);
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::CoSleep(int time)
|
|
{
|
|
if (status_ == behaviac::BT_RUNNING) {
|
|
return DoRunningCb();
|
|
}
|
|
auto context = MAKE_BTCONTEXT
|
|
(
|
|
a8::XTimerWp timer_ptr;
|
|
);
|
|
context->timer_ptr = owner_->room->xtimer.SetTimeoutWpEx
|
|
(
|
|
1000 / FRAME_RATE_MS,
|
|
[] (int event, const a8::Args* args)
|
|
{
|
|
},
|
|
&owner_->xtimer_attacher);
|
|
auto co = std::make_shared<BtCoroutine>(context, "CoSleep");
|
|
co->runing_cb =
|
|
[this, context] ()
|
|
{
|
|
if (!context->timer_ptr.expired()) {
|
|
return behaviac::BT_RUNNING;
|
|
} else {
|
|
return behaviac::BT_SUCCESS;
|
|
}
|
|
};
|
|
return StartCoroutine(co);
|
|
}
|
|
|
|
Room* HeroAgent::GetRoom()
|
|
{
|
|
return owner_->room;
|
|
}
|
|
|
|
bool HeroAgent::HasFlag(int tag)
|
|
{
|
|
return a8::HasBitFlag(flags_, tag);
|
|
}
|
|
|
|
void HeroAgent::SetFlag(int tag)
|
|
{
|
|
a8::SetBitFlag(flags_, tag);
|
|
}
|
|
|
|
void HeroAgent::UnSetFlag(int tag)
|
|
{
|
|
a8::UnSetBitFlag(flags_, tag);
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::DoRunningCb()
|
|
{
|
|
if (status_ != behaviac::BT_RUNNING) {
|
|
abort();
|
|
}
|
|
if (owner_->room->GetFrameNo() < coroutine_->sleep_end_frameno) {
|
|
return behaviac::BT_RUNNING;
|
|
}
|
|
status_ = coroutine_->runing_cb();
|
|
#ifdef DEBUG1
|
|
if ((owner_->room->GetFrameNo() - status_frameno_) % SERVER_FRAME_RATE == 0 ||
|
|
last_status_ != status_) {
|
|
last_status_ = status_;
|
|
a8::XPrintf("Running Status:%s %d\n", {status_name_, status_});
|
|
}
|
|
#endif
|
|
if (status_ != behaviac::BT_RUNNING) {
|
|
coroutine_ = nullptr;
|
|
}
|
|
return status_;
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::StartCoroutine(std::shared_ptr<BtCoroutine> coroutine)
|
|
{
|
|
#ifdef DEBUG1
|
|
a8::XPrintf("StartCoroutine:%d %s\n", {owner_->GetUniId(), coroutine->GetName()});
|
|
#endif
|
|
coroutine_ = coroutine;
|
|
#ifdef DEBUG
|
|
last_status_ = behaviac::BT_INVALID;
|
|
status_frameno_ = owner_->room->GetFrameNo();
|
|
status_name_ = coroutine_->GetName();
|
|
#endif
|
|
status_ = behaviac::BT_RUNNING;
|
|
return status_;
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::RegisterEvents(behaviac::vector<BtEvent_e> events)
|
|
{
|
|
return behaviac::BT_SUCCESS;
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::CoMoveForward(int min_val, int max_val)
|
|
{
|
|
if (status_ == behaviac::BT_RUNNING) {
|
|
return DoRunningCb();
|
|
}
|
|
float distance = a8::RandEx(min_val, max_val);
|
|
auto context = MAKE_BTCONTEXT
|
|
(
|
|
);
|
|
owner_->GetMovement()->CalcTargetPos(distance);
|
|
auto co = std::make_shared<BtCoroutine>(context, "CoMoveForward");
|
|
co->runing_cb =
|
|
[this, context] ()
|
|
{
|
|
if (owner_->GetMovement()->GetPathSize() <= 0) {
|
|
return behaviac::BT_SUCCESS;
|
|
} else {
|
|
return behaviac::BT_RUNNING;
|
|
}
|
|
};
|
|
return StartCoroutine(co);
|
|
}
|
|
|
|
behaviac::EBTStatus HeroAgent::DebugOut(std::string msg, int arg0, int arg1, int arg2)
|
|
{
|
|
#ifdef DEBUG1
|
|
a8::XPrintf((msg+"\n").c_str(), {arg0, arg1, arg2});
|
|
#endif
|
|
return behaviac::BT_SUCCESS;
|
|
}
|