mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-05-25 02:57:45 +00:00
Implement party just enough to gpose.
This commit is contained in:
parent
d60b68cfa2
commit
e80d51ff72
11 changed files with 761 additions and 55 deletions
|
@ -367,7 +367,7 @@ namespace Sapphire::Network::ActorControl
|
|||
AllotAttribute = 0x135,
|
||||
|
||||
ClearFieldMarkers = 0x13A,
|
||||
CameraMode = 0x13B, // param12, 1 = camera mode enable, 0 = disable
|
||||
CameraMode = 0x13B, // param11, 1 = enable, 0 = disable
|
||||
CharaNameReq = 0x13D, // requests character name by content id
|
||||
HuntingLogDetails = 0x194,
|
||||
|
||||
|
|
|
@ -79,6 +79,7 @@ namespace Sapphire::Network::Packets
|
|||
LogMessage = 0x00D0,
|
||||
|
||||
Chat = 0x0161, // updated 5.25
|
||||
PartyChat = 0x0065, // updated 5.25
|
||||
|
||||
WorldVisitList = 0xF0FE, // added 4.5
|
||||
|
||||
|
@ -145,7 +146,7 @@ namespace Sapphire::Network::Packets
|
|||
ActorCast = 0x00C4, // updated 5.25
|
||||
SomeCustomiseChangePacketProbably = 0x00CD, // added 5.18
|
||||
|
||||
PartyList = 0x0287, // updated 5.18
|
||||
PartyList = 0x01B7, // updated 5.25
|
||||
HateRank = 0x012E, // updated 5.25
|
||||
HateList = 0x02C4, // updated 5.25
|
||||
ObjectSpawn = 0x00D2, // updated 5.25
|
||||
|
@ -228,6 +229,13 @@ namespace Sapphire::Network::Packets
|
|||
|
||||
EquipDisplayFlags = 0x010D, // updated 5.25
|
||||
|
||||
ShopMessage = 0x00C1, // updated 5.25
|
||||
LootMessage = 0x00B1, // updated 5.25
|
||||
SocialInviteMessage = 0x02F0, // updated 5.25
|
||||
SocialInviteMessage2 = 0x026E, // updated 5.25
|
||||
SocialInviteResponseMessage = 0x0117, // updated 5.25
|
||||
PartyMessage = 0x0284, // updated 5.25
|
||||
|
||||
/// Housing //////////////////////////////////////
|
||||
|
||||
LandSetInitialize = 0x0234, // updated 5.0
|
||||
|
@ -304,6 +312,7 @@ namespace Sapphire::Network::Packets
|
|||
CreateCrossWorldLS = 0x00AF, // updated 4.3
|
||||
|
||||
ChatHandler = 0x0189, // updated 5.25
|
||||
PartyChatHandler = 0x0065, // update 5.25
|
||||
|
||||
SocialListHandler = 0x0371, // updated 5.25
|
||||
SetSearchInfoHandler = 0x00FA, // updated 5.25
|
||||
|
@ -380,8 +389,13 @@ namespace Sapphire::Network::Packets
|
|||
PerformNoteHandler = 0x029B, // updated 4.3
|
||||
|
||||
WorldInteractionHandler = 0x0285, // updated 5.25
|
||||
ShopMessage = 0x00C1, // updated 5.25
|
||||
LootMessage = 0x00B1, // updated 5.25
|
||||
|
||||
SocialInviteHandler = 0x02BA, // updated 5.25
|
||||
SocialInviteResponseHandler = 0x02C7, // updated 5.25
|
||||
PartySetLeaderHandler = 0x02A5, // updated 5.25
|
||||
LeavePartyHandler = 0x00C0, // updated 5.25
|
||||
KickPartyMemberHandler = 0x0188, // update 5.25
|
||||
DisbandPartyHandler = 0x0336, // update 5.25
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -207,6 +207,13 @@ struct FFXIVIpcChatHandler :
|
|||
/* 001A */ char message[1012];
|
||||
};
|
||||
|
||||
struct FFXIVIpcPartyChatHandler :
|
||||
FFXIVIpcBasePacket< ChatHandler >
|
||||
{
|
||||
uint64_t unknown;
|
||||
char message[1024];
|
||||
};
|
||||
|
||||
struct FFXIVIpcShopEventHandler :
|
||||
FFXIVIpcBasePacket< ShopEventHandler >
|
||||
{
|
||||
|
@ -355,6 +362,61 @@ struct FFXIVIpcWorldInteractionHandler :
|
|||
Common::FFXIVARR_POSITION3 position;
|
||||
};
|
||||
|
||||
struct FFXIVIpcSocialInviteHandler :
|
||||
FFXIVIpcBasePacket< SocialInviteHandler >
|
||||
{
|
||||
uint64_t unknown;
|
||||
uint8_t p1;
|
||||
uint8_t p2;
|
||||
uint8_t socialType;
|
||||
char name[32];
|
||||
uint8_t padding[5];
|
||||
};
|
||||
|
||||
struct FFXIVIpcSocialInviteResponseHandler :
|
||||
FFXIVIpcBasePacket< SocialInviteResponseHandler >
|
||||
{
|
||||
uint64_t contentId;
|
||||
uint8_t p1;
|
||||
uint8_t p2;
|
||||
uint8_t socialType;
|
||||
uint8_t response;
|
||||
uint32_t unknown;
|
||||
};
|
||||
|
||||
struct FFXIVIpcPartySetLeaderHandler :
|
||||
FFXIVIpcBasePacket< PartySetLeaderHandler >
|
||||
{
|
||||
uint64_t contentId;
|
||||
uint8_t p1;
|
||||
uint8_t p2;
|
||||
char name[32];
|
||||
uint8_t padding[6];
|
||||
};
|
||||
|
||||
struct FFXIVIpcLeavePartyHandler :
|
||||
FFXIVIpcBasePacket< LeavePartyHandler >
|
||||
{
|
||||
uint64_t empty;
|
||||
};
|
||||
|
||||
struct FFXIVIpcKickPartyMemberHander :
|
||||
FFXIVIpcBasePacket< KickPartyMemberHandler >
|
||||
{
|
||||
uint64_t contentId;
|
||||
uint8_t p1;
|
||||
uint8_t p2;
|
||||
char name[32];
|
||||
uint8_t padding[6];
|
||||
};
|
||||
|
||||
struct FFXIVIpcDisbandPartyHandler :
|
||||
FFXIVIpcBasePacket< DisbandPartyHandler >
|
||||
{
|
||||
uint64_t empty;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif //_CORE_NETWORK_PACKETS_ZONE_CLIENT_IPC_H
|
||||
|
|
|
@ -46,6 +46,19 @@ namespace Sapphire::Network::Packets::Server
|
|||
char msg[1012];
|
||||
};
|
||||
|
||||
struct FFXIVIpcPartyChat : FFXIVIpcBasePacket< PartyChat >
|
||||
{
|
||||
uint64_t unknown;
|
||||
uint64_t contentId;
|
||||
uint32_t charaId;
|
||||
uint8_t u1;
|
||||
uint8_t u2;
|
||||
uint8_t u3;
|
||||
char name[32];
|
||||
char message[1024];
|
||||
uint8_t padding;
|
||||
};
|
||||
|
||||
struct FFXIVIpcChatBanned : FFXIVIpcBasePacket< ChatBanned >
|
||||
{
|
||||
uint8_t padding[4]; // I was not sure reinterpreting ZST is valid behavior in C++.
|
||||
|
@ -2078,6 +2091,83 @@ namespace Sapphire::Network::Packets::Server
|
|||
uint32_t param7;
|
||||
};
|
||||
|
||||
struct FFXIVIpcSocialInviteMessage : FFXIVIpcBasePacket< SocialInviteMessage >
|
||||
{
|
||||
uint64_t contentId;
|
||||
uint32_t expireTime;
|
||||
uint8_t p1;
|
||||
uint8_t p2;
|
||||
uint8_t socialType;
|
||||
uint8_t padding;
|
||||
uint8_t type;
|
||||
uint8_t unknown4;
|
||||
char name[32];
|
||||
uint8_t padding2[6];
|
||||
};
|
||||
|
||||
struct FFXIVIpcSocialInviteMessage2 : FFXIVIpcBasePacket< SocialInviteMessage2 >
|
||||
{
|
||||
uint64_t contentId;
|
||||
uint32_t unknown3;
|
||||
uint8_t p1;
|
||||
uint8_t p2;
|
||||
uint8_t socialType;
|
||||
uint8_t padding;
|
||||
char name[32];
|
||||
};
|
||||
|
||||
struct FFXIVIpcSocialInviteResponseMessage : FFXIVIpcBasePacket< SocialInviteResponseMessage >
|
||||
{
|
||||
uint64_t contentId;
|
||||
uint32_t unknown3;
|
||||
uint8_t u1AlwaysOne;
|
||||
uint8_t response;
|
||||
uint8_t u2AlwaysOne;
|
||||
char name[32];
|
||||
uint8_t padding;
|
||||
};
|
||||
|
||||
struct FFXIVIpcPartyList : FFXIVIpcBasePacket< PartyList >
|
||||
{
|
||||
struct
|
||||
{
|
||||
char name[32];
|
||||
uint64_t contentId;
|
||||
uint32_t charaId;
|
||||
uint32_t u1;
|
||||
uint32_t u2;
|
||||
uint32_t hp;
|
||||
uint32_t maxHp;
|
||||
uint16_t mp;
|
||||
uint16_t maxMp;
|
||||
uint16_t u3;
|
||||
uint16_t zoneId;
|
||||
uint8_t gposeSelectable;
|
||||
uint8_t classId;
|
||||
uint8_t u5;
|
||||
uint8_t level;
|
||||
uint8_t otherData[368];
|
||||
} member[8];
|
||||
uint64_t someContentId1;
|
||||
uint64_t someContentId2;
|
||||
uint8_t leaderIndex;
|
||||
uint8_t partySize;
|
||||
uint16_t padding1;
|
||||
uint32_t padding2;
|
||||
};
|
||||
|
||||
struct FFXIVIpcPartyMessage : FFXIVIpcBasePacket< PartyMessage >
|
||||
{
|
||||
uint64_t leaderContentId;
|
||||
uint64_t memberContentId;
|
||||
uint8_t u1;
|
||||
uint8_t u2;
|
||||
uint16_t type;
|
||||
uint8_t partySize; // ?
|
||||
char leaderName[32];
|
||||
char memberName[32];
|
||||
uint8_t padding[3];
|
||||
};
|
||||
}
|
||||
|
||||
#endif /*_CORE_NETWORK_PACKETS_SERVER_IPC_H*/
|
||||
|
|
|
@ -232,6 +232,10 @@ Sapphire::Common::OnlineStatus Sapphire::Entity::Player::getOnlineStatus() const
|
|||
void Sapphire::Entity::Player::setOnlineStatusMask( uint64_t status )
|
||||
{
|
||||
m_onlineStatus = status;
|
||||
sendToInRangeSet( makeActorControl( getId(), SetStatusIcon, static_cast< uint8_t >( getOnlineStatus() ) ), true );
|
||||
auto statusPacket = makeZonePacket< FFXIVIpcSetOnlineStatus >( getId() );
|
||||
statusPacket->data().onlineStatusFlags = status;
|
||||
queuePacket( statusPacket );
|
||||
}
|
||||
|
||||
uint64_t Sapphire::Entity::Player::getOnlineStatusMask() const
|
||||
|
@ -1047,7 +1051,6 @@ bool Sapphire::Entity::Player::hasStateFlag( Common::PlayerStateFlag flag ) cons
|
|||
|
||||
void Sapphire::Entity::Player::setStateFlag( Common::PlayerStateFlag flag )
|
||||
{
|
||||
auto prevOnlineStatus = getOnlineStatus();
|
||||
int32_t iFlag = static_cast< uint32_t >( flag );
|
||||
|
||||
uint16_t index;
|
||||
|
@ -1056,13 +1059,6 @@ void Sapphire::Entity::Player::setStateFlag( Common::PlayerStateFlag flag )
|
|||
|
||||
m_stateFlags[ index ] |= value;
|
||||
sendStateFlags();
|
||||
|
||||
auto newOnlineStatus = getOnlineStatus();
|
||||
|
||||
if( prevOnlineStatus != newOnlineStatus )
|
||||
sendToInRangeSet( makeActorControl( getId(), SetStatusIcon,
|
||||
static_cast< uint8_t >( getOnlineStatus() ) ), true );
|
||||
|
||||
}
|
||||
|
||||
void Sapphire::Entity::Player::setStateFlags( std::vector< Common::PlayerStateFlag > flags )
|
||||
|
@ -1083,8 +1079,6 @@ void Sapphire::Entity::Player::unsetStateFlag( Common::PlayerStateFlag flag )
|
|||
if( !hasStateFlag( flag ) )
|
||||
return;
|
||||
|
||||
auto prevOnlineStatus = getOnlineStatus();
|
||||
|
||||
int32_t iFlag = static_cast< uint32_t >( flag );
|
||||
|
||||
uint16_t index;
|
||||
|
@ -1093,11 +1087,6 @@ void Sapphire::Entity::Player::unsetStateFlag( Common::PlayerStateFlag flag )
|
|||
|
||||
m_stateFlags[ index ] ^= value;
|
||||
sendStateFlags();
|
||||
|
||||
auto newOnlineStatus = getOnlineStatus();
|
||||
|
||||
if( prevOnlineStatus != newOnlineStatus )
|
||||
sendToInRangeSet( makeActorControl( getId(), SetStatusIcon, static_cast< uint8_t >( getOnlineStatus() ) ), true );
|
||||
}
|
||||
|
||||
void Sapphire::Entity::Player::update( uint64_t tickCount )
|
||||
|
@ -2269,6 +2258,218 @@ void Sapphire::Entity::Player::clearBuyBackMap()
|
|||
m_shopBuyBackMap.clear();
|
||||
}
|
||||
|
||||
bool Sapphire::Entity::Player::isPartyLeader()
|
||||
{
|
||||
if( !m_partyLeader )
|
||||
return false;
|
||||
return m_partyLeader->getId() == getId();
|
||||
}
|
||||
|
||||
bool Sapphire::Entity::Player::isInParty()
|
||||
{
|
||||
return m_partyLeader != nullptr;
|
||||
}
|
||||
|
||||
Sapphire::Entity::PlayerPtr Sapphire::Entity::Player::getPartyLeader()
|
||||
{
|
||||
return m_partyLeader;
|
||||
}
|
||||
|
||||
bool Sapphire::Entity::Player::createEmptyParty()
|
||||
{
|
||||
if( m_partyLeader )
|
||||
return false;
|
||||
m_partyLeader = getAsPlayer();
|
||||
m_partyMemberList.clear();
|
||||
m_partyMemberList.push_back( m_partyLeader );
|
||||
setOnlineStatusMask( 0x0000803000000000ui64 );
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sapphire::Entity::Player::disbandParty()
|
||||
{
|
||||
if( isPartyLeader() )
|
||||
{
|
||||
for( auto member : m_partyMemberList )
|
||||
{
|
||||
if( member->getId() == getId() )
|
||||
continue;
|
||||
member->m_partyLeader = nullptr;
|
||||
member->setOnlineStatusMask( 0x0000800000000000ui64 );
|
||||
member->clearPartyList();
|
||||
}
|
||||
m_partyLeader = nullptr;
|
||||
clearPartyList();
|
||||
}
|
||||
}
|
||||
|
||||
Sapphire::Entity::PlayerPtr Sapphire::Entity::Player::getPartyInvitationSender()
|
||||
{
|
||||
return m_partyInvitationSender;
|
||||
}
|
||||
|
||||
void Sapphire::Entity::Player::setPartyInvitationSender( PlayerPtr sender )
|
||||
{
|
||||
m_partyInvitationSender = sender;
|
||||
}
|
||||
|
||||
bool Sapphire::Entity::Player::addPartyMember( PlayerPtr member )
|
||||
{
|
||||
if( !isPartyLeader() || member->isInParty() || getPartySize() >= 8 )
|
||||
return false;
|
||||
m_partyMemberList.push_back( member );
|
||||
member->m_partyLeader = m_partyLeader;
|
||||
member->setOnlineStatusMask( 0x0000802000000000ui64 );
|
||||
sendPartyListToParty();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Sapphire::Entity::Player::removePartyMember( PlayerPtr member )
|
||||
{
|
||||
if( member->isInParty() && member->getPartyLeader()->getId() == getId() )
|
||||
{
|
||||
if( member->getId() == member->getPartyLeader()->getId() )
|
||||
{
|
||||
Entity::PlayerPtr nextLeader = nullptr;
|
||||
for( auto it = m_partyMemberList.begin(); it != m_partyMemberList.end(); it++ )
|
||||
{
|
||||
if( !( *it )->isPartyLeader() )
|
||||
{
|
||||
nextLeader = *it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( !nextLeader )
|
||||
{
|
||||
disbandParty();
|
||||
return true;
|
||||
}
|
||||
changePartyLeader( nextLeader );
|
||||
return nextLeader->removePartyMember( member );
|
||||
}
|
||||
else
|
||||
{
|
||||
bool flag = false;
|
||||
for( auto it = m_partyMemberList.begin(); it != m_partyMemberList.end(); it++ )
|
||||
{
|
||||
if( ( *it )->getId() == member->getId() )
|
||||
{
|
||||
m_partyMemberList.erase( it );
|
||||
member->m_partyLeader = nullptr;
|
||||
member->setOnlineStatusMask( 0x0000800000000000ui64 );
|
||||
member->clearPartyList();
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( flag )
|
||||
{
|
||||
sendPartyListToParty();
|
||||
if( getPartySize() == 1 )
|
||||
{
|
||||
m_partyLeader = nullptr;
|
||||
m_partyMemberList.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Sapphire::Entity::Player::changePartyLeader( PlayerPtr newLeader )
|
||||
{
|
||||
if( !isPartyLeader() || !newLeader->isInParty() || newLeader->getPartyLeader()->getId() != getId() )
|
||||
return false;
|
||||
|
||||
for( auto it = m_partyMemberList.begin(); it != m_partyMemberList.end(); it++ )
|
||||
{
|
||||
if( ( *it )->getId() == newLeader->getId() )
|
||||
{
|
||||
newLeader->m_partyMemberList = m_partyMemberList;
|
||||
m_partyMemberList.clear();
|
||||
for( auto m : newLeader->m_partyMemberList )
|
||||
{
|
||||
m->m_partyLeader = newLeader;
|
||||
}
|
||||
newLeader->setOnlineStatusMask( 0x0000803000000000ui64 );
|
||||
setOnlineStatusMask( 0x0000802000000000ui64 );
|
||||
newLeader->sendPartyListToParty();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t Sapphire::Entity::Player::getPartySize()
|
||||
{
|
||||
if( isInParty() )
|
||||
{
|
||||
return m_partyMemberList.size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Sapphire::Entity::Player::sendPartyListToParty()
|
||||
{
|
||||
assert( isPartyLeader() );
|
||||
FFXIVIpcPartyList partyList = {};
|
||||
int i = 0;
|
||||
int leaderIndex = 0;
|
||||
for( auto member : m_partyMemberList )
|
||||
{
|
||||
assert( i < 8 );
|
||||
|
||||
memcpy( partyList.member[ i ].name, member->getName().c_str(), member->getName().length() + 1 );
|
||||
partyList.member[ i ].contentId = member->getContentId();
|
||||
partyList.member[ i ].charaId = member->getId();
|
||||
partyList.member[ i ].u1 = 0xE0000000;
|
||||
partyList.member[ i ].u2 = 0xE0000000;
|
||||
partyList.member[ i ].hp = member->getHp();
|
||||
partyList.member[ i ].maxHp = member->getMaxHp();
|
||||
partyList.member[ i ].mp = member->getMp();
|
||||
partyList.member[ i ].maxMp = 10000;
|
||||
partyList.member[ i ].u3 = 0x44;
|
||||
partyList.member[ i ].zoneId = member->getCurrentTerritory()->getTerritoryTypeId();
|
||||
partyList.member[ i ].gposeSelectable = 1;
|
||||
partyList.member[ i ].classId = static_cast< uint8_t >( member->getClass() );
|
||||
partyList.member[ i ].level = member->getLevel();
|
||||
if( member->isPartyLeader() )
|
||||
leaderIndex = i;
|
||||
i++;
|
||||
}
|
||||
partyList.someContentId1 = 0x0044000000000001ui64;
|
||||
partyList.someContentId2 = 0x0044000100000001ui64;
|
||||
partyList.leaderIndex = leaderIndex;
|
||||
partyList.partySize = getPartySize();
|
||||
|
||||
for( auto member : m_partyMemberList )
|
||||
{
|
||||
auto packet = makeZonePacket< FFXIVIpcPartyList >( member->getId() );
|
||||
memcpy( &packet->data().member[ 0 ], &partyList, sizeof( partyList ) );
|
||||
member->queuePacket( packet );
|
||||
}
|
||||
}
|
||||
|
||||
void Sapphire::Entity::Player::clearPartyList()
|
||||
{
|
||||
assert( !isInParty() );
|
||||
auto packet = makeZonePacket< FFXIVIpcPartyList >( getId() );
|
||||
queuePacket( packet );
|
||||
m_partyMemberList.clear();
|
||||
}
|
||||
|
||||
void Sapphire::Entity::Player::foreachPartyMember( std::function< void( PlayerPtr member ) > callback )
|
||||
{
|
||||
if( !isPartyLeader() || !callback )
|
||||
return;
|
||||
for( auto member : m_partyMemberList )
|
||||
{
|
||||
callback( member );
|
||||
}
|
||||
}
|
||||
|
||||
void Sapphire::Entity::Player::gaugeClear()
|
||||
{
|
||||
std::memset( &m_gauge, 0, sizeof( m_gauge ) );
|
||||
|
|
|
@ -990,8 +990,29 @@ namespace Sapphire::Entity
|
|||
void sendActorGauge();
|
||||
void gaugeSetRaw( uint8_t* pData );
|
||||
|
||||
// party
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private:
|
||||
PlayerPtr m_partyLeader;
|
||||
PlayerPtr m_partyInvitationSender;
|
||||
std::vector< PlayerPtr > m_partyMemberList;
|
||||
void clearPartyList();
|
||||
void sendPartyListToParty();
|
||||
public:
|
||||
bool isPartyLeader();
|
||||
bool isInParty();
|
||||
bool createEmptyParty();
|
||||
void disbandParty();
|
||||
PlayerPtr getPartyLeader();
|
||||
PlayerPtr getPartyInvitationSender();
|
||||
void setPartyInvitationSender( PlayerPtr sender );
|
||||
bool addPartyMember( PlayerPtr member );
|
||||
bool removePartyMember( PlayerPtr member );
|
||||
bool changePartyLeader( PlayerPtr newLeader );
|
||||
uint8_t getPartySize();
|
||||
void foreachPartyMember( std::function< void( PlayerPtr member ) > callback);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Common::HuntingLogEntry& getHuntingLogEntry( uint8_t index );
|
||||
|
||||
void sendHuntingLog();
|
||||
|
|
|
@ -134,7 +134,14 @@ Sapphire::Network::GameConnection::GameConnection( Sapphire::Network::HivePtr pH
|
|||
setZoneHandler( ClientZoneIpcType::WorldInteractionHandler, "WorldInteractionHandler", &GameConnection::worldInteractionhandler );
|
||||
|
||||
setChatHandler( ClientChatIpcType::TellReq, "TellReq", &GameConnection::tellHandler );
|
||||
setChatHandler( ClientZoneIpcType::PartyChatHandler, "PartyChatHandler", &GameConnection::partyChatHandler );
|
||||
|
||||
setZoneHandler( ClientZoneIpcType::SocialInviteHandler, "SocialInviteHandler", &GameConnection::socialInviteHandler );
|
||||
setZoneHandler( ClientZoneIpcType::SocialInviteResponseHandler, "SocialInviteResponseHandler", &GameConnection::socialInviteResponseHandler );
|
||||
setZoneHandler( ClientZoneIpcType::PartySetLeaderHandler, "PartySetLeaderHandler", &GameConnection::partySetLeaderHandler );
|
||||
setZoneHandler( ClientZoneIpcType::LeavePartyHandler, "LeavePartyHandler", &GameConnection::leavePartyHandler );
|
||||
setZoneHandler( ClientZoneIpcType::KickPartyMemberHandler, "KickPartyMemberHandler", &GameConnection::kickPartyMemberHandler );
|
||||
setZoneHandler( ClientZoneIpcType::DisbandPartyHandler, "DisbandPartyHandler", &GameConnection::disbandPartyHandler );
|
||||
}
|
||||
|
||||
Sapphire::Network::GameConnection::~GameConnection() = default;
|
||||
|
|
|
@ -121,6 +121,8 @@ namespace Sapphire::Network
|
|||
|
||||
DECLARE_HANDLER( chatHandler );
|
||||
|
||||
DECLARE_HANDLER( partyChatHandler );
|
||||
|
||||
DECLARE_HANDLER( zoneLineHandler );
|
||||
|
||||
DECLARE_HANDLER( clientTriggerHandler );
|
||||
|
@ -189,6 +191,17 @@ namespace Sapphire::Network
|
|||
|
||||
DECLARE_HANDLER( worldInteractionhandler );
|
||||
|
||||
DECLARE_HANDLER( socialInviteHandler );
|
||||
|
||||
DECLARE_HANDLER( socialInviteResponseHandler );
|
||||
|
||||
DECLARE_HANDLER( partySetLeaderHandler );
|
||||
|
||||
DECLARE_HANDLER( leavePartyHandler );
|
||||
|
||||
DECLARE_HANDLER( kickPartyMemberHandler );
|
||||
|
||||
DECLARE_HANDLER( disbandPartyHandler );
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -494,7 +494,18 @@ void Sapphire::Network::GameConnection::clientTriggerHandler( const Packets::FFX
|
|||
player.sendDebug( "event battle level sync: {0}, ilevel sync?: {1}", param12, param2 );
|
||||
break;
|
||||
}
|
||||
|
||||
case ClientTriggerType::CameraMode:
|
||||
{
|
||||
if( param11 == 1 )
|
||||
{
|
||||
player.setOnlineStatusMask( player.getOnlineStatusMask() | 0x0000000000040000ui64 );
|
||||
}
|
||||
else
|
||||
{
|
||||
player.setOnlineStatusMask( player.getOnlineStatusMask() & 0xFFFFFFFFFFFBFFFFui64 );
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
Logger::debug( "[{0}] Unhandled action: {1:04X}", m_pSession->getId(), commandId );
|
||||
|
|
|
@ -256,19 +256,12 @@ void Sapphire::Network::GameConnection::gm1Handler( const Packets::FFXIVARR_PACK
|
|||
{
|
||||
targetPlayer->setOnlineStatusMask( param1 );
|
||||
|
||||
auto statusPacket = makeZonePacket< FFXIVIpcSetOnlineStatus >( player.getId() );
|
||||
statusPacket->data().onlineStatusFlags = param1;
|
||||
queueOutPacket( statusPacket );
|
||||
|
||||
auto searchInfoPacket = makeZonePacket< FFXIVIpcSetSearchInfo >( player.getId() );
|
||||
searchInfoPacket->data().onlineStatusFlags = param1;
|
||||
searchInfoPacket->data().selectRegion = targetPlayer->getSearchSelectRegion();
|
||||
strcpy( searchInfoPacket->data().searchMessage, targetPlayer->getSearchMessage() );
|
||||
targetPlayer->queuePacket( searchInfoPacket );
|
||||
|
||||
targetPlayer->sendToInRangeSet( makeActorControl( player.getId(), SetStatusIcon,
|
||||
static_cast< uint8_t >( player.getOnlineStatus() ) ),
|
||||
true );
|
||||
player.sendNotice( "Icon for {0} was set to {1}", targetPlayer->getName(), param1 );
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -87,18 +87,11 @@ void Sapphire::Network::GameConnection::setSearchInfoHandler( const Packets::FFX
|
|||
// mark player as new adventurer
|
||||
player.setNewAdventurer( true );
|
||||
|
||||
auto statusPacket = makeZonePacket< FFXIVIpcSetOnlineStatus >( player.getId() );
|
||||
statusPacket->data().onlineStatusFlags = status;
|
||||
queueOutPacket( statusPacket );
|
||||
|
||||
auto searchInfoPacket = makeZonePacket< FFXIVIpcSetSearchInfo >( player.getId() );
|
||||
searchInfoPacket->data().onlineStatusFlags = status;
|
||||
searchInfoPacket->data().selectRegion = player.getSearchSelectRegion();
|
||||
strcpy( searchInfoPacket->data().searchMessage, player.getSearchMessage() );
|
||||
queueOutPacket( searchInfoPacket );
|
||||
|
||||
player.sendToInRangeSet( makeActorControl( player.getId(), SetStatusIcon,
|
||||
static_cast< uint8_t >( player.getOnlineStatus() ) ), true );
|
||||
}
|
||||
|
||||
void Sapphire::Network::GameConnection::reqSearchInfoHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket,
|
||||
|
@ -437,31 +430,47 @@ void Sapphire::Network::GameConnection::socialListHandler( const Packets::FFXIVA
|
|||
int32_t entrysizes = sizeof( listPacket->data().entries );
|
||||
memset( listPacket->data().entries, 0, sizeof( listPacket->data().entries ) );
|
||||
|
||||
listPacket->data().entries[ 0 ].bytes[ 2 ] = player.getCurrentTerritory()->getTerritoryTypeId();
|
||||
listPacket->data().entries[ 0 ].bytes[ 3 ] = 0x80;
|
||||
listPacket->data().entries[ 0 ].bytes[ 4 ] = 0x02;
|
||||
listPacket->data().entries[ 0 ].bytes[ 6 ] = 0x3B;
|
||||
listPacket->data().entries[ 0 ].bytes[ 11 ] = 0x10;
|
||||
listPacket->data().entries[ 0 ].classJob = static_cast< uint8_t >( player.getClass() );
|
||||
listPacket->data().entries[ 0 ].contentId = player.getContentId();
|
||||
listPacket->data().entries[ 0 ].level = player.getLevel();
|
||||
listPacket->data().entries[ 0 ].zoneId = player.getCurrentTerritory()->getTerritoryTypeId();
|
||||
listPacket->data().entries[ 0 ].zoneId1 = 0x0100;
|
||||
// TODO: no idea what this does
|
||||
//listPacket.data().entries[0].one = 1;
|
||||
auto fillEntryAt = [ &listPacket ]( int i, Entity::PlayerPtr nextPlayer )
|
||||
{
|
||||
listPacket->data().entries[ i ].bytes[ 2 ] = nextPlayer->getCurrentTerritory()->getTerritoryTypeId();
|
||||
listPacket->data().entries[ i ].bytes[ 3 ] = 0x80;
|
||||
listPacket->data().entries[ i ].bytes[ 4 ] = 0x02;
|
||||
listPacket->data().entries[ i ].bytes[ 6 ] = 0x3B;
|
||||
listPacket->data().entries[ i ].bytes[ 8 ] = nextPlayer->isPartyLeader() ? 1 : 0;
|
||||
listPacket->data().entries[ i ].bytes[ 11 ] = 0x10;
|
||||
listPacket->data().entries[ i ].classJob = static_cast< uint8_t >( nextPlayer->getClass() );
|
||||
listPacket->data().entries[ i ].contentId = nextPlayer->getContentId();
|
||||
listPacket->data().entries[ i ].level = nextPlayer->getLevel();
|
||||
listPacket->data().entries[ i ].zoneId = nextPlayer->getCurrentTerritory()->getTerritoryTypeId();
|
||||
listPacket->data().entries[ i ].zoneId1 = 0x0100;
|
||||
// TODO: no idea what this does
|
||||
//listPacket.data().entries[0].one = 1;
|
||||
|
||||
memcpy( listPacket->data().entries[ 0 ].name, player.getName().c_str(), strlen( player.getName().c_str() ) );
|
||||
memcpy( listPacket->data().entries[ i ].name, nextPlayer->getName().c_str(), strlen( nextPlayer->getName().c_str() ) );
|
||||
|
||||
// GC icon
|
||||
listPacket->data().entries[ 0 ].bytes1[ 0 ] = 2;
|
||||
// client language J = 0, E = 1, D = 2, F = 3
|
||||
listPacket->data().entries[ 0 ].bytes1[ 1 ] = 1;
|
||||
// user language settings flag J = 1, E = 2, D = 4, F = 8
|
||||
listPacket->data().entries[ 0 ].bytes1[ 2 ] = 1 + 2;
|
||||
listPacket->data().entries[ 0 ].onlineStatusMask = player.getOnlineStatusMask();
|
||||
// GC icon
|
||||
listPacket->data().entries[ i ].bytes1[ 0 ] = 2;
|
||||
// client language J = 0, E = 1, D = 2, F = 3
|
||||
listPacket->data().entries[ i ].bytes1[ 1 ] = 1;
|
||||
// user language settings flag J = 1, E = 2, D = 4, F = 8
|
||||
listPacket->data().entries[ i ].bytes1[ 2 ] = 1 + 2;
|
||||
listPacket->data().entries[ i ].onlineStatusMask = nextPlayer->getOnlineStatusMask();
|
||||
};
|
||||
auto nextPlayer = player.getAsPlayer();
|
||||
fillEntryAt( 0, nextPlayer );
|
||||
if( player.isInParty() )
|
||||
{
|
||||
int i = 1;
|
||||
player.getPartyLeader()->foreachPartyMember( [ &player, &fillEntryAt, &i ]( auto m )
|
||||
{
|
||||
if( m->getId() == player.getId() )
|
||||
return;
|
||||
fillEntryAt( i, m );
|
||||
i++;
|
||||
} );
|
||||
}
|
||||
|
||||
queueOutPacket( listPacket );
|
||||
|
||||
}
|
||||
else if( type == 2 )
|
||||
{ // friend list
|
||||
|
@ -478,6 +487,34 @@ void Sapphire::Network::GameConnection::socialListHandler( const Packets::FFXIVA
|
|||
|
||||
}
|
||||
|
||||
void Sapphire::Network::GameConnection::partyChatHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, Entity::Player& player )
|
||||
{
|
||||
// proper party chat packet not working, <se.x> won't work with this workaround
|
||||
if( !player.isInParty() )
|
||||
return;
|
||||
const auto packet = ZoneChannelPacket< Client::FFXIVIpcPartyChatHandler >( inPacket );
|
||||
//FFXIVIpcPartyChat chat = {};
|
||||
//chat.unknown = packet.data().unknown;
|
||||
//chat.contentId = player.getContentId();
|
||||
//chat.charaId = player.getId();
|
||||
//chat.u1 = 0x44;
|
||||
//memcpy( chat.name, player.getName().c_str(), player.getName().length() + 1 );
|
||||
//memcpy( chat.message, packet.data().message, sizeof( chat.message ) );
|
||||
auto leader = player.getPartyLeader();
|
||||
leader->foreachPartyMember( [ &packet, &player/*, chat*/ ]( auto m )
|
||||
{
|
||||
if( player.getId() == m->getId() )
|
||||
return;
|
||||
//auto pChat = makeZonePacket< FFXIVIpcPartyChat >( m->getId() );
|
||||
//memcpy( &pChat->data(), &chat, sizeof( chat ) );
|
||||
//m->queuePacket( pChat );
|
||||
|
||||
/* workaround */
|
||||
auto chatPacket = std::make_shared< Server::ChatPacket >( player, ChatType::Party, packet.data().message );
|
||||
m->queuePacket( chatPacket );
|
||||
} );
|
||||
}
|
||||
|
||||
void Sapphire::Network::GameConnection::chatHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket,
|
||||
Entity::Player& player )
|
||||
{
|
||||
|
@ -792,4 +829,261 @@ void Sapphire::Network::GameConnection::worldInteractionhandler( const Packets::
|
|||
player.sendToInRangeSet( pSetStatusPacket );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sapphire::Network::GameConnection::socialInviteHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, Entity::Player& player )
|
||||
{
|
||||
const auto packetIn = ZoneChannelPacket< Client::FFXIVIpcSocialInviteHandler >( inPacket );
|
||||
switch( packetIn.data().socialType )
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
if( player.isInParty() && !player.isPartyLeader() )
|
||||
return;
|
||||
std::string name( packetIn.data().name );
|
||||
auto& serverMgr = Common::Service< Sapphire::World::ServerMgr >::ref();
|
||||
auto session = serverMgr.getSession( name );
|
||||
if( session )
|
||||
{
|
||||
auto targetPlayer = session->getPlayer();
|
||||
if( targetPlayer->isInParty() )
|
||||
return;
|
||||
auto packet1 = makeZonePacket< FFXIVIpcSocialInviteMessage2 >( player.getId() );
|
||||
packet1->data().contentId = targetPlayer->getContentId();
|
||||
packet1->data().p1 = packetIn.data().p1;
|
||||
packet1->data().p2 = packetIn.data().p2;
|
||||
packet1->data().socialType = packetIn.data().socialType;
|
||||
memcpy( packet1->data().name, targetPlayer->getName().c_str(), targetPlayer->getName().length() + 1 );
|
||||
player.queuePacket( packet1 );
|
||||
|
||||
auto packet2 = makeZonePacket< FFXIVIpcSocialInviteMessage >( targetPlayer->getId() );
|
||||
packet2->data().contentId = player.getContentId();
|
||||
packet2->data().expireTime = Common::Util::getTimeSeconds() + 30;
|
||||
packet2->data().p1 = packetIn.data().p1;
|
||||
packet2->data().p2 = packetIn.data().p2;
|
||||
packet2->data().socialType = packetIn.data().socialType;
|
||||
packet2->data().type = 1;
|
||||
packet2->data().unknown4 = 1;
|
||||
memcpy( packet2->data().name, player.getName().c_str(), player.getName().length() + 1 );
|
||||
targetPlayer->queuePacket( packet2 );
|
||||
|
||||
targetPlayer->setPartyInvitationSender( player.getAsPlayer() );
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sapphire::Network::GameConnection::socialInviteResponseHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, Entity::Player& player )
|
||||
{
|
||||
const auto packetIn = ZoneChannelPacket< Client::FFXIVIpcSocialInviteResponseHandler >( inPacket );
|
||||
switch( packetIn.data().socialType )
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
if( player.isInParty() || !player.getPartyInvitationSender() )
|
||||
return;
|
||||
auto response = packetIn.data().response;
|
||||
auto sender = player.getPartyInvitationSender();
|
||||
player.setPartyInvitationSender( nullptr );
|
||||
auto packet1 = makeZonePacket< FFXIVIpcSocialInviteResponseMessage >( player.getId() );
|
||||
packet1->data().contentId = sender->getContentId();
|
||||
packet1->data().u1AlwaysOne = 1;
|
||||
packet1->data().response = response;
|
||||
packet1->data().u2AlwaysOne = 1;
|
||||
memcpy( packet1->data().name, sender->getName().c_str(), sender->getName().length() + 1 );
|
||||
player.queuePacket( packet1 );
|
||||
if( response == 1 && ( !sender->isInParty() || sender->getPartyLeader()->getId() == sender->getId() ) )
|
||||
{
|
||||
if( !sender->isInParty() )
|
||||
{
|
||||
if( !sender->createEmptyParty() )
|
||||
return;
|
||||
}
|
||||
auto member = player.getAsPlayer();
|
||||
if( sender->addPartyMember( member ) )
|
||||
{
|
||||
FFXIVIpcPartyMessage msg = {};
|
||||
msg.leaderContentId = sender->getContentId();
|
||||
msg.memberContentId = player.getContentId();
|
||||
msg.u1 = 1;
|
||||
msg.type = 1;
|
||||
msg.partySize = sender->getPartySize();
|
||||
memcpy( msg.leaderName, sender->getName().c_str(), sender->getName().length() + 1 );
|
||||
memcpy( msg.memberName, player.getName().c_str(), player.getName().length() + 1 );
|
||||
sender->foreachPartyMember( [ msg ]( auto m )
|
||||
{
|
||||
auto packetMsg = makeZonePacket< FFXIVIpcPartyMessage >( m->getId() );
|
||||
memcpy( &packetMsg->data(), &msg, sizeof( msg ) );
|
||||
m->queuePacket( packetMsg );
|
||||
} );
|
||||
|
||||
auto packet2 = makeZonePacket< FFXIVIpcSocialInviteMessage >( sender->getId() );
|
||||
packet2->data().contentId = player.getContentId();
|
||||
packet2->data().expireTime = Common::Util::getTimeSeconds() + 30;
|
||||
packet2->data().p1 = packetIn.data().p1;
|
||||
packet2->data().p2 = packetIn.data().p2;
|
||||
packet2->data().socialType = packetIn.data().socialType;
|
||||
packet2->data().type = 4;
|
||||
memcpy( packet2->data().name, player.getName().c_str(), player.getName().length() + 1 );
|
||||
sender->queuePacket( packet2 );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto packet2 = makeZonePacket< FFXIVIpcSocialInviteMessage >( sender->getId() );
|
||||
packet2->data().contentId = player.getContentId();
|
||||
packet2->data().expireTime = Common::Util::getTimeSeconds() + 30;
|
||||
packet2->data().p1 = packetIn.data().p1;
|
||||
packet2->data().p2 = packetIn.data().p2;
|
||||
packet2->data().socialType = packetIn.data().socialType;
|
||||
packet2->data().type = 5;
|
||||
memcpy( packet2->data().name, player.getName().c_str(), player.getName().length() + 1 );
|
||||
sender->queuePacket( packet2 );
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sapphire::Network::GameConnection::partySetLeaderHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, Entity::Player& player )
|
||||
{
|
||||
if( !player.isPartyLeader() )
|
||||
return;
|
||||
const auto packetIn = ZoneChannelPacket< Client::FFXIVIpcPartySetLeaderHandler >( inPacket );
|
||||
Sapphire::Entity::PlayerPtr newLeader = nullptr;
|
||||
player.foreachPartyMember( [ &packetIn, &newLeader ]( auto m )
|
||||
{
|
||||
if( m->getContentId() == packetIn.data().contentId )
|
||||
{
|
||||
newLeader = m;
|
||||
}
|
||||
} );
|
||||
if( newLeader && player.changePartyLeader( newLeader ) )
|
||||
{
|
||||
FFXIVIpcPartyMessage msg = {};
|
||||
msg.leaderContentId = player.getContentId();
|
||||
msg.memberContentId = newLeader->getContentId();
|
||||
msg.type = 2;
|
||||
msg.u1 = 1;
|
||||
msg.partySize = newLeader->getPartySize();
|
||||
memcpy( msg.leaderName, player.getName().c_str(), player.getName().length() + 1 );
|
||||
memcpy( msg.memberName, newLeader->getName().c_str(), newLeader->getName().length() + 1 );
|
||||
newLeader->foreachPartyMember( [ msg ] ( auto m )
|
||||
{
|
||||
auto packetMsg = makeZonePacket< FFXIVIpcPartyMessage >( m->getId() );
|
||||
memcpy( &packetMsg->data(), &msg, sizeof( msg ) );
|
||||
m->queuePacket( packetMsg );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
void Sapphire::Network::GameConnection::leavePartyHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, Entity::Player& player )
|
||||
{
|
||||
if( !player.isInParty() )
|
||||
return;
|
||||
auto leader = player.getPartyLeader();
|
||||
if( leader->getPartySize() <= 2 )
|
||||
{
|
||||
FFXIVIpcPartyMessage msg = {};
|
||||
msg.leaderContentId = leader->getContentId();
|
||||
msg.type = 3;
|
||||
leader->foreachPartyMember( [ msg ] ( auto m )
|
||||
{
|
||||
auto packetMsg = makeZonePacket< FFXIVIpcPartyMessage >( m->getId() );
|
||||
memcpy( &packetMsg->data(), &msg, sizeof( msg ) );
|
||||
m->queuePacket( packetMsg );
|
||||
} );
|
||||
leader->disbandParty();
|
||||
}
|
||||
else
|
||||
{
|
||||
FFXIVIpcPartyMessage msg = {};
|
||||
msg.memberContentId = player.getContentId();
|
||||
leader->foreachPartyMember( [ &player, &leader, &msg ] ( auto m )
|
||||
{
|
||||
if( m->getId() == player.getId() )
|
||||
{
|
||||
msg.leaderContentId = leader->getContentId();
|
||||
msg.type = 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.leaderContentId = 0;
|
||||
msg.type = 4;
|
||||
}
|
||||
auto packetMsg = makeZonePacket< FFXIVIpcPartyMessage >( m->getId() );
|
||||
memcpy( &packetMsg->data(), &msg, sizeof( msg ) );
|
||||
m->queuePacket( packetMsg );
|
||||
} );
|
||||
leader->removePartyMember( player.getAsPlayer() );
|
||||
}
|
||||
}
|
||||
|
||||
void Sapphire::Network::GameConnection::kickPartyMemberHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, Entity::Player& player )
|
||||
{
|
||||
if( !player.isPartyLeader() )
|
||||
return;
|
||||
const auto packetIn = ZoneChannelPacket< Client::FFXIVIpcKickPartyMemberHander >( inPacket );
|
||||
Sapphire::Entity::PlayerPtr toKick = nullptr;
|
||||
player.foreachPartyMember( [ &packetIn, &toKick ]( auto m )
|
||||
{
|
||||
if( m->getContentId() == packetIn.data().contentId )
|
||||
{
|
||||
toKick = m;
|
||||
}
|
||||
} );
|
||||
if( toKick )
|
||||
{
|
||||
if( player.getPartySize() <= 2 )
|
||||
{
|
||||
FFXIVIpcPartyMessage msg = {};
|
||||
msg.leaderContentId = player.getContentId();
|
||||
msg.type = 3;
|
||||
player.foreachPartyMember( [ msg ] ( auto m )
|
||||
{
|
||||
auto packetMsg = makeZonePacket< FFXIVIpcPartyMessage >( m->getId() );
|
||||
memcpy( &packetMsg->data(), &msg, sizeof( msg ) );
|
||||
m->queuePacket( packetMsg );
|
||||
} );
|
||||
player.disbandParty();
|
||||
}
|
||||
else
|
||||
{
|
||||
FFXIVIpcPartyMessage msg = {};
|
||||
msg.leaderContentId = player.getContentId();
|
||||
msg.memberContentId = toKick->getContentId();
|
||||
player.foreachPartyMember( [ &toKick, &msg ] ( auto m )
|
||||
{
|
||||
if( m->getId() == toKick->getId() )
|
||||
{
|
||||
msg.type = 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.type = 4;
|
||||
}
|
||||
auto packetMsg = makeZonePacket< FFXIVIpcPartyMessage >( m->getId() );
|
||||
memcpy( &packetMsg->data(), &msg, sizeof( msg ) );
|
||||
m->queuePacket( packetMsg );
|
||||
} );
|
||||
player.removePartyMember( toKick );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sapphire::Network::GameConnection::disbandPartyHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, Entity::Player& player )
|
||||
{
|
||||
if( !player.isPartyLeader() )
|
||||
return;
|
||||
FFXIVIpcPartyMessage msg = {};
|
||||
msg.leaderContentId = player.getContentId();
|
||||
msg.type = 3;
|
||||
player.foreachPartyMember( [ msg ] ( auto m )
|
||||
{
|
||||
auto packetMsg = makeZonePacket< FFXIVIpcPartyMessage >( m->getId() );
|
||||
memcpy( &packetMsg->data(), &msg, sizeof( msg ) );
|
||||
m->queuePacket( packetMsg );
|
||||
} );
|
||||
player.disbandParty();
|
||||
}
|
Loading…
Add table
Reference in a new issue