mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-04-27 06:47:45 +00:00
572 lines
18 KiB
C++
572 lines
18 KiB
C++
#include "ContentFinder.h"
|
|
#include <Exd/ExdData.h>
|
|
#include <Service.h>
|
|
#include "Actor/Player.h"
|
|
|
|
#include "Network/GameConnection.h"
|
|
#include "Network/PacketWrappers/ServerNoticePacket.h"
|
|
#include "Network/PacketWrappers/UpdateFindContentPacket.h"
|
|
#include "Network/PacketWrappers/UpdateContentPacket.h"
|
|
#include "Network/PacketWrappers/NotifyFindContentPacket.h"
|
|
|
|
#include "Territory/Territory.h"
|
|
#include "Territory/InstanceContent.h"
|
|
|
|
#include "Manager/PlayerMgr.h"
|
|
#include "Manager/TerritoryMgr.h"
|
|
#include "Manager/WarpMgr.h"
|
|
|
|
#include <WorldServer.h>
|
|
|
|
using namespace Sapphire::Common;
|
|
using namespace Sapphire::Network::Packets;
|
|
using namespace Sapphire::Network::Packets::WorldPackets;
|
|
using namespace Sapphire::Network::Packets::WorldPackets::Server;
|
|
using namespace Sapphire::World::Manager;
|
|
|
|
void Sapphire::World::ContentFinder::update()
|
|
{
|
|
auto& exdData = Service< Data::ExdData >::ref();
|
|
auto& server = Service< WorldServer >::ref();
|
|
for( auto& contentIt : m_queuedContent )
|
|
{
|
|
auto& content = contentIt.second;
|
|
|
|
auto contentState = content->getState();
|
|
switch( contentState )
|
|
{
|
|
case None:
|
|
break;
|
|
case MatchingInProgress:
|
|
if( content->m_players.empty() )
|
|
{
|
|
Logger::info( "[ContentFinder] registerId#{} scheduled for removal, no more players in queue.", content->getRegisterId() );
|
|
content->setState( ToBeRemoved );
|
|
}
|
|
break;
|
|
case MatchingComplete:
|
|
{
|
|
auto contentInfo = exdData.getRow< Excel::InstanceContent >( content->getContentFinderId() );
|
|
for( auto& queuedPlayer : content->m_players )
|
|
{
|
|
uint32_t inProgress = 0; // 0x01 - in progress
|
|
uint32_t dutyProgress = 0;
|
|
uint32_t flags = 0; // 0x20 freerole, 0x40 ownrequest, 0x100 random
|
|
|
|
// Undersized
|
|
if( content->m_flags & 0x01 )
|
|
flags |= 0x20;
|
|
|
|
auto updatePacket = makeUpdateFindContent( queuedPlayer->getEntityId(), contentInfo->data().TerritoryType,
|
|
SetResultMatched, inProgress, queuedPlayer->m_classJob, dutyProgress, flags );
|
|
server.queueForPlayer( queuedPlayer->getCharacterId(), updatePacket );
|
|
}
|
|
|
|
content->setState( WaitingForAccept );
|
|
break;
|
|
}
|
|
|
|
case WaitingForAccept:
|
|
break;
|
|
case Accepted:
|
|
{
|
|
auto& terriMgr = Service< TerritoryMgr >::ref();
|
|
auto& warpMgr = Common::Service< WarpMgr >::ref();
|
|
auto contentFinderInfo = exdData.getRow< Excel::ContentFinderCondition >( content->getContentFinderId() );
|
|
if( auto instance = terriMgr.createInstanceContent( content->getContentFinderId() ) )
|
|
{
|
|
auto pInstanceContent = instance->getAsInstanceContent();
|
|
|
|
for( auto& queuedPlayer : content->m_players )
|
|
{
|
|
auto updatePacket = makeUpdateFindContent( queuedPlayer->getEntityId(), 0x06,
|
|
SetResultReadyToEnter, 0, queuedPlayer->m_classJob, 0 );
|
|
server.queueForPlayer( queuedPlayer->getCharacterId(), updatePacket );
|
|
|
|
pInstanceContent->bindPlayer( queuedPlayer->getEntityId() );
|
|
warpMgr.requestMoveTerritory( *server.getPlayer( queuedPlayer->getEntityId() ), WarpType::WARP_TYPE_INSTANCE_CONTENT, pInstanceContent->getGuId(), { 0.f, 0.f, 0.f }, 0.f );
|
|
|
|
auto zonePacket = makeUpdateContent( queuedPlayer->getEntityId(), instance->getTerritoryTypeId(), 0, pInstanceContent->getGuId() );
|
|
auto zonePacket2 = makeUpdateContent( queuedPlayer->getEntityId(), instance->getTerritoryTypeId(), content->m_partyMemberCount );
|
|
server.queueForPlayer( queuedPlayer->getCharacterId(), zonePacket );
|
|
server.queueForPlayer( queuedPlayer->getCharacterId(), zonePacket2 );
|
|
}
|
|
}
|
|
|
|
content->setState( InProgress );
|
|
break;
|
|
}
|
|
case InProgress:
|
|
break;
|
|
case InProgressRefill:
|
|
break;
|
|
case ToBeRemoved:
|
|
auto regId = content->getRegisterId();
|
|
bool contentRemoved = removeContentByRegisterId( regId );
|
|
if( contentRemoved )
|
|
{
|
|
Logger::info( "[ContentFinder] registerId#{} removed", regId );
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void Sapphire::World::ContentFinder::registerContentsRequest( Sapphire::Entity::Player &player, const std::vector< uint32_t >& contentIds )
|
|
{
|
|
queueForContent( player, contentIds );
|
|
completeRegistration( player );
|
|
}
|
|
|
|
void Sapphire::World::ContentFinder::registerContentRequest( Sapphire::Entity::Player &player, uint32_t contentId, uint8_t flags )
|
|
{
|
|
queueForContent( player, { contentId } );
|
|
completeRegistration( player, flags );
|
|
}
|
|
|
|
void Sapphire::World::ContentFinder::registerRandomContentRequest( Sapphire::Entity::Player &player, uint32_t randomContentTypeId )
|
|
{
|
|
auto& exdData = Service< Data::ExdData >::ref();
|
|
auto contentListIds = exdData.getIdList< Excel::ContentFinderCondition >();
|
|
std::vector< uint32_t > idList;
|
|
|
|
for( auto id : contentListIds )
|
|
{
|
|
auto instanceContent = exdData.getRow< Excel::ContentFinderCondition >( id );
|
|
if( instanceContent->data().RandomContentType == randomContentTypeId )
|
|
{
|
|
if( instanceContent->data().LevelMin <= player.getLevel() )
|
|
idList.push_back( id );
|
|
}
|
|
}
|
|
|
|
queueForContent( player, idList );
|
|
completeRegistration( player );
|
|
}
|
|
|
|
void Sapphire::World::ContentFinder::completeRegistration( const Sapphire::Entity::Player &player, uint8_t flags )
|
|
{
|
|
|
|
auto& server = Service< WorldServer >::ref();
|
|
auto queuedContent = m_queuedContent[ m_queuedPlayer[ player.getId() ]->getActiveRegisterId() ];
|
|
|
|
auto& exdData = Service< Data::ExdData >::ref();
|
|
auto content = exdData.getRow< Excel::InstanceContent >( queuedContent->getContentFinderId() );
|
|
|
|
// Undersized
|
|
if( flags & 0x01 )
|
|
{
|
|
auto updatePacket = makeUpdateFindContent( player.getId(), content->data().TerritoryType,
|
|
CompleteRegistration, 0x20, static_cast< uint32_t >( player.getClass() ) );
|
|
server.queueForPlayer( player.getCharacterId(), updatePacket );
|
|
|
|
queuedContent->m_flags = flags;
|
|
queuedContent->setState( MatchingComplete );
|
|
}
|
|
else
|
|
{
|
|
auto updatePacket = makeUpdateFindContent( player.getId(), content->data().TerritoryType,
|
|
CompleteRegistration, 1, static_cast< uint32_t >( player.getClass() ) );
|
|
server.queueForPlayer( player.getCharacterId(), updatePacket );
|
|
|
|
auto statusPacket = makeNotifyFindContentStatus( player.getId(), content->data().TerritoryType, 3, queuedContent->m_attackerCount + queuedContent->m_rangeCount,
|
|
queuedContent->m_healerCount, queuedContent->m_tankCount, 0xff );
|
|
|
|
for( auto& queuedPlayer : queuedContent->m_players )
|
|
{
|
|
server.queueForPlayer( queuedPlayer->getCharacterId(), statusPacket );
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sapphire::World::ContentFinder::queueForContent( Sapphire::Entity::Player &player, const std::vector< uint32_t >& contentIds )
|
|
{
|
|
for( auto contentId : contentIds )
|
|
{
|
|
auto contentList = getMatchingContentList( player, contentId );
|
|
|
|
if( contentList.empty() )
|
|
{
|
|
Logger::error( "[ContentFinder] No matching content could be found or generated." );
|
|
return;
|
|
}
|
|
|
|
for( auto &content : contentList )
|
|
{
|
|
Logger::info( "[{2}][ContentFinder] Content registered, contentId#{0} registerId#{1}", contentId, content->getRegisterId(), player.getId() );
|
|
PlayerMgr::sendDebug( player, "Content registered, contentId#{0} registerId#{1}", contentId, content->getRegisterId() );
|
|
m_queuedContent[ content->getRegisterId() ] = content;
|
|
|
|
auto qPlayerIt = m_queuedPlayer.find( player.getId() );
|
|
if( qPlayerIt == m_queuedPlayer.end() )
|
|
{
|
|
auto pQPlayer = std::make_shared< QueuedPlayer >( player, content->getRegisterId() );
|
|
m_queuedPlayer[ player.getId() ] = pQPlayer;
|
|
}
|
|
else
|
|
{
|
|
m_queuedPlayer[ player.getId() ]->setActiveRegisterId( content->getRegisterId() );
|
|
}
|
|
content->queuePlayer( m_queuedPlayer[ player.getId() ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sapphire::World::QueuedContent::queuePlayer( const std::shared_ptr< QueuedPlayer >& pQPlayer )
|
|
{
|
|
m_players.push_back( pQPlayer );
|
|
m_partyMemberCount++;
|
|
switch( pQPlayer->getRole() )
|
|
{
|
|
case Role::Tank:
|
|
m_tankCount++;
|
|
break;
|
|
case Role::Healer:
|
|
m_healerCount++;
|
|
break;
|
|
case Role::RangedPhysical:
|
|
case Role::RangedMagical:
|
|
m_rangeCount++;
|
|
break;
|
|
case Role::Melee:
|
|
m_attackerCount++;
|
|
break;
|
|
case Role::None:
|
|
case Role::Crafter:
|
|
case Role::Gatherer:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool Sapphire::World::QueuedContent::withdrawPlayer( const std::shared_ptr< QueuedPlayer >& pQPlayer )
|
|
{
|
|
auto preSize = m_players.size();
|
|
auto it = m_players.begin();
|
|
for( ; it != m_players.end(); ++it )
|
|
{
|
|
if( it->get()->getCharacterId() == pQPlayer->getCharacterId() )
|
|
{
|
|
m_players.erase( it );
|
|
break;
|
|
}
|
|
}
|
|
|
|
auto postSize = m_players.size();
|
|
if( preSize == postSize )
|
|
return false;
|
|
|
|
m_partyMemberCount--;
|
|
switch( pQPlayer->getRole() )
|
|
{
|
|
case Role::Tank:
|
|
m_tankCount--;
|
|
break;
|
|
case Role::Healer:
|
|
m_healerCount--;
|
|
break;
|
|
case Role::RangedPhysical:
|
|
case Role::RangedMagical:
|
|
m_rangeCount--;
|
|
break;
|
|
case Role::Melee:
|
|
m_attackerCount--;
|
|
break;
|
|
case Role::None:
|
|
case Role::Crafter:
|
|
case Role::Gatherer:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
uint32_t Sapphire::World::ContentFinder::getNextRegisterId()
|
|
{
|
|
return ++m_nextRegisterId;
|
|
}
|
|
|
|
Sapphire::World::ContentFinder::QueuedContentPtrList Sapphire::World::ContentFinder::getMatchingContentList( Sapphire::Entity::Player &player, uint32_t contentFinderId )
|
|
{
|
|
QueuedContentPtrList outVec;
|
|
for( auto& it : m_queuedContent )
|
|
{
|
|
auto& foundContent = it.second;
|
|
uint32_t leftContentId = foundContent->getContentFinderId();
|
|
if( leftContentId != contentFinderId )
|
|
continue;
|
|
|
|
auto& exdData = Common::Service< Data::ExdData >::ref();
|
|
auto content = exdData.getRow< Excel::ContentFinderCondition >( contentFinderId );
|
|
if( !content )
|
|
continue;
|
|
|
|
// make sure the player has at least the required level
|
|
if( player.getLevel() < content->data().LevelMin )
|
|
continue;
|
|
|
|
if( foundContent->getState() != QueuedContentState::MatchingInProgress )
|
|
continue;
|
|
|
|
auto contentMember = exdData.getRow< Excel::ContentMemberType >( content->data().ContentMemberType );
|
|
if( !contentMember )
|
|
continue;
|
|
|
|
// skip if the party is already full
|
|
if( foundContent->m_partyMemberCount >= contentMember->data().PartyMemberCount )
|
|
continue;
|
|
|
|
switch( player.getRole() )
|
|
{
|
|
case Role::Tank:
|
|
{
|
|
if( contentMember->data().TankCount <= foundContent->m_tankCount )
|
|
continue;
|
|
break;
|
|
}
|
|
case Role::Healer:
|
|
{
|
|
if( contentMember->data().HealerCount <= foundContent->m_healerCount )
|
|
continue;
|
|
break;
|
|
}
|
|
case Role::RangedPhysical:
|
|
case Role::RangedMagical:
|
|
{
|
|
if( contentMember->data().RangeCount <= foundContent->m_rangeCount )
|
|
continue;
|
|
break;
|
|
}
|
|
case Role::Melee:
|
|
{
|
|
if( contentMember->data().AttackerCount <= foundContent->m_attackerCount )
|
|
continue;
|
|
break;
|
|
}
|
|
case Role::Crafter:
|
|
case Role::Gatherer:
|
|
case Role::None:
|
|
continue;
|
|
}
|
|
|
|
outVec.push_back( foundContent );
|
|
}
|
|
|
|
if( outVec.empty() )
|
|
{
|
|
auto queuedContent = std::make_shared< QueuedContent >( getNextRegisterId(), contentFinderId );
|
|
outVec.push_back( queuedContent );
|
|
}
|
|
return outVec;
|
|
}
|
|
|
|
void Sapphire::World::ContentFinder::accept( Entity::Player& player )
|
|
{
|
|
auto& server = Service< WorldServer >::ref();
|
|
auto& exdData = Service< Data::ExdData >::ref();
|
|
|
|
auto queuedPlayer = m_queuedPlayer[ player.getId() ];
|
|
auto queuedContent = m_queuedContent[ queuedPlayer->getActiveRegisterId() ];
|
|
|
|
auto content = exdData.getRow< Excel::InstanceContent >( queuedContent->getContentFinderId() );
|
|
|
|
// Something has gone quite wrong..
|
|
if( queuedContent->getState() != WaitingForAccept )
|
|
return;
|
|
|
|
switch( queuedPlayer->getRole() )
|
|
{
|
|
case Role::Tank:
|
|
++queuedContent->m_tankAccepted;
|
|
break;
|
|
case Role::Healer:
|
|
++queuedContent->m_healerAccepted;
|
|
break;
|
|
case Role::Melee:
|
|
case Role::RangedMagical:
|
|
case Role::RangedPhysical:
|
|
++queuedContent->m_dpsAccepted;
|
|
break;
|
|
}
|
|
|
|
Logger::info( "[{2}][ContentFinder] Content accepted, contentId#{0} registerId#{1}",
|
|
queuedContent->getContentFinderId(), queuedContent->getRegisterId(), player.getId() );
|
|
|
|
auto statusPacket = makeNotifyFindContentStatus( player.getId(), content->data().TerritoryType, 4, queuedContent->m_dpsAccepted,
|
|
queuedContent->m_healerAccepted, queuedContent->m_tankAccepted, 0x01 );
|
|
|
|
for( auto& pPlayer : queuedContent->m_players )
|
|
{
|
|
if( pPlayer->getActiveRegisterId() != queuedContent->getRegisterId() )
|
|
continue;
|
|
|
|
server.queueForPlayer( pPlayer->getCharacterId(), statusPacket );
|
|
}
|
|
|
|
if( ( queuedContent->m_tankAccepted + queuedContent->m_healerAccepted + queuedContent->m_dpsAccepted ) == queuedContent->m_partyMemberCount )
|
|
queuedContent->setState( Accepted );
|
|
}
|
|
|
|
void Sapphire::World::ContentFinder::withdraw( Entity::Player& player )
|
|
{
|
|
auto& server = Service< WorldServer >::ref();
|
|
auto& exdData = Service< Data::ExdData >::ref();
|
|
|
|
auto queuedPlayer = m_queuedPlayer[ player.getId() ];
|
|
auto contentInfo = exdData.getRow< Excel::InstanceContent >( m_queuedContent[ queuedPlayer->getActiveRegisterId() ]->getContentFinderId() );
|
|
|
|
// remove the player from the global CF list
|
|
m_queuedPlayer.erase( player.getId() );
|
|
|
|
// send packet to clear CF in the client. TODO needs to be moved elsewhere
|
|
auto updatePacket = makeUpdateFindContent( player.getId(), contentInfo->data().TerritoryType, SetResultFailed2 );
|
|
server.queueForPlayer( queuedPlayer->getCharacterId(), updatePacket );
|
|
|
|
// check all queued contents for the player and remove if needed
|
|
std::set< uint32_t > updateRegisterIdSet;
|
|
for( auto& content : m_queuedContent )
|
|
{
|
|
if( content.second->withdrawPlayer( queuedPlayer ) )
|
|
{
|
|
Logger::info( "[{2}] Content withdrawn, contentId#{0} registerId#{1}",
|
|
content.second->getContentFinderId(), content.second->getRegisterId(), player.getId() );
|
|
updateRegisterIdSet.insert( content.second->getRegisterId() );
|
|
}
|
|
}
|
|
|
|
// check for each queued content if players need to be updated
|
|
for( auto& content : m_queuedContent )
|
|
{
|
|
auto regId = content.second->getRegisterId();
|
|
// if this content does not require an update, skip.
|
|
if( updateRegisterIdSet.count( regId ) == 0 )
|
|
continue;
|
|
|
|
auto queuedContentInfo = exdData.getRow< Excel::InstanceContent >( content.second->getContentFinderId() );
|
|
auto& playerList = content.second->m_players;
|
|
for( const auto& pPlayer : playerList )
|
|
{
|
|
// only update players which have this content active (shown in UI)
|
|
if( pPlayer->getActiveRegisterId() != regId )
|
|
continue;
|
|
|
|
auto statusPacket = makeNotifyFindContentStatus( player.getId(), queuedContentInfo->data().TerritoryType, 3,
|
|
content.second->m_attackerCount + content.second->m_rangeCount,
|
|
content.second->m_healerCount, content.second->m_tankCount, 0xff );
|
|
|
|
server.queueForPlayer( pPlayer->getCharacterId(), statusPacket );
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
std::shared_ptr< Sapphire::World::QueuedContent > Sapphire::World::ContentFinder::findContentByRegisterId( uint32_t registerId )
|
|
{
|
|
auto it = m_queuedContent.find( registerId );
|
|
if( it != m_queuedContent.end() )
|
|
return it->second;
|
|
return nullptr;
|
|
}
|
|
|
|
bool Sapphire::World::ContentFinder::removeContentByRegisterId( uint32_t registerId )
|
|
{
|
|
auto it = m_queuedContent.find( registerId );
|
|
if( it == m_queuedContent.end() )
|
|
return false;
|
|
m_queuedContent.erase( it );
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
uint32_t Sapphire::World::QueuedContent::getContentFinderId() const
|
|
{
|
|
return m_contentFinderId;
|
|
}
|
|
|
|
uint32_t Sapphire::World::QueuedContent::getRegisterId() const
|
|
{
|
|
return m_registerId;
|
|
}
|
|
|
|
Sapphire::World::QueuedContent::QueuedContent( uint32_t registerId, uint32_t contentId ) :
|
|
m_registerId( registerId ),
|
|
m_contentFinderId( contentId ),
|
|
m_state( QueuedContentState::MatchingInProgress ),
|
|
m_contentPopTime( 0 )
|
|
{
|
|
// auto& exdData = Common::Service< Data::ExdData >::ref();
|
|
// auto content = exdData.getRow< Excel::InstanceContent >( contentId );
|
|
|
|
|
|
}
|
|
|
|
uint8_t Sapphire::World::QueuedContent::getRoleCount( Sapphire::Common::Role role ) const
|
|
{
|
|
switch( role )
|
|
{
|
|
case Role::Tank:
|
|
return m_tankCount;
|
|
case Role::Healer:
|
|
return m_healerCount;
|
|
case Role::RangedPhysical:
|
|
case Role::RangedMagical:
|
|
return m_rangeCount;
|
|
case Role::Melee:
|
|
return m_attackerCount;
|
|
case Role::Crafter:
|
|
case Role::Gatherer:
|
|
case Role::None:
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Sapphire::World::QueuedContentState Sapphire::World::QueuedContent::getState() const
|
|
{
|
|
return m_state;
|
|
}
|
|
|
|
void Sapphire::World::QueuedContent::setState( Sapphire::World::QueuedContentState state )
|
|
{
|
|
m_state = state;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
Sapphire::World::QueuedPlayer::QueuedPlayer( const Entity::Player &player, uint8_t registerId )
|
|
{
|
|
m_characterId = player.getCharacterId();
|
|
m_classJob = static_cast< uint32_t >( player.getClass() );
|
|
m_level = player.getLevel();
|
|
m_activeRegisterId = registerId;
|
|
m_role = player.getRole();
|
|
m_entityId = player.getId();
|
|
}
|
|
|
|
Sapphire::Common::Role Sapphire::World::QueuedPlayer::getRole() const
|
|
{
|
|
return m_role;
|
|
}
|
|
|
|
void Sapphire::World::QueuedPlayer::setActiveRegisterId( uint8_t registerId )
|
|
{
|
|
m_activeRegisterId = registerId;
|
|
}
|
|
|
|
uint8_t Sapphire::World::QueuedPlayer::getActiveRegisterId() const
|
|
{
|
|
return m_activeRegisterId;
|
|
}
|
|
|
|
uint64_t Sapphire::World::QueuedPlayer::getCharacterId() const
|
|
{
|
|
return m_characterId;
|
|
}
|
|
|
|
uint32_t Sapphire::World::QueuedPlayer::getEntityId() const
|
|
{
|
|
return m_entityId;
|
|
}
|