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

510 lines
18 KiB
C++

#include "stdafx.h"
#include "StyleSkill.h"
#include <PacketStruct_CG.h>
#include <Battle.h>
#include <ServerOptionParser.h>
#include <ServerOptionParserEx.h>
#include <SkillInfoParser.h>
#include <AIParamParser.h>
#include "CharacterFormula.h"
#include "NPC.h"
#include "GameField.h"
#include "Character.h"
#include "Player.h"
#include "StatusManager.h"
#include "EtcStatus.h"
#include "EtherBulletInfoParser.h"
//==================================================================================================
// internal dice by ratio
// arg: 100% = 1.0f
inline BOOLEAN
StyleSkill::IsOccuredByDraw(float ratio_limit)
{
DWORD seed = RandomNumberGenerator::GetRandomNumberRange(0, 10000);
DWORD calc_ratio_limit = static_cast<DWORD>(ratio_limit * 10000.0f);
return seed <= calc_ratio_limit;
}
void
StyleSkill::SetExecuteInterval()
{
interval_ = 0;
if (Player* player = static_cast<Player*>(owner_))
{ // set up interval
float interval = static_cast<float>(player->GetActionDelay());
; interval *= base_style_info_->m_fThirdDelay;
interval_ = static_cast<WORD>(interval);
}
// 패킷 살~짝 일찍 주자
if (interval_ > 100) {
interval_ -= 100;
}
}
void
StyleSkill::Init(Character* owner,
const SkillInfo* skill_info,
const ROOT_SKILLINFO* root_skill_info)
{
SetupDefault(owner, skill_info, root_skill_info);
// 변수 초기화
ZeroMemory(&style_, sizeof(style_));
}
BOOL
StyleSkill::StartExecute()
{
game_field_ = owner_->GetField();
if (game_field_ == NULL)
{
MSG_CG_SKILL_ACTION_NAK msg_nak;
msg_nak.m_byErrorCode = RC::RC_SKILL_FIELD_IS_NULL;
owner_->SendPacket(&msg_nak, sizeof(msg_nak));
SUNLOG(eCRITICAL_LOG, "["__FUNCTION__"] GameField is NULL!");
return false;
}
// MainTarget이 유효한지 체크한다.(스킬 사거리 체크)
if (CheckMainTarget(NULL) == false) {
is_missed_ = true;
}
else {
CommonExecute();
}
;{ //assign values
MSGSUB_STYLE_PLAYER_ATTACK_BRD msg_brd;
msg_brd.Clear();
msg_brd.dwAttackerKey = owner_->GetObjectKey();
msg_brd.byAttackType = skill_info_data_.attack_sequence_;
#ifdef _NA003109_STYLE_PRESENTATION_IMPROVEMENT
msg_brd.style_presentation = skill_info_data_.style_presentation_;
#endif
msg_brd.StyleCode = skill_info_data_.skill_code_;
msg_brd.dwClientSerial = skill_info_data_.client_serial_;
msg_brd.dwPrimaryTargetKey = skill_info_data_.main_target_key_;
msg_brd.wvCurPos = skill_info_data_.cur_pos_;
owner_->SendPacketAroundForSync(&msg_brd);
};
return true;
}
void
StyleSkill::EndExecute()
{
game_field_ = owner_->GetField();
if (game_field_ == false)
{
MSG_CG_SKILL_ACTION_NAK msg_nak;
msg_nak.m_byErrorCode = RC::RC_SKILL_FIELD_IS_NULL;
owner_->SendPacket(&msg_nak, sizeof(msg_nak));
SUNLOG(eCRITICAL_LOG, "["__FUNCTION__"] GameField is NULL!");
return;
}
if (is_missed_)
{ // Miss 처리를 하기 위해서 결과 패킷은 보낸다.
Broadcast_Style_Result();
Skill::EndExecute();
return;
}
MSGSUB_STYLE_PLAYER_ATTACK_RESULT_BRD msg_brd;
msg_brd.Clear();
msg_brd.dwClientSerial = skill_info_data_.client_serial_;
msg_brd.dwAttackerKey = owner_->GetObjectKey();
//
style_.player_pos = *owner_->GetPosPtr();
ns_object::SearchOption::SingleSearch::result_type search_result;
if (FindTargets(&search_result) == false) {
return;
}
const int number_of_targets = search_result.number_of_selected_chars;
// 타겟별로 데미지와 이펙트를 적용한다.
Character* main_target = NULL;
for (int i = 0; i < number_of_targets; ++i)
{
Character* target = search_result.targets[i];
// NOTE: change filters
if (target == NULL) {
continue;
}
const DWORD target_object_key = target->GetObjectKey();
if (target_object_key == skill_info_data_.main_target_key_) {
main_target = target;
}
if (target->IsDead() ||
owner_->IsFriend(target) != USER_RELATION_ENEMY)
{
continue;
}
// PK 공격이라면 관련처리를 한다.
owner_->ForcedAttack(target);
if (target->CanBeAttacked() == false) {
continue;
}
// 스타일 이펙트 및 타겟위치 셋팅
skill_info_data_.skill_effect_ = style_.common_effect;
style_.target_pos = *target->GetPosPtr();
style_.thrust_pos = style_.target_pos;
// 데미지를 계산한다.
if (CalcDamage(target))
{ // 만약 어빌리티를 처리하는 중에 자기자신이 죽어서 스킬이 초기화될 경우!
//(절대 발생하면 안되지만 예외처리!)
if (owner_ == NULL)
{
SUNLOG(eCRITICAL_LOG, "[StyleSkill::CalcDamage] SkillCode[%d] owner_ is NULL!", \
GetSkillCode());
return;
}
// 공격이 성공했을 경우
target->Attacked();
//_NA_0_20100211_SD_EFFECT_MODIFY
SendAIMessage(target, style_.damage);
// 이펙트 효과 적용
ApplyEffect(target);
}
//__NA001390_090915_RIDING_SYSTEM__
StatusManager* status_manager = target->GetStatusManager();
BOOLEAN target_riding = status_manager->Status.IsRidingRider();
// (CHANGES) 넉백등의 동작을 일으키는 상황만 제거하는 방향으로 수정한다.
//_NA001385_20090924_DOMINATION_STAMP_STYLE_SKILL_BUG
const BOOLEAN target_stamp = (status_manager->FindStatus(eCHAR_STATE_STAMP) == NULL) ? 0 : 1;
if (target_riding || target_stamp)
{
skill_info_data_.skill_effect_ &= ~(SKILL_EFFECT_KNOCKBACK |
SKILL_EFFECT_KNOCKDOWN |
SKILL_EFFECT_STUN);
};
;{ //align msg
STYLE_ATTACK_RESULT_INFO& attack_info = msg_brd.AttackInfo[number_of_hit_targets_];
attack_info.dwTargetKey = target_object_key;
//_NA_0_20100211_SD_EFFECT_MODIFY
attack_info.wDamage = style_.damage;
attack_info.dwTargetHP = target->GetHP();
attack_info.wvCurPos = style_.target_pos;
attack_info.wvDestPos = style_.thrust_pos;
attack_info.byEffect = skill_info_data_.skill_effect_;
attack_info.byEtherComboCount = ether_combo_count_;
#ifdef _NA_006638_20120415_ADD_SHIELD_INFO_IN_TARGET_INFO
attack_info.target_sd = target->GetSD();
#endif // _NA_006638_20120415_ADD_SHIELD_INFO_IN_TARGET_INFO
}
++number_of_hit_targets_;
if (base_style_info_->m_wMaxTargetNo &&
number_of_hit_targets_ >= base_style_info_->m_wMaxTargetNo)
{
break;
}
}
// 캐릭터에 걸어준 속성등을 해제한다.
CommonRelease();
// 공격하면 MP, HP가 회복되는 캐릭터가 있다.
//_NA_0_20100211_SD_EFFECT_MODIFY
owner_->OnAttack(main_target, skill_info_data_.skill_code_, style_.main_target_damage);
#ifdef _NA_008804_20160502_COMBAT_LOG
time_t t;
util::TimeSync::time(&t);
msg_brd.m_SvrTime = t;
#endif //(_NA_008804_20160502_COMBAT_LOG)
msg_brd.byNumberOfTargets = static_cast<uint8_t>(number_of_hit_targets_);
owner_->SendPacketAroundForSync(&msg_brd);
Skill::EndExecute();
}
BOOL
StyleSkill::CheckMainTarget(BattleResult* const detailed_result)
{
BattleResult battle_result = { 0, };
BattleResult* result_handle = detailed_result ? detailed_result : &battle_result;
result_handle->result_code = RC::RC_BATTLE_SUCCESS;
//
if (game_field_ == NULL)
{
SUNLOG(eCRITICAL_LOG, "[StyleSkill::CheckMainTarget] GameField is NULL!");
result_handle->result_code = RC::RC_BATTLE_INVALID_MAINTARGET;
return false;
}
Character* main_target = game_field_->FindCharacter(skill_info_data_.main_target_key_);
if (main_target == NULL) {
result_handle->result_code = RC::RC_BATTLE_INVALID_MAINTARGET;
return false;
}
// 메인타겟 좌표를 설정한다.
skill_info_data_.main_target_pos_ = *main_target->GetPosPtr();
// 관통 스타일인 경우 메인타겟 좌표를 메인타겟을 관통한 뒤편 좌표로 다시 구한다.
if (base_style_info_->m_byAttRangeform == SRF_PIERCE)
{
WzVector& vDiff = skill_info_data_.main_target_pos_ - skill_info_data_.cur_pos_;
VectorNormalize(&vDiff, &vDiff);
skill_info_data_.main_target_pos_ += vDiff * base_style_info_->m_fStyleArea;
}
else
{ // 메인타겟에 대해 스킬 사거리를 체크한다.
BOOLEAN skill_range_check = \
!!owner_->CheckAttackRange(main_target, skill_info_data_.main_target_pos_);
if (skill_range_check == false) {
result_handle->result_code = RC::RC_BATTLE_OUT_OF_RANGE;
return false;
}
}
return true;
}
void
StyleSkill::CommonExecute()
{
// 스킬 구사중 맞지 않도록 해야 하는 경우 비젼을 꺼준다.
BYTE attribution = GetAttribution();
if (attribution & SKILL_ATTRIBUTION_INVISIBLE)
{
StatusManager* status_manager = owner_->GetStatusManager();
status_manager->AllocStatus(eCHAR_STATE_ETC_DISABLE_VISION);
}
// 일단 녹백은 타겟 NPC 전체에 같은 확률로 적용하기 위해 미리 계산
if (base_style_info_->m_fKnockBackRate != 0.0f &&
IsOccuredByDraw(base_style_info_->m_fKnockBackRate))
{
style_.common_effect |= SKILL_EFFECT_KNOCKBACK;
}
}
void
StyleSkill::CommonRelease()
{
// 플레이어가 스킬 구사중 몬스터의 시야에서 사라지는 스킬일 경우 Vision 속성을 켜준다.
BYTE attribution = GetAttribution();
if (attribution & SKILL_ATTRIBUTION_INVISIBLE)
{
StatusManager* status_manager = owner_->GetStatusManager();
status_manager->Remove(eCHAR_STATE_ETC_DISABLE_VISION);
}
}
bool StyleSkill::FindTargets(ns_object::SearchOption::SingleSearch::result_type* const search_result)
{
//_NA000000_110210_ENHANCED_TARGET_SEARCH_ALGORITHM
search_result->number_of_selected_chars = 0;
assert(game_field_ != NULL);
Character* main_target = game_field_->FindCharacter(skill_info_data_.main_target_key_);
const eSKILL_AREA_TYPE forms_of_application =
static_cast<eSKILL_AREA_TYPE>(base_style_info_->m_byAttRangeform);
int max_number_of_target_select = base_style_info_->m_wMaxTargetNo;
if (max_number_of_target_select == 1 ||
base_style_info_->m_byAttRangeform == SRF_FOWARD_ONE)
{ // select one
max_number_of_target_select = 1;
};
const WzVector* main_target_pos = (main_target == NULL) ?
&skill_info_data_.main_target_pos_ : NULL;
// result->targets sequence
// [MainTarget] | [OtherEnemy] | ...
;{
ns_object::SearchOption::SingleSearch search_option(
max_number_of_target_select,//int max_number_of_selected_characters,
owner_, //Character* input_actor,
main_target, //Character* input_main_target_if_exist_it,
main_target_pos, //const WzVector* input_specific_application_center_pos_if_main_target_is_not_used_to_pos,
//
owner_->GetAttackRange(), //float attack_ranges,
base_style_info_->m_fStyleArea, //float ranges_of_application,
forms_of_application); //eSKILL_AREA_TYPE forms_of_application
bool success = game_field_->FindTargetsUsingSingleSearch(search_option, search_result);
if (success == false) {
return false;
};
};
return true;
}
BOOL
StyleSkill::CalcDamage(Character* pTarget)
{
const eATTACK_TYPE attack_type_weapon = owner_->GetWeaponBaseAttType();
{ // calculate default, 'damage'
#ifdef _NA001605_EP2_ATTRIBUTES_RENEWAL_
style_.damage = ep2::CalcNormalDamage(owner_, pTarget,
attack_type_weapon, true, base_style_info_->m_wCriticalBonus,
&skill_info_data_.skill_effect_);
#else
const eATTACK_TYPE attack_type_magic = owner_->GetWeaponMagicalAttType();
style_.damage = ep1::CalcNormalDamage(owner_, pTarget,
attack_type_weapon,
attack_type_magic,
base_style_info_->m_wCriticalBonus,
&skill_info_data_.skill_effect_,
base_style_info_->m_fDefenseIgnore);
#endif
};
;{ // calculate 2nd
// formula = normal_damage * (타수에 따른 추가 데미지 비율) + (타수에 따른 순수 추가 데미지)
float probability = (1 + base_style_info_->m_fDamagePercent[2]);
const int addDamage = base_style_info_->m_iAddDamage[2];
//_NA_0_20100211_SD_EFFECT_MODIFY
style_.damage = static_cast<DAMAGETYPE>(style_.damage * probability) + addDamage;
};
if (is_applied_ether_bullet_option_ && applied_ether_bullet_info_) {
assert(SAFE_NUMERIC_TYPECAST(DWORD, applied_ether_bullet_info_->m_dwEffect, BYTE));
skill_info_data_.skill_effect_ |= applied_ether_bullet_info_->m_dwEffect; // warning, type overflow
}
const DWORD target_key = pTarget->GetObjectKey();
//
if (BOOLEAN need_apply_splash_damage = skill_info_data_.main_target_key_ != target_key)
{ // calculate splash damage
// formula : 발키리 에테르 스타일인 경우 스플래쉬 데미지 30%만 적용
// 일반 스타일 스플래쉬 데미지 50% 적용
static const SERVER_COMMON_OPTION& kServerOption = \
ServerOptionParserEx::Instance()->GetServerOption();
const float splash_damage_ratio = \
base_style_info_->m_SkillClassCode == eSTYLE_ETHER_NORMAL
? kServerOption.m_fETHER_SPLASH_DAMAGE_RATIO
: kServerOption.m_fSTYLE_SPLASH_DAMAGE_RATIO;
//_NA_0_20100211_SD_EFFECT_MODIFY
style_.damage = static_cast<DAMAGETYPE>(style_.damage * splash_damage_ratio);
}
// apply damage to target
// remove '__BUG_2572_SPIRIT_SLUG_BUG' (0817)
// f100908.1L, f110104.3L, change attack type 'eATTACK_TYPE_MELEE'
#ifdef _NA_006728_20130520_SKILL_BALANCE_MODIFY
DamageArgs damage_args(owner_, attack_type_weapon, GetSkillCode());
#else
DamageArgs damage_args(owner_, attack_type_weapon);
#endif // _NA_006728_20130520_SKILL_BALANCE_MODIFY
;{
damage_args.option_.is_critical_ = !!(skill_info_data_.skill_effect_ & SKILL_EFFECT_CRITICAL);
//damage_args.option_.is_mirror_;
//damage_args.limit_hp_;
//damage_args.attack_regist_kind_;
damage_args.sd_apply_ = eSD_APPLY_DO;
//_NA_0_20100211_SD_EFFECT_MODIFY
damage_args.damage_ = style_.damage;
//damage_args.damage_sd_;
};
;{
SkillExecutionTransaction transaction; // CHANGES: f110303.3L
pTarget->Damaged(&damage_args);
};
style_.damage = damage_args.damage_;
// MainTarget인 경우
if (skill_info_data_.main_target_key_ == target_key) {
style_.main_target_damage = style_.damage;
}
return style_.damage != 0;
}
void
StyleSkill::ApplyEffect(Character* pTarget)
{
StatusManager* status_manager = pTarget->GetStatusManager();
BOOL knockdown_execute = false;
BOOLEAN target_riding = status_manager->Status.IsRidingRider();
// 관통
if (base_style_info_->m_fPierceRate != 0.0f &&
IsOccuredByDraw(base_style_info_->m_fPierceRate))
{
skill_info_data_.skill_effect_ |= SKILL_EFFECT_PIERCE;
}
// 스턴
if (target_riding == false &&
base_style_info_->m_fStunRate != 0.0f &&
IsOccuredByDraw(base_style_info_->m_fStunRate))
{
skill_info_data_.skill_effect_ |= SKILL_EFFECT_STUN;
status_manager->AllocStatus(eCHAR_STATE_STYLE_STUN, base_style_info_->m_wStunTime, 0);
}
// 다운
if (target_riding == false &&
base_style_info_->m_fDownRate != 0.0f &&
IsOccuredByDraw(base_style_info_->m_fDownRate))
{
skill_info_data_.skill_effect_ |= SKILL_EFFECT_KNOCKDOWN;
knockdown_execute = true;
}
static const AI_COMMON_INFO& kAI_Common = AIParamParser::Instance()->GetCommonInfo();
// 밀려야 하는 경우의 처리(밀린 후 다운 될 것인가의 정보도 포함)
if (target_riding == false && (skill_info_data_.skill_effect_ & SKILL_EFFECT_KNOCKBACK))
{
WzVector wvNormalVectorP2M;
WzVector& diffVec = style_.target_pos - style_.player_pos;
VectorNormalize(&wvNormalVectorP2M, &diffVec);
wvNormalVectorP2M *= base_style_info_->m_fKnockBackRange;
if (pTarget->ExecuteThrust(true, &wvNormalVectorP2M, style_.thrust_pos,
base_style_info_->m_fKnockBackRange,
knockdown_execute))
{
const ALLOC_STATUS_PAIR result_pair = \
status_manager->AllocStatus(eCHAR_STATE_STYLE_THRUST, kAI_Common.m_wTHRUST_TIME);
// 다운은 Thrust 상태 내부에서 처리하도록 한다.
if (result_pair.first && result_pair.second)
{
EtcStatus* etc_status = static_cast<EtcStatus*>(result_pair.second);
etc_status->SetDownTime(kAI_Common.m_wKNOCKDOWN_TIME);
}
knockdown_execute = false;
}
else
{
// 지형 밀리기가 실패한 경우!!
skill_info_data_.skill_effect_ &= ~SKILL_EFFECT_KNOCKBACK;
}
}
// 밀리지는 않고 다운만 되는 경우
if (target_riding == false && knockdown_execute) {
status_manager->AllocStatus(eCHAR_STATE_STYLE_DOWN, kAI_Common.m_wKNOCKDOWN_TIME, 0);
}
}
void
StyleSkill::Broadcast_Style_Result()
{
;{
MSGSUB_STYLE_PLAYER_ATTACK_RESULT_BRD msg_brd;
msg_brd.Clear();
msg_brd.dwClientSerial = skill_info_data_.client_serial_;
msg_brd.dwAttackerKey = owner_->GetObjectKey();
msg_brd.byNumberOfTargets = static_cast<uint8_t>(number_of_hit_targets_);
#ifdef _NA_008804_20160502_COMBAT_LOG
// 이곳에서는 어텍커의 정보가 없다..
time_t t;
util::TimeSync::time(&t);
msg_brd.m_SvrTime = t;
#endif //(_NA_008804_20160502_COMBAT_LOG)
owner_->SendPacketAroundForSync(&msg_brd);
};
}