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

981 lines
32 KiB
C++

#include "StdAfx.h"
#include "FieldPvP.h"
#include "ChaoState.h"
#include "Player.h"
#include "ItemManager.h"
#include "DropManager.h"
#include "SCItemSlotContainer.h"
#include "PKScriptParser.h"
#include "GameGuildExtension.h"
#ifdef _NA_006826_20130722_DOMINATION_RENEWAL
#include "GameGuild.h"
#include "GameGuildManager.h"
#include "DominationInfoParser.h"
#include "GameDominationManager.h"
#endif // _NA_006826_20130722_DOMINATION_RENEWAL
//==================================================================================================
FieldPvPRuleManager::FieldPvPRuleManager( WORD wConflictParts )
: m_wConflictParts(wConflictParts)
{
CreateTroubledPart();
}
FieldPvPRuleManager::~FieldPvPRuleManager()
{
m_vTroubledPart.clear();
}
void
FieldPvPRuleManager::CreateTroubledPart()
{
m_vTroubledPart.clear();
if( m_wConflictParts & PTA_PK_TILE )
m_vTroubledPart.push_back(FieldPvpPtr(new BoundaryPart()));
if( m_wConflictParts & PTA_CONFLICT_TILE )
m_vTroubledPart.push_back(FieldPvpPtr(new NomalPart()));
if( m_wConflictParts & PTA_FREE_CONFLICT_TILE )
m_vTroubledPart.push_back(FieldPvpPtr(new FreePart()));
if( m_wConflictParts & PTA_DOMINATION_WAR_TILE )
m_vTroubledPart.push_back(FieldPvpPtr(new DominationPart()));
if( m_wConflictParts & PTA_SAFETY_ZONE )
m_vTroubledPart.push_back(FieldPvpPtr(new SafetyPart()));
}
FieldPvPRuleManager::FieldPvpPtr FieldPvPRuleManager::GetPart(Player* const player) const
{
if (obj_util::IsValid(player))
{
WORD tile = 0;
if (player->GetTileAtt(&tile))
{
for (FieldPvP_List::const_iterator it = m_vTroubledPart.begin();
it != m_vTroubledPart.end(); ++it)
{
FieldPvpPtr field_pvp = *it;
if (field_pvp->IsPart(tile))
{
return field_pvp;
}
}
}
}
return FieldPvpPtr();
}
//==================================================================================================
//필드 서버 공통 PvP룰
BOOL FieldPvP::ChkServer( OUT RC::eBATTLE_RESULT* pErrCode )
{
// 월드서버와 끊어져 있으면 PK를 막는다.
if( !g_pGameServer->IsConnectToWorldServer() )
{
*pErrCode = RC::RC_BATTLE_SERVER_STATE_IMPOSSIBLE_PK;
return FALSE;
// PK가 가능한 서버인지 체크
}
if( !g_pGameServer->IsPVPMode() )
{
*pErrCode = RC::RC_BATTLE_ISNOT_PK_SERVER;
return FALSE;
}
return TRUE;
}
BOOL FieldPvP::ChkTarget( Player *pTarget, OUT RC::eBATTLE_RESULT* pErrCode )
{
if( !pTarget )
{
*pErrCode = RC::RC_BATTLE_INVALID_MAINTARGET;
return FALSE;
}
// 타겟이 존재 할시 피격자 체크 (동일 지역이 아니라면 공격 불가)
WORD wTileAtt = 0;
if( FALSE == pTarget->GetTileAtt(&wTileAtt) )
{
*pErrCode = RC::RC_BATTLE_PLAYER_TILE_INVLIDPOS;
return FALSE;
}
if( !IsPart(wTileAtt) )
{
*pErrCode = RC::RC_BATTLE_ISNOT_TROUBLED_PART;
return FALSE;
}
return TRUE;
}
RC::eSKILL_RESULT FieldPvP::ConvErrCode( RC::eBATTLE_RESULT ErrCode )
{
switch( ErrCode )
{
case RC::RC_BATTLE_FAILED: return RC::RC_SKILL_FAILED;
case RC::RC_BATTLE_ISNOT_PK_SERVER: return RC::RC_SKILL_ISNOT_PK_SERVER;
case RC::RC_BATTLE_INVALID_MAINTARGET: return RC::RC_SKILL_TARGET_NOTEXIST;
case RC::RC_BATTLE_ISNOT_TROUBLED_PART: return RC::RC_SKILL_ISNOT_TROUBLED_TYPE;
case RC::RC_BATTLE_TARGET_LEVEL_IS_LOW: return RC::RC_SKILL_TARGET_LEVEL_IS_LOW;
case RC::RC_BATTLE_SAME_GUILD_CHARACTER: return RC::RC_SKILL_SAME_GUILD_CHARACTER;
case RC::RC_BATTLE_SAME_PARTY_CHARACTER: return RC::RC_SKILL_SAME_PARTY_CHARACTER;
case RC::RC_BATTLE_PLAYER_TILE_INVLIDPOS: return RC::RC_SKILL_PLAYER_TILE_INVLIDPOS;
case RC::RC_BATTLE_ATTACKER_LEVEL_IS_LOW: return RC::RC_SKILL_ATTACKER_LEVEL_IS_LOW;
case RC::RC_BATTLE_PK_IMPOSSIBLE_TARGET_TYPE: return RC::RC_SKILL_PK_IMPOSSIBLE_TARGET_TYPE;
case RC::RC_BATTLE_SERVER_STATE_IMPOSSIBLE_PK: return RC::RC_SKILL_SERVER_STATE_IMPOSSIBLE_PK;
default:
break;
}
return RC::RC_SKILL_FAILED;
}
eUSER_RELATION_TYPE FieldPvP::IsFriendPVP( Player* pAttacker, Player *pTarget )
{
RC::eBATTLE_RESULT dummy;
return PvPRule(pAttacker,pTarget,&dummy);
}
BOOL FieldPvP::CanPVP( Player* pAttacker, Player* pTarget, OUT RC::eBATTLE_RESULT* pErrCode )
{
if( !ChkServer(pErrCode) )
return FALSE; // 서버가 pvp할수 없다면 공격 불가.
PvPRule(pAttacker,pTarget,pErrCode);
if( *pErrCode != RC::RC_BATTLE_SUCCESS )
return FALSE;
return TRUE;
}
BOOL FieldPvP::CanPVP( Player* pAttacker, Player* pTarget, OUT RC::eSKILL_RESULT* pErrCode )
{
RC::eBATTLE_RESULT BattleErrCode = RC::RC_BATTLE_SUCCESS;
if( !CanPVP(pAttacker,pTarget,&BattleErrCode) )
{
*pErrCode = ConvErrCode(BattleErrCode);
return FALSE;
}
return TRUE;
}
VOID FieldPvP::Resurrection( Player* pDeadPlayer )
{
GameField* pField = pDeadPlayer->GetField();
WzVector wvPosOfPlayer;
if( pField )
wvPosOfPlayer = pField->GetRandomStartingVector();
else
pDeadPlayer->GetPos(&wvPosOfPlayer);
pDeadPlayer->SetFallDead(wvPosOfPlayer);
}
BOOL FieldPvP::ChkDropItem( Player* pAttacker, Player* pDeadPlayer )
{
// GM 맵에서는 아이템을 떨구지 않는다.
GameField *pField = pDeadPlayer->GetField();
if( pField )
{
if( HardCode::MAPCODE_for_GM == pField->GetFieldCode() )
return FALSE;
}
// 드랍 방지 아이템이 존재하는지 체크
if (pDeadPlayer->GetItemManager()->GetItemFunctionDropProtector().ProtectDrop(pAttacker)) {
return FALSE;
};
return TRUE;
}
VOID FieldPvP::DropItem( Player* pAttacker, Player* pDeadPlayer, SLOTIDX DropSlotIndex )
{
POSTYPE atSlotPos = 0;
SCSlotContainer* pContainer = NULL;
switch(DropSlotIndex)
{
case SI_EQUIPMENT:
//210924 RTX 陋구30%섯쪽딜쭝,페岱헙워딜膠틔
if(((unsigned int)rand()) % 100 <= 29){
pContainer = pDeadPlayer->GetItemManager()->GetItemSlotContainer( SI_EQUIPMENT );
if( !SelectPKDropItem( pContainer, atSlotPos ) )
{
pContainer = pDeadPlayer->GetItemManager()->GetItemSlotContainer( SI_INVENTORY );
if( !pDeadPlayer->GetCharInfo()->m_iInventoryLock && SelectPKDropItem(pContainer,atSlotPos) )
DropSlotIndex = SI_INVENTORY;
else
return;
}
}else{
pContainer = pDeadPlayer->GetItemManager()->GetItemSlotContainer( SI_INVENTORY );
if( !pDeadPlayer->GetCharInfo()->m_iInventoryLock && SelectPKDropItem(pContainer,atSlotPos) )
DropSlotIndex = SI_INVENTORY;
else
return;
}
break;
case SI_INVENTORY:
pContainer = pDeadPlayer->GetItemManager()->GetItemSlotContainer( SI_INVENTORY );
if( !pDeadPlayer->GetCharInfo()->m_iInventoryLock && SelectPKDropItem(pContainer,atSlotPos) )
{}
else
return;
break;
}
// 선택한 아이템을 필드에 떨군다.
SCItemSlot DropItemSlotOut;
SCSlotContainer * pAtContainer = pDeadPlayer->GetItemManager()->GetItemSlotContainer( DropSlotIndex );
if( !pAtContainer->DeleteSlot( atSlotPos, &DropItemSlotOut ) )
return;
if( DropItemSlotOut.IsPossessionItem() && DropItemSlotOut.GetIdentifyPossession() && !DropItemSlotOut.IsDestroyItem() )
{
switch( PKScriptParser::Instance()->GetPKOptionInfo().m_ePossessionDropOption )
{
case PKOptionInfo::ENUM_POSSESSION_DROP_NO:
SUNLOG( eCRITICAL_LOG, "Can't come to this logic...");
break;
case PKOptionInfo::ENUM_POSSESSION_DROP_ORIGINAL:
DropItemSlotOut.SetIdentifyPossession( FALSE );
break;
case PKOptionInfo::ENUM_POSSESSION_DROP_DESTROY:
DropItemSlotOut.SetDestroyItem( TRUE );
break;
}
}
WzVector wvCurPos = pDeadPlayer->GetRandomPosInAround( 2, 30 );
if( !g_DropManager.DropItemToField( pDeadPlayer->GetField(), &wvCurPos, pAttacker->GetObjectKey(), 0, DropItemSlotOut ) )
return;
MSG_CG_ITEM_PK_DROP_ITEM_CMD msg;
msg.m_atIndex = DropSlotIndex;
msg.m_atPos = atSlotPos;
pDeadPlayer->SendPacket( &msg, sizeof( msg ) );
// 게임 로그
GAMELOG->LogItem( ITEM_PK_DROP, pDeadPlayer, &DropItemSlotOut, 0, DropItemSlotOut.GetNum() );
}
BOOL FieldPvP::SelectPKDropItem( SCSlotContainer* pContainer, POSTYPE & OUT atSlotPos )
{
// 현재 컨테이너에 들어있는 아이템 개수
if( pContainer->GetSlotNum() == 0 )
return FALSE;
STLX_VECTOR<POSTYPE> vecValidSlot;
PKOptionInfo::ePoessessionDropOption DropOption = PKScriptParser::Instance()->GetPKOptionInfo().m_ePossessionDropOption;
//(shogen) for문을 두번 돌지 말고 벡터를
//두개써서 하는게 시간복잡도를 줄일건데...
//오늘 퇴사라 그냥 갑니다. 다음에 보시는 분들 수정 부탁해요 '')/
//장비만 검색해 본다.
for( POSTYPE SlotPos = 0; SlotPos < pContainer->GetMaxSlotNum(); ++SlotPos )
{
if( pContainer->IsEmpty(SlotPos) ) continue;
// 락여부 체크
if( pContainer->IsLocked(SlotPos) )
continue;
#ifdef _CN_008758_20160329_FIELD_PVP_DROPITEM_TRADESELL_CHANGE
// 드랍 불가능한 아이템인가? ( 퀘스트 아이템 )
SCItemSlot & rSlot = (SCItemSlot &)pContainer->GetSlot(SlotPos);
// PK 드랍 아이템을 제외한 모든 아이템 드랍이 가능하도록 변경
if (rSlot.CanTradeSell(eITEM_TRADESEEL_DROP_CAN_PKDROP) == false) {
continue;
}
#else
//210924 BY RTX 0x01宮밑꼇옵슥弄
SCItemSlot & rSlot = (SCItemSlot &)pContainer->GetSlot(SlotPos);
if(rSlot.item_info_->trade_sell_type & 0x01){
continue;
}
// 모두 불능의 아이템이면 바로 패스한다
//if (rSlot.CanTradeSell(eITEM_TRADESELL_DONTALL) == false) {
// continue;
//}
//드랍불가 아이템이면서 PK드랍 가능아이템인가 검사
if (rSlot.CanTradeSell(eITEM_TRADESELL_DROP) == false) {
//_NA_006052_20120904_CANNOT_DROP_CAN_PKDROP
if (rSlot.CanTradeSell(eITEM_TRADESEEL_DROP_CAN_PKDROP) == true) {
continue;
}
}
#endif //(_CN_008758_20160329_FIELD_PVP_DROPITEM_TRADESELL_CHANGE)
//장비 아이템인가?
//210924 BY RTX 혤句怜딜嶠포/뎐浪
//if( (!rSlot.GetItemInfo()->IsWeapon()) && !(rSlot.GetItemInfo()->IsArmor()) )
// continue;
if( rSlot.IsPossessionItem() && rSlot.GetIdentifyPossession() )
{
if( DropOption == PKOptionInfo::ENUM_POSSESSION_DROP_NO )
{
if( !rSlot.IsDestroyItem() )
continue;
}
}
// 드랍 가능한 아이템을 일단 setValidSlot에 넣는다.
vecValidSlot.push_back( SlotPos );
}
if( vecValidSlot.size() > 0 )
{
// 랜덤하게 섞은 후 첫번째 항목을 선택한다.
random_shuffle( vecValidSlot.begin(), vecValidSlot.end() );
atSlotPos = vecValidSlot[0];
return TRUE;
}
return FALSE;
}
BYTE FieldPvP::PKDropRatio( int iLevelDiff )
{
BYTE byDropRatio = 0;
if( iLevelDiff < 0 )
{
byDropRatio = max(0, iLevelDiff + 20 );
}
else if ( iLevelDiff == 0 )
{
#ifdef _CH_006797_20130703_PK_DROP_RULE_MODIFY
byDropRatio = 10;
#else
byDropRatio = 25;
#endif // _CH_006797_20130703_PK_DROP_RULE_MODIFY
}
else if( iLevelDiff > 0 )
{
byDropRatio = min(100, iLevelDiff + 30 );
#ifdef _CH_006797_20130703_PK_DROP_RULE_MODIFY
if (byDropRatio > 20)
{
byDropRatio = 20;
}
#else
#ifdef _NA_006057_20120904_PK_DROP_RATIO_MODIFY
if (byDropRatio > 50)
{
byDropRatio = 50;
}
#endif // _NA_006057_20120904_PK_DROP_RATIO_MODIFY
#endif // _CH_006797_20130703_PK_DROP_RULE_MODIFY
}
return byDropRatio;
}
// 길드 무관이거나 길원 일때 false, 쌍방적대 적대 선포일때 true
bool FieldPvP::HostilityRelationCheck(Player* pAttacker, Player* pTarget)
{
if( !pAttacker || !pTarget ) return false;
GUILDGUID AttkGld = pAttacker->GetPlayerOwner()->GetCharInfo()->m_GuildGuid;
GUILDGUID DefGld = pTarget->GetPlayerOwner()->GetCharInfo()->m_GuildGuid;
if( AttkGld == 0 || DefGld == 0 || AttkGld == DefGld ) return false;
return GuildSmallContentEntry::RelationSystem::IsHostilityRelation(AttkGld, DefGld);
}
bool FieldPvP::IsOneSideHostility(Player* pAttacker, Player* pTarget)
{
if( !pAttacker || !pTarget ) return false;
GUILDGUID AttkGld = pAttacker->GetPlayerOwner()->GetCharInfo()->m_GuildGuid;
GUILDGUID DefGld = pTarget->GetPlayerOwner()->GetCharInfo()->m_GuildGuid;
if( AttkGld == 0 || DefGld == 0 || AttkGld == DefGld ) return false;
return eGUILD_RELATION_ONE_SIDE_HOSTILITY ==
GuildSmallContentEntry::RelationSystem::GetRelationType(AttkGld,DefGld);
}
//////////////////////////////////////////////////////////////////////////
//자유 분쟁지역룰
BOOL FreePart::IsPart( const WORD wTileAtt )
{
static const BYTE opt = PKScriptParser::Instance()->GetPKOptionInfo().m_IgnorePKTile;
if( PKOptionInfo::ignore_FreePart & opt )
return FALSE;
return FieldPvP::IsPart(wTileAtt);
}
BOOL FreePart::RandomizePercent( Player* pAttacker, Player* pDeadPlayer )
{
// 드랍 확률을 체크한다.
PKOptionInfo & stPKOptionInfo = PKScriptParser::Instance()->GetPKOptionInfo();
int iLevelDiff = pDeadPlayer->GetLevel() - pAttacker->GetLevel();
#ifdef _NA_007710_20141014_MODIFY_CHAO_STATE_NORMAL_DO_NOT_DROP_ITEM
if( pDeadPlayer->GetChaoState().GetChaoState() == eCHAO_STATE_NORMAL )
{
if( !pAttacker->GetChaoState().FindBadFriend( pDeadPlayer->GetCharGuid() ) )
{
// 일반 상태에서는 PK당해도 리벤지 대상이 아닐경우 아이템을 드랍하지 않는다
return FALSE;
}
}
#endif // _NA_007710_20141014_MODIFY_CHAO_STATE_NORMAL_DO_NOT_DROP_ITEM
BYTE DropRatio = PKDropRatio( iLevelDiff );
return random_percent(DropRatio);
}
VOID FreePart::KillPlayer( Player* pAttacker, Player* pDeadPlayer, BOOL bSummoned, DAMAGETYPE damage_value /*= 0*/ )
{
// 소환체를 죽인 것이 아니라면 자유 분쟁 지역에서 아이템을 떨군다.
if( !bSummoned )
{
WORD wTileAtt = 0;
if( FALSE == pDeadPlayer->GetTileAtt(&wTileAtt) )
{
// 대상의 타일이 존재하지 않는다.
SUNLOG( eCRITICAL_LOG, "[TroubledPart::%s] Invalid Tile! \n", __FUNCTION__ );
return;
}
// PK 로그를 남긴다.
//_NA_008706_20160229_TEXTLOG_PK
GAMELOG->LogAction_PK( ACT_TROUBLED_KILL, pAttacker, pDeadPlayer, damage_value );
GAMELOG->LogAction_PK( ACT_TROUBLED_DIE, pDeadPlayer, pAttacker, damage_value );
//피격자가 프리 분쟁 지역일시 아이템을 떨군다.
#ifdef _NA_007710_20141014_MODIFY_CHAO_STATE_NORMAL_DO_NOT_DROP_ITEM
if( RandomizePercent(pAttacker,pDeadPlayer) &&
ChkDropItem(pAttacker,pDeadPlayer) )
#else // _NA_007710_20141014_MODIFY_CHAO_STATE_NORMAL_DO_NOT_DROP_ITEM
if( ChkDropItem(pAttacker,pDeadPlayer) &&
RandomizePercent(pAttacker,pDeadPlayer) )
#endif // _NA_007710_20141014_MODIFY_CHAO_STATE_NORMAL_DO_NOT_DROP_ITEM
{
SLOTIDX DropSlotIndex = SI_INVENTORY;
PKOptionInfo& stPKOptionInfo = PKScriptParser::Instance()->GetPKOptionInfo();
if( random_percent(stPKOptionInfo.m_EquipDropRatio) )
DropSlotIndex = SI_EQUIPMENT;
DropItem( pAttacker, pDeadPlayer, DropSlotIndex );
}
//마을에서 죽을시는 부활 시켜준다.
//MSG_CG_STATUS_RESURRECTION_TO_SAFEZONE_SYN 패킷이 들어올 경우 로직 변경 필요.
if( pDeadPlayer->GetGameZoneState() == ePRS_AT_VILLAGE ) Resurrection(pDeadPlayer);
}
}
eUSER_RELATION_TYPE FreePart::PvPRule( Player* pAttacker, Player *pTarget, OUT RC::eBATTLE_RESULT* pErrCode )
{
*pErrCode = RC::RC_BATTLE_SUCCESS;
if( pAttacker == pTarget )
return USER_RELATION_FRIEND; // 0. 자기자신인지이면 공격(버프) 가능
if( !ChkTarget(pTarget,pErrCode) )
return USER_RELATION_NEUTRALIST; // 1. 범위 공격시 CanPVP를 통하지 않으므로 체크 필요(타일속성).
if( !pAttacker->IsForceAttack() ) //.// 일반 공격일 경우
{
if( pAttacker->GetPartyState() == pTarget->GetPartyState() ) // 2. 파티원 아군
return USER_RELATION_FRIEND;
GUILDGUID AttkGld = pAttacker->GetPlayerOwner()->GetCharInfo()->m_GuildGuid;
GUILDGUID TargetGld = pTarget->GetPlayerOwner()->GetCharInfo()->m_GuildGuid;
if( AttkGld != 0 && AttkGld == TargetGld )
return USER_RELATION_FRIEND; // 3. 길드원이면 아군이다.
if (GuildSmallContentEntry::RelationSystem::GetRelationType(AttkGld, TargetGld) ==
eGUILD_RELATION_ALLIENCE)
return USER_RELATION_FRIEND; // 3_1. 길드 관계 동맹이면 아군.
}
return USER_RELATION_ENEMY; // 4. 예외에 비해당 유저 적군.
}
//////////////////////////////////////////////////////////////////////////
//일반 분쟁지역룰
BOOL NomalPart::IsPart( const WORD wTileAtt )
{
static const BYTE opt = PKScriptParser::Instance()->GetPKOptionInfo().m_IgnorePKTile;
if( PKOptionInfo::ignore_NomalPart & opt )
return FALSE;
return FieldPvP::IsPart(wTileAtt);
}
VOID NomalPart::KillPlayer( Player* pAttacker, Player* pDeadPlayer, BOOL bSummoned, DAMAGETYPE damage_value /*= 0*/ )
{
if( !bSummoned )
{
// PK 로그를 남긴다.
//_NA_008706_20160229_TEXTLOG_PK
GAMELOG->LogAction_PK( ACT_TROUBLED_KILL, pAttacker, pDeadPlayer, damage_value );
GAMELOG->LogAction_PK( ACT_TROUBLED_DIE, pDeadPlayer, pAttacker, damage_value );
//마을에서 죽을시는 부활 시켜준다.
//현재 마을에서 죽는 경우 없음.
//MSG_CG_STATUS_RESURRECTION_TO_SAFEZONE_SYN 패킷이 들어올 경우 로직 변경 필요.
if( pDeadPlayer->GetGameZoneState() == ePRS_AT_VILLAGE ) Resurrection(pDeadPlayer);
}
}
eUSER_RELATION_TYPE NomalPart::PvPRule( Player* pAttacker, Player *pTarget, OUT RC::eBATTLE_RESULT* pErrCode )
{
*pErrCode = RC::RC_BATTLE_SUCCESS;
if( pAttacker == pTarget )
return USER_RELATION_FRIEND; // 0. 자기자신인지이면 공격(버프) 가능
if( !ChkTarget(pTarget,pErrCode) )
return USER_RELATION_NEUTRALIST; // 1. 범위 공격시 CanPVP를 통하지 않으므로 체크 필요(타일속성).
if( pAttacker->GetPartyState() == pTarget->GetPartyState() ) // 2. 파티원 공격 불가
return USER_RELATION_FRIEND;
GUILDGUID AttkGld = pAttacker->GetPlayerOwner()->GetCharInfo()->m_GuildGuid;
GUILDGUID DefGld = pTarget->GetPlayerOwner()->GetCharInfo()->m_GuildGuid;
if( AttkGld != 0 && AttkGld == DefGld )
return USER_RELATION_FRIEND; // 3. 길드원이면 아군이다.
if (GuildSmallContentEntry::RelationSystem::GetRelationType(AttkGld, DefGld) ==
eGUILD_RELATION_ALLIENCE)
return USER_RELATION_FRIEND; // 3_1. 길드 관계 동맹이면 아군.
return USER_RELATION_ENEMY; // 4. 강제공격이 존재 하지 않으며, 예외에 비해당 유저 적군.
}
//////////////////////////////////////////////////////////////////////////
//경계지역룰
//길드 관계에 따른 pk룰 정리
// 기본정의 : "길드간 선전포고(적대선포)를 한 경우의 공격은 정당하며,
// 정당한 공격은 상대를 죽여도 패널티를 받지 않으며, 아이템 또한 드랍되지 않는다."
//
//경계지역의 경우 B에게 선전포고를 한 A의 공격은 정당공격으로 패널티를 받지 않으며
//B가 죽임을 당해도 아이템을 드랍하지 않는다.
//반대의 경우 B가 선전포고를 받아 들이지 않고(쌍방적대), A를 공격할 경우 패널티를 받으며,
//A가 아이템을 드랍하게 된다.
BOOL BoundaryPart::IsPart( const WORD wTileAtt )
{
static const BYTE opt = PKScriptParser::Instance()->GetPKOptionInfo().m_IgnorePKTile;
if( PKOptionInfo::ignore_BoundaryPart & opt )
return FALSE;
return FieldPvP::IsPart(wTileAtt);
}
BOOL BoundaryPart::RandomizePercent( Player* pAttacker, Player* pDeadPlayer )
{
// 드랍 확률을 체크한다.
PKOptionInfo & stPKOptionInfo = PKScriptParser::Instance()->GetPKOptionInfo();
int iLevelDiff = pDeadPlayer->GetLevel() - pAttacker->GetLevel(); BYTE DropRatio = 0;
if( pDeadPlayer->GetChaoState().GetChaoState() == eCHAO_STATE_NORMAL )
{
// 죽은넘이 킬러의 리벤지 대상이면 50% 확률을 적용시킨다.
if( pAttacker->GetChaoState().FindBadFriend( pDeadPlayer->GetCharGuid() ) )
{
DropRatio = stPKOptionInfo.m_BadFriendDropRatio;
}
else
{
//210924 BY RTX 혤句렷븐츰꼇딜괏빱
//獵契딜쪽=혤鬼(50,된섬뀌+30)
//#ifdef _NA_007710_20141014_MODIFY_CHAO_STATE_NORMAL_DO_NOT_DROP_ITEM
// 일반 상태에서는 PK당해도 리벤지 대상이 아닐경우 아이템을 드랍하지 않는다
// return FALSE;
//#else // _NA_007710_20141014_MODIFY_CHAO_STATE_NORMAL_DO_NOT_DROP_ITEM
DropRatio = PKDropRatio( iLevelDiff );
//#endif // _NA_007710_20141014_MODIFY_CHAO_STATE_NORMAL_DO_NOT_DROP_ITEM
}
}
else if( pDeadPlayer->GetChaoState().GetChaoState() == eCHAO_STATE_PREV_CHAO )
{
DropRatio = stPKOptionInfo.m_PreChaoDropRatio;
}
else if( pDeadPlayer->GetChaoState().GetChaoState() == eCHAO_STATE_CHAO )
{
DropRatio = stPKOptionInfo.m_ChaoDropRatio;
}
return random_percent(DropRatio);
}
//
VOID BoundaryPart::KillPlayer( Player* pAttacker, Player* pDeadPlayer, BOOL bSummoned, DAMAGETYPE damage_value /*= 0*/ )
{
//(lst1024)(080905)(2387)소환수를 죽일 경우 패널티 없음.
if( bSummoned ) return;
if( HostilityRelationCheck(pAttacker,pDeadPlayer) )
{
//_NA_008706_20160229_TEXTLOG_PK
GAMELOG->LogAction_PK( ACT_HOSTILITY_GUILD_KILL, pAttacker, pDeadPlayer, damage_value );
GAMELOG->LogAction_PK( ACT_HOSTILITY_GUILD_DIE, pDeadPlayer, pAttacker, damage_value );
#ifdef _CN_006894_20130913_PK_ITEMDROP_ABOUTHOSTILITYRELATION
if( ChkDropItem(pAttacker,pDeadPlayer) &&
RandomizePercent(pAttacker,pDeadPlayer) )
{
SLOTIDX DropSlotIndex = SI_INVENTORY;
PKOptionInfo& stPKOptionInfo = PKScriptParser::Instance()->GetPKOptionInfo();
if( random_percent(stPKOptionInfo.m_EquipDropRatio) )
DropSlotIndex = SI_EQUIPMENT;
DropItem( pAttacker, pDeadPlayer, DropSlotIndex );
}
#endif //_CN_006894_20130913_PK_ITEMDROP_ABOUTHOSTILITYRELATION
}
else
{
PKOptionInfo& stPKOptionInfo = PKScriptParser::Instance()->GetPKOptionInfo();
// 보라돌이 상태 해제
pAttacker->GetChaoState().EndBoradoriState();
// pTarget이 리벤지 리스트에 등록된 유저라면 월드서버에 삭제를 요청한다.
if( pAttacker->GetChaoState().FindBadFriend( pDeadPlayer->GetCharGuid() ) )
{
pAttacker->GetChaoState().RequestRemoveBadFriend( pDeadPlayer->GetCharGuid() );
}
#if !defined(_CHINA) //CHINA_PK_RULE - 중국 버전은 사용하지 않는다.
// (lst1024)(090617) 공격자가 일반이고, 상대가 프리카오일 경우는 카오성향을 올리지않는다.
else if( pAttacker->GetChaoState().GetChaoState() == eCHAO_STATE_NORMAL &&
pDeadPlayer->GetChaoState().GetChaoState() == eCHAO_STATE_PREV_CHAO )
{}
#endif
// 상대가 카오, 보라돌이 상태라면 나의 카오성향은 올리지 않는다.
else if( pDeadPlayer->GetChaoState().GetChaoState() == eCHAO_STATE_CHAO || pDeadPlayer->GetChaoState().IsBoradori() )
{}
// (lst1024)(090617)카오룰 변경 자유 분쟁에 소환수로 공격후 경계지역으로 도망, 소환수가 타 케릭을 죽일시 여기로 들어올수 있음.
// 20랩 이하는 보호필요.
else if( pAttacker->GetLevel() < stPKOptionInfo.m_PKProtectLV )
{}
else
{
// 카오성향을 올린다.
pAttacker->GetChaoState().UpChaoState();
// 상대방의 리벤지 리스트에 이미 등록된 유저인지 체크
if( !pDeadPlayer->GetChaoState().FindBadFriend( pAttacker->GetCharGuid() ) )
{
// 리벤지 리스트에 등록할 것인지 묻는다.
MSG_CG_CHAO_ASK_REVENGE_ADDITION_CMD cmdMsg;
_tcsncpy( cmdMsg.m_ptszRevengeName, pAttacker->GetCharName(), MAX_CHARNAME_LENGTH );
cmdMsg.m_ptszRevengeName[MAX_CHARNAME_LENGTH] = '\0';
pDeadPlayer->SendPacket( &cmdMsg, sizeof(cmdMsg) );
// 리벤지 대상의 이름을 기억해 놓는다.
pDeadPlayer->GetChaoState().SetRevengeName( cmdMsg.m_ptszRevengeName );
}
}
// PK 로그를 남긴다.
//_NA_008706_20160229_TEXTLOG_PK
GAMELOG->LogAction_PK( ACT_PK_KILL, pAttacker, pDeadPlayer, damage_value );
GAMELOG->LogAction_PK( ACT_PK_DIE, pDeadPlayer, pAttacker, damage_value );
#ifdef _NA_007710_20141014_MODIFY_CHAO_STATE_NORMAL_DO_NOT_DROP_ITEM
if( RandomizePercent(pAttacker,pDeadPlayer) &&
ChkDropItem(pAttacker,pDeadPlayer) )
#else // _NA_007710_20141014_MODIFY_CHAO_STATE_NORMAL_DO_NOT_DROP_ITEM
if( ChkDropItem(pAttacker,pDeadPlayer) &&
RandomizePercent(pAttacker,pDeadPlayer) )
#endif // _NA_007710_20141014_MODIFY_CHAO_STATE_NORMAL_DO_NOT_DROP_ITEM
{
SLOTIDX DropSlotIndex = SI_INVENTORY;
PKOptionInfo& stPKOptionInfo = PKScriptParser::Instance()->GetPKOptionInfo();
if( random_percent(stPKOptionInfo.m_EquipDropRatio) )
DropSlotIndex = SI_EQUIPMENT;
DropItem( pAttacker, pDeadPlayer, DropSlotIndex );
}
}
}
eUSER_RELATION_TYPE BoundaryPart::PvPRule( Player* pAttacker, Player *pTarget, OUT RC::eBATTLE_RESULT* pErrCode )
{
*pErrCode = RC::RC_BATTLE_SUCCESS;
if( pAttacker == pTarget )
return USER_RELATION_FRIEND; // 0. 자기자신인지이면 공격(버프) 가능
if( !ChkTarget(pTarget,pErrCode) )
return USER_RELATION_NEUTRALIST; // 1. 범위 공격시 CanPVP를 통하지 않으므로 체크 필요(타일속성).
GUILDGUID AttkGld = pAttacker->GetPlayerOwner()->GetCharInfo()->m_GuildGuid;
GUILDGUID DefGld = pTarget->GetPlayerOwner()->GetCharInfo()->m_GuildGuid;
if( AttkGld != 0 && AttkGld == DefGld )
{
return USER_RELATION_FRIEND; // 2. 길드원이면 아군이다.
}
//(lst1024)(080901) 길드 관계 동맹일 경우 강제공격 불가.
if (GuildSmallContentEntry::RelationSystem::GetRelationType(AttkGld, DefGld) ==
eGUILD_RELATION_ALLIENCE)
return USER_RELATION_FRIEND; // 3. 길드 관계 동맹이면 아군.
if (GuildSmallContentEntry::RelationSystem::IsHostilityRelation(AttkGld, DefGld))
return USER_RELATION_ENEMY; // 4. 길드 관계 적대면 적군.
if( pAttacker->GetPartyState() == pTarget->GetPartyState() )
{
return USER_RELATION_FRIEND; // 5. 파티원 이면 아군.
}
#if defined(_CHINA) //CHINA_PK_RULE - 중국 보라돌이 자동 강제공격 삭제.
if( pTarget->GetChaoState().IsChaoState() )
#else
if( pTarget->GetChaoState().IsBoradori() ||
pTarget->GetChaoState().IsChaoState() )
#endif
return USER_RELATION_ENEMY; // 6. 보라돌이, 카오상태이면 적군.
if( pAttacker->IsForceAttack() ) //.// 강제 공격룰
{
if( IsOneSideHostility(pAttacker,pTarget) )
return USER_RELATION_ENEMY; // 7. PK룰 보다 길드룰이 우선 하므로, 일방적대시에도 뉴비 공격이 가능하다.
PKOptionInfo& stPKOptionInfo = PKScriptParser::Instance()->GetPKOptionInfo();
if( pTarget->GetLevel() < stPKOptionInfo.m_PKProtectLV )
{
*pErrCode = RC::RC_BATTLE_TARGET_LEVEL_IS_LOW;
return USER_RELATION_NEUTRALIST; // 8. 피격자 레벨 체크
}
if( pAttacker->GetLevel() < stPKOptionInfo.m_PKAllowableLV )
{
*pErrCode = RC::RC_BATTLE_ATTACKER_LEVEL_IS_LOW;
return USER_RELATION_NEUTRALIST; // 9. 공격자 레벨 체크
}
return USER_RELATION_ENEMY; // 10. 일반 카오
}
return USER_RELATION_FRIEND; // 11. 예외에 비해당 하는 유저 아군
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//지역점령전룰
BOOL DominationPart::IsPart( const WORD wTileAtt )
{
static const BYTE opt = PKScriptParser::Instance()->GetPKOptionInfo().m_IgnorePKTile;
if( PKOptionInfo::ignore_Domination & opt )
return FALSE;
return FieldPvP::IsPart(wTileAtt);
}
VOID DominationPart::KillPlayer( Player* pAttacker, Player* pDeadPlayer, BOOL bSummoned, DAMAGETYPE damage_value /*= 0*/ )
{
if( !bSummoned )
{
//_NA_008706_20160229_TEXTLOG_PK
// PK 로그를 남긴다.
GAMELOG->LogAction_PK( ACT_DOMINATION_KILL, pAttacker, pDeadPlayer, damage_value );
GAMELOG->LogAction_PK( ACT_DOMINATION_DIE, pDeadPlayer, pAttacker, damage_value );
}
}
eUSER_RELATION_TYPE DominationPart::PvPRule( Player* pAttacker, Player *pTarget, OUT RC::eBATTLE_RESULT* pErrCode )
{
*pErrCode = RC::RC_BATTLE_SUCCESS;
if( pAttacker == pTarget )
return USER_RELATION_FRIEND; // 0. 자기자신인지이면 공격(버프) 가능
if( !ChkTarget(pTarget,pErrCode) )
return USER_RELATION_NEUTRALIST; // 1. 범위 공격시 CanPVP를 통하지 않으므로 체크 필요(타일속성).
GUILDGUID AttkGld = pAttacker->GetPlayerOwner()->GetCharInfo()->m_GuildGuid;
GUILDGUID DefGld = pTarget->GetPlayerOwner()->GetCharInfo()->m_GuildGuid;
if (AttkGld == 0 || DefGld == 0)
return USER_RELATION_ENEMY;
else if (AttkGld == DefGld )
return USER_RELATION_FRIEND; // 3. 길드원이면 아군이다.
#ifdef _NA_006826_20130722_DOMINATION_RENEWAL
// 4. 공격자와 피격자의 관계가 적대라도 각자의 길드가 수성측과 동맹일 경우.. 아군이다.
GameField* field = pAttacker->GetField();
if (field == NULL)
{
SUNLOG(eFULL_LOG, "["__FUNCTION__"]Not found field");
return USER_RELATION_NEUTRALIST;
}
MAPCODE map_code = \
DominationInfoParser::Instance()->GetDominationFieldCode(field->GetFieldCode());
if (map_code == 0)
{
SUNLOG(eFULL_LOG, "["__FUNCTION__"]Not found mapcode");
return USER_RELATION_NEUTRALIST;
}
const DOMINATION_INFO* domination_info = GameDominationManager::Instance()->GetDominationInfo(map_code);
if (domination_info == NULL)
{
SUNLOG(eFULL_LOG, "["__FUNCTION__"]Not found domination info");
return USER_RELATION_NEUTRALIST;
}
const GUILDGUID defense_guild_guid = domination_info->m_inGuildInfo.m_GuildGuid;
GameGuild* const defense_game_guild = g_GameGuildManager.FindGuild(defense_guild_guid);
// 수성 길드가 있을 경우..
if (defense_game_guild)
{
if (AttkGld == defense_guild_guid ||
DefGld == defense_guild_guid)
{
GameGuild* const attacker_game_guild = g_GameGuildManager.FindGuild(AttkGld);
GuildRelationSystem::GuildRelationEntry* attacker_guild_entry = \
attacker_game_guild->GetRelationEntry();
eGUILD_RELATION relation = attacker_guild_entry->GetRelationType(DefGld);
if (relation == eGUILD_RELATION_ALLIENCE)
{
return USER_RELATION_FRIEND;
}
}
GuildRelationSystem::GuildRelationEntry* defense_guild_entry = \
defense_game_guild->GetRelationEntry();
eGUILD_RELATION attacker_relation = defense_guild_entry->GetRelationType(AttkGld);
eGUILD_RELATION target_relation = defense_guild_entry->GetRelationType(DefGld);
if (attacker_relation == eGUILD_RELATION_ALLIENCE &&
target_relation == eGUILD_RELATION_ALLIENCE)
{
return USER_RELATION_FRIEND;
}
else if (attacker_relation != eGUILD_RELATION_ALLIENCE &&
target_relation != eGUILD_RELATION_ALLIENCE)
{
GameGuild* const attacker_game_guild = g_GameGuildManager.FindGuild(AttkGld);
GuildRelationSystem::GuildRelationEntry* attacker_guild_entry = \
attacker_game_guild->GetRelationEntry();
eGUILD_RELATION relation = attacker_guild_entry->GetRelationType(DefGld);
if (relation == eGUILD_RELATION_ALLIENCE)
{
return USER_RELATION_FRIEND;
}
}
}
// 수성 길드가 없을 경우.. (빈성)
else
{
GameGuild* const attacker_game_guild = g_GameGuildManager.FindGuild(AttkGld);
GuildRelationSystem::GuildRelationEntry* attacker_guild_entry = \
attacker_game_guild->GetRelationEntry();
eGUILD_RELATION relation = attacker_guild_entry->GetRelationType(DefGld);
if (relation == eGUILD_RELATION_ALLIENCE)
{
return USER_RELATION_FRIEND;
}
}
#endif // _NA_006826_20130722_DOMINATION_RENEWAL
return USER_RELATION_ENEMY;
}
//////////////////////////////////////////////////////////////////////////
//==================================================================================================
BOOL
SafetyPart::IsPart(const WORD wTileAtt)
{
static const BYTE opt = PKScriptParser::Instance()->GetPKOptionInfo().m_IgnorePKTile;
// (NOTE) 어차피 옵션 자체가 PvP방지이므로 로직은 동일하게 false반환으로...
if(PKOptionInfo::ignore_SafetyPart & opt)
return false;
// 하기 내용은 좀 더 검증 후 'false'로 수정할 필요 있다.
return FieldPvP::IsPart(wTileAtt);
}
VOID
SafetyPart::KillPlayer(Player* pAttacker, Player* pDeadPlayer, BOOL bSummoned, BOOL is_oneshot)
{
SUNLOG(eCRITICAL_LOG,
"["__FUNCTION__"]|斤口=violation PvP rule|Attacker=%s|Dead player=%s|Option=%d|",
pAttacker->GetCharName(), pDeadPlayer->GetCharName(), !!bSummoned);
}
eUSER_RELATION_TYPE
SafetyPart::PvPRule(Player* pAttacker, Player* pTarget, OUT RC::eBATTLE_RESULT* pErrCode)
{
// (NOTE) NormalPart와 유사하다.
*pErrCode = RC::RC_BATTLE_SUCCESS;
// 0. 자기자신인지이면 공격(버프) 가능
if(pAttacker == pTarget)
return USER_RELATION_FRIEND;
// 1. 파티원 공격 불가
if(pAttacker->GetPartyState() == pTarget->GetPartyState())
return USER_RELATION_FRIEND;
GUILDGUID guild_of_attacker = pAttacker->GetCharInfo()->m_GuildGuid;
if(guild_of_attacker != 0) {
GUILDGUID guild_of_target = pTarget->GetCharInfo()->m_GuildGuid;
if(guild_of_target != 0) {
if(guild_of_attacker == guild_of_target)
return USER_RELATION_FRIEND;
// (NOTE) 이건 무리하게 검사하지 않도록 한다.
//if(GetRelationType(guild_of_attacker, guild_of_target) == eGUILD_RELATION_ALLIENCE)
// return USER_RELATION_FRIEND;
}
}
// (WARNING) 좀 더 룰에 대한 고려를 할 필요가 있다.
return USER_RELATION_NEUTRALIST;
}