Files
Sun1602/Server/GameServer/Player_Attack.cpp
T
2022-10-26 12:25:11 +08:00

717 lines
27 KiB
C++

#include "stdafx.h"
#include <float.h>
#include <PublicMath.h>
#include <ItemFormula.h>
#include <SkillInfoParser.h>
#include "GameZoneCommon.h"
#include "CharacterFormula.h"
#include "Player.h"
#include "StatusManager.h"
#include "ItemManager.h"
#include "PVPInfo.h"
#include "Summoned.h"
#include "ChaoState.h"
#include "DragonTransStatus.h"
#include "WarehouseSlotContainer.h"
#include "StateInfoParser.h"
#include "./PlayerRider.h"
#include "GameZone.h"
//────────────────────────────────────────────────────────────────
BOOL
Player::CanAttack()
{
// (WAVERIX)(080907)(NOTE)
// - 이것 어캐 안되나? 그때 그때 이런 식으로 예외처리가 수행되다보니 구조상 또는 유지보수도 힘든
// 예외들이 아래와 같이 추가되고 있다. 일단, SSQ관련 작업 이전에 로직 처리 순서를 변경해서
// 디버깅도 더 쉽고, 더 가볍도록 만들기로 한다.
const BOOL bCanAttack = Character::CanAttack();
if( !bCanAttack )
return FALSE;
// 플레이어의 상태가 기본상태가 아니면( 예:현재 개인상점을 개설한 상태등 ) 공격할 수 없다.
const ePLAYER_BEHAVE_STATE state_of_behave = GetBehave();
#ifdef _NA_006699_20130506_DONOT_ATTACK_WHILE_BATTLE_GROUND_RESERVATION
if( state_of_behave != PLAYER_BEHAVE_IDLE_STATE &&
state_of_behave != PLAYER_BEHAVE_RESERVATION_BATTLE_GROUND_ZONE)
#else
if( state_of_behave != PLAYER_BEHAVE_IDLE_STATE )
#endif // _NA_006699_20130506_DONOT_ATTACK_WHILE_BATTLE_GROUND_RESERVATION
return FALSE;
// (WAVERIX)(080907)(NOTE)
// - 아래와 같은 상황이 쓴웃음이 나오게 하는 내용... 작업 단계가 서로 틀리니 각각 구현해버리는 (그게 더 작업할 때는 편하니까...)
// 상황에 의한 것이라 생각된다. 좀 더 고민되더라도 앞선 ePLAYER_BEHAVE_STATE와 같은 통합 일괄체계를 갖추는 편이 나을 거라
// 생각된다. 적어도 작업할 때, 선행작업(분석단계)를 가지고 기존에 있는 기능의 활용방안(또는 확장방안)을 고민해보면 좋을 것이다.
// 그런 것이 조금 고민스럽더라도 자기 발전에도 이득일 거라 생각된다. 뭐... 작업일정 압박이나 회사니까 또는 잘 돌아가고 있는
// 것을 건드려서 뭐할려구? 라고 한다면 씁쓸하지만 무시할 수는 없는 부분이고 나 역시 예외일 수는 없으므로 별로 항변하고 싶지 않다.
// 단지 그런 생각이 만연되는 것이 무서울 뿐이다.
// PVP 상태 체크
const ePVP_USER_STATE state_of_pvp = m_pPVPInfo->GetPVPState();
if( !( state_of_pvp == ePVP_USER_NONE
|| state_of_pvp == ePVP_USER_MATCH
) )
return FALSE;
// (WAVERIX)(080907)(NOTE)
// - 상동의 이유로 쓴웃음이 나온다. 그렇다면... 길드 창고 추가된 현 시점에서 길드 창고에 대해서는 검사하지 않아도 되는가
// 하는 문제가 생긴다. 그게 문제라면 여기에 또 추가할 것인가? 아이러니다...
// - 분석적인 문제, pWarehouse 포인터 상태를 검사하는 것은 타당하나 pWarehouse가 null이 되는 상황은
// GameServer가 종료될 때 뿐이다. 뭐... 포인터 문제나 메모리 할당 방법을 달리하거나, 하면 별개의 문제가 되기는 하지만, 그런
// 경우라면, 일괄적으로 해당 플레이어의 Action 허용 여부라는 단순한 평가로 이뤄지는 게 더 간단해 질 것이다.
// 흔히 하지 않는가? pPlayer->GetField() 검사...
// 현재 창고가 열려 있는 상태면 공격할 수 없다.
CWarehouseSlotContainer* const pWareHouse = GetWarehouseContainer();
if( !pWareHouse || pWareHouse->IsStarted() ) //창고가 열려 있으면.. < (WAVERIX) ㅋㅋ 상태 한 가지가 더 있다. 아래 첨삭했음.
return FALSE;
// (WAVERIX)(080907)(NOTE)
// if( !pWareHouse || pWareHouse->IsStarted() )
// - 알고 이렇게 했는지 아닌지는 파악할 수 없지만 그 논리 결과에는 재미있는 부분이 있다. 이는 다음과 같이 풀이된다.
// if( !pWareHouse || (pWareHouse && pWareHouse->IsStarted()) )
// pWareHouse이 0이거나 pWareHouse->IsStarted()이라면... ==> (0이거나 0이 아니고 started라면...)
// - 결과적으로 위 논리에서 타당하기는 하지만, CanAttack의 목적과는 다른 부분이 생겨난다.
// - 참고로 본 작성자의 경우는 굳이 표현한다면 아래처럼 한다.
// if( pWareHouse && pWareHouse->IsStarted() )
// 위의 상태보다는 조건이 하나 빠져 있다. 이는 pWareHouse가 0이냐 아니냐는 여기서 고려할 대상이 아니기 때문이다. 그런것을
// 고민하면 프로그램의 복잡도는 예측하기 힘들다. 적어도 pWareHouse를 검사한 단일한 목적(창고가 열려 있으면)에는 부합하게 된다.
// 창고의 무결성은 창고 관련 처리시에 해결하면 된다.
// 일단 이와 같은 처리로 다른 곳에 영향을 주었을 가능성도 있고, 창고가 없을 리도 없으니 논리 블록은 그대로 유지한다.
//
// 결론적으로 위의 상황을 직설적으로 표현하면 다음과 같다.
// "공격은 창고를 가지고 있는 사람만의 특권이다!" ... 아이러니다. 왜?
return TRUE;
}
//────────────────────────────────────────────────────────────────
BOOL
Player::CanBeAttacked() const
{
const BOOL bCanBeAttacked = Character::CanBeAttacked();
if( !bCanBeAttacked )
return FALSE;
// PVP 상태 체크
const ePVP_USER_STATE state_of_pvp = m_pPVPInfo->GetPVPState();
if( !( state_of_pvp == ePVP_USER_NONE
|| state_of_pvp == ePVP_USER_MATCH
) )
return FALSE;
return TRUE;
}
//────────────────────────────────────────────────────────────────
// CHANGES, f100830.4L, replace an arguement block for a skill to a SkillInfo
RC::eBATTLE_RESULT Player::CanNormalAttack(SkillInfo* preparing_skill_info_data)
{
const SkillInfo& skill_info_data = *preparing_skill_info_data; // safe-control
// (WAVERIX)(080907)(NOTE) 너무 복잡해졌으므로 현 시점 적용되는 기능을 추출한다.
//////////////////////////////////////////////////////////////////////////
// <LOGIC CONTROL>
FlowControl flow;
FLOWCONTROL_START()
{
// 플레이어가 필드에 없으면 리턴
GameField* const pField = GetField();
if (!(pField && pField->IsReady())) {
return RC::RC_BATTLE_PLAYER_NOTEXIST_TO_FIELD;
}
// (WAVERIX)(080907)(NOTE) ... 이 상태 찾기 (특히 eCHAR_STATE_TRANSFORMATION)나중에 개선 방안 강구하자.
StatusManager* const status_manager = GetStatusManager();
// 공격 타수 체크(일단 틀려도 넘어가 준다...) ... 그런데 왜?
StatusField& rStatus = status_manager->Status;
const bool isCheck_AtkSeq = !(rStatus.IsDragonTransforming());
if (isCheck_AtkSeq)
{
const BYTE last_atkseq = GetLastAttackSeq();
if (eATTACK_SEQUENCE_SECOND == skill_info_data.attack_sequence_)
{
if (last_atkseq != eATTACK_SEQUENCE_FIRST)
{
SECURELOG(SECURELOG_PLAYERINFO(this, \
eABUSE_LOGIC_VIOLATION, eABUSE_SUB_LOGIC_VIOLATION_ATTACK_SEQUENCE),
_T("["__FUNCTION__"] Attack Logic Violation, received 2nd, except 1st"));
}
}
else if (eATTACK_SEQUENCE_THIRD == skill_info_data.attack_sequence_)
{
if (last_atkseq != eATTACK_SEQUENCE_SECOND)
{
SECURELOG(SECURELOG_PLAYERINFO(this, \
eABUSE_LOGIC_VIOLATION, eABUSE_SUB_LOGIC_VIOLATION_ATTACK_SEQUENCE),
_T("["__FUNCTION__"] Attack Logic Violation, received 3rd, except 2nd"));
}
}
}
// sequence update는 field check 이전에 해줘야 하는가? 좀더 따져볼 필요는 있다.
SetLastAttackSeq(skill_info_data.attack_sequence_);
// 플레이어가 공격할 수 없는 상태이면 리턴 < atk seq는 맞춰줘야 한다.
if (!CanAttack()) {
return RC::RC_BATTLE_PLAYER_STATE_WHERE_CANNOT_ATTACK_ENEMY;
}
if (!rStatus.IsPassConstraint(this, (SkillScriptInfo*)0)) {
return RC::RC_BATTLE_PLAYER_STATE_WHERE_CANNOT_ATTACK_ENEMY;
}
// 아직 액션 도중인 경우 이 패킷은 해킹으로 간주해도 무방함
IfFailThenBreak(!IsDoingAction(), RC::RC_BATTLE_ALREADY_DOING_ACTION);
// 현재 좌표가 유효한지 체크
IfFailThenBreak(WzVector_FiniteCheck(&skill_info_data.cur_pos_), RC::RC_BATTLE_INVLIDPOS);
// 목적지 좌표가 유효한지 체크
IfFailThenBreak(WzVector_FiniteCheck(&skill_info_data.dest_pos_), RC::RC_BATTLE_INVLIDPOS);
// (WAVERIX)(080907)(NOTE) 이후 딜레이 처리가 고려되면 이 문제도 좀 더 고민해봐야 한다.
// 밀리는 거리(어택시작점-어택끝점)가 너무 길면 접속을 끊는다.
const float fDiffBetween_Cur_With_Dest_Q2 = 3.0f * 3.0f;
;{
WzVector diff = skill_info_data.cur_pos_;
; diff -= skill_info_data.dest_pos_;
IfFailThenBreak(Math_Vector2DLength2(&diff) <= fDiffBetween_Cur_With_Dest_Q2,
RC::RC_BATTLE_THRUST_DIST_OVER);
};
// (NOTE) to support a shadow control, __NA_S00015_20080828_NEW_HOPE_SHADOW
;{
// 공격시 사라지는 상태들......
Player_TransOff(); //_NA001385_20090924_DOMINATION_SHADOW
#if !defined(_NA_005159_20120807_DISABLE_REMOVE_RUSH_STATE)
//러쉬상태이면 사라진다.
BaseStatus* status = status_manager->FindStatus(eCHAR_STATE_RUSH);
if (status) {
status->StopStatus();
}
#endif //_NA_005159_20120807_DISABLE_REMOVE_RUSH_STATE
};
return RC::RC_BATTLE_SUCCESS;
}
FLOWCONTROL_END;
//////////////////////////////////////////////////////////////////////////
// <ERROR CONTROL>
switch (flow.GetErrorCode())
{
case RC::RC_BATTLE_INVLIDPOS:
{
const WzVector& cur_pos = skill_info_data.cur_pos_;
const WzVector& dest_pos = skill_info_data.dest_pos_;
SECURELOG(SECURELOG_PLAYERINFO(this, \
eABUSE_LOGIC_VIOLATION, eABUSE_SUB_LOGIC_VIOLATION_INVALID_POSITION),
_T("["__FUNCTION__"] Invalid Position! ")
_T("CurPos={%.2f,%.2f,%.2f} DestPos={%.2f,%.2f,%.2f}"),
cur_pos.x, cur_pos.y, cur_pos.z, dest_pos.x, dest_pos.y, dest_pos.z);
}
return RC::RC_BATTLE_INVLIDPOS;
case RC::RC_BATTLE_THRUST_DIST_OVER:
{
SECURELOG(SECURELOG_PLAYERINFO(this, \
eABUSE_LOGIC_VIOLATION, eABUSE_SUB_LOGIC_VIOLATION_ATTACK_RANGE),
_T("["__FUNCTION__"] Coordinates Hack, End to End Attck Distance is too long"));
}
return RC::RC_BATTLE_THRUST_DIST_OVER;
case RC::RC_BATTLE_ALREADY_DOING_ACTION:
{
SECURELOG(SECURELOG_PLAYERINFO(this, \
eABUSE_LOGIC_VIOLATION, eABUSE_SUB_LOGIC_VIOLATION_ATTACK_SPEED),
_T("["__FUNCTION__"] Already Doing Action! TimeInterval [%d] ProgressRatio [%f]"),
m_ActionTimer.GetIntervalTime() , m_ActionTimer.GetProgressRatio());
}
return RC::RC_BATTLE_ALREADY_DOING_ACTION;
}
SUNLOG(eCRITICAL_LOG, _T("["__FUNCTION__"] 스킵이 발생하는 상황은 무엇인가?|Error=%d|"),
flow.GetErrorCode());
return RC::RC_BATTLE_FAILED;
}
//────────────────────────────────────────────────────────────────
// CHANGES, f100830.4L, replace an arguement block for a skill to a SkillInfo
RC::eBATTLE_RESULT Player::CanStyleAttack(SkillInfo* preparing_skill_info_data)
{
// for safe-control. if you want modify a SkillInfo, do access to preparing_skill_info_data.
const SkillInfo& skill_info_data = *preparing_skill_info_data;
//////////////////////////////////////////////////////////////////////////
// <LOGIC CONTROL>
FlowControl flow;
FLOWCONTROL_START()
{
// 공격 가능한지 기본적인 사항을 체크한다.
const RC::eBATTLE_RESULT result = CanNormalAttack(preparing_skill_info_data);
if (result != RC::RC_BATTLE_SUCCESS) {
return result;
};
const SkillInfoParser* const skill_parser = SkillInfoParser::Instance();
const SLOTCODE style_code = GetSelectedStyle();
const BASE_STYLEINFO* const style_info = skill_parser->GetStyleInfo(style_code);
if (style_info == NULL) {
return RC::RC_BATTLE_BASEINFO_NOTEXIST; // 이거야 말로 abusing이 아닌가?
}
StatusManager* const status_manager = GetStatusManager();
// (WAVERIX)(080907)(NOTE)
// - ... 또 드래곤변신이다... -.-;;;
// - 클래스 타입 관련은 특정 위치에서 해결이 가능하다. 그쪽을 다시 한번 살펴보고 개선 방안을 강구하자.
// 클래스 제한에 걸리는지 체크
DWORD current_class_type = (DWORD)GetCharType(); // eCHAR_TYPE | eCHAREX_TYPE
//StatusField& rStatus = pStatusManager->Status;
if (status_manager->Status.IsDragonTransforming())
{
DragonTransStatus* const status = static_cast<DragonTransStatus*>(\
status_manager->FindStatus(eCHAR_STATE_TRANSFORMATION));
current_class_type = (DWORD)status->GetCharType();
}
IfFailThenBreak((DWORD)style_info->m_byClassDefine == current_class_type,
RC::RC_BATTLE_CHAR_CLASS_LIMIT);
// 무기제한에 걸리는지 체크
const DWORD weapon_kind_by_script = (DWORD)style_info->m_iWeaponDefine;
if (weapon_kind_by_script != ULONG_MAX)
{
const DWORD weapon_kind = GetWeaponKind();
IfFailThenBreak(weapon_kind == weapon_kind_by_script, RC::RC_BATTLE_WEAPON_LIMIT);
}
;{
// CHANGES, f100830.5L, update a skill_info_data.
preparing_skill_info_data->skill_code_ = style_code;
preparing_skill_info_data->root_skill_info_ = style_info;
};
//
return RC::RC_BATTLE_SUCCESS;
}
FLOWCONTROL_END;
//////////////////////////////////////////////////////////////////////////
// <ERROR CONTROL>
switch (const RC::eBATTLE_RESULT result = (RC::eBATTLE_RESULT)flow.GetErrorCode())
{
case RC::RC_BATTLE_CHAR_CLASS_LIMIT:
{
SECURELOG(SECURELOG_PLAYERINFO(this,
eABUSE_LOGIC_VIOLATION, eABUSE_SUB_LOGIC_VIOLATION_ATTACK_TYPE),
_T("["__FUNCTION__"] The Type Of Character is Invalid! Style[%d] CharType[%d]"),
GetSelectedStyle(), GetCharType()); // 드래곤 변신은 고려되지 않은 로그다...
}
return result;
case RC::RC_BATTLE_WEAPON_LIMIT:
{
SECURELOG(SECURELOG_PLAYERINFO(this, \
eABUSE_LOGIC_VIOLATION, eABUSE_SUB_LOGIC_VIOLATION_ATTACK_TYPE),
_T("["__FUNCTION__"] The Weapon Type is Invalid! Style[%d] Weapon[%d]"),
GetSelectedStyle(), GetWeaponKind());
}
return result;
}
return RC::RC_BATTLE_FAILED;
}
//────────────────────────────────────────────────────────────────
VOID
Player::SetAttackDelay( BYTE byAttackSequence, SLOTCODE StyleCode )
{
BASE_STYLEINFO *pBaseStyleInfo = SkillInfoParser::Instance()->GetStyleInfo( StyleCode );
// 액션 딜레이 설정
DWORD dwActionDelay;
switch( byAttackSequence )
{
case eATTACK_SEQUENCE_FIRST:
dwActionDelay = pBaseStyleInfo->m_wTimeFirst;
break;
case eATTACK_SEQUENCE_SECOND:
dwActionDelay = pBaseStyleInfo->m_wTimeSecond;
break;
case eATTACK_SEQUENCE_THIRD:
dwActionDelay = pBaseStyleInfo->m_wTimeThird;
break;
default:
SECURELOG( SECURELOG_PLAYERINFO(this, eABUSE_LOGIC_VIOLATION, eABUSE_SUB_LOGIC_VIOLATION_ATTACK_SEQUENCE),
_T("[Player::SetAttackDelay] Invalid attack sequence! Style[%d] byAttackSequence[%d]"), StyleCode, byAttackSequence );
ForceDisconnect( RC::RC_DISCONNECT_INVALID_ATTACK_SEQUENCE );
return;
}
// 서버측 액션 딜레이를 약간 여유있게 잡는다.
dwActionDelay = (DWORD)(dwActionDelay * 0.9f);
if( 0 != GetPhysicalAttackSpeed() )
{
SetActionDelay( (DWORD)( dwActionDelay / GetPhysicalAttackSpeed() ) );
}
else
{
SUNLOG( eCRITICAL_LOG, _T("[Player::SetAttackDelay] GetPhysicalAttackSpeed() = 0!") );
return;
}
}
//────────────────────────────────────────────────────────────────
VOID
Player::OnAttack( Character *pTarget, SLOTCODE skillcode, DAMAGETYPE damage )
{
// 공격자의 무기 내구도 감소
//밀리공격이면 명중률 적용 하여 내구도 감소
DWORD dwDecreaseDuraVal = CalcDecreaseItemDuraValue( m_Attr.GetDecreaseItemDuraRatio() );
bool apply_decrease_durability = false;
if (skillcode == SKILLCODE_NORMAL_ATTACK) {
//명중했으니 내구도 감소
if (damage != 0) {
apply_decrease_durability = true;
}
}
//마법공격이면 명중률 적용 없이 내구도 감소
else
{
apply_decrease_durability = true;
};
// CHANGES: f110405.1L
if (apply_decrease_durability) {
if (nsSlot::DurabilityImpl* const impl = this->GetItemManager()->GetDurabilityImpl()) {
impl->DecreaseDurability(dwDecreaseDuraVal, eEQUIPCONTAINER_WEAPON);
};
};
Character::OnAttack( pTarget, skillcode, damage );
DWORD skillRecoverPercent = 100;
DWORD skillRequireMP = 0;
SkillScriptInfo *pSkillInfo = SkillInfoParser::Instance()->GetSkillInfo( skillcode );
if( pSkillInfo )
{
skillRequireMP = pSkillInfo->m_wMPSpend;
}
// 공격할때 회복량
DWORD recoverMP = CalcMPRecoverByAttack( GetCharType(), m_Attr.GetSPR(), skillRecoverPercent, skillRequireMP );
OnRecover( 0, recoverMP );
#ifdef _NA_007514_20140828_NEW_CHARACTER_WITCHBLADE
if (GetCharType() == eCHAR_WITCHBLADE && damage > 0) // 평타로 피해를 주면 적용
{
PLAYERFP recoverFP = CalcFPRecoverByAttack(GetCharType());
OnRecoverFP(recoverFP);
}
#endif //_NA_007514_20140828_NEW_CHARACTER_WITCHBLADE
}
//────────────────────────────────────────────────────────────────
void
Player::_Damaged(DamageArgs* damage_arg)
{
Character* pAttacker = damage_arg->actor_;
// (WAVERIX) (081013) (NOTE) 너무 길고 중복이 많다. 디파인 제거 작업이 끝나면 정리하기로 한다.
if (Policyment.FIELD & Policyment.PCC_IN_SSQ_FIELD)
{
int damage = damage_arg->damage_;
if (damage) {
damage = (damage >> 1);
if (damage == 0) {
damage = 1;
}
damage_arg->damage_ = damage;
}
/*
// (WAVERIX) (090824) (CHANGES) 3항 연산은 2회 중복 조건을 true 계산 방식으로 풀어내면
// 난해해지는 문제점때문에 다음과 같이 수정.
if(damage) {
damage = (damage >> 1) ? damage >> 1
: 1;
}
// (NOTE) 앞에서 말한 내용을 다음과 같이 변환하면 좀 더 가독성이 좋아진다.
// 하지만 이럴 필요 있나? 상수화 목적이 아닌 이상 이렇게 무리하게 하지는 말자.
// (advanced) | (original code)
//damage = (damage == 0) ? 0 | damage = damage
// : (damage >> 1) ? damage >> 1 | ? (damage >> 1) ? damage >> 1
// : 1; | : 1
// | : 0;
*/
}
// || 이미 죽은 유저에게 데미지를 주어도 소용없다...
if (damage_arg->damage_ == 0 || IsDead()) {
return;
}
if (damage_arg->damage_) // ... 이 앞단으로 추가 로직이 삽입되는 경우를 감안해서 제거하지 않는다.
{
#if !defined(_NA_000000_20121015_DISABLE_REMOVE_HIDE_STATE)
Player_TransOff();
#endif //_NA_000000_20121015_DISABLE_REMOVE_HIDE_STATE
// 방어자의 방어구 내구도 감소
//POSTYPE RandomPos = random(eEQUIPCONTAINER_ARMOR, eEQUIPCONTAINER_SHIRTS); // unused
// (CHANGES) (091212) (WAVERIX) to support simple debugging facility, \
// more reduce overhead and code arrangement
const int iDuraRatio = m_Attr.GetDecreaseItemDuraRatio();
const DWORD duraVal = CalcDecreaseItemDuraValue(iDuraRatio);
// CHANGES: f110404.2L
if (nsSlot::DurabilityImpl* const impl = this->GetItemManager()->GetDurabilityImpl()) {
impl->DecreaseDurabilityWithRanges(duraVal,
eEQUIPCONTAINER_ARMOR, eEQUIPCONTAINER_SHIRTS);
};
}
{ // Recover MP
DWORD recoverMP = CalcMPRecoverByAttacked(GetCharType(), m_Attr.GetSPR(), damage_arg->damage_);
OnRecover(0, recoverMP);
};
// Character Damage Process
Character::_Damaged(damage_arg); //여기서 실제 데미지 감소가 일어난다.
if(damage_arg->damage_ > 0 && IsAlive())
{
// (WAVERIX) (090821) (BUG-FIX) HP Recovery기능 관련 버그 수정
// (해당 회복률은 최종 계산 결과 값이 아닌 비율)만으로 사용해야 할 듯, HP의 값은 Attribute에
// 변동치를 반영할 수 없으며, (설사 반영하더라도 복잡해지거나, 매번 update수행해야 한다.)
// 결과적으로 현 시점, value(0) * ratio로 연산되어 항상 0이 나오게 된다.
// (Addin Note)
// 초기 구현자와 기능 추가자가 동일하지만, 이 사람은 테스트는 개뿔로 생각하는 사람이라
// 단정지어야(거짓은 아니니 뭐...) 이후 해당 작업자가 발생시킨 (현재 스킬 관련 버그들
// 내가 가지고 있는) 버그들을 수정하면서 스트레스 덜 받을 것 같다. 현재 위도 아파오는데... ㅆx!
// 리커버리량 증가 패시브 스킬이 있으면..
int reserveHp_Addin = (damage_arg->damage_ >> 1);
if(reserveHp_Addin) { //'+Ratio' (ADD-IN)
int reserveHp_Ratio = m_Attr.GetRatioIncreaseReserveHPRatio();
if(reserveHp_Ratio) // = (src * percent) / 100
reserveHp_Addin = int((reserveHp_Addin * (100 + reserveHp_Ratio)) / 100.0f);
}
DWORD reserveHP = GetReserveHP() + reserveHp_Addin;
//// old code backup
//int nReserveHPRatio = GetAttr().GetIncreaseReserveHPRatio(); // 리커버리량 증가 패시브 스킬이 있으면..
//double dbTempReserveHP = (double)damage / 2;
//double dbCalcReserveHPRatio = (double)(nReserveHPRatio/100.0f)*dbTempReserveHP;
//
//DWORD reserveHP = GetReserveHP() + dbTempReserveHP + dbCalcReserveHPRatio;
{// (WAVERIX) (090821) (CHANGES) 코드 검증 후 수정
DWORD calcHP = GetHP() + reserveHP;
const DWORD maxHP = GetMaxHP();
// (NOTE) curHP + reserveHP > maxHP 상황이 발생하지만, 실제 회복 시점에 잉여 값이 고려된다.
if(maxHP < calcHP)
reserveHP = calcHP - maxHP;
}
SetReserveHP(reserveHP);
}
if(pAttacker)
{
if(IsDead()) // 플레이어가 강제공격(PK)을 받아서 죽었을 경우
{
Player* pPlayer = pAttacker->GetPlayerOwner();
if(pPlayer)
{
#ifdef _NA_008706_20160229_TEXTLOG_PK
pPlayer->KillPlayer(this, FALSE, damage_arg->damage_first_);
#else
pPlayer->KillPlayer(this);
#endif //_NA_008706_20160229_TEXTLOG_PK
}
}
else if(damage_arg->option_.is_mirror_ == false && GetReflectDamageRatio())
{
// f100908.1L
DamageArgs mirror_damage_args(this, damage_arg->attack_type_);
;{
//mirror_damage_args.option_.is_normal_;
//mirror_damage_args.option_.is_critical_;
mirror_damage_args.option_.is_mirror_ = true;
//mirror_damage_args.target_;
//mirror_damage_args.limit_hp_;
mirror_damage_args.attack_regist_kind_ = damage_arg->attack_regist_kind_;
// 이 부분은 옵션에 대한 처리 이나, 상태의 SDApply 값을 따른다.
BASE_STATEINFO* pStateInfo = \
StateInfoParser::Instance()->GetStateInfo(eCHAR_STATE_REFLECT_DAMAGE);
if (pStateInfo) {
mirror_damage_args.sd_apply_ = (eSD_APPLY)pStateInfo->m_bySDApply;
}
mirror_damage_args.damage_ = damage_arg->damage_ * GetReflectDamageRatio();
//mirror_damage_args.damage_sd_;
};
pAttacker->Damaged(&mirror_damage_args); // 데미지 반사 속성 체크
DWORD dwAttackerReserveHP = pAttacker->GetReserveHP();
if(dwAttackerReserveHP > 0)
dwAttackerReserveHP = dwAttackerReserveHP / 10 + mirror_damage_args.damage_ / 2; //기본의 적립HP 10% + Damage의 50%를 적립한다.
else
dwAttackerReserveHP = mirror_damage_args.damage_ / 2; //기본의 적립HP 10% + Damage의 50%를 적립한다.
pAttacker->SetReserveHP(dwAttackerReserveHP);
//반사 데미지는 내가 공격 받았는데, 반사적으로 나를 공격한 놈에게 데미지를 주는 것이다.
//따라서 공격 주체가 "내"가 된다.
;{
MSGSUB_SKILL_REFLECT_DAMAGE_BRD msg_brd;
msg_brd.Clear();
msg_brd.m_dwAttackerKey = GetObjectKey();
msg_brd.m_dwTargetKey = pAttacker->GetObjectKey();
msg_brd.m_wDamage = mirror_damage_args.damage_;
msg_brd.m_dwTargetHP = pAttacker->GetHP();
msg_brd.m_dwTargetResrveHP = pAttacker->GetReserveHP();
msg_brd.m_SkillCode = 0;
msg_brd.m_wAbilityCode = 0;
#ifdef _NA_008804_20160502_COMBAT_LOG
time_t t;
util::TimeSync::time(&t);
msg_brd.m_SvrTime = t;
#endif //(_NA_008804_20160502_COMBAT_LOG)
this->SendPacketAroundForSync(&msg_brd);
};
}
}
#if !defined(_NA_005159_20120807_DISABLE_REMOVE_RUSH_STATE)
;{ // (NOTE) to support a shadow control, __NA_S00015_20080828_NEW_HOPE_SHADOW
// 플레이어가 공격 당했을때 사라지는 상태들..
//러쉬상태이면 사라진다.
BaseStatus* pStatus = GetStatusManager()->FindStatus(eCHAR_STATE_RUSH);
if (pStatus) {
pStatus->StopStatus();
}
};
#endif //_NA_005159_20120807_DISABLE_REMOVE_RUSH_STATE
IVehicleEvent::OnEvent(this, IVehicleEvent::Hit()); // for vehicles 'Hit' event processing
#ifdef _NA_007667_20141001_WITCHBLADE_SKILL
//위치블레이드, 라이징포스의 처리
ProcessRisingForce();
#endif //_NA_007667_20141001_WITCHBLADE_SKILL
}
//────────────────────────────────────────────────────────────────
VOID
Player::ForcedAttack( Character *pTarget )
{
if( !pTarget ) return;
if( !pTarget || !pTarget->GetPlayerOwner() ) return;
const BOOLEAN bApplyGeneralForcedAtkRule = !( Policyment.FIELD & Policyment.PCC_IN_SSQ_FIELD );
if( !bApplyGeneralForcedAtkRule )
return;
#if defined(_CHINA) //CHINA_PK_RULE
if( m_ForceAttack || pTarget->GetPlayerOwner()->GetChaoState().IsChaoState() )
#else
if( m_ForceAttack )
#endif
m_pChaoState->StartBoradoriState( pTarget->GetPlayerOwner() );
}
//────────────────────────────────────────────────────────────────
BOOL
Player::CanResurrection( Character *pTarget )
{
if( !pTarget )
return FALSE;
//파티원이 아니면 자신만 부활
if( !GetPartyState().IsPartyMember() )
{
if( GetObjectKey() == pTarget->GetObjectKey() )
return TRUE;
#ifdef _NA_006826_20130722_DOMINATION_RENEWAL
GameZone* pZone = GetGameZonePtr();
if (pZone && pZone->GetZoneType() == eZONETYPE_DOMINATION_FIELD)
{
if (pTarget->IsEqualObjectKind(PLAYER_OBJECT))
{
Player* target_player = static_cast<Player*>(pTarget);
if (pZone->IsFriendPVP(this, target_player) == USER_RELATION_FRIEND) {
return TRUE;
}
}
}
#endif //_NA_006826_20130722_DOMINATION_RENEWAL
return FALSE;
}
//파티원이면 파티원들 부활
else
{
if( GetPartyState().GetPartyKey() != pTarget->GetPartyState().GetPartyKey() )
return FALSE;
}
return TRUE;
}