diff --git a/src/common/Network/CommonActorControl.h b/src/common/Network/CommonActorControl.h index a3049715..54120624 100644 --- a/src/common/Network/CommonActorControl.h +++ b/src/common/Network/CommonActorControl.h @@ -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, diff --git a/src/common/Network/PacketDef/Ipcs.h b/src/common/Network/PacketDef/Ipcs.h index b212d056..483e074c 100644 --- a/src/common/Network/PacketDef/Ipcs.h +++ b/src/common/Network/PacketDef/Ipcs.h @@ -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 }; //////////////////////////////////////////////////////////////////////////////// diff --git a/src/common/Network/PacketDef/Zone/ClientZoneDef.h b/src/common/Network/PacketDef/Zone/ClientZoneDef.h index 7df75c0e..70fe59bf 100644 --- a/src/common/Network/PacketDef/Zone/ClientZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ClientZoneDef.h @@ -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 diff --git a/src/common/Network/PacketDef/Zone/ServerZoneDef.h b/src/common/Network/PacketDef/Zone/ServerZoneDef.h index f941f744..a030c454 100644 --- a/src/common/Network/PacketDef/Zone/ServerZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ServerZoneDef.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*/ diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index 5eefff94..aeb5cfac 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -234,6 +234,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 @@ -1043,7 +1047,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; @@ -1052,13 +1055,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 ) @@ -1079,8 +1075,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; @@ -1089,11 +1083,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 ) @@ -2297,6 +2286,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 ) ); diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index de0452b7..fef5a740 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -1024,8 +1024,29 @@ namespace Sapphire::Entity Common::SamSen gaugeSamGetSenRaw(); bool gaugeSamHasAnySen(); + // 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); + ////////////////////////////////////////////////////////////////////////////////////////////////////// std::unordered_map< uint32_t, TerritoryPtr > m_privateInstanceMap; TerritoryPtr getOrCreatePrivateInstance( uint32_t zoneId ); bool enterPredefinedPrivateInstance( uint32_t zoneId ); diff --git a/src/world/Network/GameConnection.cpp b/src/world/Network/GameConnection.cpp index f8603f17..45ca24d0 100644 --- a/src/world/Network/GameConnection.cpp +++ b/src/world/Network/GameConnection.cpp @@ -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; diff --git a/src/world/Network/GameConnection.h b/src/world/Network/GameConnection.h index 8f343dbc..0fca2a7a 100644 --- a/src/world/Network/GameConnection.h +++ b/src/world/Network/GameConnection.h @@ -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 ); }; } diff --git a/src/world/Network/Handlers/ClientTriggerHandler.cpp b/src/world/Network/Handlers/ClientTriggerHandler.cpp index bfec398c..d6c6d203 100644 --- a/src/world/Network/Handlers/ClientTriggerHandler.cpp +++ b/src/world/Network/Handlers/ClientTriggerHandler.cpp @@ -494,6 +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; + } case ClientTriggerType::Trigger612: { player.sendStateFlags(); diff --git a/src/world/Network/Handlers/GMCommandHandlers.cpp b/src/world/Network/Handlers/GMCommandHandlers.cpp index c68fab97..b3703a43 100644 --- a/src/world/Network/Handlers/GMCommandHandlers.cpp +++ b/src/world/Network/Handlers/GMCommandHandlers.cpp @@ -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; } diff --git a/src/world/Network/Handlers/PacketHandlers.cpp b/src/world/Network/Handlers/PacketHandlers.cpp index 99e09152..dfd16443 100644 --- a/src/world/Network/Handlers/PacketHandlers.cpp +++ b/src/world/Network/Handlers/PacketHandlers.cpp @@ -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, 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 ) { @@ -838,4 +875,261 @@ void Sapphire::Network::GameConnection::worldInteractionhandler( const Packets:: player.sendDebug( "Unknown dwarf house." ); } } +} + +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(); } \ No newline at end of file diff --git a/src/world/Session.cpp b/src/world/Session.cpp index b446f293..76629f80 100644 --- a/src/world/Session.cpp +++ b/src/world/Session.cpp @@ -73,6 +73,10 @@ void Sapphire::World::Session::close() if( m_pPlayer ) { m_pPlayer->clearBuyBackMap(); + if( m_pPlayer->isInParty() ) + { + m_pPlayer->getPartyLeader()->removePartyMember( m_pPlayer ); + } // do one last update to db m_pPlayer->updateSql(); // reset the zone, so the zone handler knows to remove the actor