638 lines
20 KiB
C++
638 lines
20 KiB
C++
#include "precompile.h"
|
|
|
|
#include "human.h"
|
|
#include "cs_proto.pb.h"
|
|
#include "metamgr.h"
|
|
#include "room.h"
|
|
#include "bullet.h"
|
|
#include "collider.h"
|
|
#include "loot.h"
|
|
|
|
Human::Human()
|
|
{
|
|
default_weapon.weapon_idx = 0;
|
|
default_weapon.weapon_id = 12101;
|
|
default_weapon.weapon_lv = 1;
|
|
default_weapon.ammo = 1;
|
|
default_weapon.meta = MetaMgr::Instance()->GetEquip(default_weapon.weapon_id);
|
|
weapons.reserve(MAX_WEAPON_NUM);
|
|
for (size_t i = 0; i < MAX_WEAPON_NUM; ++i) {
|
|
auto& weapon = a8::FastAppend(weapons);
|
|
weapon.weapon_idx = i;
|
|
weapon.weapon_id = 0;
|
|
weapon.weapon_lv = 0;
|
|
weapon.ammo = 0;
|
|
}
|
|
weapons[0] = default_weapon;
|
|
curr_weapon = &weapons[0];
|
|
inventory.reserve(MAX_INVENTORY_NUM);
|
|
for (size_t i = 0; i < MAX_INVENTORY_NUM; ++i) {
|
|
inventory.push_back(0);
|
|
}
|
|
inventory[12] = 1;
|
|
}
|
|
|
|
Human::~Human()
|
|
{
|
|
}
|
|
|
|
void Human::Initialize()
|
|
{
|
|
Entity::Initialize();
|
|
}
|
|
|
|
float Human::GetSpeed()
|
|
{
|
|
return meta->i->move_speed();
|
|
}
|
|
|
|
void Human::FillMFObjectPart(cs::MFObjectPart* part_data)
|
|
{
|
|
part_data->set_object_type(ET_Player);
|
|
cs::MFPlayerPart* p = part_data->mutable_union_obj_1();
|
|
p->set_obj_uniid(entity_uniid);
|
|
pos.ToPB(p->mutable_pos());
|
|
attack_dir.ToPB(p->mutable_dir());
|
|
}
|
|
|
|
void Human::FillMFObjectFull(cs::MFObjectFull* full_data)
|
|
{
|
|
full_data->set_object_type(ET_Player);
|
|
cs::MFPlayerFull* p = full_data->mutable_union_obj_1();
|
|
p->set_obj_uniid(entity_uniid);
|
|
pos.ToPB(p->mutable_pos());
|
|
attack_dir.ToPB(p->mutable_dir());
|
|
|
|
p->set_health(health);
|
|
p->set_max_health(GetMaxHP());
|
|
p->set_dead(dead);
|
|
p->set_downed(downed);
|
|
p->set_disconnected(disconnected);
|
|
p->set_anim_type(anim_type);
|
|
p->set_anim_seq(anim_seq);
|
|
p->set_skin(skin);
|
|
p->set_backpack(backpack);
|
|
p->set_helmet(helmet);
|
|
p->set_chest(chest);
|
|
curr_weapon->ToPB(p->mutable_weapon());
|
|
p->set_energy_shield(energy_shield);
|
|
#if 1
|
|
{
|
|
int max_energy_shield = MetaMgr::Instance()->GetSysParamAsInt("max_energy_shield");
|
|
p->set_max_energy_shield(max_energy_shield);
|
|
}
|
|
#endif
|
|
p->set_vip(vip);
|
|
p->set_sdmg(sdmg);
|
|
if (pain_killer_timer) {
|
|
int passed_time = (room->frame_no - pain_killer_frameno) * FRAME_RATE_MS;
|
|
int left_time = std::max(0, pain_killer_lastingtime * 1000 - passed_time);
|
|
int anodyne_max_time = MetaMgr::Instance()->GetSysParamAsInt("anodyne_max_time");
|
|
left_time = std::min(left_time, anodyne_max_time * 1000);
|
|
|
|
cs::MFBodyState* state = p->add_states();
|
|
state->set_state_type(1);
|
|
state->set_left_time(left_time);
|
|
state->set_lasting_time(anodyne_max_time * 1000);
|
|
}
|
|
if (a8::HasBitFlag(status, HS_Fly)) {
|
|
cs::MFBodyState* state = p->add_states();
|
|
state->set_state_type(2);
|
|
}
|
|
if (a8::HasBitFlag(status, HS_Jump)) {
|
|
int passed_time = (room->frame_no - jump_frameno) * FRAME_RATE_MS;
|
|
cs::MFBodyState* state = p->add_states();
|
|
state->set_state_type(3);
|
|
state->set_left_time(std::min(0, MetaMgr::Instance()->jump_time * 1000 - passed_time));
|
|
state->set_lasting_time(MetaMgr::Instance()->jump_time * 1000);
|
|
}
|
|
}
|
|
|
|
void Human::FillMFPlayerStats(cs::MFPlayerStats* stats_pb)
|
|
{
|
|
stats_pb->set_player_id(entity_uniid);
|
|
stats_pb->set_player_avatar_url(avatar_url);
|
|
|
|
if (!dead) {
|
|
stats_pb->set_time_alive(room->frame_no * 1000.0f / SERVER_FRAME_RATE);
|
|
} else {
|
|
stats_pb->set_time_alive(dead_frameno * 1000.0f / SERVER_FRAME_RATE);
|
|
}
|
|
stats_pb->set_kills(stats.kills);
|
|
stats_pb->set_damage_amount(stats.damage_amount);
|
|
stats_pb->set_heal_amount(stats.heal_amount);
|
|
|
|
stats_pb->set_history_time_alive(stats.history_time_alive);
|
|
stats_pb->set_history_kills(stats.history_kills);
|
|
stats_pb->set_history_damage_amount(stats.history_damage_amount);
|
|
stats_pb->set_history_heal_amount(stats.history_heal_amount);
|
|
|
|
stats_pb->set_gold(stats.gold);
|
|
stats_pb->set_score(stats.score);
|
|
|
|
stats_pb->set_dead(dead);
|
|
stats_pb->set_killer_id(stats.killer_id);
|
|
stats_pb->set_killer_name(stats.killer_name);
|
|
|
|
stats_pb->set_account_id(account_id);
|
|
}
|
|
|
|
void Human::FillMFTeamData(cs::MFTeamData* team_data)
|
|
{
|
|
team_data->set_player_id(entity_uniid);
|
|
team_data->set_name(name);
|
|
pos.ToPB(team_data->mutable_pos());
|
|
attack_dir.ToPB(team_data->mutable_dir());
|
|
team_data->set_health(health);
|
|
team_data->set_max_health(GetMaxHP());
|
|
team_data->set_disconnected(false);
|
|
team_data->set_dead(dead);
|
|
team_data->set_downed(downed);
|
|
}
|
|
|
|
void Human::Shot(Vector2D& target_dir)
|
|
{
|
|
if (!curr_weapon->meta) {
|
|
return;
|
|
}
|
|
|
|
if (curr_weapon->weapon_idx != 0 &&
|
|
curr_weapon->ammo <= 0) {
|
|
AutoLoadingBullet();
|
|
return;
|
|
}
|
|
|
|
#if 1
|
|
float fly_distance = 5;
|
|
#endif
|
|
{
|
|
::google::protobuf::RepeatedPtrField<::cs::MFShot>* shots = nullptr;
|
|
{
|
|
{
|
|
auto itr = room->frame_data.shots_hash.find(room->frame_no);
|
|
if (itr == room->frame_data.shots_hash.end()) {
|
|
room->frame_data.shots_hash[room->frame_no] = ::google::protobuf::RepeatedPtrField<::cs::MFShot>();
|
|
itr = room->frame_data.shots_hash.find(room->frame_no);
|
|
}
|
|
shots = &itr->second;
|
|
}
|
|
}
|
|
|
|
cs::MFShot* shot = shots->Add();
|
|
shot->set_player_id(entity_uniid);
|
|
curr_weapon->ToPB(shot->mutable_weapon());
|
|
shot->set_offhand(true);
|
|
shot->set_bullskin(10001);
|
|
}
|
|
for (auto& tuple : curr_weapon->meta->bullet_born_offset) {
|
|
Vector2D bullet_born_offset = Vector2D(std::get<0>(tuple), std::get<1>(tuple));
|
|
bullet_born_offset.Rotate(attack_dir.CalcAngle(Vector2D::UP));
|
|
Vector2D bullet_born_pos = pos + bullet_born_offset;
|
|
::google::protobuf::RepeatedPtrField<::cs::MFBullet>* bullets = nullptr;
|
|
{
|
|
{
|
|
auto itr = room->frame_data.bullets_hash.find(room->frame_no);
|
|
if (itr == room->frame_data.bullets_hash.end()) {
|
|
room->frame_data.bullets_hash[room->frame_no] = ::google::protobuf::RepeatedPtrField<::cs::MFBullet>();
|
|
itr = room->frame_data.bullets_hash.find(room->frame_no);
|
|
}
|
|
bullets = &itr->second;
|
|
}
|
|
}
|
|
{
|
|
cs::MFBullet* bullet = bullets->Add();
|
|
bullet->set_player_id(entity_uniid);
|
|
bullet->set_bullet_id(curr_weapon->meta->i->use_bullet());
|
|
bullet_born_pos.ToPB(bullet->mutable_pos());
|
|
attack_dir.ToPB(bullet->mutable_dir());
|
|
bullet->set_bulletskin(10001);
|
|
bullet->set_gun_id(curr_weapon->meta->i->id());
|
|
bullet->set_fly_distance(fly_distance);
|
|
}
|
|
room->CreateBullet(this, curr_weapon->meta, bullet_born_pos, attack_dir, fly_distance);
|
|
}
|
|
--curr_weapon->ammo;
|
|
int slot_id = curr_weapon->meta->i->_inventory_slot();
|
|
switch (slot_id) {
|
|
case 5:
|
|
{
|
|
//手雷
|
|
if (inventory[slot_id] > 0) {
|
|
--inventory[slot_id];
|
|
++curr_weapon->ammo;
|
|
} else {
|
|
int weapon_idx = curr_weapon->weapon_idx;
|
|
*curr_weapon = Weapon();
|
|
curr_weapon->weapon_idx = weapon_idx;
|
|
if (weapons[SMOKE_SLOT].weapon_id != 0) {
|
|
curr_weapon = &weapons[SMOKE_SLOT];
|
|
} else {
|
|
curr_weapon = &weapons[0];
|
|
}
|
|
AutoLoadingBullet();
|
|
}
|
|
need_sync_active_player = true;
|
|
SyncAroundPlayers();
|
|
}
|
|
break;
|
|
case 6:
|
|
{
|
|
//烟雾弹
|
|
if (inventory[slot_id] > 0) {
|
|
--inventory[slot_id];
|
|
++curr_weapon->ammo;
|
|
} else {
|
|
int weapon_idx = curr_weapon->weapon_idx;
|
|
*curr_weapon = Weapon();
|
|
curr_weapon->weapon_idx = weapon_idx;
|
|
if (weapons[FRAG_SLOT].weapon_id != 0) {
|
|
curr_weapon = &weapons[FRAG_SLOT];
|
|
} else {
|
|
curr_weapon = &weapons[0];
|
|
}
|
|
AutoLoadingBullet();
|
|
}
|
|
need_sync_active_player = true;
|
|
SyncAroundPlayers();
|
|
}
|
|
break;
|
|
}
|
|
last_shot_frameno_ = room->frame_no;
|
|
need_sync_active_player = true;
|
|
}
|
|
|
|
void Human::RecalcSelfCollider()
|
|
{
|
|
if (!self_collider_) {
|
|
self_collider_ = new CircleCollider();
|
|
self_collider_->owner = this;
|
|
colliders.push_back(self_collider_);
|
|
}
|
|
self_collider_->pos = Vector2D();
|
|
self_collider_->rad = meta->i->radius();
|
|
}
|
|
|
|
bool Human::IsCollision()
|
|
{
|
|
//检查x轴
|
|
{
|
|
int left_x = pos.x - meta->i->radius();
|
|
if (left_x < 0.001f) {
|
|
return true;
|
|
}
|
|
int right_x = pos.x + meta->i->radius();
|
|
if (right_x > MAP_WIDTH) {
|
|
return true;
|
|
}
|
|
}
|
|
//检查y轴
|
|
{
|
|
int up_y = pos.y + meta->i->radius();
|
|
if (up_y > MAP_HEIGHT) {
|
|
return true;
|
|
}
|
|
int down_y = pos.y - meta->i->radius();
|
|
if (down_y < 0.001f) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (a8::HasBitFlag(status, HS_Jump)) {
|
|
return false;
|
|
}
|
|
|
|
int detection_flags = 0;
|
|
a8::SetBitFlag(detection_flags, ET_Obstacle);
|
|
a8::SetBitFlag(detection_flags, ET_Building);
|
|
std::vector<Entity*> objects;
|
|
room->CollisionDetection(this, detection_flags, objects);
|
|
return !objects.empty();
|
|
}
|
|
|
|
ColliderComponent* Human::GetFirstCollision()
|
|
{
|
|
int detection_flags = 0;
|
|
a8::SetBitFlag(detection_flags, ET_Obstacle);
|
|
a8::SetBitFlag(detection_flags, ET_Building);
|
|
std::vector<Entity*> objects;
|
|
room->CollisionDetection(this, detection_flags, objects);
|
|
return objects.empty() ? *objects[0]->colliders.begin() : nullptr;
|
|
}
|
|
|
|
void Human::FindPath()
|
|
{
|
|
ColliderComponent* first_collider = nullptr;
|
|
Vector2D old_pos = pos;
|
|
{
|
|
float up_dot = Vector2D::UP.Dot(move_dir);
|
|
bool at_left_side = Vector2D::LEFT.Dot(move_dir) > 0.0001f;
|
|
if (std::abs(up_dot) <= 0.001f) { //相互垂直
|
|
//向上
|
|
pos = old_pos + Vector2D::UP;
|
|
if (!IsCollision()) {
|
|
return;
|
|
} else {
|
|
//向下
|
|
pos = old_pos + Vector2D::DOWN;
|
|
if (!IsCollision()) {
|
|
return;
|
|
}
|
|
}
|
|
} else if (up_dot > 0.001f) { //基本相同
|
|
pos = old_pos + (at_left_side ? Vector2D::LEFT : Vector2D::RIGHT);
|
|
if (!IsCollision()) {
|
|
return;
|
|
} else {
|
|
//向上
|
|
pos = old_pos + Vector2D::UP;
|
|
if (!IsCollision()) {
|
|
return;
|
|
}
|
|
}
|
|
} else if (up_dot < 0.001f) { //基本相反
|
|
pos = old_pos + (at_left_side ? Vector2D::LEFT : Vector2D::RIGHT);
|
|
if (!IsCollision()) {
|
|
return;
|
|
} else {
|
|
//向下
|
|
pos = old_pos + Vector2D::DOWN;
|
|
if (!IsCollision()) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pos = old_pos;
|
|
}
|
|
|
|
float Human::GetRadius()
|
|
{
|
|
return meta->i->radius();
|
|
}
|
|
|
|
float Human::GetMaxHP()
|
|
{
|
|
return meta->i->health();
|
|
}
|
|
|
|
void Human::UpdatePoisoning()
|
|
{
|
|
if (dead) {
|
|
return;
|
|
}
|
|
bool need_notify = poisoning_time > 1000;
|
|
while (poisoning_time > 1000) {
|
|
if (room->gas_data.is_last_gas) {
|
|
DecHP(room->gas_data.new_area_meta->i->hurt(), -1, "安全区");
|
|
} else {
|
|
DecHP(room->gas_data.old_area_meta->i->hurt(), -1, "安全区");
|
|
}
|
|
if (dead) {
|
|
poisoning_time = 0;
|
|
break;
|
|
}
|
|
poisoning_time -= 1000;
|
|
}
|
|
if (need_notify && entity_subtype == EST_Player) {
|
|
SyncAroundPlayers();
|
|
}
|
|
}
|
|
|
|
void Human::SyncAroundPlayers()
|
|
{
|
|
room->TouchHumanList(a8::XParams(),
|
|
[this] (Human* hum, a8::XParams& param) -> bool
|
|
{
|
|
hum->new_objects.insert(this);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
void Human::AutoLoadingBullet(bool manual)
|
|
{
|
|
if (curr_weapon->weapon_idx != 0 &&
|
|
(curr_weapon->ammo <= 0 ||
|
|
(manual && curr_weapon->ammo < curr_weapon->meta->i->clip_volume()))
|
|
) {
|
|
MetaData::Equip* bullet_meta = MetaMgr::Instance()->GetEquip(curr_weapon->meta->i->use_bullet());
|
|
if (bullet_meta &&
|
|
bullet_meta->i->_inventory_slot() >= 0 &&
|
|
bullet_meta->i->_inventory_slot() < MAX_INVENTORY_NUM
|
|
) {
|
|
if (inventory[bullet_meta->i->_inventory_slot()] > 0) {
|
|
StartAction(AT_Reload,
|
|
curr_weapon->meta->i->reload_time(),
|
|
curr_weapon->weapon_id,
|
|
curr_weapon->weapon_idx);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Human::StartAction(ActionType_e action_type,
|
|
int action_duration,
|
|
int item_id,
|
|
int target_id)
|
|
{
|
|
if (this->action_type == action_type &&
|
|
this->action_item_id == item_id &&
|
|
this->action_target_id == target_id) {
|
|
return;
|
|
}
|
|
this->action_type = action_type;
|
|
this->action_frameno = room->frame_no;
|
|
this->action_duration = action_duration;
|
|
this->action_item_id = item_id;
|
|
this->action_target_id = target_id;
|
|
need_sync_active_player = true;
|
|
}
|
|
|
|
void Human::CancelAction()
|
|
{
|
|
ResetAction();
|
|
}
|
|
|
|
void Human::ResetAction()
|
|
{
|
|
action_type = AT_None;
|
|
action_duration = 0;
|
|
action_frameno = 0;
|
|
action_item_id = 0;
|
|
action_target_id = 0;
|
|
need_sync_active_player = true;
|
|
}
|
|
|
|
void Human::FillSMGameOver(cs::SMGameOver& msg)
|
|
{
|
|
std::vector<Human*> human_list;
|
|
room->TouchHumanList(a8::XParams(),
|
|
[&human_list] (Human* hum, a8::XParams& param) -> bool
|
|
{
|
|
human_list.push_back(hum);
|
|
return true;
|
|
});
|
|
std::sort(human_list.begin(), human_list.end(),
|
|
[] (Human* a, Human* b )
|
|
{
|
|
if (a->dead_frameno == b->dead_frameno) {
|
|
return a->entity_uniid < b->entity_uniid;
|
|
} else {
|
|
return a->dead_frameno == 0 ||
|
|
(b->dead_frameno != 0 && a->dead_frameno > b->dead_frameno);
|
|
}
|
|
});
|
|
int rank = human_list.size();
|
|
for (size_t i = 0; i < human_list.size(); ++i) {
|
|
if (human_list[i] == this) {
|
|
rank = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
msg.set_team_id(0);
|
|
msg.set_team_rank(rank);
|
|
msg.set_team_allcnt(1);
|
|
msg.set_game_over(room->game_over);
|
|
msg.set_victory(!dead);
|
|
|
|
cs::MFPlayerStats* p = msg.add_player_stats();
|
|
FillMFPlayerStats(p);
|
|
}
|
|
|
|
void Human::BeKill(int killer_id, const std::string& killer_name)
|
|
{
|
|
if (!dead && !room->game_over) {
|
|
Entity* hum = room->GetEntityByUniId(killer_id);
|
|
if (hum && hum->entity_type == ET_Player) {
|
|
((Human*)hum)->stats.kills++;
|
|
}
|
|
stats.killer_id = killer_id;
|
|
stats.killer_name = killer_name;
|
|
dead = true;
|
|
health = 0.0f;
|
|
dead_frameno = room->frame_no;
|
|
send_gameover = true;
|
|
room->OnHumanDie(this);
|
|
SyncAroundPlayers();
|
|
if (team_members) {
|
|
for (auto& hum : *team_members) {
|
|
if (hum != this && hum->action_type == AT_Relive &&
|
|
hum->action_target_id == entity_uniid) {
|
|
hum->CancelAction();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Human::DecHP(float dec_hp, int killer_id, const std::string& killer_name)
|
|
{
|
|
auto downed_func = [] (const a8::XParams& param)
|
|
{
|
|
Human* hum = (Human*)param.sender.GetUserData();
|
|
if (!hum->downed) {
|
|
hum->room->xtimer.DeleteTimer(hum->downed_timer);
|
|
return;
|
|
}
|
|
if (hum->dead) {
|
|
return;
|
|
}
|
|
if (!hum->HasLiveTeammate()) {
|
|
hum->BeKill(param.param1, param.param2);
|
|
return;
|
|
}
|
|
int dec_hp = MetaMgr::Instance()->GetSysParamAsInt("downed_dec_hp");
|
|
hum->DecHP(dec_hp, param.param1, param.param2);
|
|
};
|
|
if (energy_shield > 0.001f) {
|
|
energy_shield = std::max(0.0f, energy_shield - dec_hp);
|
|
} else {
|
|
health = std::max(0.0f, health - dec_hp);
|
|
if (health <= 0.0001f && !dead) {
|
|
if (downed) {
|
|
if (downed_timer) {
|
|
room->xtimer.DeleteTimer(downed_timer);
|
|
}
|
|
downed = false;
|
|
downed_timer = nullptr;
|
|
BeKill(killer_id, killer_name);
|
|
} else {
|
|
if (HasLiveTeammate()) {
|
|
health = MetaMgr::Instance()->GetSysParamAsInt("downed_recover_hp");
|
|
downed = true;
|
|
downed_timer = room->xtimer.AddRepeatTimerAndAttach(
|
|
SERVER_FRAME_RATE,
|
|
a8::XParams()
|
|
.SetSender(this)
|
|
.SetParam1(killer_id)
|
|
.SetParam2(killer_name),
|
|
downed_func,
|
|
&xtimer_attacher.timer_list_
|
|
);
|
|
} else {
|
|
BeKill(killer_id, killer_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SyncAroundPlayers();
|
|
}
|
|
|
|
void Human::AddToNewObjects(Entity* entity)
|
|
{
|
|
new_objects.insert(entity);
|
|
}
|
|
|
|
void Human::AddToPartObjects(Entity* entity)
|
|
{
|
|
part_objects.insert(entity);
|
|
}
|
|
|
|
void Human::RemoveNewObjects(Entity* entity)
|
|
{
|
|
new_objects.erase(entity);
|
|
}
|
|
|
|
void Human::RemovePartObjects(Entity* entity)
|
|
{
|
|
part_objects.erase(entity);
|
|
}
|
|
|
|
bool Human::HasLiveTeammate()
|
|
{
|
|
if (team_members) {
|
|
for (auto& hum : *team_members) {
|
|
if (!hum->dead) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Human::Land()
|
|
{
|
|
a8::UnSetBitFlag(status, HS_Jump);
|
|
SyncAroundPlayers();
|
|
}
|
|
|
|
void Human::DoJump()
|
|
{
|
|
if (a8::HasBitFlag(status, HS_Fly)) {
|
|
a8::UnSetBitFlag(status, HS_Fly);
|
|
a8::SetBitFlag(status, HS_Jump);
|
|
jump_frameno = room->frame_no;
|
|
SyncAroundPlayers();
|
|
room->xtimer.AddDeadLineTimerAndAttach(MetaMgr::Instance()->jump_time,
|
|
a8::XParams()
|
|
.SetSender(this),
|
|
[] (const a8::XParams& param)
|
|
{
|
|
Human* hum = (Human*)param.sender.GetUserData();
|
|
hum->Land();
|
|
},
|
|
&xtimer_attacher.timer_list_);
|
|
}
|
|
}
|