#include #include #include #include #include #include "Network/GameConnection.h" #include "PartyMgr.h" #include "WorldServer.h" #include "ChatChannelMgr.h" #include "Session.h" #include "Actor/Player.h" #include "Network/PacketWrappers/PcPartyUpdatePacket.h" using namespace Sapphire; using namespace Sapphire::World::Manager; using namespace Sapphire::Network::Packets; using namespace Sapphire::Network::Packets::WorldPackets::Server; void PartyMgr::onJoin( uint32_t joinerId, uint32_t inviterId ) { auto& server = Common::Service< World::WorldServer >::ref(); auto& ccMgr = Common::Service< World::Manager::ChatChannelMgr >::ref(); auto pInvitee = server.getSession( joinerId ); auto pInviter = server.getSession( inviterId ); if( !pInvitee || !pInviter ) { Logger::error( "Joining player or inviter is null!!" ); return; } auto& inviteePlayer = *pInvitee->getPlayer(); auto& invitingPlayer = *pInviter->getPlayer(); if( inviteePlayer.getPartyId() != 0 ) { Logger::error( "Player#{} already in a party, cannot be invited!!", inviteePlayer.getId() ); return; } uint64_t partyId; // if there is no party yet, one has to be created PartyPtr party; if( inviteePlayer.getPartyId() == 0 && invitingPlayer.getPartyId() == 0 ) { partyId = createParty(); party = getParty( partyId ); assert( party ); inviteePlayer.setPartyId( partyId ); inviteePlayer.addOnlineStatus( Common::OnlineStatus::PartyMember ); invitingPlayer.setPartyId( partyId ); invitingPlayer.addOnlineStatus( Common::OnlineStatus::PartyLeader ); ccMgr.addToChannel( party->ChatChannel, invitingPlayer ); ccMgr.addToChannel( party->ChatChannel, inviteePlayer ); party->MemberId.push_back( invitingPlayer.getId() ); party->MemberId.push_back( inviteePlayer.getId() ); party->PartyCount = 2; party->LeaderId = invitingPlayer.getId(); } else if( inviteePlayer.getPartyId() == 0 ) { partyId = invitingPlayer.getPartyId(); party = getParty( partyId ); inviteePlayer.setPartyId( partyId ); inviteePlayer.addOnlineStatus( Common::OnlineStatus::PartyMember ); ccMgr.addToChannel( party->ChatChannel, inviteePlayer ); party->MemberId.push_back( inviteePlayer.getId() ); party->PartyCount++; } auto pcUpdateParty = makePcPartyUpdate( invitingPlayer, inviteePlayer, UpdateStatus::JOINED, party->PartyCount ); auto members = getPartyMembers( *party ); sendPartyUpdate( *party ); for( const auto& member : members ) { server.queueForPlayer( member->getCharacterId(), pcUpdateParty ); } } void PartyMgr::onLeave( Sapphire::Entity::Player &leavingPlayer ) { auto& server = Common::Service< World::WorldServer >::ref(); auto party = getParty( leavingPlayer.getPartyId() ); assert( party ); auto leadingPlayer = getPartyLeader( *party ); assert( leadingPlayer ); if( !leadingPlayer ) return; if( party->PartyCount == 2 ) { onDisband( *leadingPlayer ); } else { auto members = getPartyMembers( *party ); removeMember( *party, leavingPlayer.getAsPlayer() ); uint32_t newLeaderId = 0; for( const auto& member : members ) { if( member->getId() == leavingPlayer.getId() ) { member->removeOnlineStatus( { Common::OnlineStatus::PartyMember, Common::OnlineStatus::PartyLeader } ); server.queueForPlayer( leavingPlayer.getCharacterId() , makeZonePacket< FFXIVIpcUpdateParty >( leavingPlayer.getId() ) ); server.queueForPlayer( member->getCharacterId(), makePcPartyUpdate( leadingPlayer, nullptr, UpdateStatus::KICK_SELF, party->PartyCount ) ); } else { if( leavingPlayer.getId() == party->LeaderId ) { newLeaderId = party->MemberId[ 0 ]; auto pSession = server.getSession( newLeaderId ); if( !pSession ) continue; pSession->getPlayer()->addOnlineStatus( Common::OnlineStatus::PartyLeader ); server.queueForPlayer( member->getCharacterId(), makePcPartyUpdate( leavingPlayer.getAsPlayer(), pSession->getPlayer(), UpdateStatus::LEAVELEADER_LEAVED_MEMBER, party->PartyCount ) ); } else { server.queueForPlayer( member->getCharacterId(), makePcPartyUpdate( leavingPlayer.getAsPlayer(), nullptr, UpdateStatus::LEAVE_MEMBER, party->PartyCount ) ); } } } if( newLeaderId != 0 ) party->LeaderId = newLeaderId; party->PartyCount--; sendPartyUpdate( *party ); } } void PartyMgr::onDisband( Entity::Player& disbandingPlayer ) { auto& server = Common::Service< World::WorldServer >::ref(); auto party = getParty( disbandingPlayer.getPartyId() ); assert( party ); auto members = getPartyMembers( *party ); for( const auto& member : members ) { removeMember( *party, member ); member->removeOnlineStatus( { Common::OnlineStatus::PartyMember, Common::OnlineStatus::PartyLeader } ); server.queueForPlayer( member->getCharacterId(), { makePcPartyUpdate( disbandingPlayer, disbandingPlayer, UpdateStatus::DISBAND, party->PartyCount ), makeZonePacket< FFXIVIpcUpdateParty >( member->getId() ) } ); } removeParty( party->PartyID ); } void PartyMgr::onMoveZone( Sapphire::Entity::Player &movingPlayer ) { if( movingPlayer.getPartyId() == 0 ) return; auto party = getParty( movingPlayer.getPartyId() ); assert( party ); sendPartyUpdate( *party ); } void PartyMgr::onMemberDisconnect( Entity::Player& disconnectingPlayer ) { if( disconnectingPlayer.getPartyId() == 0 ) return; auto& server = Common::Service< World::WorldServer >::ref(); auto party = getParty( disconnectingPlayer.getPartyId() ); assert( party ); auto members = getPartyMembers( *party ); auto pLeader = getPartyLeader( *party ); bool anyMembersOnline = false; for( const auto& member : members ) { bool isConnected = server.getSession( member->getCharacterId() ) != nullptr; if( isConnected ) { anyMembersOnline = true; break; } } // if there are no party members online, destroy the party if( !anyMembersOnline ) return onDisband( *pLeader ); for( const auto& member : members ) { // TODO: 2nd argument here makes it automatically send passing leadership message server.queueForPlayer( member->getCharacterId(), { makePcPartyUpdate( disconnectingPlayer, UpdateStatus::OFFLINE_MEMBER, party->PartyCount ), makeZonePacket< FFXIVIpcUpdateParty >( member->getId() ) } ); } sendPartyUpdate( *party ); } void PartyMgr::onMemberRejoin( Entity::Player& joiningPlayer ) { auto party = getParty( joiningPlayer.getPartyId() ); assert( party ); // TODO: do we need a party update here? move zone handler already handles it } void PartyMgr::onKick( const std::string& kickPlayerName, Entity::Player& leader ) { auto& server = Common::Service< World::WorldServer >::ref(); auto party = getParty( leader.getPartyId() ); assert( party ); auto pLeader = getPartyLeader( *party ); auto members = getPartyMembers( *party ); auto pKickedSession = server.getSession( kickPlayerName ); if( !pKickedSession ) { Logger::error( "Target player for kicking not found (\"{t}\")", kickPlayerName ); return; } if( party->PartyCount == 2 ) { onDisband( *pLeader ); } else { for( const auto &member: members ) { if( kickPlayerName == member->getName() ) { removeMember( *party, member ); member->removeOnlineStatus( Common::OnlineStatus::PartyMember ); server.queueForPlayer( member->getCharacterId(), { makePcPartyUpdate( *pLeader, *member, UpdateStatus::KICK_SELF, party->PartyCount ), makeZonePacket< FFXIVIpcUpdateParty >( member->getId() ) } ); } else { server.queueForPlayer( member->getCharacterId(), makePcPartyUpdate( *pKickedSession->getPlayer(), UpdateStatus::KICK_MEMBER, party->PartyCount ) ); } } party->PartyCount--; sendPartyUpdate( *party ); } } void PartyMgr::onChangeLeader( const std::string& newLeaderName, Entity::Player& oldLeader ) { auto& server = Common::Service< World::WorldServer >::ref(); auto party = getParty( oldLeader.getPartyId() ); auto pNewLeader = server.getPlayer( newLeaderName ); if( !pNewLeader ) { Logger::error( "Target player for new leader not found (\"{t}\")", newLeaderName ); return; } for( auto memberId : party->MemberId ) { if( memberId == pNewLeader->getId() ) { pNewLeader->addOnlineStatus( Common::OnlineStatus::PartyLeader ); // this is not ideal, probably better to have a function which can add // and remove at the same time so packets are only triggered once oldLeader.addOnlineStatus( Common::OnlineStatus::PartyMember ); oldLeader.removeOnlineStatus( Common::OnlineStatus::PartyLeader ); party->LeaderId = pNewLeader->getId(); break; } } auto members = getPartyMembers( *party ); for( auto& member : members ) { auto pcUpdateParty = makePcPartyUpdate( oldLeader.getAsPlayer(), pNewLeader, UpdateStatus::CHANGELEADER, party->PartyCount ); server.queueForPlayer( member->getCharacterId(), pcUpdateParty ); } sendPartyUpdate( *party ); } /////////////////////////////////////////////////////////////////////////////////////////////////////////// uint64_t PartyMgr::createParty() { auto& chatChannelMgr = Common::Service< ChatChannelMgr >::ref(); auto party = std::make_shared< Party >(); party->PartyID = getNextPartyId(); party->ChatChannel = chatChannelMgr.createChatChannel( Common::ChatChannelType::PartyChat ); m_partyIdMap[ party->PartyID ] = party; return party->PartyID; } uint64_t PartyMgr::getNextPartyId() { return ++m_maxPartyId; } PartyPtr PartyMgr::getParty( uint64_t partyId ) { auto it = m_partyIdMap.find( partyId ); if( it != m_partyIdMap.end() ) return it->second; return nullptr; } std::vector< Entity::PlayerPtr > PartyMgr::getPartyMembers( Party& party ) { std::vector< Entity::PlayerPtr > members; auto& server = Common::Service< World::WorldServer >::ref(); for( auto& memberId : party.MemberId ) { if( memberId == 0 ) continue; auto pPlayer = server.getPlayer( memberId ); members.push_back( pPlayer ); } return members; } Entity::PlayerPtr PartyMgr::getPartyLeader( Party& party ) { auto& server = Common::Service< World::WorldServer >::ref(); if( party.LeaderId == 0 ) return nullptr; auto pLeader = server.getPlayer( party.LeaderId ); return pLeader; } void PartyMgr::sendPartyUpdate( Party& party ) { auto& exdData = Common::Service< Data::ExdData >::ref(); auto partyMembers = getPartyMembers( party ); std::vector< ZoneProtoDownPartyMember > entries; auto& server = Common::Service< World::WorldServer >::ref(); for( const auto& member : partyMembers ) { auto classJob = exdData.getRow< Excel::ClassJob >( static_cast< uint8_t >( member->getClass() ) ); if( !classJob ) continue; ZoneProtoDownPartyMember memberEntry{}; memberEntry.ParentEntityId = Common::INVALID_GAME_OBJECT_ID; memberEntry.PetEntityId = Common::INVALID_GAME_OBJECT_ID; memberEntry.Hp = member->getHp(); memberEntry.HpMax = member->getMaxHp(); memberEntry.Mp = member->getMp(); memberEntry.MpMax = member->getMaxMp(); memberEntry.ClassJob = static_cast< uint8_t >( member->getClass() ); memberEntry.Lv = member->getLevel(); memberEntry.ObjType = 4; // 1 PC, 2 Buddy ?? memberEntry.TerritoryType = member->getTerritoryTypeId(); memberEntry.Valid = 1; memberEntry.Tp = member->getTp(); memberEntry.Role = classJob->data().Role; /* memberEntry.CharaId = member->getContentId(); memberEntry.EntityId = member->getId(); strcpy( memberEntry.Name, member->getName().c_str() );*/ entries.push_back( memberEntry ); } for( const auto& pMember : partyMembers ) { size_t idx = 0; auto updatePartyPacket = makeZonePacket< FFXIVIpcUpdateParty >( partyMembers[ 0 ]->getId() ); auto& data = updatePartyPacket->data(); data.PartyID = party.PartyID; data.LeaderIndex = getPartyLeaderIndex( party ); data.ChatChannel = party.ChatChannel; data.PartyCount = party.PartyCount; for( const auto& member : partyMembers ) { bool isConnected = server.getSession( member->getCharacterId() ) != nullptr; // if player is online and in the same zone as current member in party, display more data in partylist bool hasInfo = isConnected && member->getTerritoryTypeId() == pMember->getTerritoryTypeId(); if( hasInfo ) { data.Member[ idx ] = entries[ idx ]; } data.Member[ idx ].CharaId = member->getCharacterId(); data.Member[ idx ].EntityId = member->getId(); strcpy( data.Member[ idx ].Name, member->getName().c_str() ); idx++; } server.queueForPlayer( pMember->getCharacterId(), updatePartyPacket ); } } void PartyMgr::removeParty( uint64_t partyId ) { m_partyIdMap.erase( partyId ); } int8_t PartyMgr::getPartyLeaderIndex( const Party &party ) { size_t idx = 0; for( const auto& memberId : party.MemberId ) { if( memberId == party.LeaderId ) return static_cast< int8_t >( idx ); idx++; } return -1; } void PartyMgr::removeMember( Party &party, const Entity::PlayerPtr& pMember ) { auto& ccMgr = Common::Service< World::Manager::ChatChannelMgr >::ref(); pMember->setPartyId( 0 ); ccMgr.removeFromChannel( party.ChatChannel, *pMember ); party.MemberId.erase( std::remove( party.MemberId.begin(), party.MemberId.end(), pMember->getId() ), party.MemberId.end() ); }