#include "stdafx.h" #include "config.h" #include "char.h" #include "char_manager.h" #include "affect.h" #include "packet.h" #include "buffer_manager.h" #include "desc_client.h" #include "battle.h" #include "guild.h" #include "utils.h" #include "locale_service.h" #include "lua_incl.h" #include "arena.h" #include "horsename_manager.h" #include "item.h" #include "DragonSoul.h" #define IS_NO_SAVE_AFFECT(type) ((type) == AFFECT_WAR_FLAG || (type) == AFFECT_REVIVE_INVISIBLE || ((type) >= AFFECT_PREMIUM_START && (type) <= AFFECT_PREMIUM_END)) #define IS_NO_CLEAR_ON_DEATH_AFFECT(type) ((type) == AFFECT_BLOCK_CHAT || ((type) >= 500 && (type) < 600)) void SendAffectRemovePacket(LPDESC d, DWORD pid, DWORD type, BYTE point) { TPacketGCAffectRemove ptoc; ptoc.bHeader = HEADER_GC_AFFECT_REMOVE; ptoc.dwType = type; ptoc.bApplyOn = point; d->Packet(&ptoc, sizeof(TPacketGCAffectRemove)); TPacketGDRemoveAffect ptod; ptod.dwPID = pid; ptod.dwType = type; ptod.bApplyOn = point; db_clientdesc->DBPacket(HEADER_GD_REMOVE_AFFECT, 0, &ptod, sizeof(ptod)); } void SendAffectAddPacket(LPDESC d, CAffect * pkAff) { TPacketGCAffectAdd ptoc; ptoc.bHeader = HEADER_GC_AFFECT_ADD; ptoc.elem.dwType = pkAff->dwType; ptoc.elem.bApplyOn = pkAff->bApplyOn; ptoc.elem.lApplyValue = pkAff->lApplyValue; ptoc.elem.dwFlag = pkAff->dwFlag; ptoc.elem.lDuration = pkAff->lDuration; ptoc.elem.lSPCost = pkAff->lSPCost; d->Packet(&ptoc, sizeof(TPacketGCAffectAdd)); } //////////////////////////////////////////////////////////////////// // Affect CAffect * CHARACTER::FindAffect(DWORD dwType, BYTE bApply) const { itertype(m_list_pkAffect) it = m_list_pkAffect.begin(); while (it != m_list_pkAffect.end()) { CAffect * pkAffect = *it++; if (pkAffect->dwType == dwType && (bApply == APPLY_NONE || bApply == pkAffect->bApplyOn)) return pkAffect; } return NULL; } EVENTFUNC(affect_event) { char_event_info* info = dynamic_cast( event->info ); if ( info == NULL ) { SPDLOG_ERROR("affect_event> Null pointer" ); return 0; } LPCHARACTER ch = info->ch; if (ch == NULL) { // return 0; } if (!ch->UpdateAffect()) return 0; else return passes_per_sec; // 1초 } bool CHARACTER::UpdateAffect() { // affect_event 에서 처리할 일은 아니지만, 1초짜리 이벤트에서 처리하는 것이 // 이것 뿐이라 여기서 물약 처리를 한다. if (GetPoint(POINT_HP_RECOVERY) > 0) { if (GetMaxHP() <= GetHP()) { PointChange(POINT_HP_RECOVERY, -GetPoint(POINT_HP_RECOVERY)); } else { int iVal = 0; if (LC_IsYMIR()) { iVal = std::min(GetPoint(POINT_HP_RECOVERY), GetMaxHP() * 9 / 100); } else { iVal = std::min(GetPoint(POINT_HP_RECOVERY), GetMaxHP() * 7 / 100); } PointChange(POINT_HP, iVal); PointChange(POINT_HP_RECOVERY, -iVal); } } if (GetPoint(POINT_SP_RECOVERY) > 0) { if (GetMaxSP() <= GetSP()) PointChange(POINT_SP_RECOVERY, -GetPoint(POINT_SP_RECOVERY)); else { int iVal; if (!g_iUseLocale) iVal = std::min(GetPoint(POINT_SP_RECOVERY), GetMaxSP() * 7 / 100); else iVal = std::min(GetPoint(POINT_SP_RECOVERY), GetMaxSP() * 7 / 100); PointChange(POINT_SP, iVal); PointChange(POINT_SP_RECOVERY, -iVal); } } if (GetPoint(POINT_HP_RECOVER_CONTINUE) > 0) { PointChange(POINT_HP, GetPoint(POINT_HP_RECOVER_CONTINUE)); } if (GetPoint(POINT_SP_RECOVER_CONTINUE) > 0) { PointChange(POINT_SP, GetPoint(POINT_SP_RECOVER_CONTINUE)); } AutoRecoveryItemProcess( AFFECT_AUTO_HP_RECOVERY ); AutoRecoveryItemProcess( AFFECT_AUTO_SP_RECOVERY ); // 스테미나 회복 if (GetMaxStamina() > GetStamina()) { int iSec = (get_dword_time() - GetStopTime()) / 3000; if (iSec) PointChange(POINT_STAMINA, GetMaxStamina()/1); } // ProcessAffect는 affect가 없으면 true를 리턴한다. if (ProcessAffect()) if (GetPoint(POINT_HP_RECOVERY) == 0 && GetPoint(POINT_SP_RECOVERY) == 0 && GetStamina() == GetMaxStamina()) { m_pkAffectEvent = NULL; return false; } return true; } void CHARACTER::StartAffectEvent() { if (m_pkAffectEvent) return; char_event_info* info = AllocEventInfo(); info->ch = this; m_pkAffectEvent = event_create(affect_event, info, passes_per_sec); SPDLOG_DEBUG("StartAffectEvent {} {} {}", GetName(), (void*) this, (void*) get_pointer(m_pkAffectEvent)); } void CHARACTER::ClearAffect(bool bSave) { TAffectFlag afOld = m_afAffectFlag; WORD wMovSpd = GetPoint(POINT_MOV_SPEED); WORD wAttSpd = GetPoint(POINT_ATT_SPEED); itertype(m_list_pkAffect) it = m_list_pkAffect.begin(); while (it != m_list_pkAffect.end()) { CAffect * pkAff = *it; if (bSave) { if ( IS_NO_CLEAR_ON_DEATH_AFFECT(pkAff->dwType) || IS_NO_SAVE_AFFECT(pkAff->dwType) ) { ++it; continue; } if (IsPC()) { SendAffectRemovePacket(GetDesc(), GetPlayerID(), pkAff->dwType, pkAff->bApplyOn); } } ComputeAffect(pkAff, false); it = m_list_pkAffect.erase(it); CAffect::Release(pkAff); } if (afOld != m_afAffectFlag || wMovSpd != GetPoint(POINT_MOV_SPEED) || wAttSpd != GetPoint(POINT_ATT_SPEED)) UpdatePacket(); CheckMaximumPoints(); if (m_list_pkAffect.empty()) event_cancel(&m_pkAffectEvent); } int CHARACTER::ProcessAffect() { bool bDiff = false; CAffect *pkAff = NULL; // // 프리미엄 처리 // for (int i = 0; i <= PREMIUM_MAX_NUM; ++i) { int aff_idx = i + AFFECT_PREMIUM_START; pkAff = FindAffect(aff_idx); if (!pkAff) continue; int remain = GetPremiumRemainSeconds(i); if (remain < 0) { RemoveAffect(aff_idx); bDiff = true; } else pkAff->lDuration = remain + 1; } ////////// HAIR_AFFECT pkAff = FindAffect(AFFECT_HAIR); if (pkAff) { // IF HAIR_LIMIT_TIME() < CURRENT_TIME() if ( this->GetQuestFlag("hair.limit_time") < get_global_time()) { // SET HAIR NORMAL this->SetPart(PART_HAIR, 0); // REMOVE HAIR AFFECT RemoveAffect(AFFECT_HAIR); } else { // INCREASE AFFECT DURATION ++(pkAff->lDuration); } } ////////// HAIR_AFFECT // CHorseNameManager::instance().Validate(this); TAffectFlag afOld = m_afAffectFlag; int lMovSpd = GetPoint(POINT_MOV_SPEED); int lAttSpd = GetPoint(POINT_ATT_SPEED); itertype(m_list_pkAffect) it; it = m_list_pkAffect.begin(); while (it != m_list_pkAffect.end()) { pkAff = *it; bool bEnd = false; if (pkAff->dwType >= GUILD_SKILL_START && pkAff->dwType <= GUILD_SKILL_END) { if (!GetGuild() || !GetGuild()->UnderAnyWar()) bEnd = true; } if (pkAff->lSPCost > 0) { if (GetSP() < pkAff->lSPCost) bEnd = true; else PointChange(POINT_SP, -pkAff->lSPCost); } // AFFECT_DURATION_BUG_FIX // 무한 효과 아이템도 시간을 줄인다. // 시간을 매우 크게 잡기 때문에 상관 없을 것이라 생각됨. if ( --pkAff->lDuration <= 0 ) { bEnd = true; } // END_AFFECT_DURATION_BUG_FIX if (bEnd) { it = m_list_pkAffect.erase(it); ComputeAffect(pkAff, false); bDiff = true; if (IsPC()) { SendAffectRemovePacket(GetDesc(), GetPlayerID(), pkAff->dwType, pkAff->bApplyOn); } CAffect::Release(pkAff); continue; } ++it; } if (bDiff) { if (afOld != m_afAffectFlag || lMovSpd != GetPoint(POINT_MOV_SPEED) || lAttSpd != GetPoint(POINT_ATT_SPEED)) { UpdatePacket(); } CheckMaximumPoints(); } if (m_list_pkAffect.empty()) return true; return false; } void CHARACTER::SaveAffect() { TPacketGDAddAffect p; itertype(m_list_pkAffect) it = m_list_pkAffect.begin(); while (it != m_list_pkAffect.end()) { CAffect * pkAff = *it++; if (IS_NO_SAVE_AFFECT(pkAff->dwType)) continue; SPDLOG_DEBUG("AFFECT_SAVE: {} {} {} {}", pkAff->dwType, pkAff->bApplyOn, pkAff->lApplyValue, pkAff->lDuration); p.dwPID = GetPlayerID(); p.elem.dwType = pkAff->dwType; p.elem.bApplyOn = pkAff->bApplyOn; p.elem.lApplyValue = pkAff->lApplyValue; p.elem.dwFlag = pkAff->dwFlag; p.elem.lDuration = pkAff->lDuration; p.elem.lSPCost = pkAff->lSPCost; db_clientdesc->DBPacket(HEADER_GD_ADD_AFFECT, 0, &p, sizeof(p)); } } EVENTINFO(load_affect_login_event_info) { DWORD pid; DWORD count; char* data; load_affect_login_event_info() : pid( 0 ) , count( 0 ) , data( 0 ) { } }; EVENTFUNC(load_affect_login_event) { load_affect_login_event_info* info = dynamic_cast( event->info ); if ( info == NULL ) { SPDLOG_ERROR("load_affect_login_event_info> Null pointer" ); return 0; } DWORD dwPID = info->pid; LPCHARACTER ch = CHARACTER_MANAGER::instance().FindByPID(dwPID); if (!ch) { M2_DELETE_ARRAY(info->data); return 0; } LPDESC d = ch->GetDesc(); if (!d) { M2_DELETE_ARRAY(info->data); return 0; } if (d->IsPhase(PHASE_HANDSHAKE) || d->IsPhase(PHASE_LOGIN) || d->IsPhase(PHASE_SELECT) || d->IsPhase(PHASE_DEAD) || d->IsPhase(PHASE_LOADING)) { return PASSES_PER_SEC(1); } else if (d->IsPhase(PHASE_CLOSE)) { M2_DELETE_ARRAY(info->data); return 0; } else if (d->IsPhase(PHASE_GAME)) { SPDLOG_DEBUG("Affect Load by Event"); ch->LoadAffect(info->count, (TPacketAffectElement*)info->data); M2_DELETE_ARRAY(info->data); return 0; } else { SPDLOG_ERROR("input_db.cpp:quest_login_event INVALID PHASE pid {}", ch->GetPlayerID()); M2_DELETE_ARRAY(info->data); return 0; } } void CHARACTER::LoadAffect(DWORD dwCount, TPacketAffectElement * pElements) { m_bIsLoadedAffect = false; if (!GetDesc()->IsPhase(PHASE_GAME)) { SPDLOG_TRACE("LOAD_AFFECT: Creating Event", GetName(), dwCount); load_affect_login_event_info* info = AllocEventInfo(); info->pid = GetPlayerID(); info->count = dwCount; info->data = M2_NEW char[sizeof(TPacketAffectElement) * dwCount]; memcpy(info->data, pElements, sizeof(TPacketAffectElement) * dwCount); event_create(load_affect_login_event, info, PASSES_PER_SEC(1)); return; } ClearAffect(true); SPDLOG_TRACE("LOAD_AFFECT: {} count {}", GetName(), dwCount); TAffectFlag afOld = m_afAffectFlag; int lMovSpd = GetPoint(POINT_MOV_SPEED); int lAttSpd = GetPoint(POINT_ATT_SPEED); for (DWORD i = 0; i < dwCount; ++i, ++pElements) { // 무영진은 로드하지않는다. if (pElements->dwType == SKILL_MUYEONG) continue; if (AFFECT_AUTO_HP_RECOVERY == pElements->dwType || AFFECT_AUTO_SP_RECOVERY == pElements->dwType) { LPITEM item = FindItemByID( pElements->dwFlag ); if (NULL == item) continue; item->Lock(true); } if (pElements->bApplyOn >= POINT_MAX_NUM) { SPDLOG_ERROR("invalid affect data {} ApplyOn {} ApplyValue {}", GetName(), pElements->bApplyOn, pElements->lApplyValue); continue; } SPDLOG_TRACE("Load Affect : Affect {} {} {}", GetName(), pElements->dwType, pElements->bApplyOn ); CAffect* pkAff = CAffect::Acquire(); m_list_pkAffect.push_back(pkAff); pkAff->dwType = pElements->dwType; pkAff->bApplyOn = pElements->bApplyOn; pkAff->lApplyValue = pElements->lApplyValue; pkAff->dwFlag = pElements->dwFlag; pkAff->lDuration = pElements->lDuration; pkAff->lSPCost = pElements->lSPCost; SendAffectAddPacket(GetDesc(), pkAff); ComputeAffect(pkAff, true); } if ( CArenaManager::instance().IsArenaMap(GetMapIndex()) == true ) { RemoveGoodAffect(); } if (afOld != m_afAffectFlag || lMovSpd != GetPoint(POINT_MOV_SPEED) || lAttSpd != GetPoint(POINT_ATT_SPEED)) { UpdatePacket(); } StartAffectEvent(); m_bIsLoadedAffect = true; // 용혼석 셋팅 로드 및 초기화 DragonSoul_Initialize(); } bool CHARACTER::AddAffect(DWORD dwType, BYTE bApplyOn, int lApplyValue, DWORD dwFlag, int lDuration, int lSPCost, bool bOverride, bool IsCube ) { // CHAT_BLOCK if (dwType == AFFECT_BLOCK_CHAT && lDuration > 1) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Your chat has been blocked by a GM.")); } // END_OF_CHAT_BLOCK if (lDuration == 0) { SPDLOG_ERROR("Character::AddAffect lDuration == 0 type {}", lDuration, dwType); lDuration = 1; } CAffect * pkAff = NULL; if (IsCube) pkAff = FindAffect(dwType,bApplyOn); else pkAff = FindAffect(dwType); if (dwFlag == AFF_STUN) { if (m_posDest.x != GetX() || m_posDest.y != GetY()) { m_posDest.x = m_posStart.x = GetX(); m_posDest.y = m_posStart.y = GetY(); battle_end(this); SyncPacket(); } } // 이미 있는 효과를 덮어 쓰는 처리 if (pkAff && bOverride) { ComputeAffect(pkAff, false); // 일단 효과를 삭제하고 if (GetDesc()) SendAffectRemovePacket(GetDesc(), GetPlayerID(), pkAff->dwType, pkAff->bApplyOn); } else { // // 새 에펙를 추가 // // NOTE: 따라서 같은 type 으로도 여러 에펙트를 붙을 수 있다. // pkAff = CAffect::Acquire(); m_list_pkAffect.push_back(pkAff); } SPDLOG_DEBUG("AddAffect {} type {} apply {} {} flag {} duration {}", GetName(), dwType, bApplyOn, lApplyValue, dwFlag, lDuration); pkAff->dwType = dwType; pkAff->bApplyOn = bApplyOn; pkAff->lApplyValue = lApplyValue; pkAff->dwFlag = dwFlag; pkAff->lDuration = lDuration; pkAff->lSPCost = lSPCost; WORD wMovSpd = GetPoint(POINT_MOV_SPEED); WORD wAttSpd = GetPoint(POINT_ATT_SPEED); ComputeAffect(pkAff, true); if (pkAff->dwFlag || wMovSpd != GetPoint(POINT_MOV_SPEED) || wAttSpd != GetPoint(POINT_ATT_SPEED)) UpdatePacket(); StartAffectEvent(); if (IsPC()) { SendAffectAddPacket(GetDesc(), pkAff); if (IS_NO_SAVE_AFFECT(pkAff->dwType)) return true; TPacketGDAddAffect p; p.dwPID = GetPlayerID(); p.elem.dwType = pkAff->dwType; p.elem.bApplyOn = pkAff->bApplyOn; p.elem.lApplyValue = pkAff->lApplyValue; p.elem.dwFlag = pkAff->dwFlag; p.elem.lDuration = pkAff->lDuration; p.elem.lSPCost = pkAff->lSPCost; db_clientdesc->DBPacket(HEADER_GD_ADD_AFFECT, 0, &p, sizeof(p)); } return true; } void CHARACTER::RefreshAffect() { itertype(m_list_pkAffect) it = m_list_pkAffect.begin(); while (it != m_list_pkAffect.end()) { CAffect * pkAff = *it++; ComputeAffect(pkAff, true); } } void CHARACTER::ComputeAffect(CAffect * pkAff, bool bAdd) { if (bAdd && pkAff->dwType >= GUILD_SKILL_START && pkAff->dwType <= GUILD_SKILL_END) { if (!GetGuild()) return; if (!GetGuild()->UnderAnyWar()) return; } if (pkAff->dwFlag) { if (!bAdd) m_afAffectFlag.Reset(pkAff->dwFlag); else m_afAffectFlag.Set(pkAff->dwFlag); } if (bAdd) PointChange(pkAff->bApplyOn, pkAff->lApplyValue); else PointChange(pkAff->bApplyOn, -pkAff->lApplyValue); if (pkAff->dwType == SKILL_MUYEONG) { if (bAdd) StartMuyeongEvent(); else StopMuyeongEvent(); } } bool CHARACTER::RemoveAffect(CAffect * pkAff) { if (!pkAff) return false; // AFFECT_BUF_FIX m_list_pkAffect.remove(pkAff); // END_OF_AFFECT_BUF_FIX ComputeAffect(pkAff, false); // 백기 버그 수정. // 백기 버그는 버프 스킬 시전->둔갑->백기 사용(AFFECT_REVIVE_INVISIBLE) 후 바로 공격 할 경우에 발생한다. // 원인은 둔갑을 시전하는 시점에, 버프 스킬 효과를 무시하고 둔갑 효과만 적용되게 되어있는데, // 백기 사용 후 바로 공격하면 RemoveAffect가 불리게 되고, ComputePoints하면서 둔갑 효과 + 버프 스킬 효과가 된다. // ComputePoints에서 둔갑 상태면 버프 스킬 효과 안 먹히도록 하면 되긴 하는데, // ComputePoints는 광범위하게 사용되고 있어서 큰 변화를 주는 것이 꺼려진다.(어떤 side effect가 발생할지 알기 힘들다.) // 따라서 AFFECT_REVIVE_INVISIBLE가 RemoveAffect로 삭제되는 경우만 수정한다. // 시간이 다 되어 백기 효과가 풀리는 경우는 버그가 발생하지 않으므로 그와 똑같이 함. // (ProcessAffect를 보면 시간이 다 되어서 Affect가 삭제되는 경우, ComputePoints를 부르지 않는다.) if (AFFECT_REVIVE_INVISIBLE != pkAff->dwType) { ComputePoints(); } else { // Fix invisibility bug on login/revive/teleport UpdatePacket(); } CheckMaximumPoints(); SPDLOG_TRACE("AFFECT_REMOVE: {} (flag {} apply: {})", GetName(), pkAff->dwFlag, pkAff->bApplyOn); if (IsPC()) { SendAffectRemovePacket(GetDesc(), GetPlayerID(), pkAff->dwType, pkAff->bApplyOn); } CAffect::Release(pkAff); return true; } bool CHARACTER::RemoveAffect(DWORD dwType) { // CHAT_BLOCK if (dwType == AFFECT_BLOCK_CHAT) { ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Your chat block has been lifted.")); } // END_OF_CHAT_BLOCK bool flag = false; CAffect * pkAff; while ((pkAff = FindAffect(dwType))) { RemoveAffect(pkAff); flag = true; } return flag; } bool CHARACTER::IsAffectFlag(DWORD dwAff) const { return m_afAffectFlag.IsSet(dwAff); } void CHARACTER::RemoveGoodAffect() { RemoveAffect(AFFECT_MOV_SPEED); RemoveAffect(AFFECT_ATT_SPEED); RemoveAffect(AFFECT_STR); RemoveAffect(AFFECT_DEX); RemoveAffect(AFFECT_INT); RemoveAffect(AFFECT_CON); RemoveAffect(AFFECT_CHINA_FIREWORK); RemoveAffect(SKILL_JEONGWI); RemoveAffect(SKILL_GEOMKYUNG); RemoveAffect(SKILL_CHUNKEON); RemoveAffect(SKILL_EUNHYUNG); RemoveAffect(SKILL_GYEONGGONG); RemoveAffect(SKILL_GWIGEOM); RemoveAffect(SKILL_TERROR); RemoveAffect(SKILL_JUMAGAP); RemoveAffect(SKILL_MANASHILED); RemoveAffect(SKILL_HOSIN); RemoveAffect(SKILL_REFLECT); RemoveAffect(SKILL_KWAESOK); RemoveAffect(SKILL_JEUNGRYEOK); RemoveAffect(SKILL_GICHEON); } bool CHARACTER::IsGoodAffect(BYTE bAffectType) const { switch (bAffectType) { case (AFFECT_MOV_SPEED): case (AFFECT_ATT_SPEED): case (AFFECT_STR): case (AFFECT_DEX): case (AFFECT_INT): case (AFFECT_CON): case (AFFECT_CHINA_FIREWORK): case (SKILL_JEONGWI): case (SKILL_GEOMKYUNG): case (SKILL_CHUNKEON): case (SKILL_EUNHYUNG): case (SKILL_GYEONGGONG): case (SKILL_GWIGEOM): case (SKILL_TERROR): case (SKILL_JUMAGAP): case (SKILL_MANASHILED): case (SKILL_HOSIN): case (SKILL_REFLECT): case (SKILL_KWAESOK): case (SKILL_JEUNGRYEOK): case (SKILL_GICHEON): return true; } return false; } void CHARACTER::RemoveBadAffect() { SPDLOG_DEBUG("RemoveBadAffect {}", GetName()); // 독 RemovePoison(); RemoveFire(); // 스턴 : Value%로 상대방을 5초간 머리 위에 별이 돌아간다. (때리면 1/2 확률로 풀림) AFF_STUN RemoveAffect(AFFECT_STUN); // 슬로우 : Value%로 상대방의 공속/이속 모두 느려진다. 수련도에 따라 달라짐 기술로 사용 한 경우에 AFF_SLOW RemoveAffect(AFFECT_SLOW); // 투속마령 RemoveAffect(SKILL_TUSOK); // 저주 //RemoveAffect(SKILL_CURSE); // 파법술 //RemoveAffect(SKILL_PABUP); // 기절 : Value%로 상대방을 기절시킨다. 2초 AFF_FAINT //RemoveAffect(AFFECT_FAINT); // 다리묶임 : Value%로 상대방의 이동속도를 떨어트린다. 5초간 -40 AFF_WEB //RemoveAffect(AFFECT_WEB); // 잠들기 : Value%로 상대방을 10초간 잠재운다. (때리면 풀림) AFF_SLEEP //RemoveAffect(AFFECT_SLEEP); // 저주 : Value%로 상대방의 공등/방등 모두 떨어트린다. 수련도에 따라 달라짐 기술로 사용 한 경우에 AFF_CURSE //RemoveAffect(AFFECT_CURSE); // 마비 : Value%로 상대방을 4초간 마비시킨다. AFF_PARA //RemoveAffect(AFFECT_PARALYZE); // 부동박부 : 무당 기술 //RemoveAffect(SKILL_BUDONG); }