From 67e17143c411d67d3f8c6c15bec8efe93978adb2 Mon Sep 17 00:00:00 2001 From: Mordred Date: Tue, 30 May 2023 22:42:20 +0900 Subject: [PATCH 1/8] PartyMgr, ChatChannelMgr ported from 3.x --- src/common/Common.h | 27 +- .../Network/PacketDef/Chat/ServerChatDef.h | 13 + src/common/Network/PacketDef/Ipcs.h | 24 +- .../Network/PacketDef/Zone/ClientZoneDef.h | 30 +- .../Network/PacketDef/Zone/ServerZoneDef.h | 87 ++-- src/world/Actor/Player.cpp | 82 +++- src/world/Actor/Player.h | 11 + src/world/Manager/ChatChannelMgr.cpp | 144 ++++++ src/world/Manager/ChatChannelMgr.h | 45 ++ src/world/Manager/PartyMgr.cpp | 436 ++++++++++++++++++ src/world/Manager/PartyMgr.h | 88 ++++ src/world/Network/GameConnection.cpp | 8 + src/world/Network/GameConnection.h | 10 + .../Network/Handlers/GMCommandHandlers.cpp | 7 - src/world/Network/Handlers/PacketHandlers.cpp | 91 ++-- src/world/Network/Handlers/PartyHandlers.cpp | 73 +++ .../PacketWrappers/ChannelChatPacket.h | 41 ++ .../Network/PacketWrappers/InviteHandlers.cpp | 127 +++++ .../PacketWrappers/PartyUpdatePacket.h | 83 ++++ src/world/ServerMgr.cpp | 35 +- src/world/ServerMgr.h | 8 +- src/world/Session.cpp | 9 + 22 files changed, 1343 insertions(+), 136 deletions(-) create mode 100644 src/world/Manager/ChatChannelMgr.cpp create mode 100644 src/world/Manager/ChatChannelMgr.h create mode 100644 src/world/Manager/PartyMgr.cpp create mode 100644 src/world/Manager/PartyMgr.h create mode 100644 src/world/Network/Handlers/PartyHandlers.cpp create mode 100644 src/world/Network/PacketWrappers/ChannelChatPacket.h create mode 100644 src/world/Network/PacketWrappers/InviteHandlers.cpp create mode 100644 src/world/Network/PacketWrappers/PartyUpdatePacket.h diff --git a/src/common/Common.h b/src/common/Common.h index e095a77d..f7806247 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -67,11 +67,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, @@ -803,6 +812,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... 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 0db60930..8c5d43c5 100644 --- a/src/common/Network/PacketDef/Ipcs.h +++ b/src/common/Network/PacketDef/Ipcs.h @@ -73,15 +73,14 @@ namespace Sapphire::Network::Packets SocialRequestError = 0xF0AD, CFRegistered = 0x029F, // updated 5.58 hotfix - SocialRequestResponse = 0x0082, // updated 5.58 hotfix - SocialMessage = 0x03CB, // updated 5.58 hotfix - SocialMessage2 = 0x01D7, // updated 5.58 hotfix + SocialInviteResponse = 0x0082, // updated 5.58 hotfix + SocialInviteUpdate = 0x03CB, // updated 5.58 hotfix + SocialInviteResult = 0x01D7, // updated 5.58 hotfix CancelAllianceForming = 0xF0C6, // updated 4.2 LogMessage = 0x0118, // updated 5.58 hotfix Chat = 0x00FE, // updated 5.58 hotfix - PartyChat = 0x0065, WorldVisitList = 0xF0FE, // added 4.5 @@ -149,7 +148,7 @@ namespace Sapphire::Network::Packets SomeCustomiseChangePacketProbably = 0x00CD, // added 5.18 PartyList = 0x0349, // updated 5.58 hotfix - PartyMessage = 0x00A4, // updated 5.58 hotfix + PartyUpdate = 0x00A4, // updated 5.58 hotfix HateRank = 0x0150, // updated 5.58 hotfix HateList = 0x0243, // updated 5.58 hotfix ObjectSpawn = 0x0125, // updated 5.58 hotfix @@ -333,16 +332,15 @@ namespace Sapphire::Network::Packets CancelLogout = 0x01AC, // updated 5.58 hotfix CFDutyInfoHandler = 0xF078, // updated 4.2 - SocialReqSendHandler = 0x00D7, // updated 5.58 hotfix - SocialResponseHandler = 0x023B, // updated 5.58 hotfix + SocialInviteHandler = 0x00D7, // updated 5.58 hotfix + SocialReplyHandler = 0x023B, // updated 5.58 hotfix CreateCrossWorldLS = 0x035D, // updated 5.58 hotfix ChatHandler = 0x03B0, // updated 5.58 hotfix - PartyChatHandler = 0x0065, - PartySetLeaderHandler = 0x036C, // updated 5.58 hotfix - LeavePartyHandler = 0x019D, // updated 5.58 hotfix - KickPartyMemberHandler = 0x0262, // updated 5.58 hotfix - DisbandPartyHandler = 0x0276, // updated 5.58 hotfix + PartyChangeLeaderHandler = 0x036C, // updated 5.58 hotfix + PartyLeaveHandler = 0x019D, // updated 5.58 hotfix + PartyKickHandler = 0x0262, // updated 5.58 hotfix + PartyDisbandHandler = 0x0276, // updated 5.58 hotfix SocialListHandler = 0x01CA, // updated 5.58 hotfix SetSearchInfoHandler = 0x01D4, // updated 5.58 hotfix @@ -433,6 +431,7 @@ namespace Sapphire::Network::Packets enum ServerChatIpcType : uint16_t { Tell = 0x0064, // updated for sb + ChannelChat = 0x0065, PublicContentTell = 0x00FB, // added 4.5, this is used when receiving a /tell in PublicContent instances such as Eureka or Bozja TellErrNotFound = 0x0066, @@ -445,6 +444,7 @@ namespace Sapphire::Network::Packets 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 7e33d643..c3ddf9bd 100644 --- a/src/common/Network/PacketDef/Zone/ServerZoneDef.h +++ b/src/common/Network/PacketDef/Zone/ServerZoneDef.h @@ -46,19 +46,6 @@ namespace Sapphire::Network::Packets::Server char msg[1012]; }; - struct FFXIVIpcPartyChat : FFXIVIpcBasePacket< PartyChat > - { - uint64_t unknown; - uint64_t contentId; - uint32_t charaId; - uint8_t u1; - uint8_t u2; - uint8_t u3; - char name[32]; - char message[1024]; - uint8_t padding; - }; - struct FFXIVIpcChatBanned : FFXIVIpcBasePacket< ChatBanned > { uint8_t padding[4]; // I was not sure reinterpreting ZST is valid behavior in C++. @@ -2178,7 +2165,7 @@ namespace Sapphire::Network::Packets::Server uint32_t param7; }; - struct FFXIVIpcSocialMessage : FFXIVIpcBasePacket< SocialMessage > + struct FFXIVIpcSocialInviteUpdate : FFXIVIpcBasePacket< SocialInviteUpdate > { uint64_t contentId; uint32_t expireTime; @@ -2187,12 +2174,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; @@ -2203,40 +2190,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; @@ -2245,16 +2234,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/Actor/Player.cpp b/src/world/Actor/Player.cpp index 4d8d2667..366fb022 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() ); @@ -1080,7 +1127,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 +1135,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 +1155,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 +1163,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 +1388,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(); @@ -2333,6 +2373,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 57a3ca8d..71c99741 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -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/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/PartyMgr.cpp b/src/world/Manager/PartyMgr.cpp new file mode 100644 index 00000000..04257392 --- /dev/null +++ b/src/world/Manager/PartyMgr.cpp @@ -0,0 +1,436 @@ +#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 ) + 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/Network/GameConnection.cpp b/src/world/Network/GameConnection.cpp index 25d65cf0..15504246 100644 --- a/src/world/Network/GameConnection.cpp +++ b/src/world/Network/GameConnection.cpp @@ -141,7 +141,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 576a7bb9..2a4c65b8 100644 --- a/src/world/Network/GameConnection.h +++ b/src/world/Network/GameConnection.h @@ -177,6 +177,8 @@ namespace Sapphire::Network DECLARE_HANDLER( tellHandler ); + DECLARE_HANDLER( channelChatHandler ); + DECLARE_HANDLER( reqPlaceHousingItem ); DECLARE_HANDLER( reqMoveHousingItem ); @@ -198,6 +200,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 From c1aea6298eeef46d4d3e45a7c67f1908d11e011a Mon Sep 17 00:00:00 2001 From: collett Date: Thu, 1 Jun 2023 02:05:39 +0900 Subject: [PATCH 2/8] fix event item action not executing without cast time --- src/world/Action/EventItemAction.cpp | 2 ++ 1 file changed, 2 insertions(+) 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(); } From e8ab350ad22d67d50e61a7b0d854cf770d37b522 Mon Sep 17 00:00:00 2001 From: collett Date: Wed, 19 Jul 2023 03:52:16 +0900 Subject: [PATCH 3/8] update WAR to 5.58 --- src/common/Common.h | 10 ++++ src/world/Action/Action.cpp | 77 ++++++++++++++++++++++++- src/world/Action/Action.h | 2 + src/world/Action/ActionLut.cpp | 75 +++++++++++++++++++----- src/world/Action/ActionLut.h | 9 +++ src/world/Action/ActionLutData.cpp | 27 +++++---- src/world/Action/EffectBuilder.cpp | 8 +-- src/world/Action/EffectBuilder.h | 4 +- src/world/Action/EffectResult.cpp | 15 ++--- src/world/Action/EffectResult.h | 5 +- src/world/StatusEffect/StatusEffect.cpp | 6 ++ src/world/StatusEffect/StatusEffect.h | 1 + 12 files changed, 195 insertions(+), 44 deletions(-) diff --git a/src/common/Common.h b/src/common/Common.h index 5fd6def3..837bb249 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -1195,6 +1195,16 @@ namespace Sapphire::Common RequireCorrectPositional = 2, }; + enum class StatusRefreshPolicy : uint8_t + { + Stack = 0, + ReplaceOrApply = 1, + Extend = 2, + ExtendOrApply = 3, + Reject = 4, + Custom = 15, // script handled + }; + enum class AstCardType : uint8_t { None = 0, diff --git a/src/world/Action/Action.cpp b/src/world/Action/Action.cpp index 898512ec..73cc9ad4 100644 --- a/src/world/Action/Action.cpp +++ b/src/world/Action/Action.cpp @@ -721,7 +721,7 @@ void Action::Action::buildEffects() if( shouldTriggerActionBonus || actor->getObjKind() == m_pSource->getObjKind() /* is friendly target, this will do for now */ ) { if( !isComboAction() || isCorrectCombo() ) - m_effectBuilder->applyStatusEffect( actor, m_pSource, m_lutEntry.targetStatus, m_lutEntry.targetStatusDuration, m_lutEntry.targetStatusParam, getExecutionDelay() + victimCounter * 100 ); + applyStatusEffect( false, actor, m_pSource, m_lutEntry.targetStatus, m_lutEntry.targetStatusDuration, m_lutEntry.targetStatusParam, getExecutionDelay() + victimCounter * 100 ); } else if( actor->hasInvulnerableEffect() ) { @@ -735,9 +735,9 @@ void Action::Action::buildEffects() if( !isComboAction() || isCorrectCombo() ) { if( firstValidVictim ) - m_effectBuilder->applyStatusEffect( firstValidVictim, m_pSource, m_lutEntry.selfStatus, m_lutEntry.selfStatusDuration, m_lutEntry.selfStatusParam, getExecutionDelay(), true ); + applyStatusEffect( true, firstValidVictim, m_pSource, m_lutEntry.selfStatus, m_lutEntry.selfStatusDuration, m_lutEntry.selfStatusParam, getExecutionDelay(), true ); else if ( m_lutEntry.damagePotency == 0 ) // only non-offensive actions can apply self status without a valid victim - m_effectBuilder->applyStatusEffect( m_pSource, m_pSource, m_lutEntry.selfStatus, m_lutEntry.selfStatusDuration, m_lutEntry.selfStatusParam, getExecutionDelay() ); + applyStatusEffect( true, m_pSource, m_pSource, m_lutEntry.selfStatus, m_lutEntry.selfStatusDuration, m_lutEntry.selfStatusParam, getExecutionDelay() ); } } @@ -1334,4 +1334,75 @@ uint64_t Action::Action::getExecutionDelay() const { // let's see how 3.x is going to do it return 600; +} + +void Sapphire::World::Action::Action::applyStatusEffect( bool selfStatus, Entity::CharaPtr& target, Entity::CharaPtr& source, uint16_t statusId, uint32_t duration, uint16_t param, uint64_t resultDelayMs, bool statusToSource ) +{ + auto hasSameStatus = false; + auto hasSameStatusFromSameCaster = false; + StatusEffect::StatusEffectPtr referenceStatus = nullptr; + for( auto const& entry : statusToSource ? source->getStatusEffectMap() : target->getStatusEffectMap() ) + { + auto statusEffect = entry.second; + if( statusEffect->getId() == statusId ) + { + hasSameStatus = true; + if( !referenceStatus ) + referenceStatus = statusEffect; + if( statusEffect->getSrcActorId() == source->getId() ) + { + hasSameStatusFromSameCaster = true; + referenceStatus = statusEffect; + break;; + } + } + } + auto policy = selfStatus ? m_lutEntry.getSelfStatusRefreshPolicy( hasSameStatusFromSameCaster ) : m_lutEntry.getTargetStatusRefreshPolicy( hasSameStatusFromSameCaster ); + int64_t policyValue = selfStatus ? m_lutEntry.getSelfStatusRefreshValue() : m_lutEntry.getTargetStatusRefreshValue(); + switch( policy ) + { + case Common::StatusRefreshPolicy::Stack: + { + m_effectBuilder->applyStatusEffect( target, source, statusId, duration, param, resultDelayMs, statusToSource, false ); + break; + } + case Common::StatusRefreshPolicy::ReplaceOrApply: + { + m_effectBuilder->applyStatusEffect( target, source, statusId, duration, param, resultDelayMs, statusToSource, true ); + break; + } + case Common::StatusRefreshPolicy::Extend: + case Common::StatusRefreshPolicy::ExtendOrApply: + { + int64_t remainingDuration = 0; + if( hasSameStatus ) + { + remainingDuration = static_cast< int64_t >( referenceStatus->getDuration() ) - ( Common::Util::getTimeMs() - referenceStatus->getStartTimeMs() ) - resultDelayMs; + if( remainingDuration < 0 ) + remainingDuration = 0; + } + if( hasSameStatus || policy == Common::StatusRefreshPolicy::ExtendOrApply ) + { + m_effectBuilder->applyStatusEffect( target, source, statusId, std::min( duration + remainingDuration, policyValue ), param, resultDelayMs, statusToSource, true ); + } + break; + } + case Common::StatusRefreshPolicy::Reject: + { + if( !hasSameStatus ) + { + m_effectBuilder->applyStatusEffect( target, source, statusId, duration, param, resultDelayMs, statusToSource, true ); + } + else + { + m_effectBuilder->statusNoEffect( target, statusId ); + } + break; + } + case Common::StatusRefreshPolicy::Custom: + { + // expect script to handle it + break; + } + } } \ No newline at end of file diff --git a/src/world/Action/Action.h b/src/world/Action/Action.h index 3826c8fc..87c597fc 100644 --- a/src/world/Action/Action.h +++ b/src/world/Action/Action.h @@ -176,6 +176,8 @@ namespace Sapphire::World::Action bool hasValidLutEntry() const; + void applyStatusEffect( bool selfStatus, Entity::CharaPtr& target, Entity::CharaPtr& source, uint16_t statusId, uint32_t duration, uint16_t param, uint64_t resultDelayMs = 500, bool statusToSource = false ); + uint32_t m_id; uint16_t m_sequence; diff --git a/src/world/Action/ActionLut.cpp b/src/world/Action/ActionLut.cpp index 0a533b78..c432d7a4 100644 --- a/src/world/Action/ActionLut.cpp +++ b/src/world/Action/ActionLut.cpp @@ -351,21 +351,46 @@ bool Sapphire::World::Action::StatusEffectEntry::canApplyToAction( uint32_t acti } } -Sapphire::World::Action::ActionEntry::ActionEntry( uint16_t dp, uint16_t dcp, uint16_t ddp, uint16_t hp, uint16_t ss, uint32_t ssd, uint16_t ssp, uint16_t ts, uint32_t tsd, uint16_t tsp, uint8_t be, uint8_t br, uint32_t bdu32 ) +Sapphire::World::Action::ActionEntry::ActionEntry( uint16_t dp, uint16_t dcp, uint16_t ddp, uint16_t hp, uint16_t ss, uint32_t ssd, uint16_t ssp, uint16_t ts, uint32_t tsd, uint16_t tsp, uint8_t be, uint8_t br, uint32_t bdu32 ) : + damagePotency( dp ), + damageComboPotency( dcp ), + damageDirectionalPotency( ddp ), + healPotency( hp ), + selfStatus( ss ), + selfStatusDuration( ssd ), + selfStatusParam( ssp ), + targetStatus( ts ), + targetStatusDuration( tsd ), + targetStatusParam ( tsp ), + bonusEffect( be ), + bonusRequirement( br ), + bonusDataUInt32 ( bdu32 ), + selfStatusRefreshPolicy( 1 ), + selfStatusRefreshValue( 0 ), + targetStatusRefreshPolicy( 1 ), + targetStatusRefreshValue( 0 ) +{ +} + +Sapphire::World::Action::ActionEntry::ActionEntry( uint16_t dp, uint16_t dcp, uint16_t ddp, uint16_t hp, uint16_t ss, uint32_t ssd, uint16_t ssp, uint16_t ts, uint32_t tsd, uint16_t tsp, uint8_t be, uint8_t br, uint32_t bdu32, int ssrp, int32_t ssrv, int tsrp, int32_t tsrv ) : + damagePotency( dp ), + damageComboPotency( dcp ), + damageDirectionalPotency( ddp ), + healPotency( hp ), + selfStatus( ss ), + selfStatusDuration( ssd ), + selfStatusParam( ssp ), + targetStatus( ts ), + targetStatusDuration( tsd ), + targetStatusParam ( tsp ), + bonusEffect( be ), + bonusRequirement( br ), + bonusDataUInt32 ( bdu32 ), + selfStatusRefreshPolicy( static_cast< uint8_t >( ssrp ) ), + selfStatusRefreshValue( ssrv ), + targetStatusRefreshPolicy( static_cast< uint8_t >( tsrp ) ), + targetStatusRefreshValue( tsrv ) { - damagePotency = dp; - damageComboPotency = dcp; - damageDirectionalPotency = ddp; - healPotency = hp; - selfStatus = ss; - selfStatusDuration = ssd; - selfStatusParam = ssp; - targetStatus = ts; - targetStatusDuration = tsd; - targetStatusParam = tsp; - bonusEffect = be; - bonusRequirement = br; - bonusDataUInt32 = bdu32; } uint32_t Sapphire::World::Action::ActionEntry::getRawBonusData() const @@ -429,3 +454,25 @@ uint16_t Sapphire::World::Action::ActionEntry::getDirectHitRateBonus() const return bonusDataUInt16L; return 0; } + +Sapphire::Common::StatusRefreshPolicy Sapphire::World::Action::ActionEntry::getSelfStatusRefreshPolicy( bool sameSource ) +{ + uint8_t policy = sameSource ? selfStatusRefreshPolicy >> 4 : selfStatusRefreshPolicy & 0x0F; + return static_cast< Sapphire::Common::StatusRefreshPolicy >( policy ); +} + +Sapphire::Common::StatusRefreshPolicy Sapphire::World::Action::ActionEntry::getTargetStatusRefreshPolicy( bool sameSource ) +{ + uint8_t policy = sameSource ? targetStatusRefreshPolicy >> 4 : targetStatusRefreshPolicy & 0x0F; + return static_cast< Sapphire::Common::StatusRefreshPolicy >( policy ); +} + +int32_t Sapphire::World::Action::ActionEntry::getSelfStatusRefreshValue() +{ + return selfStatusRefreshValue; +} + +int32_t Sapphire::World::Action::ActionEntry::getTargetStatusRefreshValue() +{ + return targetStatusRefreshValue; +} diff --git a/src/world/Action/ActionLut.h b/src/world/Action/ActionLut.h index a2784b68..48e49157 100644 --- a/src/world/Action/ActionLut.h +++ b/src/world/Action/ActionLut.h @@ -40,9 +40,14 @@ namespace Sapphire::World::Action uint8_t bonusDataByte4; }; }; + uint8_t selfStatusRefreshPolicy; + int32_t selfStatusRefreshValue; + uint8_t targetStatusRefreshPolicy; + int32_t targetStatusRefreshValue; public: ActionEntry() = default; ActionEntry( uint16_t dp, uint16_t dcp, uint16_t ddp, uint16_t hp, uint16_t ss, uint32_t ssd, uint16_t ssp, uint16_t ts, uint32_t tsd, uint16_t tsp, uint8_t be, uint8_t br, uint32_t bdu32 ); + ActionEntry( uint16_t dp, uint16_t dcp, uint16_t ddp, uint16_t hp, uint16_t ss, uint32_t ssd, uint16_t ssp, uint16_t ts, uint32_t tsd, uint16_t tsp, uint8_t be, uint8_t br, uint32_t bdu32, int ssrp, int32_t ssrv, int tsrp, int32_t tsrv ); uint32_t getRawBonusData() const; uint8_t getDamageFallOffPercentage() const; // as the result percentage for 2nd (or more) victims, not the percentage to subtract from 100% uint16_t getSelfHealPotency() const; @@ -52,6 +57,10 @@ namespace Sapphire::World::Action uint16_t getJobTimerGain() const; uint16_t getCritRateBonus() const; uint16_t getDirectHitRateBonus() const; + Common::StatusRefreshPolicy getSelfStatusRefreshPolicy( bool sameSource ); + Common::StatusRefreshPolicy getTargetStatusRefreshPolicy( bool sameSource ); + int32_t getSelfStatusRefreshValue(); + int32_t getTargetStatusRefreshValue(); }; struct StatusEffectEntry diff --git a/src/world/Action/ActionLutData.cpp b/src/world/Action/ActionLutData.cpp index f4ec9195..9381254f 100644 --- a/src/world/Action/ActionLutData.cpp +++ b/src/world/Action/ActionLutData.cpp @@ -135,14 +135,15 @@ ActionLut::Lut ActionLut::m_actionLut = { 31, { 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, //Maim, メイム - //has damage: potency 100, combo potency 300, directional potency 0 + //has damage: potency 100, combo potency 320, directional potency 0 //has bonus effect: GainJobResource, 169148416 //bonus effect requirement: RequireCorrectCombo - { 37, { 100, 300, 0, 0, 0, 0, 0, 0, 0, 0, 8, 1, 169148416 } }, + { 37, { 100, 320, 0, 0, 0, 0, 0, 0, 0, 0, 8, 1, 169148416 } }, //Berserk, バーサク //applies to self: Berserk, バーサク, duration 10000, param 0 - { 38, { 0, 0, 0, 0, 86, 10000, 0, 0, 0, 0, 0, 0, 0 } }, + //applies to target: Storm's Eye, シュトルムブレハ, duration 15000, param 0, Extend, Extend, 60000 + { 38, { 0, 0, 0, 0, 86, 10000, 0, 90, 15000, 0, 0, 0, 0, 0, 0, ( 2 << 4 ) + 2, 60000 } }, //Overpower, オーバーパワー //has damage: potency 130, combo potency 0, directional potency 0 @@ -158,10 +159,10 @@ ActionLut::Lut ActionLut::m_actionLut = { 46, { 140, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, //Storm's Path, シュトルムヴィント - //has damage: potency 100, combo potency 380, directional potency 0 + //has damage: potency 100, combo potency 420, directional potency 0 //has bonus effect: 24, 336920826 //bonus effect requirement: RequireCorrectCombo - { 42, { 100, 380, 0, 0, 0, 0, 0, 0, 0, 0, 24, 1, 336920826 } }, + { 42, { 100, 420, 0, 0, 0, 0, 0, 0, 0, 0, 24, 1, 336920826 } }, //Thrill of Battle, スリル・オブ・バトル //applies to targets: Thrill of Battle, スリル・オブ・バトル, duration 20000, param 0 @@ -179,11 +180,11 @@ ActionLut::Lut ActionLut::m_actionLut = { 43, { 0, 0, 0, 0, 409, 8000, 0, 88, 8000, 0, 0, 0, 0 } }, //Storm's Eye, シュトルムブレハ - //has damage: potency 100, combo potency 380, directional potency 0 - //applies to self: Storm's Eye, シュトルムブレハ, duration 30000, param 0 + //has damage: potency 100, combo potency 420, directional potency 0 + //applies to self: Storm's Eye, シュトルムブレハ, duration 30000, param 0, ExtendOrApply, ExtendOrApply, 60000 //has bonus effect: GainJobResource, 169148416 //bonus effect requirement: RequireCorrectCombo - { 45, { 100, 380, 0, 0, 90, 30000, 0, 0, 0, 0, 8, 1, 169148416 } }, + { 45, { 100, 420, 0, 0, 90, 30000, 0, 0, 0, 0, 8, 1, 169148416, ( 3 << 4 ) + 3, 60000, 0, 0 } }, //Inner Beast, 原初の魂 //has damage: potency 350, combo potency 0, directional potency 0 @@ -191,7 +192,8 @@ ActionLut::Lut ActionLut::m_actionLut = //Mythril Tempest, ミスリルテンペスト //has damage: potency 100, combo potency 200, directional potency 0 - { 16462, { 100, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, + //applies to self: Storm's Eye, シュトルムブレハ, duration 30000, param 0, Extend, Extend, 60000 + { 16462, { 100, 200, 0, 0, 90, 30000, 0, 0, 0, 0, 0, 0, 0, ( 2 << 4 ) + 2, 60000, 0, 0 } }, //Steel Cyclone, スチールサイクロン //has damage: potency 220, combo potency 0, directional potency 0 @@ -231,7 +233,8 @@ ActionLut::Lut ActionLut::m_actionLut = //Inner Release, 原初の解放 //applies to self: Inner Release, 原初の解放, duration 10000, param 65436 - { 7389, { 0, 0, 0, 0, 1177, 10000, 65436, 0, 0, 0, 0, 0, 0 } }, + //applies to target: Storm's Eye, シュトルムブレハ, duration 15000, param 0, Extend, Extend, 60000 + { 7389, { 0, 0, 0, 0, 1177, 10000, 65436, 90, 15000, 0, 0, 0, 0, 0, 0, ( 2 << 4 ) + 2, 60000 } }, //Chaotic Cyclone, カオティックサイクロン //has damage: potency 400, combo potency 0, directional potency 0 @@ -239,8 +242,8 @@ ActionLut::Lut ActionLut::m_actionLut = { 16463, { 400, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 100 } }, //Nascent Flash, 原初の猛り - //applies to targets: Sleep, 睡眠, duration 30000, param 0 - { 16464, { 0, 0, 0, 0, 0, 0, 0, 3, 30000, 0, 0, 0, 0 } }, + //applies to targets: Nascent Flash, 原初の猛り, duration 6000, param 0 + { 16464, { 0, 0, 0, 0, 0, 0, 0, 1857, 6000, 0, 0, 0, 0 } }, //Inner Chaos, インナーカオス //has damage: potency 920, combo potency 0, directional potency 0 diff --git a/src/world/Action/EffectBuilder.cpp b/src/world/Action/EffectBuilder.cpp index 8be1fd62..668b79b8 100644 --- a/src/world/Action/EffectBuilder.cpp +++ b/src/world/Action/EffectBuilder.cpp @@ -99,17 +99,17 @@ void EffectBuilder::comboSucceed( Entity::CharaPtr& target ) moveToResultList( target, nextResult ); } -void EffectBuilder::applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, uint16_t statusId, uint32_t duration, uint16_t param, uint64_t resultDelayMs, bool statusToSource ) +void EffectBuilder::applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, uint16_t statusId, uint32_t duration, uint16_t param, uint64_t resultDelayMs, bool statusToSource, bool shouldReuse ) { EffectResultPtr nextResult = make_EffectResult( target, source, Common::Util::getTimeMs() + resultDelayMs ); - nextResult->applyStatusEffect( statusId, duration, param, statusToSource ); + nextResult->applyStatusEffect( statusId, duration, param, statusToSource, shouldReuse ); moveToResultList( target, nextResult ); } -void EffectBuilder::applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, StatusEffect::StatusEffectPtr pStatusEffect, uint64_t resultDelayMs, bool statusToSource ) +void EffectBuilder::applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, StatusEffect::StatusEffectPtr pStatusEffect, uint64_t resultDelayMs, bool statusToSource, bool shouldReuse ) { EffectResultPtr nextResult = make_EffectResult( target, source, Common::Util::getTimeMs() + resultDelayMs ); - nextResult->applyStatusEffect( pStatusEffect, statusToSource ); + nextResult->applyStatusEffect( pStatusEffect, statusToSource, shouldReuse ); moveToResultList( target, nextResult ); } diff --git a/src/world/Action/EffectBuilder.h b/src/world/Action/EffectBuilder.h index b8fbaf16..f9fa6842 100644 --- a/src/world/Action/EffectBuilder.h +++ b/src/world/Action/EffectBuilder.h @@ -36,8 +36,8 @@ namespace Sapphire::World::Action void comboSucceed( Entity::CharaPtr& target ); - void applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, uint16_t statusId, uint32_t duration, uint16_t param, uint64_t resultDelayMs = 500, bool statusToSource = false ); - void applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, StatusEffect::StatusEffectPtr pStatusEffect, uint64_t resultDelayMs = 500, bool statusToSource = false ); + void applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, uint16_t statusId, uint32_t duration, uint16_t param, uint64_t resultDelayMs = 500, bool statusToSource = false, bool shouldReuse = true ); + void applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, StatusEffect::StatusEffectPtr pStatusEffect, uint64_t resultDelayMs = 500, bool statusToSource = false, bool shouldReuse = true ); void statusNoEffect( Entity::CharaPtr& target, uint16_t statusId ); diff --git a/src/world/Action/EffectResult.cpp b/src/world/Action/EffectResult.cpp index 2f4688d4..aee21580 100644 --- a/src/world/Action/EffectResult.cpp +++ b/src/world/Action/EffectResult.cpp @@ -20,7 +20,8 @@ EffectResult::EffectResult( Entity::CharaPtr target, Entity::CharaPtr source, ui m_param1( 0 ), m_param2( 0 ), m_flag( Common::ActionEffectResultFlag::None ), - m_pPreBuiltStatusEffect( nullptr ) + m_pPreBuiltStatusEffect( nullptr ), + m_statusShouldReuse( true ) { } @@ -123,23 +124,23 @@ void EffectResult::comboSucceed() m_type = Common::ActionEffectType::ComboSucceed; } -void EffectResult::applyStatusEffect( uint16_t statusId, uint32_t duration, uint16_t param, bool statusToSource ) +void EffectResult::applyStatusEffect( uint16_t statusId, uint32_t duration, uint16_t param, bool statusToSource, bool shouldReuse ) { m_value = statusId; m_statusDuration = duration; m_param2 = param; m_flag = statusToSource ? Common::ActionEffectResultFlag::EffectOnSource : Common::ActionEffectResultFlag::None; - + m_statusShouldReuse = shouldReuse; m_type = statusToSource ? Common::ActionEffectType::ApplyStatusEffectSource : Common::ActionEffectType::ApplyStatusEffectTarget; } -void EffectResult::applyStatusEffect( StatusEffect::StatusEffectPtr pStatusEffect, bool statusToSource ) +void EffectResult::applyStatusEffect( StatusEffect::StatusEffectPtr pStatusEffect, bool statusToSource, bool shouldReuse ) { m_value = pStatusEffect->getId(); m_param2 = pStatusEffect->getParam(); m_pPreBuiltStatusEffect = std::move( pStatusEffect ); m_flag = statusToSource ? Common::ActionEffectResultFlag::EffectOnSource : Common::ActionEffectResultFlag::None; - + m_statusShouldReuse = shouldReuse; m_type = statusToSource ? Common::ActionEffectType::ApplyStatusEffectSource : Common::ActionEffectType::ApplyStatusEffectTarget; } @@ -224,7 +225,7 @@ void EffectResult::execute() for( auto const& entry : applyTarget->getStatusEffectMap() ) { auto statusEffect = entry.second; - if( statusEffect->getId() == m_value && statusEffect->getSrcActorId() == m_source->getId() ) + if( statusEffect->getId() == m_value && m_statusShouldReuse ) { if( m_pPreBuiltStatusEffect ) { @@ -232,7 +233,7 @@ void EffectResult::execute() } else { - statusEffect->refresh(); + statusEffect->refresh( m_statusDuration ); } applyTarget->sendStatusEffectUpdate(); return; diff --git a/src/world/Action/EffectResult.h b/src/world/Action/EffectResult.h index 23862d39..7cda001b 100644 --- a/src/world/Action/EffectResult.h +++ b/src/world/Action/EffectResult.h @@ -26,8 +26,8 @@ namespace Sapphire::World::Action void restoreMP( uint32_t amount, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None ); void startCombo( uint16_t actionId ); void comboSucceed(); - void applyStatusEffect( uint16_t statusId, uint32_t duration, uint16_t param, bool statusToSource = false ); - void applyStatusEffect( StatusEffect::StatusEffectPtr pStatusEffect, bool statusToSource = false ); + void applyStatusEffect( uint16_t statusId, uint32_t duration, uint16_t param, bool statusToSource = false, bool shouldReuse = true ); + void applyStatusEffect( StatusEffect::StatusEffectPtr pStatusEffect, bool statusToSource = false, bool shouldReuse = true ); void statusNoEffect( uint16_t statusId ); void mount( uint16_t mountId ); void provoke(); @@ -61,6 +61,7 @@ namespace Sapphire::World::Action Common::ActionEffectResultFlag m_flag; StatusEffect::StatusEffectPtr m_pPreBuiltStatusEffect; + bool m_statusShouldReuse; }; } diff --git a/src/world/StatusEffect/StatusEffect.cpp b/src/world/StatusEffect/StatusEffect.cpp index be51a5ea..5bcb4878 100644 --- a/src/world/StatusEffect/StatusEffect.cpp +++ b/src/world/StatusEffect/StatusEffect.cpp @@ -341,6 +341,12 @@ void Sapphire::StatusEffect::StatusEffect::refresh() applyStatus(); } +void Sapphire::StatusEffect::StatusEffect::refresh( uint32_t newDuration ) +{ + m_duration = newDuration; + refresh(); +} + void Sapphire::StatusEffect::StatusEffect::refresh( Sapphire::World::Action::StatusEffectEntry newEntry ) { m_effectEntry = newEntry; diff --git a/src/world/StatusEffect/StatusEffect.h b/src/world/StatusEffect/StatusEffect.h index d0436374..100eca94 100644 --- a/src/world/StatusEffect/StatusEffect.h +++ b/src/world/StatusEffect/StatusEffect.h @@ -68,6 +68,7 @@ public: void markToRemove(); void refresh(); + void refresh( uint32_t newDuration ); void refresh( Sapphire::World::Action::StatusEffectEntry newEntry ); private: From adfd397bba474c034eb5f71afd767481bb055195 Mon Sep 17 00:00:00 2001 From: collett Date: Wed, 19 Jul 2023 04:09:55 +0900 Subject: [PATCH 4/8] fix player combat state --- src/world/Actor/BNpc.cpp | 2 +- src/world/Actor/Player.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/world/Actor/BNpc.cpp b/src/world/Actor/BNpc.cpp index f80d3253..ba981e6f 100644 --- a/src/world/Actor/BNpc.cpp +++ b/src/world/Actor/BNpc.cpp @@ -329,7 +329,7 @@ void Sapphire::Entity::BNpc::hateListAddOrUpdate( Sapphire::Entity::CharaPtr pCh if( pChara->isPlayer() ) { auto pPlayer = pChara->getAsPlayer(); - pPlayer->hateListAdd( getAsBNpc() ); + pPlayer->onMobAggro( getAsBNpc() ); } auto& scriptMgr = Common::Service< Sapphire::Scripting::ScriptMgr >::ref(); scriptMgr.onBNpcHateListAdd( *this, *pChara ); diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index 689a540f..030c5c06 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -1657,14 +1657,17 @@ void Sapphire::Entity::Player::sendHateList() void Sapphire::Entity::Player::onMobAggro( BNpcPtr pBNpc ) { hateListAdd( pBNpc ); - queuePacket( makeActorControl( getId(), ToggleAggro, 1 ) ); - setStateFlag( Common::PlayerStateFlag::InCombat ); + if( !hasStateFlag( Common::PlayerStateFlag::InCombat ) ) + { + queuePacket( makeActorControl( getId(), ToggleAggro, 1 ) ); + setStateFlag( Common::PlayerStateFlag::InCombat ); + } } void Sapphire::Entity::Player::onMobDeaggro( BNpcPtr pBNpc ) { hateListRemove( pBNpc ); - if( m_actorIdTohateSlotMap.empty() ) + if( m_actorIdTohateSlotMap.empty() && hasStateFlag( Common::PlayerStateFlag::InCombat ) ) { queuePacket( makeActorControl( getId(), ToggleAggro ) ); unsetStateFlag( Common::PlayerStateFlag::InCombat ); From 249194630cc5b5e4c11c272612245adc84b351c9 Mon Sep 17 00:00:00 2001 From: collett Date: Thu, 20 Jul 2023 15:30:55 +0900 Subject: [PATCH 5/8] fix mob movement, hopefully. --- src/world/Actor/BNpc.cpp | 59 +++++++++++---------------------- src/world/Actor/BNpc.h | 3 +- src/world/Actor/Player.cpp | 4 +-- src/world/Navi/NaviProvider.cpp | 14 ++++---- 4 files changed, 31 insertions(+), 49 deletions(-) diff --git a/src/world/Actor/BNpc.cpp b/src/world/Actor/BNpc.cpp index ba981e6f..cfb7ec7a 100644 --- a/src/world/Actor/BNpc.cpp +++ b/src/world/Actor/BNpc.cpp @@ -83,6 +83,7 @@ Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX m_pCurrentTerritory = std::move( pZone ); m_spawnPos = m_pos; + m_isMoving = false; m_timeOfDeath = 0; m_targetId = Common::INVALID_GAME_OBJECT_ID64; @@ -116,9 +117,7 @@ Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX m_radius *= modelSkeleton->radius; } - // todo: is this actually good? - //m_naviTargetReachedDistance = m_scale * 2.f; - m_naviTargetReachedDistance = 4.f; + m_naviTargetReachedDistance = m_radius; calculateStats(); auto& scriptMgr = Common::Service< Sapphire::Scripting::ScriptMgr >::ref(); @@ -194,7 +193,7 @@ void Sapphire::Entity::BNpc::setState( BNpcState state ) m_state = state; } -bool Sapphire::Entity::BNpc::moveTo( const FFXIVARR_POSITION3& pos ) +bool Sapphire::Entity::BNpc::moveTo( const FFXIVARR_POSITION3& pos, float radius ) { auto pNaviProvider = m_pCurrentTerritory->getNaviProvider(); @@ -208,14 +207,15 @@ bool Sapphire::Entity::BNpc::moveTo( const FFXIVARR_POSITION3& pos ) } auto pos1 = pNaviProvider->getMovePos( *this ); - - if( Util::distance( pos1, pos ) < getRadius() + 3.f ) + + if( Util::distance( pos1, pos ) < getNaviTargetReachedDistance() + radius ) { // Reached destination face( pos ); setPos( pos1 ); sendPositionUpdate(); pNaviProvider->updateAgentPosition( *this ); + m_isMoving = false; return true; } @@ -223,59 +223,38 @@ bool Sapphire::Entity::BNpc::moveTo( const FFXIVARR_POSITION3& pos ) face( pos ); setPos( pos1 ); sendPositionUpdate(); + m_isMoving = true; return false; } bool Sapphire::Entity::BNpc::moveTo( const Entity::Chara& targetChara ) { - - auto pNaviProvider = m_pCurrentTerritory->getNaviProvider(); - - if( !pNaviProvider ) - { - Logger::error( "No NaviProvider for zone#{0} - {1}", - m_pCurrentTerritory->getGuId(), - m_pCurrentTerritory->getInternalName() ); - return false; - } - - auto pos1 = pNaviProvider->getMovePos( *this ); - - if( Util::distance( pos1, targetChara.getPos() ) <= ( getRadius() + targetChara.getRadius() ) + 3.f ) - { - // Reached destination - face( targetChara.getPos() ); - setPos( pos1 ); - sendPositionUpdate(); - pNaviProvider->updateAgentPosition( *this ); - return true; - } - - m_pCurrentTerritory->updateActorPosition( *this ); - face( targetChara.getPos() ); - setPos( pos1 ); - sendPositionUpdate(); - return false; + return moveTo( targetChara.getPos(), targetChara.getRadius() ); } void Sapphire::Entity::BNpc::stopMoving() { auto pNaviProvider = m_pCurrentTerritory->getNaviProvider(); - if( !pNaviProvider ) + if( !pNaviProvider || !m_isMoving ) return; sendPositionUpdate(); pNaviProvider->updateAgentPosition( *this ); + m_isMoving = false; } void Sapphire::Entity::BNpc::sendPositionUpdate() { uint8_t unk1 = 0x3a; uint8_t animationType = 2; + uint16_t moveSpeed = MoveSpeed::Walk; if( m_state == BNpcState::Combat || m_state == BNpcState::Retreat ) + { animationType = 0; + moveSpeed = MoveSpeed::Run; + } - auto movePacket = std::make_shared< MoveActorPacket >( *getAsChara(), unk1, animationType, 0, 0x5A ); + auto movePacket = std::make_shared< MoveActorPacket >( *getAsChara(), unk1, animationType, 0, moveSpeed ); sendToInRangeSet( movePacket ); } @@ -496,6 +475,7 @@ void Sapphire::Entity::BNpc::doDefaultBNpcUpdate( uint64_t tickCount ) m_roamPos = pNaviProvider->findRandomPositionInCircle( m_spawnPos, 5 ); m_state = BNpcState::Roaming; + pNaviProvider->updateAgentParameters( *this ); } checkAggro(); @@ -527,9 +507,6 @@ void Sapphire::Entity::BNpc::doDefaultBNpcUpdate( uint64_t tickCount ) return; } - if( pNaviProvider->syncPosToChara( *this ) ) - sendPositionUpdate(); - auto distance = Util::distance( getPos().x, getPos().y, getPos().z, pHatedActor->getPos().x, pHatedActor->getPos().y, pHatedActor->getPos().z ); @@ -543,7 +520,7 @@ void Sapphire::Entity::BNpc::doDefaultBNpcUpdate( uint64_t tickCount ) break; } - if( distance > ( getRadius() + pHatedActor->getRadius() ) ) + if( distance > ( getNaviTargetReachedDistance() + pHatedActor->getRadius() ) ) { if( hasFlag( Immobile ) ) break; @@ -556,6 +533,8 @@ void Sapphire::Entity::BNpc::doDefaultBNpcUpdate( uint64_t tickCount ) if( distance < ( getRadius() + pHatedActor->getRadius() + 3.f ) ) { + stopMoving(); + if( !hasFlag( TurningDisabled ) && face( pHatedActor->getPos() ) ) sendPositionUpdate(); diff --git a/src/world/Actor/BNpc.h b/src/world/Actor/BNpc.h index 7e693a1c..20c2e5b4 100644 --- a/src/world/Actor/BNpc.h +++ b/src/world/Actor/BNpc.h @@ -75,7 +75,7 @@ namespace Sapphire::Entity float getNaviTargetReachedDistance() const; // return true if it reached the position - bool moveTo( const Common::FFXIVARR_POSITION3& pos ); + bool moveTo( const Common::FFXIVARR_POSITION3& pos, float radius = 0 ); bool moveTo( const Entity::Chara& targetChara ); @@ -149,6 +149,7 @@ namespace Sapphire::Entity Common::FFXIVARR_POSITION3 m_spawnPos; Common::FFXIVARR_POSITION3 m_roamPos; + bool m_isMoving; BNpcState m_state; std::set< std::shared_ptr< HateListEntry > > m_hateList; diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index 030c5c06..7aa9ff58 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -1244,13 +1244,13 @@ void Sapphire::Entity::Player::update( uint64_t tickCount ) auto chara = actor->getAsChara(); // default autoattack range - float range = 3.f + chara->getRadius(); + float range = 3.f + chara->getRadius() + getRadius() * 0.5f; // default autoattack range for ranged classes if( getClass() == ClassJob::Machinist || getClass() == ClassJob::Bard || getClass() == ClassJob::Archer ) - range = 25; + range = 25.f + chara->getRadius() + getRadius() * 0.5f; if( Util::distance( getPos().x, getPos().y, getPos().z, diff --git a/src/world/Navi/NaviProvider.cpp b/src/world/Navi/NaviProvider.cpp index e118734b..84922b01 100644 --- a/src/world/Navi/NaviProvider.cpp +++ b/src/world/Navi/NaviProvider.cpp @@ -24,9 +24,9 @@ Sapphire::World::Navi::NaviProvider::NaviProvider( const std::string& internalNa m_internalName( internalName ) { // Set defaults - m_polyFindRange[ 0 ] = 10; + m_polyFindRange[ 0 ] = 20; m_polyFindRange[ 1 ] = 20; - m_polyFindRange[ 2 ] = 10; + m_polyFindRange[ 2 ] = 20; } bool Sapphire::World::Navi::NaviProvider::init() @@ -355,7 +355,7 @@ std::vector< Sapphire::Common::FFXIVARR_POSITION3 > // iterPos[ 0 ], iterPos[ 1 ], iterPos[ 2 ], // targetPos[ 0 ], targetPos[ 1 ], targetPos[ 2 ] ); - const float STEP_SIZE = 1.2f; + const float STEP_SIZE = 0.5f; const float SLOP = 0.15f; int32_t numSmoothPath = 0; @@ -573,7 +573,8 @@ int32_t Sapphire::World::Navi::NaviProvider::addAgent( Entity::Chara& chara ) std::memset( ¶ms, 0, sizeof( params ) ); params.height = 3.f; params.maxAcceleration = 25.f; - params.maxSpeed = std::pow( 2, chara.getRadius() * 0.35f ) + 1.f; + //params.maxSpeed = std::pow( 2, chara.getRadius() * 0.35f ) + 1.f; + params.maxSpeed = ( std::pow( 2.f, 1.f * 0.35f ) + 1.f ) * 0.5f; params.radius = ( chara.getRadius() ) * 0.75f; params.collisionQueryRange = params.radius * 12.0f; params.pathOptimizationRange = params.radius * 20.0f; @@ -589,9 +590,10 @@ void Sapphire::World::Navi::NaviProvider::updateAgentParameters( Entity::BNpc& b std::memset( ¶ms, 0, sizeof( params ) ); params.height = 3.f; params.maxAcceleration = 25.f; - params.maxSpeed = std::pow( 2, bnpc.getRadius() * 0.35f ) + 1.f; + //params.maxSpeed = std::pow( 2, bnpc.getRadius() * 0.35f ) + 1.f; + params.maxSpeed = ( std::pow( 2.f, 1.f * 0.35f ) + 1.f ) * 0.5f; if( bnpc.getState() == Entity::BNpcState::Combat || bnpc.getState() == Entity::BNpcState::Retreat ) - params.maxSpeed *= 2; + params.maxSpeed *= 2.5f; params.radius = ( bnpc.getRadius() ) * 0.75f; params.collisionQueryRange = params.radius * 12.0f; params.pathOptimizationRange = params.radius * 20.0f; From 0a4bd0dee48f9abfa3dc16c40bc4952631cece4e Mon Sep 17 00:00:00 2001 From: collett Date: Mon, 24 Jul 2023 07:33:09 +0900 Subject: [PATCH 6/8] stop ticking status if dead --- src/world/Actor/Chara.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/world/Actor/Chara.cpp b/src/world/Actor/Chara.cpp index 45e27b04..a23410f9 100644 --- a/src/world/Actor/Chara.cpp +++ b/src/world/Actor/Chara.cpp @@ -355,13 +355,16 @@ bool Sapphire::Entity::Chara::checkAction() void Sapphire::Entity::Chara::update( uint64_t tickCount ) { - updateStatusEffects(); - - if( std::difftime( static_cast< time_t >( tickCount ), m_lastTickTime ) > 3000 ) + if( isAlive() ) { - onTick(); + updateStatusEffects(); - m_lastTickTime = static_cast< time_t >( tickCount ); + if( std::difftime( static_cast< time_t >( tickCount ), m_lastTickTime ) > 3000 ) + { + onTick(); + + m_lastTickTime = static_cast< time_t >( tickCount ); + } } m_lastUpdate = static_cast< time_t >( tickCount ); From ddbb6b8403cd66e5473dde11e3fc48cb45508b88 Mon Sep 17 00:00:00 2001 From: collett Date: Mon, 24 Jul 2023 08:25:13 +0900 Subject: [PATCH 7/8] fix WHM Divine Benison, Temperance, Assize --- .../action/whm/ActionDivineBenison7432.cpp | 46 ++++ src/world/Action/Action.cpp | 222 +++++++++--------- src/world/Action/ActionLutData.cpp | 11 +- 3 files changed, 164 insertions(+), 115 deletions(-) create mode 100644 src/scripts/action/whm/ActionDivineBenison7432.cpp diff --git a/src/scripts/action/whm/ActionDivineBenison7432.cpp b/src/scripts/action/whm/ActionDivineBenison7432.cpp new file mode 100644 index 00000000..1392ed6f --- /dev/null +++ b/src/scripts/action/whm/ActionDivineBenison7432.cpp @@ -0,0 +1,46 @@ +#include