1912 lines
74 KiB
C++
1912 lines
74 KiB
C++
#include "precompile.h"
|
|
|
|
#include <a8/mutable_xobject.h>
|
|
|
|
#include "human.h"
|
|
#include "cs_proto.pb.h"
|
|
#include "metamgr.h"
|
|
#include "room.h"
|
|
#include "bullet.h"
|
|
#include "collider.h"
|
|
#include "loot.h"
|
|
#include "collision.h"
|
|
#include "building.h"
|
|
#include "hero.h"
|
|
#include "app.h"
|
|
#include "roommgr.h"
|
|
|
|
#include "framework/cpp/utils.h"
|
|
#include "framework/cpp/httpclientpool.h"
|
|
|
|
Human::Human():Entity()
|
|
{
|
|
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);
|
|
default_weapon.Recalc();
|
|
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_[IS_1XSCOPE] = 1;
|
|
|
|
if (MetaMgr::Instance()->fighting_mode) {
|
|
inventory_[IS_9MM] = FIGHTING_MODE_BULLET_NUM;
|
|
inventory_[IS_556MM] = FIGHTING_MODE_BULLET_NUM;
|
|
inventory_[IS_762MM] = FIGHTING_MODE_BULLET_NUM;
|
|
inventory_[IS_12GAUGE] = FIGHTING_MODE_BULLET_NUM;
|
|
inventory_[IS_RPG] = FIGHTING_MODE_BULLET_NUM;
|
|
}
|
|
}
|
|
|
|
Human::~Human()
|
|
{
|
|
}
|
|
|
|
void Human::Initialize()
|
|
{
|
|
Entity::Initialize();
|
|
RecalcSelfCollider();
|
|
volume_ = meta->volume;
|
|
observers_.insert(this);
|
|
}
|
|
|
|
float Human::GetSpeed()
|
|
{
|
|
if (a8::HasBitFlag(status, HS_Jump)) {
|
|
return meta->i->jump_speed() + buff.speed;
|
|
} if (downed) {
|
|
return meta->i->move_speed3() + buff.speed;
|
|
} else {
|
|
return meta->i->move_speed() + buff.speed;
|
|
}
|
|
}
|
|
|
|
float Human::GetSpeed4()
|
|
{
|
|
return meta->i->move_speed4();
|
|
}
|
|
|
|
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);
|
|
skin.ToPB(p->mutable_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
|
|
{
|
|
p->set_max_energy_shield(max_energy_shield);
|
|
}
|
|
#endif
|
|
p->set_vip(vip);
|
|
p->set_sdmg(sdmg);
|
|
FillBodyState(p->mutable_states());
|
|
}
|
|
|
|
ColliderComponent* Human::GetBoxBound()
|
|
{
|
|
CircleCollider* collider = new CircleCollider();
|
|
collider->owner = this;
|
|
collider->pos = Vector2D();
|
|
collider->rad = GetRadius();
|
|
return collider;
|
|
}
|
|
|
|
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_out);
|
|
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
|
|
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;
|
|
if (room->OverBorder(bullet_born_pos, 0)) {
|
|
return;
|
|
}
|
|
}
|
|
room->frame_event.AddShot(this);
|
|
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;
|
|
Vector2D bullet_dir = attack_dir;
|
|
float bullet_angle = std::get<2>(tuple);
|
|
if (curr_weapon->meta->i->bullet_angle() >= 0.10f) {
|
|
int angle = (int)curr_weapon->meta->i->bullet_angle() * 1000;
|
|
if (curr_weapon->upgrade_meta) {
|
|
angle -= curr_weapon->upgrade_meta->attr[EA_BulletAngle] * 1000;
|
|
}
|
|
if (angle > 0) {
|
|
bullet_angle += (rand() % angle) / 1000.0f * (rand() % 2 == 0 ? 1 : -1);
|
|
}
|
|
}
|
|
bullet_dir.Rotate(bullet_angle / 180.0f);
|
|
room->frame_event.AddBullet(this, bullet_born_pos, attack_dir, fly_distance);
|
|
room->CreateBullet(this, curr_weapon, 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 (GetInventory(slot_id) > 0) {
|
|
DecInventory(slot_id, 1);
|
|
++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 (GetInventory(slot_id) > 0) {
|
|
DecInventory(slot_id, 1);
|
|
++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()
|
|
{
|
|
if (room->OverBorder(pos, meta->i->radius())){
|
|
return true;
|
|
}
|
|
|
|
if (a8::HasBitFlag(status, HS_Jump)) {
|
|
return false;
|
|
}
|
|
|
|
std::vector<Entity*> objects;
|
|
for (auto& grid : grid_list) {
|
|
for (Entity* entity : grid->entity_list) {
|
|
switch (entity->entity_type) {
|
|
case ET_Obstacle:
|
|
{
|
|
if (
|
|
(last_collision_door == nullptr || last_collision_door != entity) &&
|
|
TestCollision(entity)
|
|
){
|
|
objects.push_back(entity);
|
|
}
|
|
}
|
|
break;
|
|
case ET_Building:
|
|
{
|
|
if (TestCollision(entity)) {
|
|
objects.push_back(entity);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return !objects.empty();
|
|
}
|
|
|
|
void Human::FindPath()
|
|
{
|
|
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(), VP_SafeArea, "安全区", VW_SafeArea);
|
|
} else {
|
|
DecHP(room->gas_data.old_area_meta->i->hurt(), VP_SafeArea, "安全区", VW_SafeArea);
|
|
}
|
|
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->GetClipVolume()))
|
|
) {
|
|
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() < inventory_.size()
|
|
) {
|
|
if (GetInventory(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()
|
|
{
|
|
if (action_type == AT_Relive) {
|
|
Entity* entity = room->GetEntityByUniId(action_target_id);
|
|
if (entity->entity_type != ET_Player) {
|
|
Human* hum = (Human*)entity;
|
|
if (hum->action_type == AT_Rescue) {
|
|
hum->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
|
|
{
|
|
if (hum->leave_frameno_ == 0 || hum->leave_frameno_ > hum->room->battle_start_frameno_) {
|
|
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(team_id);
|
|
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, int weapon_id)
|
|
{
|
|
if (!dead && !room->game_over) {
|
|
Entity* hum = room->GetEntityByUniId(killer_id);
|
|
if (hum && hum->entity_type == ET_Player) {
|
|
if (killer_id == entity_uniid) {
|
|
std::string msg = a8::Format("%s 自杀",
|
|
{
|
|
killer_name,
|
|
});
|
|
SendRollMsg(msg);
|
|
} else {
|
|
((Human*)hum)->stats.kills++;
|
|
((Human*)hum)->kill_humans.insert(this);
|
|
MetaData::Equip* equip_meta = MetaMgr::Instance()->GetEquip(weapon_id);
|
|
if (equip_meta) {
|
|
std::string msg = a8::Format("%s 使用 %s 干掉了 %s",
|
|
{
|
|
killer_name,
|
|
equip_meta->i->name(),
|
|
name
|
|
});
|
|
SendRollMsg(msg);
|
|
}
|
|
}
|
|
} else {
|
|
switch (weapon_id) {
|
|
case VW_SafeArea:
|
|
{
|
|
std::string msg = a8::Format("%s 被毒圈干掉",
|
|
{
|
|
name
|
|
});
|
|
SendRollMsg(msg);
|
|
}
|
|
break;
|
|
case VW_Spectate:
|
|
{
|
|
std::string msg = a8::Format("%s 自杀",
|
|
{
|
|
name
|
|
});
|
|
SendRollMsg(msg);
|
|
}
|
|
break;
|
|
case VW_SelfDetonate:
|
|
{
|
|
std::string msg = a8::Format("%s 被炸死",
|
|
{
|
|
name
|
|
});
|
|
SendRollMsg(msg);
|
|
}
|
|
break;
|
|
case VW_Mine:
|
|
{
|
|
std::string msg = a8::Format("%s 被地雷炸死",
|
|
{
|
|
name
|
|
});
|
|
SendRollMsg(msg);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
stats.killer_id = killer_id;
|
|
stats.killer_name = killer_name;
|
|
stats.weapon_id = weapon_id;
|
|
dead = true;
|
|
health = 0.0f;
|
|
dead_frameno = room->frame_no;
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
if (!HasNoDownedTeammate() && !leave_) {
|
|
if (team_members) {
|
|
for (auto& member : *team_members) {
|
|
if (member == this) {
|
|
member->SendGameOver();
|
|
} else {
|
|
if (member->dead) {
|
|
member->SendGameOver();
|
|
} else if (member->downed) {
|
|
a8::XParams& timer_param = room->xtimer.GetTimerXParams(member->downed_timer);
|
|
member->stats.killer_id = timer_param.param1;
|
|
member->stats.killer_name = timer_param.param2.GetString();
|
|
member->stats.weapon_id = timer_param.param2;
|
|
member->dead = true;
|
|
member->health = 0.0f;
|
|
member->dead_frameno = room->frame_no;
|
|
member->room->OnHumanDie(this);
|
|
member->SyncAroundPlayers();
|
|
member->SendGameOver();
|
|
room->xtimer.DeleteTimer(member->downed_timer);
|
|
member->downed_timer = nullptr;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
SendGameOver();
|
|
}
|
|
}
|
|
if (room->GetAliveTeamNum() == 1) {
|
|
std::set<Human*>* members = room->GetAliveTeam();
|
|
if (members) {
|
|
for (Human* member : *members) {
|
|
member->SendGameOver();
|
|
}
|
|
}
|
|
}
|
|
DeadDrop();
|
|
}
|
|
}
|
|
|
|
void Human::DecHP(float dec_hp, int killer_id, const std::string& killer_name, int weapon_id)
|
|
{
|
|
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, param.param3);
|
|
return;
|
|
}
|
|
int dec_hp = MetaMgr::Instance()->GetSysParamAsInt("downed_dec_hp");
|
|
hum->DecHP(dec_hp, param.param1, param.param2, param.param3);
|
|
};
|
|
if (energy_shield > 0.001f) {
|
|
energy_shield = std::max(0.0f, energy_shield - dec_hp);
|
|
} else {
|
|
float old_health = health;
|
|
health = std::max(0.0f, health - dec_hp);
|
|
if (health - old_health > 0.001f) {
|
|
stats.damage_amount_in += health - old_health;
|
|
}
|
|
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, weapon_id);
|
|
} else {
|
|
if (HasNoDownedTeammate()) {
|
|
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)
|
|
.SetParam3(weapon_id),
|
|
downed_func,
|
|
&xtimer_attacher.timer_list_
|
|
);
|
|
} else {
|
|
BeKill(killer_id, killer_name, weapon_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SyncAroundPlayers();
|
|
}
|
|
|
|
void Human::AddToNewObjects(Entity* entity)
|
|
{
|
|
new_objects.insert(entity);
|
|
}
|
|
|
|
void Human::AddToPartObjects(Entity* entity)
|
|
{
|
|
part_objects.insert(entity);
|
|
}
|
|
|
|
void Human::RemovePartObjects(Entity* entity)
|
|
{
|
|
part_objects.erase(entity);
|
|
}
|
|
|
|
void Human::RemoveObjects(Entity* entity)
|
|
{
|
|
del_objects.insert(entity->entity_uniid);
|
|
}
|
|
|
|
void Human::AddOutObjects(Entity* entity)
|
|
{
|
|
out_objects.insert(entity->entity_uniid);
|
|
}
|
|
|
|
void Human::RemoveOutObjects(Entity* entity)
|
|
{
|
|
out_objects.erase(entity->entity_uniid);
|
|
}
|
|
|
|
bool Human::HasLiveTeammate()
|
|
{
|
|
if (team_members) {
|
|
for (auto& hum : *team_members) {
|
|
if (hum != this && !hum->dead) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Human::HasNoDownedTeammate()
|
|
{
|
|
if (team_members) {
|
|
for (auto& hum : *team_members) {
|
|
if (hum != this && !hum->dead && !hum->downed) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Human::Land()
|
|
{
|
|
a8::UnSetBitFlag(status, HS_Jump);
|
|
if (entity_subtype == EST_Android) {
|
|
MetaData::Equip* weapon_meta = MetaMgr::Instance()->GetEquip(a8::RandEx(12103, 12122));
|
|
if (weapon_meta) {
|
|
weapons[GUN_SLOT1].weapon_idx = GUN_SLOT1;
|
|
weapons[GUN_SLOT1].weapon_id = weapon_meta->i->id();
|
|
weapons[GUN_SLOT1].weapon_lv = 1;
|
|
weapons[GUN_SLOT1].ammo = 0;
|
|
weapons[GUN_SLOT1].meta = weapon_meta;
|
|
weapons[GUN_SLOT1].Recalc();
|
|
curr_weapon = &weapons[GUN_SLOT1];
|
|
}
|
|
}
|
|
FindLocation();
|
|
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 * SERVER_FRAME_RATE,
|
|
a8::XParams()
|
|
.SetSender(this),
|
|
[] (const a8::XParams& param)
|
|
{
|
|
Human* hum = (Human*)param.sender.GetUserData();
|
|
hum->Land();
|
|
},
|
|
&xtimer_attacher.timer_list_);
|
|
}
|
|
}
|
|
|
|
void Human::DoSkill()
|
|
{
|
|
if (skill_meta && skill_meta->i->condition() == SC_Active) {
|
|
int passed_time = (room->frame_no - last_use_skill_frameno_) * FRAME_RATE_MS;
|
|
int skill_left_time = std::max(0, skill_meta->GetLastTime(skin.skin_lv) * 1000 - passed_time);
|
|
if (skill_left_time <= 0 || last_use_skill_frameno_ == 0) {
|
|
skill_xtimer_attacher_.ClearTimerList();
|
|
switch (skill_meta->i->type()) {
|
|
case ST_Hide:
|
|
{
|
|
hide_frameno_ = room->frame_no;
|
|
a8::SetBitFlag(status, HS_Hide);
|
|
room->xtimer.AddDeadLineTimerAndAttach(skill_meta->GetLastTime(skin.skin_lv) * SERVER_FRAME_RATE,
|
|
a8::XParams()
|
|
.SetSender(this),
|
|
[] (const a8::XParams& param)
|
|
{
|
|
},
|
|
&skill_xtimer_attacher_.timer_list_,
|
|
[] (const a8::XParams& param)
|
|
{
|
|
Human* hum = (Human*)param.sender.GetUserData();
|
|
a8::UnSetBitFlag(hum->status, HS_Hide);
|
|
hum->need_sync_active_player = true;
|
|
hum->BroadcastFullState();
|
|
}
|
|
);
|
|
need_sync_active_player = true;
|
|
BroadcastFullState();
|
|
}
|
|
break;
|
|
case ST_SummonHero:
|
|
{
|
|
SummonHero();
|
|
}
|
|
break;
|
|
case ST_Accelerate:
|
|
{
|
|
accelerate_frameno_ = room->frame_no;
|
|
a8::SetBitFlag(status, HS_Accelerate);
|
|
buff.speed += skill_meta->GetValue1(skin.skin_lv);
|
|
room->xtimer.AddDeadLineTimerAndAttach(skill_meta->GetLastTime(skin.skin_lv) * SERVER_FRAME_RATE,
|
|
a8::XParams()
|
|
.SetSender(this),
|
|
[] (const a8::XParams& param)
|
|
{
|
|
},
|
|
&skill_xtimer_attacher_.timer_list_,
|
|
[] (const a8::XParams& param)
|
|
{
|
|
Human* hum = (Human*)param.sender.GetUserData();
|
|
a8::UnSetBitFlag(hum->status, HS_Accelerate);
|
|
hum->buff.speed = 0.0f;
|
|
hum->need_sync_active_player = true;
|
|
hum->BroadcastFullState();
|
|
}
|
|
);
|
|
need_sync_active_player = true;
|
|
BroadcastFullState();
|
|
}
|
|
break;
|
|
case ST_DamageAdd:
|
|
{
|
|
damageadd_frameno_ = room->frame_no;
|
|
a8::SetBitFlag(status, HS_DamageAdd);
|
|
room->xtimer.AddDeadLineTimerAndAttach(skill_meta->GetLastTime(skin.skin_lv) * SERVER_FRAME_RATE,
|
|
a8::XParams()
|
|
.SetSender(this),
|
|
[] (const a8::XParams& param)
|
|
{
|
|
},
|
|
&skill_xtimer_attacher_.timer_list_,
|
|
[] (const a8::XParams& param)
|
|
{
|
|
Human* hum = (Human*)param.sender.GetUserData();
|
|
a8::UnSetBitFlag(hum->status, HS_DamageAdd);
|
|
hum->need_sync_active_player = true;
|
|
hum->BroadcastFullState();
|
|
}
|
|
);
|
|
need_sync_active_player = true;
|
|
BroadcastFullState();
|
|
}
|
|
break;
|
|
case ST_DefAdd:
|
|
{
|
|
defadd_frameno_ = room->frame_no;
|
|
a8::SetBitFlag(status, HS_DefAdd);
|
|
room->xtimer.AddDeadLineTimerAndAttach(skill_meta->GetLastTime(skin.skin_lv) * SERVER_FRAME_RATE,
|
|
a8::XParams()
|
|
.SetSender(this),
|
|
[] (const a8::XParams& param)
|
|
{
|
|
},
|
|
&skill_xtimer_attacher_.timer_list_,
|
|
[] (const a8::XParams& param)
|
|
{
|
|
Human* hum = (Human*)param.sender.GetUserData();
|
|
a8::UnSetBitFlag(hum->status, HS_DefAdd);
|
|
hum->need_sync_active_player = true;
|
|
hum->BroadcastFullState();
|
|
}
|
|
);
|
|
need_sync_active_player = true;
|
|
BroadcastFullState();
|
|
}
|
|
break;
|
|
case ST_RecoverHP:
|
|
{
|
|
recover_hp_frameno_ = room->frame_no;
|
|
a8::SetBitFlag(status, HS_RecoverHP);
|
|
room->xtimer.AddRepeatTimerAndAttach(SERVER_FRAME_RATE,
|
|
a8::XParams()
|
|
.SetSender(this)
|
|
.SetParam1(skill_meta->i->value1()),
|
|
[] (const a8::XParams& param)
|
|
{
|
|
Human* hum = (Human*)param.sender.GetUserData();
|
|
if (a8::HasBitFlag(hum->status, HS_RecoverHP)) {
|
|
hum->RecoverHp(param.param1);
|
|
hum->need_sync_active_player = true;
|
|
}
|
|
},
|
|
&skill_xtimer_attacher_.timer_list_
|
|
);
|
|
room->xtimer.AddDeadLineTimerAndAttach(skill_meta->GetLastTime(skin.skin_lv) * SERVER_FRAME_RATE,
|
|
a8::XParams()
|
|
.SetSender(this),
|
|
[] (const a8::XParams& param)
|
|
{
|
|
},
|
|
&skill_xtimer_attacher_.timer_list_,
|
|
[] (const a8::XParams& param)
|
|
{
|
|
Human* hum = (Human*)param.sender.GetUserData();
|
|
a8::UnSetBitFlag(hum->status, HS_RecoverHP);
|
|
hum->need_sync_active_player = true;
|
|
hum->BroadcastFullState();
|
|
}
|
|
);
|
|
need_sync_active_player = true;
|
|
BroadcastFullState();
|
|
}
|
|
break;
|
|
case ST_ReflectDamage:
|
|
{
|
|
reflect_damage_frameno_ = room->frame_no;
|
|
a8::SetBitFlag(status, HS_ReflectDamage);
|
|
room->xtimer.AddDeadLineTimerAndAttach(skill_meta->GetLastTime(skin.skin_lv) * SERVER_FRAME_RATE,
|
|
a8::XParams()
|
|
.SetSender(this),
|
|
[] (const a8::XParams& param)
|
|
{
|
|
},
|
|
&skill_xtimer_attacher_.timer_list_,
|
|
[] (const a8::XParams& param)
|
|
{
|
|
Human* hum = (Human*)param.sender.GetUserData();
|
|
a8::UnSetBitFlag(hum->status, HS_ReflectDamage);
|
|
hum->need_sync_active_player = true;
|
|
hum->BroadcastFullState();
|
|
}
|
|
);
|
|
need_sync_active_player = true;
|
|
BroadcastFullState();
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
}
|
|
break;
|
|
}
|
|
last_use_skill_frameno_ = room->frame_no;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Human::FindLocation()
|
|
{
|
|
{
|
|
std::vector<Entity*> objects;
|
|
room->BuildingBoxBoundCollisionDetection(this, objects);
|
|
if (objects.size() > 1) {
|
|
abort();
|
|
}
|
|
if (!objects.empty()) {
|
|
Building* building = (Building*)objects[0];
|
|
Vector2D b_min = Vector2D(
|
|
building->pos.x - building->meta->i->tilewidth()/2.0,
|
|
building->pos.y - building->meta->i->tileheight()/2.0
|
|
);
|
|
Vector2D b_max = Vector2D(
|
|
building->pos.x + building->meta->i->tilewidth()/2.0,
|
|
building->pos.y + building->meta->i->tileheight()/2.0
|
|
);
|
|
Vector2D new_pos;
|
|
bool ret = CalcCircleAabbSafePoint(
|
|
pos,
|
|
GetRadius(),
|
|
b_min,
|
|
b_max,
|
|
new_pos
|
|
);
|
|
if (!ret) {
|
|
abort();
|
|
}
|
|
if (ret) {
|
|
pos = new_pos;
|
|
room->grid_service.MoveHuman(this);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
{
|
|
std::vector<Entity*> objects;
|
|
for (auto& grid : grid_list) {
|
|
for (Entity* entity : grid->entity_list) {
|
|
switch (entity->entity_type) {
|
|
case ET_Obstacle:
|
|
{
|
|
if (TestCollision(entity)){
|
|
objects.push_back(entity);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (objects.empty()) {
|
|
return;
|
|
}
|
|
std::sort(objects.begin(), objects.end(),
|
|
[this] (Entity* a, Entity *b) -> bool
|
|
{
|
|
return (this->pos - a->pos).Norm() < (this->pos - b->pos).Norm();
|
|
});
|
|
Entity* target = objects[0];
|
|
FindLocationWithTarget(target);
|
|
}
|
|
}
|
|
|
|
void Human::RefreshView()
|
|
{
|
|
for (auto& cell : grid_list) {
|
|
for (Human* hum : cell->human_list) {
|
|
hum->AddToNewObjects(this);
|
|
hum->AddToPartObjects(this);
|
|
AddToNewObjects(hum);
|
|
AddToPartObjects(hum);
|
|
}
|
|
for (Entity* entity : cell->entity_list) {
|
|
switch (entity->entity_type) {
|
|
case ET_Building:
|
|
case ET_Obstacle:
|
|
case ET_Hero:
|
|
{
|
|
AddToNewObjects(entity);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Human::OnGridListChange(std::set<GridCell*>& old_grid_list,
|
|
std::set<GridCell*>& inc_grid_list,
|
|
std::set<GridCell*>& dec_grid_list
|
|
)
|
|
{
|
|
for (GridCell* cell : inc_grid_list) {
|
|
for (Human* hum : cell->human_list) {
|
|
if (!room->grid_service.HumanInGridList(hum, old_grid_list)) {
|
|
hum->AddToNewObjects(this);
|
|
hum->AddToPartObjects(this);
|
|
hum->RemoveOutObjects(this);
|
|
AddToNewObjects(hum);
|
|
AddToPartObjects(hum);
|
|
RemoveOutObjects(hum);
|
|
}
|
|
}
|
|
for (Entity* entity : cell->entity_list) {
|
|
if (!room->grid_service.EntityInGridList(entity, old_grid_list)) {
|
|
switch (entity->entity_type) {
|
|
case ET_Building:
|
|
case ET_Obstacle:
|
|
case ET_Hero:
|
|
{
|
|
AddToNewObjects(entity);
|
|
RemoveOutObjects(entity);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (GridCell* cell : dec_grid_list) {
|
|
for (Human* hum : cell->human_list) {
|
|
if (!room->grid_service.HumanInGridList(hum, grid_list)) {
|
|
AddOutObjects(hum);
|
|
hum->AddOutObjects(this);
|
|
}
|
|
}
|
|
for (Entity* entity : cell->entity_list) {
|
|
if (!room->grid_service.EntityInGridList(entity, grid_list)) {
|
|
switch (entity->entity_type) {
|
|
case ET_Building:
|
|
case ET_Obstacle:
|
|
case ET_Hero:
|
|
{
|
|
AddOutObjects(entity);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Human::FillMFActivePlayerData(cs::MFActivePlayerData* player_data)
|
|
{
|
|
{
|
|
player_data->set_action_type(action_type);
|
|
if (action_type != AT_None) {
|
|
int duration = std::max(0,
|
|
action_duration -
|
|
(int)((room->frame_no - action_frameno) * 1.0f / SERVER_FRAME_RATE) * 1000
|
|
);
|
|
player_data->set_action_item_id(action_item_id);
|
|
player_data->set_action_duration(duration);
|
|
player_data->set_action_target_id(action_target_id);
|
|
}
|
|
}
|
|
skin.ToPB(player_data->mutable_skin());
|
|
player_data->set_backpack(backpack);
|
|
player_data->set_helmet(helmet);
|
|
player_data->set_chest(chest);
|
|
player_data->set_health(health);
|
|
player_data->set_max_health(GetMaxHP());
|
|
player_data->set_cur_weapon_idx(curr_weapon->weapon_idx);
|
|
player_data->set_cur_scope(curr_scope_idx);
|
|
for (auto& weapon : weapons) {
|
|
auto p = player_data->add_weapons();
|
|
weapon.ToPB(p);
|
|
}
|
|
for (auto& num : inventory_) {
|
|
player_data->add_inventory(num);
|
|
}
|
|
player_data->set_energy_shield(energy_shield);
|
|
#if 1
|
|
{
|
|
player_data->set_max_energy_shield(max_energy_shield);
|
|
}
|
|
#endif
|
|
#if 1
|
|
if (skill_meta) {
|
|
if (last_use_skill_frameno_ == 0) {
|
|
player_data->set_skill_left_time(0);
|
|
player_data->set_skill_cd_time(skill_meta->i->cd_time() * 1000);
|
|
} else {
|
|
int passed_time = (room->frame_no - last_use_skill_frameno_) * FRAME_RATE_MS;
|
|
int skill_left_time = std::max(0, skill_meta->i->cd_time() * 1000 - passed_time);
|
|
player_data->set_skill_left_time(skill_left_time);
|
|
player_data->set_skill_cd_time(skill_meta->i->cd_time() * 1000);
|
|
}
|
|
}
|
|
#endif
|
|
FillBodyState(player_data->mutable_states());
|
|
}
|
|
|
|
void Human::FillMFGasData(cs::MFGasData* gas_data)
|
|
{
|
|
gas_data->set_mode(room->gas_data.gas_mode);
|
|
if (room->gas_data.gas_mode == GasInactive) {
|
|
long long duration = MetaMgr::Instance()->gas_inactive_time * SERVER_FRAME_RATE -
|
|
(room->frame_no - room->gas_data.gas_start_frameno);
|
|
gas_data->set_duration(std::max(duration * 50, (long long)1000) / 1000);
|
|
} else if (room->gas_data.gas_mode == GasJump) {
|
|
gas_data->set_duration(0);
|
|
} else if (room->gas_data.gas_mode == GasMoving) {
|
|
if (room->gas_data.new_area_meta->i->shrink_speed() > 0.01f) {
|
|
long long duration = (room->gas_data.old_area_meta->i->rad() - room->gas_data.new_area_meta->i->rad()) /
|
|
room->gas_data.new_area_meta->i->shrink_speed();
|
|
++duration;
|
|
gas_data->set_duration(++duration);
|
|
} else {
|
|
gas_data->set_duration(0);
|
|
}
|
|
} else {
|
|
if (room->gas_data.old_area_meta->i->wait_time() <= 0) {
|
|
gas_data->set_duration(0);
|
|
} else {
|
|
long long duration = room->gas_data.old_area_meta->i->wait_time() * 20 -
|
|
(room->frame_no - room->gas_data.gas_start_frameno);
|
|
gas_data->set_duration(std::max(duration * 50, (long long)1000) / 1000);
|
|
}
|
|
}
|
|
room->gas_data.pos_old.ToPB(gas_data->mutable_pos_old());
|
|
room->gas_data.pos_new.ToPB(gas_data->mutable_pos_new());
|
|
gas_data->set_rad_old(room->gas_data.rad_old);
|
|
gas_data->set_rad_new(room->gas_data.rad_new);
|
|
}
|
|
|
|
bool Human::CanSee(const Human* hum) const
|
|
{
|
|
return room->grid_service.InView(grid_id, hum->grid_id);
|
|
}
|
|
|
|
void Human::RecalcAttr()
|
|
{
|
|
def = meta->i->def();
|
|
MetaData::Equip* chest_meta = MetaMgr::Instance()->GetEquip(chest);
|
|
if (chest_meta) {
|
|
def += chest_meta->i->def();
|
|
}
|
|
MetaData::Equip* helmet_meta = MetaMgr::Instance()->GetEquip(helmet);
|
|
if (helmet_meta) {
|
|
def += helmet_meta->i->def();
|
|
}
|
|
}
|
|
|
|
void Human::RecalcVolume()
|
|
{
|
|
MetaData::Equip* backpack_meta = MetaMgr::Instance()->GetEquip(backpack);
|
|
if (backpack_meta) {
|
|
for (size_t i = 0; i < backpack_meta->volume.size(); ++i) {
|
|
volume_[i] = meta->volume[i] + backpack_meta->volume[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
void Human::RecalcBuff()
|
|
{
|
|
buff = HumanAbility();
|
|
if (skin_meta) {
|
|
|
|
}
|
|
}
|
|
|
|
int Human::GetInventory(int slot_id)
|
|
{
|
|
if (!IsValidSlotId(slot_id)) {
|
|
abort();
|
|
}
|
|
return inventory_[slot_id];
|
|
}
|
|
|
|
void Human::AddInventory(int slot_id, int num)
|
|
{
|
|
assert(num > 0);
|
|
if (!IsValidSlotId(slot_id)) {
|
|
abort();
|
|
}
|
|
inventory_[slot_id] += num;
|
|
}
|
|
|
|
void Human::DecInventory(int slot_id, int num)
|
|
{
|
|
assert(num > 0);
|
|
if (!IsValidSlotId(slot_id)) {
|
|
abort();
|
|
}
|
|
inventory_[slot_id] -= num;
|
|
}
|
|
|
|
int Human::GetVolume(int slot_id)
|
|
{
|
|
if (!IsValidSlotId(slot_id)) {
|
|
abort();
|
|
}
|
|
return volume_[slot_id];
|
|
}
|
|
|
|
void Human::RecoverHp(int inc_hp)
|
|
{
|
|
if (!dead) {
|
|
health += inc_hp;
|
|
health = std::max(health, GetMaxHP());
|
|
}
|
|
}
|
|
|
|
void Human::FillBodyState(::google::protobuf::RepeatedPtrField<::cs::MFBodyState>* states)
|
|
{
|
|
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 = states->Add();
|
|
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 = states->Add();
|
|
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 = states->Add();
|
|
state->set_state_type(3);
|
|
state->set_left_time(std::max(0, MetaMgr::Instance()->jump_time * 1000 - passed_time));
|
|
state->set_lasting_time(MetaMgr::Instance()->jump_time * 1000);
|
|
}
|
|
if (a8::HasBitFlag(status, HS_Hide) && skill_meta) {
|
|
int passed_time = (room->frame_no - hide_frameno_) * FRAME_RATE_MS;
|
|
cs::MFBodyState* state = states->Add();
|
|
state->set_state_type(HS_Hide);
|
|
state->set_left_time(std::max(0, skill_meta->GetLastTime(skin.skin_lv) * 1000 - passed_time));
|
|
state->set_lasting_time(skill_meta->GetLastTime(skin.skin_lv) * 1000);
|
|
}
|
|
if (a8::HasBitFlag(status, HS_Accelerate) && skill_meta) {
|
|
int passed_time = (room->frame_no - accelerate_frameno_) * FRAME_RATE_MS;
|
|
cs::MFBodyState* state = states->Add();
|
|
state->set_state_type(HS_Accelerate);
|
|
state->set_left_time(std::max(0, skill_meta->GetLastTime(skin.skin_lv) * 1000 - passed_time));
|
|
state->set_lasting_time(skill_meta->GetLastTime(skin.skin_lv) * 1000);
|
|
}
|
|
if (a8::HasBitFlag(status, HS_DamageAdd) && skill_meta) {
|
|
int passed_time = (room->frame_no - damageadd_frameno_) * FRAME_RATE_MS;
|
|
cs::MFBodyState* state = states->Add();
|
|
state->set_state_type(HS_DamageAdd);
|
|
state->set_left_time(std::max(0, skill_meta->GetLastTime(skin.skin_lv) * 1000 - passed_time));
|
|
state->set_lasting_time(skill_meta->GetLastTime(skin.skin_lv) * 1000);
|
|
}
|
|
if (a8::HasBitFlag(status, HS_DefAdd) && skill_meta) {
|
|
int passed_time = (room->frame_no - defadd_frameno_) * FRAME_RATE_MS;
|
|
cs::MFBodyState* state = states->Add();
|
|
state->set_state_type(HS_DefAdd);
|
|
state->set_left_time(std::max(0, skill_meta->GetLastTime(skin.skin_lv) * 1000 - passed_time));
|
|
state->set_lasting_time(skill_meta->GetLastTime(skin.skin_lv) * 1000);
|
|
}
|
|
if (a8::HasBitFlag(status, HS_RecoverHP) && skill_meta) {
|
|
int passed_time = (room->frame_no - recover_hp_frameno_) * FRAME_RATE_MS;
|
|
cs::MFBodyState* state = states->Add();
|
|
state->set_state_type(HS_RecoverHP);
|
|
state->set_left_time(std::max(0, skill_meta->GetLastTime(skin.skin_lv) * 1000 - passed_time));
|
|
state->set_lasting_time(skill_meta->GetLastTime(skin.skin_lv) * 1000);
|
|
}
|
|
if (a8::HasBitFlag(status, HS_ReflectDamage) && skill_meta) {
|
|
int passed_time = (room->frame_no - reflect_damage_frameno_) * FRAME_RATE_MS;
|
|
cs::MFBodyState* state = states->Add();
|
|
state->set_state_type(HS_ReflectDamage);
|
|
state->set_left_time(std::max(0, skill_meta->GetLastTime(skin.skin_lv) * 1000 - passed_time));
|
|
state->set_lasting_time(skill_meta->GetLastTime(skin.skin_lv) * 1000);
|
|
}
|
|
if (a8::HasBitFlag(status, HS_SummonHero) && skill_meta) {
|
|
int passed_time = (room->frame_no - summon_hero_frameno_) * FRAME_RATE_MS;
|
|
cs::MFBodyState* state = states->Add();
|
|
state->set_state_type(HS_SummonHero);
|
|
state->set_left_time(std::max(0, skill_meta->GetLastTime(skin.skin_lv) * 1000 - passed_time));
|
|
state->set_lasting_time(skill_meta->GetLastTime(skin.skin_lv) * 1000);
|
|
}
|
|
}
|
|
|
|
void Human::SummonHero()
|
|
{
|
|
Hero* hero = room->CreateHero(this);
|
|
if (hero) {
|
|
summon_hero_frameno_ = room->frame_no;
|
|
a8::SetBitFlag(status, HS_SummonHero);
|
|
room->xtimer.AddDeadLineTimerAndAttach(skill_meta->GetLastTime(skin.skin_lv) * SERVER_FRAME_RATE,
|
|
a8::XParams()
|
|
.SetSender(this)
|
|
.SetParam1(hero->entity_uniid),
|
|
[] (const a8::XParams& param)
|
|
{
|
|
},
|
|
&skill_xtimer_attacher_.timer_list_,
|
|
[] (const a8::XParams& param)
|
|
{
|
|
Human* hum = (Human*)param.sender.GetUserData();
|
|
Entity* hero = hum->room->GetEntityByUniId(param.param1);
|
|
if (hero && hero->entity_type == ET_Hero) {
|
|
hum->room->RemoveObjectLater(hero);
|
|
}
|
|
a8::UnSetBitFlag(hum->status, HS_SummonHero);
|
|
hum->need_sync_active_player = true;
|
|
hum->BroadcastFullState();
|
|
}
|
|
);
|
|
need_sync_active_player = true;
|
|
BroadcastFullState();
|
|
}
|
|
}
|
|
|
|
void Human::AddObserver(Human* observer)
|
|
{
|
|
observers_.insert(observer);
|
|
}
|
|
|
|
void Human::RemoveObserver(Human* observer)
|
|
{
|
|
observers_.erase(observer);
|
|
}
|
|
|
|
void Human::SendUpdateMsg()
|
|
{
|
|
if (!follow_target_) {
|
|
if (send_msg_times == 0) {
|
|
room->FetchBuilding(this);
|
|
}
|
|
cs::MFActivePlayerData* active_player_data_pb = nullptr;
|
|
if (send_msg_times == 0 || need_sync_active_player) {
|
|
active_player_data_pb = new cs::MFActivePlayerData();
|
|
FillMFActivePlayerData(active_player_data_pb);
|
|
need_sync_active_player = false;
|
|
}
|
|
|
|
cs::SMUpdate* msg = room->frame_maker.MakeUpdateMsg(this);
|
|
if (send_msg_times == 0 || last_sync_gas_frameno < room->gas_data.gas_start_frameno) {
|
|
last_sync_gas_frameno = room->gas_data.gas_start_frameno;
|
|
FillMFGasData(msg->mutable_gas_data());
|
|
}
|
|
bool refreshed_view = false;
|
|
std::set<Entity*> view_objects;
|
|
for (Human* observer : observers_) {
|
|
msg->clear_team_data();
|
|
if (observer->team_members) {
|
|
for (auto& itr : *observer->team_members) {
|
|
if (itr != observer) {
|
|
itr->FillMFTeamData(msg->add_team_data());
|
|
}
|
|
}
|
|
}
|
|
if (observer != this && !observer->follow_synced_active_player) {
|
|
msg->set_active_player_id(entity_uniid);
|
|
FillMFActivePlayerData(msg->mutable_active_player_data());
|
|
if (!refreshed_view) {
|
|
for (auto& cell : grid_list) {
|
|
for (Human* hum : cell->human_list) {
|
|
view_objects.insert(hum);
|
|
}
|
|
for (Entity* entity : cell->entity_list) {
|
|
switch (entity->entity_type) {
|
|
case ET_Building:
|
|
case ET_Obstacle:
|
|
case ET_Hero:
|
|
{
|
|
view_objects.insert(entity);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (Entity* entity : view_objects) {
|
|
if (new_objects.find(entity) == new_objects.end()) {
|
|
entity->FillMFObjectFull(msg->add_full_objects());
|
|
}
|
|
}
|
|
refreshed_view = true;
|
|
}
|
|
observer->follow_synced_active_player = true;
|
|
} else {
|
|
if (active_player_data_pb) {
|
|
msg->set_active_player_id(entity_uniid);
|
|
*msg->mutable_active_player_data() = *active_player_data_pb;
|
|
} else {
|
|
msg->clear_active_player_id();
|
|
msg->clear_active_player_data();
|
|
}
|
|
}
|
|
observer->SendNotifyMsg(*msg);
|
|
}
|
|
delete msg;
|
|
|
|
if (active_player_data_pb) {
|
|
delete active_player_data_pb;
|
|
}
|
|
++send_msg_times;
|
|
}
|
|
ClearFrameData();
|
|
}
|
|
|
|
void Human::SendGameOver()
|
|
{
|
|
if (!sending_gameover_) {
|
|
InternalSendGameOver();
|
|
}
|
|
}
|
|
|
|
void Human::FollowTarget(Human* target)
|
|
{
|
|
if (target == this) {
|
|
return;
|
|
}
|
|
if (follow_target_) {
|
|
follow_target_->RemoveObserver(this);
|
|
}
|
|
target->AddObserver(this);
|
|
follow_target_ = target;
|
|
follow_synced_active_player = false;
|
|
}
|
|
|
|
void Human::SendDebugMsg(const std::string& debug_msg)
|
|
{
|
|
cs::SMDebugMsg notify_msg;
|
|
notify_msg.set_debug_msg(debug_msg);
|
|
SendNotifyMsg(notify_msg);
|
|
}
|
|
|
|
void Human::SendRollMsg(const std::string& roll_msg)
|
|
{
|
|
room->xtimer.AddDeadLineTimerAndAttach(
|
|
0,
|
|
a8::XParams()
|
|
.SetSender(this)
|
|
.SetParam1(roll_msg),
|
|
[] (const a8::XParams& param)
|
|
{
|
|
Human* target = (Human*)param.sender.GetUserData();
|
|
std::string roll_msg = param.param1;
|
|
target->room->TouchHumanList(a8::XParams(),
|
|
[target, roll_msg] (Human* hum, a8::XParams& param) -> bool
|
|
{
|
|
if (target != hum) {
|
|
cs::SMRollMsg msg;
|
|
msg.set_msg(roll_msg);
|
|
hum->SendNotifyMsg(msg);
|
|
}
|
|
return true;
|
|
});
|
|
},
|
|
&xtimer_attacher.timer_list_
|
|
);
|
|
}
|
|
|
|
void Human::UpdateAction()
|
|
{
|
|
int duration = std::max(0,
|
|
action_duration -
|
|
(int)((room->frame_no - action_frameno) * 1.0f / SERVER_FRAME_RATE) * 1000
|
|
);
|
|
if (duration <= 0) {
|
|
switch (action_type) {
|
|
case AT_Reload:
|
|
{
|
|
if (curr_weapon->weapon_idx == action_target_id &&
|
|
curr_weapon->weapon_id == action_item_id &&
|
|
curr_weapon->weapon_idx != 0) {
|
|
MetaData::Equip* bullet_meta = MetaMgr::Instance()->GetEquip(curr_weapon->meta->i->use_bullet());
|
|
if (bullet_meta) {
|
|
int ammo = curr_weapon->ammo;
|
|
if (ammo < curr_weapon->GetClipVolume()) {
|
|
if (bullet_meta->i->_inventory_slot() >= 0 &&
|
|
bullet_meta->i->_inventory_slot() < IS_END) {
|
|
if (GetInventory(bullet_meta->i->_inventory_slot()) > 0) {
|
|
int add_num = 0;
|
|
if (GetInventory(bullet_meta->i->_inventory_slot()) <=
|
|
curr_weapon->GetClipVolume() - ammo) {
|
|
add_num = GetInventory(bullet_meta->i->_inventory_slot());
|
|
DecInventory(bullet_meta->i->_inventory_slot(), add_num);
|
|
} else {
|
|
add_num = curr_weapon->GetClipVolume() - ammo;
|
|
DecInventory(bullet_meta->i->_inventory_slot(), add_num);
|
|
}
|
|
curr_weapon->ammo += add_num;
|
|
need_sync_active_player = true;;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case AT_UseItem:
|
|
{
|
|
switch (action_item_id) {
|
|
case IS_HEALTHKIT:
|
|
{
|
|
MetaData::Equip* item_meta = MetaMgr::Instance()->GetEquipBySlotId(action_item_id);
|
|
if (item_meta){
|
|
if (GetInventory(item_meta->i->_inventory_slot()) > 0) {
|
|
float old_health = health;
|
|
health += item_meta->i->heal();
|
|
health = std::min(health, GetMaxHP());
|
|
stats.heal_amount += health - old_health;
|
|
DecInventory(item_meta->i->_inventory_slot(), 1);
|
|
need_sync_active_player = true;
|
|
SyncAroundPlayers();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case IS_PAIN_KILLER:
|
|
{
|
|
MetaData::Equip* item_meta = MetaMgr::Instance()->GetEquipBySlotId(action_item_id);
|
|
if (item_meta){
|
|
if (GetInventory(item_meta->i->_inventory_slot()) > 0) {
|
|
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);
|
|
pain_killer_lastingtime += std::min(item_meta->i->time() * 1000, anodyne_max_time * 1000 - left_time) / 1000;
|
|
need_sync_active_player = true;
|
|
} else {
|
|
pain_killer_frameno = room->frame_no;
|
|
pain_killer_lastingtime = item_meta->i->time();
|
|
pain_killer_timer = room->xtimer.AddRepeatTimerAndAttach(
|
|
SERVER_FRAME_RATE,
|
|
a8::XParams()
|
|
.SetSender(this)
|
|
.SetParam1(item_meta->i->heal()),
|
|
[] (const a8::XParams& param)
|
|
{
|
|
Human* hum = (Human*)param.sender.GetUserData();
|
|
float old_health = hum->health;
|
|
hum->health += param.param1.GetDouble();
|
|
hum->health = std::min(hum->health, hum->GetMaxHP());
|
|
hum->stats.heal_amount += hum->health - old_health;
|
|
hum->SyncAroundPlayers();
|
|
if (hum->room->frame_no - hum->pain_killer_frameno > hum->pain_killer_lastingtime * SERVER_FRAME_RATE) {
|
|
hum->room->xtimer.DeleteTimer(hum->pain_killer_timer);
|
|
hum->pain_killer_timer = nullptr;
|
|
}
|
|
},
|
|
&xtimer_attacher.timer_list_
|
|
);
|
|
}
|
|
DecInventory(item_meta->i->_inventory_slot(), 1);
|
|
need_sync_active_player = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case AT_Relive:
|
|
{
|
|
Entity* entity = room->GetEntityByUniId(action_target_id);
|
|
if (entity->entity_type != ET_Player) {
|
|
return;
|
|
}
|
|
Human* hum = (Human*)entity;
|
|
if (hum->action_type == AT_Rescue) {
|
|
hum->CancelAction();
|
|
return;
|
|
}
|
|
if (!hum->dead && hum->downed) {
|
|
hum->health = MetaMgr::Instance()->GetSysParamAsInt("downed_relive_recover_hp");
|
|
hum->downed = false;
|
|
if (hum->downed_timer) {
|
|
room->xtimer.DeleteTimer(hum->downed_timer);
|
|
hum->downed_timer = nullptr;
|
|
}
|
|
++hum->stats.rescue_member;
|
|
}
|
|
hum->SyncAroundPlayers();
|
|
}
|
|
break;
|
|
}
|
|
ResetAction();
|
|
}
|
|
}
|
|
|
|
void Human::SendUIUpdate()
|
|
{
|
|
cs::SMUiUpdate notifymsg;
|
|
notifymsg.set_alive_count(room->AliveCount());
|
|
notifymsg.set_kill_count(stats.kills);
|
|
SendNotifyMsg(notifymsg);
|
|
}
|
|
|
|
void Human::SendWxVoip()
|
|
{
|
|
cs::SMWxVoip notifymsg;
|
|
notifymsg.set_group_id(a8::XValue(room->room_uuid).GetString());
|
|
SendNotifyMsg(notifymsg);
|
|
}
|
|
|
|
int Human::GetWeaponConfigLv(int weapon_id)
|
|
{
|
|
auto itr = weapon_configs.find(weapon_id);
|
|
return itr != weapon_configs.end() ? itr->second : 0;
|
|
}
|
|
|
|
int Human::GetSkinConfigLv(int skin_id)
|
|
{
|
|
auto itr = skin_configs.find(skin_id);
|
|
return itr != skin_configs.end() ? itr->second : 0;
|
|
}
|
|
|
|
void Human::ClearFrameData()
|
|
{
|
|
if (!new_objects.empty()) {
|
|
new_objects.clear();
|
|
}
|
|
if (!del_objects.empty()) {
|
|
for (auto& itr : del_objects) {
|
|
Entity* entity = room->GetEntityByUniId(itr);
|
|
if (entity) {
|
|
part_objects.erase(entity);
|
|
}
|
|
}
|
|
del_objects.clear();
|
|
}
|
|
if (!out_objects.empty()) {
|
|
for (auto& itr : out_objects) {
|
|
Entity* entity = room->GetEntityByUniId(itr);
|
|
if (entity) {
|
|
part_objects.erase(entity);
|
|
}
|
|
}
|
|
out_objects.clear();
|
|
}
|
|
if (!shots_.empty()) {
|
|
shots_.clear();
|
|
}
|
|
if (!bullets_.empty()) {
|
|
bullets_.clear();
|
|
}
|
|
if (!explosions_.empty()) {
|
|
explosions_.clear();
|
|
}
|
|
if (!smokes_.empty()) {
|
|
smokes_.clear();
|
|
}
|
|
if (!emotes_.empty()) {
|
|
emotes_.clear();
|
|
}
|
|
}
|
|
|
|
void Human::GenBattleReportData(a8::MutableXObject* params)
|
|
{
|
|
int rank = 0;
|
|
{
|
|
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);
|
|
}
|
|
});
|
|
rank = human_list.size();
|
|
for (size_t i = 0; i < human_list.size(); ++i) {
|
|
if (human_list[i] == this) {
|
|
rank = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
params->SetVal("account_id", account_id);
|
|
params->SetVal("map_name", room->map_meta->i->map_name());
|
|
params->SetVal("game_time", time(nullptr));
|
|
params->SetVal("hurt", stats.damage_amount_in);
|
|
params->SetVal("rank", rank);
|
|
params->SetVal("kills", stats.kills);
|
|
params->SetVal("harm", stats.damage_amount_out);
|
|
params->SetVal("add_HP", stats.heal_amount);
|
|
if (!dead) {
|
|
params->SetVal("alive_time", room->frame_no * 1000.0f / SERVER_FRAME_RATE);
|
|
} else {
|
|
params->SetVal("alive_time", dead_frameno * 1000.0f / SERVER_FRAME_RATE);
|
|
}
|
|
params->SetVal("team_status", team_id != 0);
|
|
|
|
int snipe_kill = 0;
|
|
int rifle_kill = 0;
|
|
int pistol_kill = 0;
|
|
int submachine_kill = 0;
|
|
for (Human* hum : kill_humans) {
|
|
MetaData::Equip* equip_meta = MetaMgr::Instance()->GetEquip(hum->lethal_weapon);
|
|
if (equip_meta) {
|
|
switch (equip_meta->i->equip_subtype()) {
|
|
case 7:
|
|
{
|
|
//狙击枪
|
|
++snipe_kill;
|
|
}
|
|
break;
|
|
case 5:
|
|
{
|
|
//步枪
|
|
++rifle_kill;
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
//手枪
|
|
++pistol_kill;
|
|
}
|
|
break;
|
|
case 3:
|
|
{
|
|
//冲锋枪
|
|
++submachine_kill;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
params->SetVal("snipe_kill", snipe_kill);
|
|
params->SetVal("rifle_kill", rifle_kill);
|
|
params->SetVal("pistol_kill", pistol_kill);
|
|
params->SetVal("submachine_kill", submachine_kill);
|
|
|
|
params->SetVal("rescue_member", stats.rescue_member);
|
|
{
|
|
int coin_num = (rank * MetaMgr::Instance()->rank_param) + (stats.kills * MetaMgr::Instance()->kill_param);
|
|
stats.gold = coin_num;
|
|
params->SetVal("coin_num", coin_num);
|
|
}
|
|
params->SetVal("score", 0);
|
|
}
|
|
|
|
void Human::InternalSendGameOver()
|
|
{
|
|
if (already_report_battle_) {
|
|
cs::SMGameOver msg;
|
|
FillSMGameOver(msg);
|
|
SendNotifyMsg(msg);
|
|
return;
|
|
}
|
|
a8::MutableXObject* params = a8::MutableXObject::NewObject();
|
|
GenBattleReportData(params);
|
|
auto on_ok = [] (a8::XParams& param, a8::XObject& data)
|
|
{
|
|
long long room_uuid = param.sender;
|
|
int hum_uniid = param.param1;
|
|
Room* room = RoomMgr::Instance()->GetRoomByUuid(room_uuid);
|
|
if (room) {
|
|
Entity* entity = room->GetEntityByUniId(hum_uniid);
|
|
if (entity && entity->entity_type == ET_Player) {
|
|
Human* hum = (Human*)entity;
|
|
hum->sending_gameover_ = false;
|
|
hum->already_report_battle_ = true;
|
|
hum->stats.history_time_alive = data.Get("alive_time_his");
|
|
hum->stats.history_kills = data.Get("kill_his");
|
|
hum->stats.history_damage_amount = data.Get("harm_his");
|
|
hum->stats.history_heal_amount = data.Get("add_HP_his");
|
|
cs::SMGameOver msg;
|
|
hum->FillSMGameOver(msg);
|
|
hum->SendNotifyMsg(msg);
|
|
}
|
|
}
|
|
};
|
|
auto on_error = [] (a8::XParams& param, const std::string& response)
|
|
{
|
|
long long room_uuid = param.sender;
|
|
int hum_uniid = param.param1;
|
|
Room* room = RoomMgr::Instance()->GetRoomByUuid(room_uuid);
|
|
if (room) {
|
|
Entity* entity = room->GetEntityByUniId(hum_uniid);
|
|
if (entity && entity->entity_type == ET_Player) {
|
|
Human* hum = (Human*)entity;
|
|
hum->sending_gameover_ = false;
|
|
++hum->send_gameover_trycount_;
|
|
if (hum->send_gameover_trycount_ < 10){
|
|
hum->SendGameOver();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
std::string url;
|
|
if (!f8::IsOnlineEnv()) {
|
|
if (App::Instance()->flags.find(3) != App::Instance()->flags.end()) {
|
|
url = "http://192.168.100.41/webapp/index.php?c=Role&a=battleReport";
|
|
} else {
|
|
url = "https://game2001api-test.kingsome.cn/webapp/index.php?c=Role&a=battleReport";
|
|
}
|
|
} else {
|
|
url = "https://game2001api.kingsome.cn/webapp/index.php?c=Role&a=battleReport";
|
|
}
|
|
f8::HttpClientPool::Instance()->HttpGet(
|
|
a8::XParams()
|
|
.SetSender(room->room_uuid)
|
|
.SetParam1(entity_uniid),
|
|
on_ok,
|
|
on_error,
|
|
url.c_str(),
|
|
*params,
|
|
rand()
|
|
);
|
|
delete params;
|
|
sending_gameover_ = true;
|
|
}
|
|
|
|
void Human::DeadDrop()
|
|
{
|
|
for (auto& weapon : weapons) {
|
|
if (weapon.weapon_id != 0 && weapon.weapon_id != default_weapon.weapon_id) {
|
|
Vector2D drop_pos = pos;
|
|
room->DropItem(drop_pos, weapon.weapon_id, 1, weapon.weapon_lv);
|
|
}
|
|
}
|
|
for (size_t slot = 0; slot < inventory_.size(); ++slot) {
|
|
if (inventory_[slot] > 0) {
|
|
MetaData::Equip* equip_meta = MetaMgr::Instance()->GetEquipBySlotId(slot);
|
|
if (equip_meta) {
|
|
if (equip_meta->i->equip_type() == 2 &&
|
|
MetaMgr::Instance()->fighting_mode) {
|
|
return;
|
|
}
|
|
Vector2D drop_pos = pos;
|
|
room->DropItem(drop_pos, equip_meta->i->id(), inventory_[slot], 1);
|
|
}
|
|
}
|
|
}
|
|
}
|