diff --git a/src/common/Common.h b/src/common/Common.h index 5fb9444d..61a69cc4 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -68,11 +68,20 @@ namespace Sapphire::Common French = 8 }; - enum TellFlags : uint8_t + enum ChatFromType : uint8_t { GmTellMsg = 0x4, }; + enum ChatChannelType : uint16_t + { + CWLinkshellChat = 0x0, + PartyChat = 0x1, + LinkshellChat = 0x2, + FreeCompanyChat = 0x3, + NoviceNetworkChat = 0x4 + }; + enum BNpcType : uint8_t { Friendly = 0, @@ -804,6 +813,22 @@ namespace Sapphire::Common InvincibilityIgnoreDamage, }; + enum InviteReplyType : int32_t + { + DENY = 0x0, + ACCEPT = 0x1, + CANCEL = 0x2, + }; + + enum InviteUpdateType : uint8_t + { + NEW_INVITE = 0x01, + INVITE_CANCEL = 0x02, + JOINED_PARTY = 0x03, + ACCEPT_INVITE = 0x04, + REJECT_INVITE = 0x05, + }; + enum PlayerStateFlag : uint8_t { HideUILockChar = 0, // as the name suggests, hides the ui and logs the char... @@ -1340,6 +1365,12 @@ namespace Sapphire::Common GetGil = 9, // p1: gil EmptyCoffer = 11, // seems like no param }; + + enum ItemFlag + { + FlagNone = 0, + FlagHq = 1, + }; } #endif diff --git a/src/common/Database/ZoneDbConnection.cpp b/src/common/Database/ZoneDbConnection.cpp index 773f8cef..a23a1a72 100644 --- a/src/common/Database/ZoneDbConnection.cpp +++ b/src/common/Database/ZoneDbConnection.cpp @@ -241,7 +241,7 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements() CONNECTION_BOTH ); prepareStatement( CHARA_ITEMGLOBAL_UP, - "UPDATE charaglobalitem SET stack = ?, durability = ?, stain = ? WHERE ItemId = ?;", + "UPDATE charaglobalitem SET stack = ?, durability = ?, flags = ?, reservedFlag = ?, stain = ? WHERE ItemId = ?;", CONNECTION_BOTH ); prepareStatement( CHARA_ITEMGLOBAL_DELETE, diff --git a/src/common/Network/PacketDef/Chat/ServerChatDef.h b/src/common/Network/PacketDef/Chat/ServerChatDef.h index a91004b7..e74006cb 100644 --- a/src/common/Network/PacketDef/Chat/ServerChatDef.h +++ b/src/common/Network/PacketDef/Chat/ServerChatDef.h @@ -20,6 +20,19 @@ struct FFXIVIpcTell : FFXIVIpcBasePacket< Tell > char msg[1029]; }; +struct FFXIVIpcChannelChat : FFXIVIpcBasePacket< ChannelChat > +{ + uint64_t channelId; + uint64_t contentId; + uint32_t charaId; + uint8_t type; + uint8_t unknown1; + uint8_t unknown2; + char name[32]; + char message[1024]; + uint8_t padding; +}; + /** * Structural representation of the packet sent by the server as response * to a failed tell because of unavailable target player diff --git a/src/common/Network/PacketDef/Ipcs.h b/src/common/Network/PacketDef/Ipcs.h index c1a44b74..1dc42c7a 100644 --- a/src/common/Network/PacketDef/Ipcs.h +++ b/src/common/Network/PacketDef/Ipcs.h @@ -81,15 +81,14 @@ enum ServerZoneIpcType : SocialRequestError = 0xF0AD, CFRegistered = 0x029F, // updated 5.58h - SocialRequestResponse = 0x373, // updated 6.48 - SocialMessage = 0x03CB, // updated 5.58h - SocialMessage2 = 0x01D7, // updated 5.58h + SocialInviteResponse = 0x373, // updated 6.48 + SocialInviteUpdate = 0x03CB, // updated 5.58h + SocialInviteResult = 0x01D7, // updated 5.58h CancelAllianceForming = 0xF0C6, // updated 4.2 LogMessage = 0x316, // updated 6.48 Chat = 0x0325, // updated 6.58 hotfix 2 - PartyChat = 0x0065, WorldVisitList = 0xF0FE, // added 4.5 @@ -164,7 +163,7 @@ enum ServerZoneIpcType : SomeCustomiseChangePacketProbably = 0x00CD, // added 5.18 PartyList = 0x16f, // updated 6.48 - PartyMessage = 0x336, // updated 6.48 + PartyUpdate = 0x336, // updated 6.48 HateRank = 0x2A7, // updated 6.58 hotfix 2 HateList = 0x26B, // updated 6.58 hotfix 2 ObjectSpawn = 0x03B8, // updated 6.58 hotfix 2 @@ -352,15 +351,15 @@ enum ClientZoneIpcType : CancelLogout = 0x01e3, // updated 6.31h CFDutyInfoHandler = 0xF078, // updated 4.2 - SocialReqSendHandler = 0x00D7, // updated 5.58h - SocialResponseHandler = 0x023B, // updated 5.58h + SocialInviteHandler = 0x00D7, // updated 5.58h + SocialReplyHandler = 0x023B, // updated 5.58h CreateCrossWorldLS = 0x9999, // updated 5.58h ChatHandler = 0x246, // updated 6.58 hotfix 2 - PartyChatHandler = 0x0065, PartySetLeaderHandler = 0x036C, // updated 5.58h - LeavePartyHandler = 0x019D, // updated 5.58h - KickPartyMemberHandler = 0x0262, // updated 5.58h - DisbandPartyHandler = 0x0276, // updated 5.58h + PartyChangeLeaderHandler = 0x036C, // updated 5.58h + PartyLeaveHandler = 0x019D, // updated 5.58h + PartyKickHandler = 0x0262, // updated 5.58h + PartyDisbandHandler = 0x0276, // updated 5.58h SocialListHandler = 0x10B, // updated 6.58 hotfix 2 SetSearchInfoHandler = 0x01A0, // updated 6.58 hotfix 2 @@ -451,6 +450,7 @@ enum ServerChatIpcType : uint16_t { Tell = 0x0064, // updated for sb + ChannelChat = 0x0065, PublicContentTell = 0xF0FB, // added 4.5, this is used when receiving a /tell in PublicContent instances such as Eureka or Bozja (prepended F conflicts with TradeReturnEventHandler 6.38) TellErrNotFound = 0x0066, @@ -464,6 +464,7 @@ enum ClientChatIpcType : uint16_t { TellReq = 0x0064, + ChannelChatReq = 0x0065, PublicContentTellReq = 0x0326, // updated 5.35 hotfix, this is used when sending a /tell in PublicContent instances such as Eureka or Bozja }; diff --git a/src/common/Network/PacketDef/Zone/ClientZoneDef.h b/src/common/Network/PacketDef/Zone/ClientZoneDef.h index f15ca0bf..baa0944c 100644 --- a/src/common/Network/PacketDef/Zone/ClientZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ClientZoneDef.h @@ -207,10 +207,10 @@ struct FFXIVIpcChatHandler : /* 001A */ char message[1012]; }; -struct FFXIVIpcPartyChatHandler : - FFXIVIpcBasePacket< ChatHandler > +struct FFXIVIpcChannelChatHandler : + FFXIVIpcBasePacket< ChannelChatReq > { - uint64_t unknown; + uint64_t channelId; char message[1024]; }; @@ -362,8 +362,8 @@ struct FFXIVIpcWorldInteractionHandler : Common::FFXIVARR_POSITION3 position; }; -struct FFXIVIpcSocialReqSendHandler : - FFXIVIpcBasePacket< SocialReqSendHandler > +struct FFXIVIpcSocialInviteHandler : + FFXIVIpcBasePacket< SocialInviteHandler > { uint64_t unknown; uint8_t p1; @@ -373,8 +373,8 @@ struct FFXIVIpcSocialReqSendHandler : uint8_t padding[5]; }; -struct FFXIVIpcSocialResponseHandler : - FFXIVIpcBasePacket< SocialResponseHandler > +struct FFXIVIpcSocialReplyHandler : + FFXIVIpcBasePacket< SocialReplyHandler > { uint64_t contentId; uint8_t p1; @@ -384,8 +384,8 @@ struct FFXIVIpcSocialResponseHandler : uint32_t unknown; }; -struct FFXIVIpcPartySetLeaderHandler : - FFXIVIpcBasePacket< PartySetLeaderHandler > +struct FFXIVIpcPartyChangeLeaderHandler : + FFXIVIpcBasePacket< PartyChangeLeaderHandler > { uint64_t contentId; uint8_t p1; @@ -394,14 +394,14 @@ struct FFXIVIpcPartySetLeaderHandler : uint8_t padding[6]; }; -struct FFXIVIpcLeavePartyHandler : - FFXIVIpcBasePacket< LeavePartyHandler > +struct FFXIVIpcPartyLeaveHandler : + FFXIVIpcBasePacket< PartyLeaveHandler > { uint64_t empty; }; -struct FFXIVIpcKickPartyMemberHander : - FFXIVIpcBasePacket< KickPartyMemberHandler > +struct FFXIVIpcPartyKickHandler : + FFXIVIpcBasePacket< PartyKickHandler > { uint64_t contentId; uint8_t p1; @@ -410,8 +410,8 @@ struct FFXIVIpcKickPartyMemberHander : uint8_t padding[6]; }; -struct FFXIVIpcDisbandPartyHandler : - FFXIVIpcBasePacket< DisbandPartyHandler > +struct FFXIVIpcPartyDisbandHandler : + FFXIVIpcBasePacket< PartyDisbandHandler > { uint64_t empty; }; diff --git a/src/common/Network/PacketDef/Zone/ServerZoneDef.h b/src/common/Network/PacketDef/Zone/ServerZoneDef.h index ceb69f89..87aec5cd 100644 --- a/src/common/Network/PacketDef/Zone/ServerZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ServerZoneDef.h @@ -47,19 +47,6 @@ namespace Sapphire::Network::Packets::Server uint8_t unknown[12]; //possibly padding? }; - 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++. @@ -2183,7 +2170,7 @@ namespace Sapphire::Network::Packets::Server uint32_t param7; }; - struct FFXIVIpcSocialMessage : FFXIVIpcBasePacket< SocialMessage > + struct FFXIVIpcSocialInviteUpdate : FFXIVIpcBasePacket< SocialInviteUpdate > { uint64_t contentId; uint32_t expireTime; @@ -2192,12 +2179,12 @@ namespace Sapphire::Network::Packets::Server uint8_t socialType; uint8_t padding; uint8_t type; - uint8_t unknown4; + uint8_t gender; char name[32]; uint8_t padding2[6]; }; - struct FFXIVIpcSocialMessage2 : FFXIVIpcBasePacket< SocialMessage2 > + struct FFXIVIpcSocialInviteResult : FFXIVIpcBasePacket< SocialInviteResult > { uint64_t contentId; uint32_t unknown3; @@ -2208,40 +2195,42 @@ namespace Sapphire::Network::Packets::Server char name[32]; }; - struct FFXIVIpcSocialRequestResponse : FFXIVIpcBasePacket< SocialRequestResponse > + struct FFXIVIpcSocialInviteResponse : FFXIVIpcBasePacket< SocialInviteResponse > { uint64_t contentId; uint32_t unknown3; - uint8_t u1AlwaysOne; + uint8_t socialType; uint8_t response; - uint8_t u2AlwaysOne; + uint8_t gender; char name[32]; uint8_t padding; }; + struct PartyMember + { + char name[32]; + uint64_t contentId; + uint32_t charaId; + uint32_t u1; // 3.x ParentEntityId? + uint32_t u2; // 3.x PetEntityId? + uint32_t hp; + uint32_t maxHp; + uint16_t mp; + uint16_t maxMp; + uint16_t u3; + uint16_t zoneId; + uint8_t gposeSelectable; // 3.x Valid? + uint8_t classId; + uint8_t u5; // 3.x ObjType? + uint8_t level; + uint8_t isLevelSync; + uint8_t unknown[7]; + Common::StatusEffect effect[30]; + }; + 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 isLevelSync; - uint8_t unknown[7]; - Common::StatusEffect effect[30]; - } member[8]; + PartyMember member[8]; uint64_t partyId; uint64_t channelId; uint8_t leaderIndex; @@ -2250,16 +2239,16 @@ namespace Sapphire::Network::Packets::Server uint32_t padding2; }; - struct FFXIVIpcPartyMessage : FFXIVIpcBasePacket< PartyMessage > + struct FFXIVIpcPartyUpdate : FFXIVIpcBasePacket< PartyUpdate > { - uint64_t leaderContentId; - uint64_t memberContentId; - uint8_t u1; - uint8_t u2; - uint16_t type; - uint8_t partySize; // ? - char leaderName[32]; - char memberName[32]; + uint64_t executeContentId; + uint64_t targetContentId; + uint8_t executeGender; + uint8_t targetGender; + uint16_t updateStatus; + uint8_t partySize; + char executeName[32]; + char targetName[32]; uint8_t padding[3]; }; diff --git a/src/world/Action/EventItemAction.cpp b/src/world/Action/EventItemAction.cpp index f833c420..ab201aac 100644 --- a/src/world/Action/EventItemAction.cpp +++ b/src/world/Action/EventItemAction.cpp @@ -67,4 +67,6 @@ void EventItemAction::execute() void EventItemAction::start() { m_startTime = Common::Util::getTimeMs(); + if( !hasCastTime() ) + execute(); } diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index 7ecb39a0..b88f9b3b 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -85,6 +85,7 @@ Sapphire::Entity::Player::Player() : m_onEnterEventDone( false ), m_falling( false ), m_pQueuedAction( nullptr ), + m_partyId( 0 ), m_cfNotifiedContent( 0 ) { m_id = 0; @@ -134,7 +135,8 @@ uint32_t Sapphire::Entity::Player::getMaxHp() uint32_t Sapphire::Entity::Player::getMaxMp() { - return m_baseStats.max_mp; + //return m_baseStats.max_mp; + return 10000; } uint16_t Sapphire::Entity::Player::getZoneId() const @@ -236,6 +238,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 @@ -243,6 +249,47 @@ uint64_t Sapphire::Entity::Player::getOnlineStatusMask() const return m_onlineStatus; } +void Sapphire::Entity::Player::addOnlineStatus( OnlineStatus status ) +{ + uint64_t statusValue = 1ull << static_cast< uint8_t >( status ); + uint64_t newFlags = getOnlineStatusMask() | statusValue; + + setOnlineStatusMask( newFlags ); +} + +void Sapphire::Entity::Player::addOnlineStatus( const std::vector< Common::OnlineStatus >& status ) +{ + uint64_t newFlags = getOnlineStatusMask(); + for( const auto& state : status ) + { + uint64_t statusValue = 1ull << static_cast< uint8_t >( state ); + newFlags |= statusValue; + } + + setOnlineStatusMask( newFlags ); +} + +void Sapphire::Entity::Player::removeOnlineStatus( OnlineStatus status ) +{ + uint64_t statusValue = 1ull << static_cast< uint8_t >( status ); + uint64_t newFlags = getOnlineStatusMask(); + newFlags &= ~statusValue; + + setOnlineStatusMask( newFlags ); +} + +void Sapphire::Entity::Player::removeOnlineStatus( const std::vector< Common::OnlineStatus >& status ) +{ + uint64_t newFlags = getOnlineStatusMask(); + for( const auto& state : status ) + { + uint64_t statusValue = 1ull << static_cast< uint8_t >( state ); + newFlags &= ~statusValue; + } + + setOnlineStatusMask( newFlags ); +} + void Sapphire::Entity::Player::prepareZoning( uint16_t targetZone, bool fadeOut, uint8_t fadeOutTime, uint16_t animation, uint8_t param4, uint8_t param7, uint8_t unknown ) { auto preparePacket = makeZonePacket< FFXIVIpcPrepareZoning >( getId() ); @@ -513,14 +560,15 @@ bool Sapphire::Entity::Player::exitInstance() { auto& teriMgr = Common::Service< TerritoryMgr >::ref(); - auto d = getCurrentTerritory()->getAsDirector(); + auto d = getCurrentTerritory()->getAsInstanceContent(); if( d && d->getContentFinderConditionId() > 0 ) { + // shows correct name when leaving dungeon auto p = makeZonePacket< FFXIVDirectorUnk4 >( getId() ); p->data().param[0] = d->getDirectorId(); p->data().param[1] = 1534; p->data().param[2] = 1; - p->data().param[3] = d->getContentFinderConditionId(); + p->data().param[3] = d->getContentId(); queuePacket( p ); prepareZoning( 0, 1, 1, 0, 0, 1, 9 ); @@ -1080,7 +1128,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; @@ -1089,13 +1136,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 ) @@ -1116,8 +1156,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; @@ -1126,11 +1164,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 ) @@ -1356,6 +1389,14 @@ void Sapphire::Entity::Player::queuePacket( Network::Packets::FFXIVPacketBasePtr } +void Sapphire::Entity::Player::queuePacket( std::vector< Network::Packets::FFXIVPacketBasePtr > packets ) +{ + for( auto& packet : packets ) + { + queuePacket( packet ); + } +} + void Sapphire::Entity::Player::queueChatPacket( Network::Packets::FFXIVPacketBasePtr pPacket ) { auto& serverMgr = Common::Service< World::ServerMgr >::ref(); @@ -2334,6 +2375,16 @@ bool Sapphire::Entity::Player::checkAction() return true; } +uint64_t Sapphire::Entity::Player::getPartyId() const +{ + return m_partyId; +} + +void Sapphire::Entity::Player::setPartyId( uint64_t partyId ) +{ + m_partyId = partyId; +} + std::vector< Sapphire::Entity::ShopBuyBackEntry >& Sapphire::Entity::Player::getBuyBackListForShop( uint32_t shopId ) { return m_shopBuyBackMap[ shopId ]; diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index 2e36ba69..a304b0fc 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -340,7 +340,7 @@ namespace Sapphire::Entity void equipSoulCrystal( ItemPtr pItem, bool updateClass ); /*! unequip a soul crystal, returning to the base class*/ - void unequipSoulCrystal( ItemPtr pItem ); + void unequipSoulCrystal(); /*! get player ilvl */ uint16_t getItemLevel() const; @@ -543,6 +543,11 @@ namespace Sapphire::Entity /*! returns the current online status */ uint64_t getOnlineStatusMask() const; + void addOnlineStatus( Common::OnlineStatus status ); + void addOnlineStatus( const std::vector< Common::OnlineStatus >& status ); + void removeOnlineStatus( Common::OnlineStatus status ); + void removeOnlineStatus( const std::vector< Common::OnlineStatus >& status ); + /*! perform a teleport of a specified type ( teleport,return,aethernet ) */ void teleport( uint16_t aetheryteId, uint8_t type = 1 ); @@ -769,6 +774,7 @@ namespace Sapphire::Entity /*! queue a packet for the player */ void queuePacket( Network::Packets::FFXIVPacketBasePtr pPacket ); + void queuePacket( std::vector< Network::Packets::FFXIVPacketBasePtr > packets ); /*! queue a char connection packet for the player */ void queueChatPacket( Network::Packets::FFXIVPacketBasePtr pPacket ); @@ -1028,6 +1034,9 @@ namespace Sapphire::Entity void updateHuntingLog( uint16_t id ); + uint64_t getPartyId() const; + void setPartyId( uint64_t partyId ); + World::SessionPtr getSession(); uint64_t m_lastMoveTime; @@ -1181,6 +1190,8 @@ namespace Sapphire::Entity Common::Util::SpawnIndexAllocator< uint8_t > m_objSpawnIndexAllocator; Common::Util::SpawnIndexAllocator< uint8_t > m_actorSpawnIndexAllocator; + uint64_t m_partyId; + std::array< Common::HuntingLogEntry, 12 > m_huntingLogEntries; std::unordered_map< uint32_t, std::vector< ShopBuyBackEntry > > m_shopBuyBackMap; }; diff --git a/src/world/Actor/PlayerInventory.cpp b/src/world/Actor/PlayerInventory.cpp index b3bedbb0..c818244a 100644 --- a/src/world/Actor/PlayerInventory.cpp +++ b/src/world/Actor/PlayerInventory.cpp @@ -148,9 +148,9 @@ void Sapphire::Entity::Player::equipSoulCrystal( ItemPtr pItem, bool updateJob ) void Sapphire::Entity::Player::updateModels( GearSetSlot equipSlotId, const Sapphire::ItemPtr& pItem, bool updateClass ) { - uint64_t model = pItem->getModelId1(); - uint64_t model2 = pItem->getModelId2(); - uint64_t stain = pItem->getStain(); + uint64_t model = pItem ? pItem->getModelId1() : 0; + uint64_t model2 = pItem ? pItem->getModelId2() : 0; + uint64_t stain = pItem ? pItem->getStain() : 0; switch( equipSlotId ) { @@ -172,7 +172,10 @@ void Sapphire::Entity::Player::updateModels( GearSetSlot equipSlotId, const Sapp break; case SoulCrystal: - equipSoulCrystal( pItem, updateClass ); + if( pItem ) + equipSoulCrystal( pItem, updateClass ); + else + unequipSoulCrystal(); break; case Waist: @@ -265,15 +268,12 @@ void Sapphire::Entity::Player::unequipItem( Common::GearSetSlot equipSlotId, Ite if( sendUpdate ) { + updateModels( equipSlotId, nullptr, true ); sendModel(); - m_itemLevel = calculateEquippedGearItemLevel(); sendItemLevel(); } - if ( equipSlotId == SoulCrystal ) - unequipSoulCrystal( pItem ); - auto baseParams = pItem->getBaseParams(); for( auto i = 0; i < 6; ++i ) { @@ -293,7 +293,7 @@ void Sapphire::Entity::Player::unequipItem( Common::GearSetSlot equipSlotId, Ite } } -void Sapphire::Entity::Player::unequipSoulCrystal( ItemPtr pItem ) +void Sapphire::Entity::Player::unequipSoulCrystal() { auto& exdData = Common::Service< Sapphire::Data::ExdDataGenerated >::ref(); @@ -547,38 +547,6 @@ void Sapphire::Entity::Player::writeInventory( InventoryType type ) db.execute( query ); } -void Sapphire::Entity::Player::updateItemDb( Sapphire::ItemPtr pItem ) const -{ - if( pItem->getUId() == 0 ) - writeItemDb( pItem ); - - auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref(); - auto stmt = db.getPreparedStatement( Db::CHARA_ITEMGLOBAL_UP ); - - // todo: add more fields - stmt->setInt( 1, pItem->getStackSize() ); - stmt->setInt( 2, pItem->getDurability() ); - stmt->setInt( 3, pItem->getStain() ); - - stmt->setInt64( 4, pItem->getUId() ); - - db.directExecute( stmt ); -} - -void Sapphire::Entity::Player::deleteItemDb( Sapphire::ItemPtr item ) const -{ - if( item->getUId() == 0 ) - return; - - auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref(); - auto stmt = db.getPreparedStatement( Db::CHARA_ITEMGLOBAL_DELETE ); - - stmt->setInt64( 1, item->getUId() ); - - db.directExecute( stmt ); -} - - bool Sapphire::Entity::Player::isObtainable( uint32_t catalogId, uint8_t quantity ) { return true; @@ -748,7 +716,19 @@ Sapphire::Entity::Player::moveItem( uint16_t fromInventoryId, uint8_t fromSlotId auto& itemMap = m_storageMap[ fromInventoryId ]->getItemMap(); if( tmpItem == nullptr ) + { + sendUrgent( "trying to move EMPTY item from [container{}, slot{}] to [container{}, slot{}], potential client desync, no action is performed.", + fromInventoryId, fromSlotId, toInventoryId, toSlot ); return; + } + + if( auto target = m_storageMap[ toInventoryId ]->getItem( toSlot ) ) + { + sendUrgent( "trying to move item from [container{}, slot{}] to NON-EMPTY [container{}, slot{}], potential client desync, swapItem is performed instead.", + fromInventoryId, fromSlotId, toInventoryId, toSlot ); + swapItem( fromInventoryId, fromSlotId, toInventoryId, toSlot, sendUpdate ); + return; + } itemMap[ fromSlotId ].reset(); @@ -885,8 +865,28 @@ void Sapphire::Entity::Player::swapItem( uint16_t fromInventoryId, uint8_t fromS auto toItem = m_storageMap[ toInventoryId ]->getItem( toSlot ); auto& itemMap = m_storageMap[ fromInventoryId ]->getItemMap(); - if( fromItem == nullptr || toItem == nullptr ) + if( fromItem == nullptr && toItem == nullptr ) + { + sendUrgent( "trying to swap TWO EMPTY ITEMS from [container{}, slot{}] to [container{}, slot{}], potential client desync, no action is performed.", + fromInventoryId, fromSlotId, toInventoryId, toSlot ); return; + } + + if( fromItem != nullptr && toItem == nullptr ) + { + sendUrgent( "trying to swap item from [container{}, slot{}] to EMPTY [container{}, slot{}], potential client desync, moveItem is performed instead.", + fromInventoryId, fromSlotId, toInventoryId, toSlot ); + moveItem( fromInventoryId, fromSlotId, toInventoryId, toSlot, sendUpdate ); + return; + } + + if( fromItem == nullptr && toItem != nullptr ) + { + sendUrgent( "trying to swap EMPTY item from [container{}, slot{}] to [container{}, slot{}], potential client desync, moveItem is performed instead.", + fromInventoryId, fromSlotId, toInventoryId, toSlot ); + moveItem( toInventoryId, toSlot, fromInventoryId, fromSlotId, sendUpdate ); // we are moving the non-empty toSlot back to fromSlot. + return; + } // An item is being moved from bag0-3 to equippment, meaning // the swapped out item will be placed in the matching armory. diff --git a/src/world/Actor/PlayerSql.cpp b/src/world/Actor/PlayerSql.cpp index bd3fe3d3..4b0bb68b 100644 --- a/src/world/Actor/PlayerSql.cpp +++ b/src/world/Actor/PlayerSql.cpp @@ -650,10 +650,13 @@ void Sapphire::Entity::Player::writeItemDb( Sapphire::ItemPtr pItem ) const auto& itemMgr = Common::Service< World::Manager::ItemMgr >::ref(); uint8_t flags = 0; + if( pItem->isHq() ) + flags |= Common::ItemFlag::FlagHq; pItem->setUId( itemMgr.getNextUId() ); - std::string sql = "INSERT INTO charaglobalitem ( CharacterId, itemId, catalogId, stack, flags ) VALUES ( " + + std::string sql = "INSERT INTO charaglobalitem ( CharacterId, itemId, reservedFlag, catalogId, stack, flags ) VALUES ( " + std::to_string( getId() ) + ", " + std::to_string( pItem->getUId() ) + ", " + + std::to_string( pItem->getReservedFlag() ) + ", " + std::to_string( pItem->getId() ) + ", " + std::to_string( pItem->getStackSize() ) + ", " + std::to_string( flags ) + ");"; @@ -661,6 +664,46 @@ void Sapphire::Entity::Player::writeItemDb( Sapphire::ItemPtr pItem ) const } } +void Sapphire::Entity::Player::updateItemDb( Sapphire::ItemPtr pItem ) const +{ + if( pItem->getUId() == 0 ) + { + writeItemDb( pItem ); + return; + } + + uint8_t flags = 0; + if( pItem->isHq() ) + flags |= Common::ItemFlag::FlagHq; + + auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref(); + auto stmt = db.getPreparedStatement( Db::CHARA_ITEMGLOBAL_UP ); + + // todo: add more fields + stmt->setInt( 1, pItem->getStackSize() ); + stmt->setInt( 2, pItem->getDurability() ); + stmt->setInt( 3, flags ); + stmt->setInt( 4, pItem->getReservedFlag() ); + stmt->setInt( 5, pItem->getStain() ); + + stmt->setInt64( 6, pItem->getUId() ); + + db.directExecute( stmt ); +} + +void Sapphire::Entity::Player::deleteItemDb( Sapphire::ItemPtr item ) const +{ + if( item->getUId() == 0 ) + return; + + auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref(); + auto stmt = db.getPreparedStatement( Db::CHARA_ITEMGLOBAL_DELETE ); + + stmt->setInt64( 1, item->getUId() ); + + db.directExecute( stmt ); +} + bool Sapphire::Entity::Player::loadInventory() { auto& itemMgr = Common::Service< World::Manager::ItemMgr >::ref(); diff --git a/src/world/Manager/ChatChannelMgr.cpp b/src/world/Manager/ChatChannelMgr.cpp new file mode 100644 index 00000000..1fd0e50c --- /dev/null +++ b/src/world/Manager/ChatChannelMgr.cpp @@ -0,0 +1,144 @@ +#include +#include +#include + +#include "ChatChannelMgr.h" + +#include "Actor/Player.h" +#include "ServerMgr.h" +#include "Session.h" +#include "Network/GameConnection.h" + +using namespace Sapphire; +using namespace Sapphire::Network; +using namespace Sapphire::Network::Packets; +using namespace Sapphire::World::Manager; + +const uint64_t ChatChannelMgr::createChatChannel( Common::ChatChannelType type ) +{ + auto& server = Common::Service< World::ServerMgr >::ref(); + + // get next id for new channel + + uint32_t cNo = m_lastChatNo; + + m_lastChatNo++; + + uint16_t chatType = static_cast< uint16_t >( type ); + uint16_t worldId = server.getWorldId(); + + Data::ChatChannel cId; + + cId.data.ChannelNo = cNo; + cId.data.ChannelType = type; + cId.data.WorldId = worldId; + + // create our new chat channel + + Data::ChatChannelMembers newChatChannel = {}; + + m_channels[ cId.ChannelID ] = newChatChannel; + + Logger::debug( "Chat channel ID " + + std::to_string( cId.ChannelID ) + + " created" + ); + + return cId.ChannelID; +} + +void ChatChannelMgr::addToChannel( uint64_t channelId, Entity::Player& player ) +{ + if( !isChannelValid( channelId ) ) + { + // channel id is invalid + + Logger::warn( "Attempted to add player " + + std::to_string( player.getId() ) + + " to invalid channel ID " + + std::to_string( channelId ) + ); + + return; + } + + auto& channelMembers = m_channels[ channelId ]; + auto id = player.getId(); + + if( std::find( channelMembers.begin(), channelMembers.end(), id ) == channelMembers.end() ) + m_channels[ channelId ].emplace_back( id ); +} + +void ChatChannelMgr::removeFromChannel( uint64_t channelId, Entity::Player& player ) +{ + if( !isChannelValid( channelId ) ) + { + // channel id is invalid + + Logger::warn( "Attempted to remove player " + + std::to_string( player.getId() ) + + " from invalid channel ID " + + std::to_string( channelId ) + ); + + return; + } + + auto& channelMembers = m_channels[ channelId ]; + auto id = player.getId(); + + auto it = std::find( channelMembers.begin(), channelMembers.end(), id ); + if( it != channelMembers.end() ) + channelMembers.erase( it ); +} + +void ChatChannelMgr::sendMessageToChannel( uint64_t channelId, Entity::Player& sender, const std::string& message ) +{ + if( !isChannelValid( channelId ) ) + { + // channel id is invalid + + Logger::warn( "Attempted to send message from player " + + std::to_string( sender.getId() ) + + " to invalid channel ID " + + std::to_string( channelId ) + ); + + return; + } + + auto& channelMembers = m_channels[ channelId ]; + + auto& server = Common::Service< World::ServerMgr >::ref(); + + // send message to all players in chat channel + for( const auto id : channelMembers ) + { + // skip sender from getting their own message + if( id == sender.getId() ) + continue; + + auto pPlayer = server.getSession( id )->getPlayer(); + + // check if player is online to recv message + if( !pPlayer/*->isConnected()*/ ) + continue; + + // prepare message packet, associating message and sender info with channel data + auto chatToChannelPacket = std::make_shared< Packets::Server::ChannelChatPacket >( *pPlayer, sender, channelId, message ); + pPlayer->queueChatPacket( chatToChannelPacket ); + } +} + +bool ChatChannelMgr::isChannelValid( uint64_t channelId ) const +{ + return !( m_channels.find( channelId ) == m_channels.end() ); +} + +const Data::ChatChannelMembers& ChatChannelMgr::getChatChannel( uint64_t channelId ) +{ + bool channelValid = isChannelValid( channelId ); + assert( channelValid ); + + return m_channels[ channelId ]; +} \ No newline at end of file diff --git a/src/world/Manager/ChatChannelMgr.h b/src/world/Manager/ChatChannelMgr.h new file mode 100644 index 00000000..16dec38e --- /dev/null +++ b/src/world/Manager/ChatChannelMgr.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include "ForwardsZone.h" + +namespace Sapphire::Data +{ + using ChatChannelMembers = std::vector< uint32_t >; + + union ChatChannel + { + uint64_t ChannelID; + + struct ChannelData { + uint32_t ChannelNo; + uint16_t ChannelType; + uint16_t WorldId; + } data; + }; +} + +namespace Sapphire::World::Manager +{ + class ChatChannelMgr + { + public: + ChatChannelMgr() = default; + ~ChatChannelMgr() = default; + + const uint64_t createChatChannel( Common::ChatChannelType type ); + + void addToChannel( uint64_t channelId, Entity::Player& player ); + void removeFromChannel( uint64_t channelId, Entity::Player& player ); + + void sendMessageToChannel( uint64_t channelId, Entity::Player& sender, const std::string& message ); + + bool isChannelValid( uint64_t channelId ) const; + const Data::ChatChannelMembers& getChatChannel( uint64_t channelId ); + + private: + std::map< uint64_t, Data::ChatChannelMembers > m_channels; + uint32_t m_lastChatNo = 0x1000; + }; +} \ No newline at end of file diff --git a/src/world/Manager/ItemMgr.cpp b/src/world/Manager/ItemMgr.cpp index c8331dac..01d2b03c 100644 --- a/src/world/Manager/ItemMgr.cpp +++ b/src/world/Manager/ItemMgr.cpp @@ -132,15 +132,16 @@ Sapphire::ItemPtr Sapphire::World::Manager::ItemMgr::loadItem( uint64_t uId ) try { auto itemInfo = exdData.get< Sapphire::Data::Item >( itemRes->getUInt( 1 ) ); - bool isHq = itemRes->getUInt( 3 ) == 1; + bool isHq = itemRes->getUInt( 5 ) & Common::ItemFlag::FlagHq; ItemPtr pItem = make_Item( uId, itemRes->getUInt( 1 ), isHq ); pItem->setStackSize( itemRes->getUInt( 2 ) ); - pItem->setStain( itemRes->getUInt16( 13 ) ); + pItem->setReservedFlag( itemRes->getUInt( 3 ) ); pItem->setDurability( itemRes->getInt16( 6 ) ); + pItem->setStain( itemRes->getUInt16( 13 ) ); return pItem; } diff --git a/src/world/Manager/PartyMgr.cpp b/src/world/Manager/PartyMgr.cpp new file mode 100644 index 00000000..4d00bfe1 --- /dev/null +++ b/src/world/Manager/PartyMgr.cpp @@ -0,0 +1,439 @@ +#include +#include +#include +#include + +#include +#include + +#include "Network/GameConnection.h" + +#include "PartyMgr.h" +#include "ServerMgr.h" +#include "ChatChannelMgr.h" +#include "PlayerMgr.h" + +#include "Session.h" + +#include "Actor/Player.h" + +#include "Network/PacketWrappers/PartyUpdatePacket.h" + +using namespace Sapphire; +using namespace Sapphire::World::Manager; +using namespace Sapphire::Network::Packets; +using namespace Sapphire::Network::Packets::Server; + +void PartyMgr::onJoin( Entity::Player& joiner, Entity::Player& inviter ) +{ + auto& server = Common::Service< World::ServerMgr >::ref(); + auto& ccMgr = Common::Service< World::Manager::ChatChannelMgr >::ref(); + + auto& inviteePlayer = joiner; + auto& invitingPlayer = inviter; + + 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 = makePartyUpdate( invitingPlayer, inviteePlayer, UpdateStatus::JOINED, party->PartyCount ); + auto members = getPartyMembers( *party ); + sendPartyUpdate( *party ); + for( const auto& member : members ) + { + member->queuePacket( pcUpdateParty ); + } +} + +void PartyMgr::onLeave( Sapphire::Entity::Player &leavingPlayer ) +{ + auto& server = Common::Service< World::ServerMgr >::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 } ); + + leavingPlayer.queuePacket( makeZonePacket< FFXIVIpcPartyList >( leavingPlayer.getId() ) ); + member->queuePacket( makePartyUpdate( leadingPlayer, nullptr, UpdateStatus::KICK_SELF, party->PartyCount ) ); + } + else + { + if( leavingPlayer.getId() == party->LeaderId ) + { + newLeaderId = party->MemberId[ 0 ]; + auto pPlayer = server.getSession( newLeaderId )->getPlayer(); + if( !pPlayer /*|| !pPlayer->isConnected() */) + continue; + pPlayer->addOnlineStatus( Common::OnlineStatus::PartyLeader ); + member->queuePacket( makePartyUpdate( leavingPlayer.getAsPlayer(), pPlayer, UpdateStatus::LEAVELEADER_LEAVED_MEMBER, party->PartyCount ) ); + } + else + { + member->queuePacket( makePartyUpdate( 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::ServerMgr >::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 } ); + member->queuePacket( { makePartyUpdate( disbandingPlayer, disbandingPlayer, UpdateStatus::DISBAND, party->PartyCount ), makeZonePacket< FFXIVIpcPartyList >( member->getId() ) } ); + } + + removeParty( party->PartyID ); +} + +void PartyMgr::onMoveZone( Sapphire::Entity::Player &movingPlayer ) +{ + if( movingPlayer.getPartyId() == 0 ) + { + movingPlayer.queuePacket( makeZonePacket< FFXIVIpcPartyList >( movingPlayer.getId() ) ); + 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::ServerMgr >::ref(); + auto party = getParty( disconnectingPlayer.getPartyId() ); + assert( party ); + auto members = getPartyMembers( *party ); + auto pLeader = getPartyLeader( *party ); + + bool anyMembersOnline = false; + + for( const auto& member : members ) + { + if( member/*->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 + member->queuePacket( { makePartyUpdate( disconnectingPlayer, UpdateStatus::OFFLINE_MEMBER, party->PartyCount ), makeZonePacket< FFXIVIpcPartyList >( 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::ServerMgr >::ref(); + auto& playerMgr = Common::Service< World::Manager::PlayerMgr >::ref(); + auto party = getParty( leader.getPartyId() ); + assert( party ); + auto pLeader = getPartyLeader( *party ); + auto members = getPartyMembers( *party ); + auto pKickedPlayer = server.getSession( kickPlayerName )->getPlayer(); + if( !pKickedPlayer ) + { + 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 ); + + member->queuePacket( { makePartyUpdate( *pLeader, *member, UpdateStatus::KICK_SELF, party->PartyCount ), + makeZonePacket< FFXIVIpcPartyList >( member->getId() ) } ); + } + else + { + member->queuePacket( makePartyUpdate( *pKickedPlayer, UpdateStatus::KICK_MEMBER, party->PartyCount ) ); + } + } + party->PartyCount--; + sendPartyUpdate( *party ); + } +} + +void PartyMgr::onChangeLeader( const std::string& newLeaderName, Entity::Player& oldLeader ) +{ + auto& server = Common::Service< World::ServerMgr >::ref(); + auto& playerMgr = Common::Service< World::Manager::PlayerMgr >::ref(); + auto party = getParty( oldLeader.getPartyId() ); + + auto pNewLeader = server.getSession( newLeaderName )->getPlayer(); + + 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 = makePartyUpdate( oldLeader.getAsPlayer(), pNewLeader, UpdateStatus::CHANGELEADER, party->PartyCount ); + member->queuePacket( 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::ServerMgr >::ref(); + for( auto& memberId : party.MemberId ) + { + if( memberId == 0 ) + continue; + + auto pPlayer = server.getSession( memberId )->getPlayer(); + + members.push_back( pPlayer ); + } + return members; +} + +Entity::PlayerPtr PartyMgr::getPartyLeader( Party& party ) +{ + auto& server = Common::Service< World::ServerMgr >::ref(); + + if( party.LeaderId == 0 ) + return nullptr; + + auto pLeader = server.getSession( party.LeaderId )->getPlayer(); + + return pLeader; +} + +void PartyMgr::sendPartyUpdate( Party& party ) +{ + auto& exdData = Common::Service< Data::ExdDataGenerated >::ref(); + auto partyMembers = getPartyMembers( party ); + std::vector< PartyMember > entries; + auto& server = Common::Service< World::ServerMgr >::ref(); + + for( const auto& member : partyMembers ) + { + auto classJob = exdData.get< Data::ClassJob >( static_cast< uint8_t >( member->getClass() ) ); + if( !classJob ) + continue; + + PartyMember memberEntry{}; + + memberEntry./*ParentEntityId*/u1 = Common::INVALID_GAME_OBJECT_ID; + memberEntry./*PetEntityId*/u2 = Common::INVALID_GAME_OBJECT_ID; + memberEntry.hp = member->getHp(); + memberEntry.maxHp = member->getMaxHp(); + memberEntry.mp = member->getMp(); + memberEntry.maxMp = member->getMaxMp(); + memberEntry.classId = static_cast< uint8_t >( member->getClass() ); + memberEntry.level = member->getLevel(); + //memberEntry.ObjType = 4; // 1 PC, 2 Buddy ?? + memberEntry.zoneId = member->getTerritoryTypeId(); + memberEntry./*Valid*/gposeSelectable = 1; + //memberEntry.Tp = member->getTp(); + //memberEntry.Role = classJob->role; + + entries.push_back( memberEntry ); + } + + for( const auto& pMember : partyMembers ) + { + size_t idx = 0; + + auto updatePartyPacket = makeZonePacket< FFXIVIpcPartyList >( partyMembers[ 0 ]->getId() ); + auto& data = updatePartyPacket->data(); + data.partyId = party.PartyID; + data.leaderIndex = getPartyLeaderIndex( party ); + data.channelId = party.ChatChannel; + data.partySize = party.PartyCount; + + for( const auto& member : partyMembers ) + { + bool isConnected = /*member->isConnected()*/true; + // 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 ].contentId = member->getContentId(); + data.member[ idx ].charaId = member->getId(); + strcpy( data.member[ idx ].name, member->getName().c_str() ); + + idx++; + } + + pMember->queuePacket( 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() ); +} diff --git a/src/world/Manager/PartyMgr.h b/src/world/Manager/PartyMgr.h new file mode 100644 index 00000000..7548b35f --- /dev/null +++ b/src/world/Manager/PartyMgr.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Sapphire::World::Manager +{ + + enum UpdateStatus : int32_t + { + NONE_8 = 0x0, + JOINED = 0x1, + CHANGELEADER = 0x2, + DISBAND = 0x3, + KICK_MEMBER = 0x4, + KICK_SELF = 0x5, + LEAVE_MEMBER = 0x6, + LEAVE_SELF = 0x7, + MOVEZONE = 0x8, + MOVETERRITORY = 0x9, + OFFLINE_MEMBER = 0xA, + RECOVERY_MEMBER = 0xB, + LEAVELEADER_LEAVED_MEMBER = 0xC, + LEAVELEADER_LEAVED_SELF = 0xD, + ADDMEMBER_BUDDY = 0xE, + REMOVEMEMBER_BUDDY = 0xF, + SENDREADYCHECK = 0x10, + REPLYREADYCHECK = 0x11, + }; + + struct Party + { + std::vector< uint32_t > MemberId; + uint64_t PartyID; + uint64_t ChatChannel; + uint32_t LeaderId; + uint8_t PartyCount; + }; + + using PartyPtr = std::shared_ptr< Party >; + + class PartyMgr + { + public: + PartyMgr() = default; + + /// Perform required actions for events + void onJoin( Entity::Player& joiner, Entity::Player& inviter ); + void onLeave( Entity::Player& leavingPlayer ); + void onMoveZone( Entity::Player& movingPlayer ); + void onDisband( Entity::Player& disbandingPlayer ); + void onKick( const std::string& kickPlayerName, Entity::Player& leader ); + void onChangeLeader( const std::string& newLeaderName, Entity::Player& oldLeader ); + + void onMemberDisconnect( Entity::Player& disconnectingPlayer ); + void onMemberRejoin( Entity::Player& joiningPlayer ); + + void onJoinBuddy( Entity::Player& buddyOwner, Party& party ); + void onLeaveBuddy( Entity::Player& buddyOwner, Party& party ); + void onStartReadyCheck( Entity::Player& startingPlayer, Party& party ); + void onReplyReadyCheck( Entity::Player& replyingPlayer, Party& party ); + + /////////////////////////// + PartyPtr getParty( uint64_t partyId ); + + private: + // arbitrary start range for party ids + uint64_t m_maxPartyId = 0x0000044000000000; + + uint64_t createParty(); + void removeParty( uint64_t partyId ); + uint64_t getNextPartyId(); + std::unordered_map< uint64_t, PartyPtr > m_partyIdMap; + + static void sendPartyUpdate( Party& party ); + static void removeMember( Party& party, const Entity::PlayerPtr& pMember ); + static std::vector< Entity::PlayerPtr > getPartyMembers( Party& party ); + static Entity::PlayerPtr getPartyLeader( Party& party ); + + static int8_t getPartyLeaderIndex( const Party& party ); + + }; + +} diff --git a/src/world/Manager/TerritoryMgr.cpp b/src/world/Manager/TerritoryMgr.cpp index 8b4703f6..bf20bcae 100644 --- a/src/world/Manager/TerritoryMgr.cpp +++ b/src/world/Manager/TerritoryMgr.cpp @@ -6,6 +6,7 @@ #include #include +#include "Manager/PartyMgr.h" #include "Actor/Player.h" @@ -759,6 +760,9 @@ bool Sapphire::World::Manager::TerritoryMgr::movePlayer( TerritoryPtr pZone, Sap pPlayer->sendZonePackets(); + auto& partyMgr = Common::Service< World::Manager::PartyMgr >::ref(); + partyMgr.onMoveZone( *pPlayer ); + return true; } diff --git a/src/world/Network/GameConnection.cpp b/src/world/Network/GameConnection.cpp index ef9be721..90e5d7cf 100644 --- a/src/world/Network/GameConnection.cpp +++ b/src/world/Network/GameConnection.cpp @@ -143,7 +143,15 @@ Sapphire::Network::GameConnection::GameConnection( Sapphire::Network::HivePtr pH setZoneHandler( ClientZoneIpcType::InventoryEquipRecommendedItems, "InventoryEquipRecommendedItemsHandler", &GameConnection::inventoryEquipRecommendedItemsHandler ); setChatHandler( ClientChatIpcType::TellReq, "TellReq", &GameConnection::tellHandler ); + setChatHandler( ClientChatIpcType::ChannelChatReq, "ChannelChatReq", &GameConnection::channelChatHandler ); + setZoneHandler( ClientZoneIpcType::SocialInviteHandler, "SocialInviteHandler", &GameConnection::socialInviteHandler ); + setZoneHandler( ClientZoneIpcType::SocialReplyHandler, "SocialReplyHandler", &GameConnection::socialReplyHandler ); + + setZoneHandler( ClientZoneIpcType::PartyLeaveHandler, "PartyLeaveHandler", &GameConnection::partyLeaveHandler ); + setZoneHandler( ClientZoneIpcType::PartyDisbandHandler, "PartyDisbandHandler", &GameConnection::partyDisbandHandler ); + setZoneHandler( ClientZoneIpcType::PartyKickHandler, "PartyKickHandler", &GameConnection::partyKickHandler ); + setZoneHandler( ClientZoneIpcType::PartyChangeLeaderHandler, "PartyChangeLeaderHandler", &GameConnection::partyChangeLeaderHandler ); } Sapphire::Network::GameConnection::~GameConnection() = default; diff --git a/src/world/Network/GameConnection.h b/src/world/Network/GameConnection.h index c8ff3e97..a49ddad8 100644 --- a/src/world/Network/GameConnection.h +++ b/src/world/Network/GameConnection.h @@ -179,6 +179,8 @@ namespace Sapphire::Network DECLARE_HANDLER( tellHandler ); + DECLARE_HANDLER( channelChatHandler ); + DECLARE_HANDLER( reqPlaceHousingItem ); DECLARE_HANDLER( reqMoveHousingItem ); @@ -200,6 +202,14 @@ namespace Sapphire::Network DECLARE_HANDLER( eventYieldHandler ); DECLARE_HANDLER( inventoryEquipRecommendedItemsHandler ); + + DECLARE_HANDLER( socialInviteHandler ); + DECLARE_HANDLER( socialReplyHandler ); + + DECLARE_HANDLER( partyLeaveHandler ); + DECLARE_HANDLER( partyDisbandHandler ); + DECLARE_HANDLER( partyKickHandler ); + DECLARE_HANDLER( partyChangeLeaderHandler ); }; } diff --git a/src/world/Network/Handlers/GMCommandHandlers.cpp b/src/world/Network/Handlers/GMCommandHandlers.cpp index af3b5335..5a4265c7 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 97ab09ab..2988e8fe 100644 --- a/src/world/Network/Handlers/PacketHandlers.cpp +++ b/src/world/Network/Handlers/PacketHandlers.cpp @@ -47,6 +47,8 @@ #include "Manager/HousingMgr.h" #include "Manager/RNGMgr.h" #include "Manager/ItemMgr.h" +#include "Manager/PartyMgr.h" +#include "Manager/ChatChannelMgr.h" #include "Action/Action.h" #include "Inventory/Item.h" @@ -91,18 +93,12 @@ 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, @@ -444,31 +440,58 @@ 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, bool isLeader ) + { + 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 ] = isLeader; + 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 + 4 + 8; + listPacket->data().entries[ i ].onlineStatusMask = nextPlayer->getOnlineStatusMask(); + }; + auto nextPlayer = player.getAsPlayer(); + fillEntryAt( 0, nextPlayer, false ); + if( player.getPartyId() != 0 ) + { + // fill party members + auto& partyMgr = Common::Service< World::Manager::PartyMgr >::ref(); + auto& server = Common::Service< World::ServerMgr >::ref(); + auto pParty = partyMgr.getParty( player.getPartyId() ); + assert( pParty ); + + int i = 1; + for( auto id : pParty->MemberId ) + { + nextPlayer = server.getSession( id )->getPlayer(); + if( nextPlayer->getId() == player.getId() ) + { + // data already in entry 0, only change the leader flag + listPacket->data().entries[ 0 ].bytes[ 8 ] = pParty->LeaderId == id; + continue; + } + fillEntryAt( i, nextPlayer, pParty->LeaderId == id ); + i++; + } + } queueOutPacket( listPacket ); - } else if( type == 2 ) { // friend list @@ -604,12 +627,24 @@ void Sapphire::Network::GameConnection::tellHandler( const Packets::FFXIVARR_PAC if( player.isActingAsGm() ) { - tellPacket->data().flags |= TellFlags::GmTellMsg; + tellPacket->data().flags |= ChatFromType::GmTellMsg; } pTargetPlayer->queueChatPacket( tellPacket ); } +void Sapphire::Network::GameConnection::channelChatHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, Entity::Player& player ) +{ + const auto packet = ChatChannelPacket< Client::FFXIVIpcChannelChatHandler >( inPacket ); + auto& data = packet.data(); + + auto& chatChannelMgr = Common::Service< ChatChannelMgr >::ref(); + + std::string message = std::string( data.message ); + + chatChannelMgr.sendMessageToChannel( data.channelId, player, message ); +} + void Sapphire::Network::GameConnection::performNoteHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, Entity::Player& player ) { diff --git a/src/world/Network/Handlers/PartyHandlers.cpp b/src/world/Network/Handlers/PartyHandlers.cpp new file mode 100644 index 00000000..80208996 --- /dev/null +++ b/src/world/Network/Handlers/PartyHandlers.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +#include +#include +#include + +#include "Manager/PartyMgr.h" + +#include "Network/GameConnection.h" + +#include "Session.h" +#include "Actor/Player.h" + +using namespace Sapphire::Common; +using namespace Sapphire::Network::Packets; +using namespace Sapphire::World::Manager; + + +void Sapphire::Network::GameConnection::partyLeaveHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, + Entity::Player& player ) +{ + if( player.getPartyId() == 0 ) + return; + + auto& partyMgr = Common::Service< Sapphire::World::Manager::PartyMgr >::ref(); + + partyMgr.onLeave( player ); + +} + +void Sapphire::Network::GameConnection::partyDisbandHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, + Entity::Player& player ) +{ + if( player.getPartyId() == 0 ) + return; + + auto& partyMgr = Common::Service< Sapphire::World::Manager::PartyMgr >::ref(); + + partyMgr.onDisband( player ); + +} + +void Sapphire::Network::GameConnection::partyKickHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, + Entity::Player& player ) +{ + if( player.getPartyId() == 0 ) + return; + + const auto packet = ZoneChannelPacket< Client::FFXIVIpcPartyKickHandler >( inPacket ); + const auto& data = packet.data(); + + auto& partyMgr = Common::Service< Sapphire::World::Manager::PartyMgr >::ref(); + + partyMgr.onKick( std::string( data.name ), player ); + +} + +void Sapphire::Network::GameConnection::partyChangeLeaderHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, + Entity::Player& player ) +{ + if( player.getPartyId() == 0 ) + return; + + const auto packet = ZoneChannelPacket< Client::FFXIVIpcPartyChangeLeaderHandler >( inPacket ); + const auto& data = packet.data(); + + auto& partyMgr = Common::Service< Sapphire::World::Manager::PartyMgr >::ref(); + + partyMgr.onChangeLeader( std::string( data.name ), player ); + +} diff --git a/src/world/Network/PacketWrappers/ChannelChatPacket.h b/src/world/Network/PacketWrappers/ChannelChatPacket.h new file mode 100644 index 00000000..8542ef1e --- /dev/null +++ b/src/world/Network/PacketWrappers/ChannelChatPacket.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Forwards.h" +#include "Actor/Player.h" +#include +#include + +namespace Sapphire::Network::Packets::Server +{ + + /** + * @brief The Chat packet. + */ + class ChannelChatPacket : public ChatChannelPacket< FFXIVIpcChannelChat > + { + public: + ChannelChatPacket( Entity::Player& target, + Entity::Player& sender, + uint64_t channelId, + const std::string& msg ) : + ChatChannelPacket< FFXIVIpcChannelChat >( target.getId(), target.getId() ) + { + initialize( sender, channelId, msg ); + }; + + private: + void initialize( Entity::Player& sender, uint64_t channelId, const std::string& msg ) + { + strcpy( m_data.message, msg.c_str() ); + strcpy( m_data.name, sender.getName().c_str() ); + + m_data.channelId = channelId; + + m_data.contentId = sender.getContentId(); + m_data.charaId = sender.getId(); + + m_data.type = 0; + }; + }; + +} \ No newline at end of file diff --git a/src/world/Network/PacketWrappers/InviteHandlers.cpp b/src/world/Network/PacketWrappers/InviteHandlers.cpp new file mode 100644 index 00000000..97f3d41f --- /dev/null +++ b/src/world/Network/PacketWrappers/InviteHandlers.cpp @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "Network/GameConnection.h" +#include "Session.h" + +#include "Territory/Territory.h" + +#include "Network/PacketWrappers/PlayerSetupPacket.h" + +//#include "Manager/FriendListMgr.h" +#include "Manager/PartyMgr.h" +#include "Manager/PlayerMgr.h" +//#include "Manager/FreeCompanyMgr.h" + +#include "Action/Action.h" + +#include "ServerMgr.h" +#include "Forwards.h" + +using namespace Sapphire::Common; +using namespace Sapphire::Network::Packets; +using namespace Sapphire::Network::Packets::Server; +using namespace Sapphire::Network::Packets::Client; +using namespace Sapphire::World::Manager; + + +void Sapphire::Network::GameConnection::socialInviteHandler( const FFXIVARR_PACKET_RAW& inPacket, Entity::Player& player ) +{ + const auto packet = ZoneChannelPacket< Client::FFXIVIpcSocialInviteHandler >( inPacket ); + + player.sendDebug( "Auth Type#{0}", packet.data().socialType ); + player.sendDebug( "Target Name: {0}", packet.data().name ); + + std::string name( packet.data().name ); + + auto& playerMgr = Common::Service< World::Manager::PlayerMgr >::ref(); + auto& server = Common::Service< Sapphire::World::ServerMgr >::ref(); + auto pTargetPlayer = server.getSession( name )->getPlayer(); + + if( !pTargetPlayer ) + return; + + switch( packet.data().socialType ) + { + case 1: + { + auto inviteResultPacket = makeZonePacket< Server::FFXIVIpcSocialInviteResult >( player.getId() ); + auto& data = inviteResultPacket->data(); + data.contentId = pTargetPlayer->getContentId(); + data.p1 = packet.data().p1; + data.p2 = packet.data().p2; + data.socialType = packet.data().socialType; + strcpy( data.name, packet.data().name ); + player.queuePacket( inviteResultPacket ); + + auto inviteUpdatePacket = makeZonePacket< Server::FFXIVIpcSocialInviteUpdate >( pTargetPlayer->getId() ); + inviteUpdatePacket->data().contentId = player.getContentId(); + inviteUpdatePacket->data().expireTime = Common::Util::getTimeSeconds() + 30; + inviteUpdatePacket->data().p1 = packet.data().p1; + inviteUpdatePacket->data().p2 = packet.data().p2; + inviteUpdatePacket->data().socialType = packet.data().socialType; + inviteUpdatePacket->data().type = 1; + inviteUpdatePacket->data().gender = player.getGender(); + strcpy( inviteUpdatePacket->data().name, player.getName().c_str() ); + pTargetPlayer->queuePacket( inviteUpdatePacket ); + + break; + } + } +} + +void Sapphire::Network::GameConnection::socialReplyHandler( const FFXIVARR_PACKET_RAW& inPacket, Entity::Player& player ) +{ + const auto packet = ZoneChannelPacket< Client::FFXIVIpcSocialReplyHandler >( inPacket ); + const auto& data = packet.data(); + + auto& playerMgr = Common::Service< World::Manager::PlayerMgr >::ref(); + auto& server = Common::Service< Sapphire::World::ServerMgr >::ref(); + auto pPlayer = server.getSession( data.contentId )->getPlayer(); + + if( !pPlayer ) + return; + + auto inviteReplyPacket = makeZonePacket< Server::FFXIVIpcSocialInviteResponse >( player.getId() ); + auto& inviteReplyData = inviteReplyPacket->data(); + inviteReplyData.response = data.response; + + switch( data.socialType ) + { + case 1: + { + auto& partyMgr = Common::Service< PartyMgr >::ref(); + + if( data.response == InviteReplyType::ACCEPT ) + { + partyMgr.onJoin( player, *pPlayer ); + } + + auto inviteUpPacket = makeZonePacket< Server::FFXIVIpcSocialInviteUpdate >( pPlayer->getId() ); + inviteUpPacket->data().contentId = player.getContentId(); + inviteUpPacket->data().expireTime = Common::Util::getTimeSeconds() + 30; + inviteUpPacket->data().p1 = packet.data().p1; + inviteUpPacket->data().p2 = packet.data().p2; + inviteUpPacket->data().socialType = packet.data().socialType; + inviteUpPacket->data().type = data.response == InviteReplyType::ACCEPT ? InviteUpdateType::ACCEPT_INVITE : InviteUpdateType::REJECT_INVITE; + strcpy( inviteUpPacket->data().name, player.getName().c_str() ); + pPlayer->queuePacket( inviteUpPacket ); + + inviteReplyData.contentId == pPlayer->getContentId(); + inviteReplyData.socialType = data.socialType; + inviteReplyData.gender = pPlayer->getGender(); + strcpy( inviteReplyData.name, pPlayer->getName().c_str() ); + player.queuePacket( inviteReplyPacket ); + + break; + } + } +} diff --git a/src/world/Network/PacketWrappers/PartyUpdatePacket.h b/src/world/Network/PacketWrappers/PartyUpdatePacket.h new file mode 100644 index 00000000..71899f01 --- /dev/null +++ b/src/world/Network/PacketWrappers/PartyUpdatePacket.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include "Actor/Player.h" +#include "Forwards.h" + +namespace Sapphire::Network::Packets::Server +{ + class PartyUpdatePacket : public ZoneChannelPacket< FFXIVIpcPartyUpdate > + { + public: + PartyUpdatePacket( Entity::Player& executePlayer, Entity::Player& targetPlayer, uint8_t updateStatus, uint8_t count ) : + ZoneChannelPacket< FFXIVIpcPartyUpdate >( executePlayer.getId(), executePlayer.getId() ) + { + initialize( executePlayer, targetPlayer, updateStatus, count ); + }; + + PartyUpdatePacket( Entity::Player& executePlayer, uint8_t updateStatus, uint8_t count ) : + ZoneChannelPacket< FFXIVIpcPartyUpdate >( executePlayer.getId(), executePlayer.getId() ) + { + initialize( executePlayer, updateStatus, count ); + }; + + PartyUpdatePacket( const Entity::PlayerPtr& executePlayer, const Entity::PlayerPtr& targetPlayer, uint8_t updateStatus, uint8_t count ) : + ZoneChannelPacket< FFXIVIpcPartyUpdate >( executePlayer->getId(), executePlayer->getId() ) + { + initialize( executePlayer, targetPlayer, updateStatus, count ); + }; + + private: + void initialize( Entity::Player& executePlayer, Entity::Player& targetPlayer, uint8_t updateStatus, uint8_t partySize ) + { + m_data.executeContentId = executePlayer.getContentId(); + m_data.targetContentId = targetPlayer.getContentId(); + m_data.executeGender = executePlayer.getGender(); + m_data.targetGender = targetPlayer.getGender(); + m_data.updateStatus = updateStatus; + m_data.partySize = partySize; + strcpy( m_data.executeName, executePlayer.getName().c_str() ); + strcpy( m_data.targetName, targetPlayer.getName().c_str() ); + }; + + void initialize( Entity::Player& executePlayer, uint8_t updateStatus, uint8_t partySize ) + { + m_data.executeContentId = executePlayer.getContentId(); + m_data.targetContentId = 0; + m_data.executeGender = executePlayer.getGender(); + m_data.targetGender = 0; + m_data.updateStatus = updateStatus; + m_data.partySize = partySize; + strcpy( m_data.targetName, executePlayer.getName().c_str() ); + }; + + void initialize( const Entity::PlayerPtr& executePlayer, const Entity::PlayerPtr& targetPlayer, uint8_t updateStatus, uint8_t partySize ) + { + if( targetPlayer ) + { + m_data.targetContentId = targetPlayer->getContentId(); + m_data.targetGender = targetPlayer->getGender(); + strcpy( m_data.targetName, targetPlayer->getName().c_str() ); + } + + if( executePlayer ) + { + m_data.executeContentId = executePlayer->getContentId(); + m_data.executeGender = executePlayer->getGender(); + strcpy( m_data.executeName, executePlayer->getName().c_str() ); + } + + m_data.updateStatus = updateStatus; + m_data.partySize = partySize; + + }; + + }; + + template< typename... Args > + std::shared_ptr< PartyUpdatePacket > makePartyUpdate( Args... args ) + { + return std::make_shared< PartyUpdatePacket >( args... ); + } + +} diff --git a/src/world/ServerMgr.cpp b/src/world/ServerMgr.cpp index c600b0d6..dde311d3 100644 --- a/src/world/ServerMgr.cpp +++ b/src/world/ServerMgr.cpp @@ -42,6 +42,8 @@ #include "Manager/NaviMgr.h" #include "Manager/ActionMgr.h" #include "Manager/MapMgr.h" +#include "Manager/ChatChannelMgr.h" +#include "Manager/PartyMgr.h" #include "Territory/InstanceObjectCache.h" @@ -153,6 +155,9 @@ void Sapphire::World::ServerMgr::run( int32_t argc, char* argv[] ) } Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::set( pDb ); + auto pChatChannelMgr = std::make_shared< Manager::ChatChannelMgr >(); + Common::Service< Manager::ChatChannelMgr >::set( pChatChannelMgr ); + Logger::info( "LinkshellMgr: Caching linkshells" ); auto pLsMgr = std::make_shared< Manager::LinkshellMgr >(); if( !pLsMgr->loadLinkshells() ) @@ -227,6 +232,7 @@ void Sapphire::World::ServerMgr::run( int32_t argc, char* argv[] ) auto pEventMgr = std::make_shared< Manager::EventMgr >(); auto pItemMgr = std::make_shared< Manager::ItemMgr >(); auto pRNGMgr = std::make_shared< Manager::RNGMgr >(); + auto pPartyMgr = std::make_shared< Manager::PartyMgr >(); Common::Service< DebugCommandMgr >::set( pDebugCom ); Common::Service< Manager::PlayerMgr >::set( pPlayerMgr ); @@ -235,6 +241,7 @@ void Sapphire::World::ServerMgr::run( int32_t argc, char* argv[] ) Common::Service< Manager::EventMgr >::set( pEventMgr ); Common::Service< Manager::ItemMgr >::set( pItemMgr ); Common::Service< Manager::RNGMgr >::set( pRNGMgr ); + Common::Service< Manager::PartyMgr >::set( pPartyMgr ); Logger::info( "World server running on {0}:{1}", m_ip, m_port ); @@ -320,6 +327,7 @@ void Sapphire::World::ServerMgr::mainLoop() { Logger::info( "[{0}] Session removal", it->second->getId() ); it = m_sessionMapById.erase( it ); + removeSession( pPlayer->getContentId() ); removeSession( pPlayer->getName() ); continue; } @@ -334,6 +342,7 @@ void Sapphire::World::ServerMgr::mainLoop() // if( it->second.unique() ) { it = m_sessionMapById.erase( it ); + removeSession( pPlayer->getContentId() ); removeSession( pPlayer->getName() ); } } @@ -372,17 +381,13 @@ bool Sapphire::World::ServerMgr::createSession( uint32_t sessionId ) return false; } + m_sessionMapByContentId[ newSession->getPlayer()->getContentId() ] = newSession; m_sessionMapByName[ newSession->getPlayer()->getName() ] = newSession; return true; } -void Sapphire::World::ServerMgr::removeSession( uint32_t sessionId ) -{ - m_sessionMapById.erase( sessionId ); -} - Sapphire::World::SessionPtr Sapphire::World::ServerMgr::getSession( uint32_t id ) { //std::lock_guard lock( m_sessionMutex ); @@ -394,6 +399,16 @@ Sapphire::World::SessionPtr Sapphire::World::ServerMgr::getSession( uint32_t id return nullptr; } +Sapphire::World::SessionPtr Sapphire::World::ServerMgr::getSession( uint64_t contentId ) +{ + auto it = m_sessionMapByContentId.find( contentId ); + + if( it != m_sessionMapByContentId.end() ) + return ( it->second ); + + return nullptr; +} + Sapphire::World::SessionPtr Sapphire::World::ServerMgr::getSession( const std::string& playerName ) { //std::lock_guard lock( m_sessionMutex ); @@ -406,6 +421,16 @@ Sapphire::World::SessionPtr Sapphire::World::ServerMgr::getSession( const std::s return nullptr; } +void Sapphire::World::ServerMgr::removeSession( uint32_t sessionId ) +{ + m_sessionMapById.erase( sessionId ); +} + +void Sapphire::World::ServerMgr::removeSession( uint64_t contentId ) +{ + m_sessionMapByContentId.erase( contentId ); +} + void Sapphire::World::ServerMgr::removeSession( const std::string& playerName ) { m_sessionMapByName.erase( playerName ); diff --git a/src/world/ServerMgr.h b/src/world/ServerMgr.h index 1add030a..1de5b977 100644 --- a/src/world/ServerMgr.h +++ b/src/world/ServerMgr.h @@ -24,10 +24,8 @@ namespace Sapphire::World bool createSession( uint32_t sessionId ); - void removeSession( uint32_t sessionId ); - void removeSession( const std::string& playerName ); - World::SessionPtr getSession( uint32_t id ); + World::SessionPtr getSession( uint64_t contentId ); World::SessionPtr getSession( const std::string& playerName ); size_t getSessionCount() const; @@ -66,11 +64,15 @@ namespace Sapphire::World Sapphire::Common::Config::WorldConfig m_config; std::map< uint32_t, SessionPtr > m_sessionMapById; + std::map< uint64_t, SessionPtr > m_sessionMapByContentId; std::map< std::string, SessionPtr > m_sessionMapByName; std::map< uint32_t, std::string > m_playerNameMapById; std::map< uint32_t, uint32_t > m_zones; std::map< std::string, Entity::BNpcTemplatePtr > m_bNpcTemplateMap; + void removeSession( uint32_t sessionId ); + void removeSession( uint64_t contentId ); + void removeSession( const std::string& playerName ); }; } diff --git a/src/world/Session.cpp b/src/world/Session.cpp index b446f293..5a2562cc 100644 --- a/src/world/Session.cpp +++ b/src/world/Session.cpp @@ -8,6 +8,9 @@ #include "Network/GameConnection.h" #include "Actor/Player.h" +#include "Service.h" +#include "Manager/PartyMgr.h" + #include "Session.h" namespace fs = std::filesystem; @@ -73,6 +76,12 @@ void Sapphire::World::Session::close() if( m_pPlayer ) { m_pPlayer->clearBuyBackMap(); + if( m_pPlayer->getPartyId() != 0 ) + { + // offline player is removed from party for now; + auto& partyMgr = Common::Service< World::Manager::PartyMgr >::ref(); + partyMgr.onLeave( *m_pPlayer ); + } // do one last update to db m_pPlayer->updateSql(); // reset the zone, so the zone handler knows to remove the actor