1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-27 06:47:45 +00:00

Merge remote-tracking branch 'remotes/collett/Sapphire5.58' into retail

This commit is contained in:
collett 2024-06-12 05:35:17 +09:00
commit 46295cc7dd
28 changed files with 1452 additions and 184 deletions

View file

@ -68,11 +68,20 @@ namespace Sapphire::Common
French = 8 French = 8
}; };
enum TellFlags : uint8_t enum ChatFromType : uint8_t
{ {
GmTellMsg = 0x4, GmTellMsg = 0x4,
}; };
enum ChatChannelType : uint16_t
{
CWLinkshellChat = 0x0,
PartyChat = 0x1,
LinkshellChat = 0x2,
FreeCompanyChat = 0x3,
NoviceNetworkChat = 0x4
};
enum BNpcType : uint8_t enum BNpcType : uint8_t
{ {
Friendly = 0, Friendly = 0,
@ -804,6 +813,22 @@ namespace Sapphire::Common
InvincibilityIgnoreDamage, 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 enum PlayerStateFlag : uint8_t
{ {
HideUILockChar = 0, // as the name suggests, hides the ui and logs the char... HideUILockChar = 0, // as the name suggests, hides the ui and logs the char...
@ -1340,6 +1365,12 @@ namespace Sapphire::Common
GetGil = 9, // p1: gil GetGil = 9, // p1: gil
EmptyCoffer = 11, // seems like no param EmptyCoffer = 11, // seems like no param
}; };
enum ItemFlag
{
FlagNone = 0,
FlagHq = 1,
};
} }
#endif #endif

View file

@ -241,7 +241,7 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements()
CONNECTION_BOTH ); CONNECTION_BOTH );
prepareStatement( CHARA_ITEMGLOBAL_UP, prepareStatement( CHARA_ITEMGLOBAL_UP,
"UPDATE charaglobalitem SET stack = ?, durability = ?, stain = ? WHERE ItemId = ?;", "UPDATE charaglobalitem SET stack = ?, durability = ?, flags = ?, reservedFlag = ?, stain = ? WHERE ItemId = ?;",
CONNECTION_BOTH ); CONNECTION_BOTH );
prepareStatement( CHARA_ITEMGLOBAL_DELETE, prepareStatement( CHARA_ITEMGLOBAL_DELETE,

View file

@ -20,6 +20,19 @@ struct FFXIVIpcTell : FFXIVIpcBasePacket< Tell >
char msg[1029]; 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 * Structural representation of the packet sent by the server as response
* to a failed tell because of unavailable target player * to a failed tell because of unavailable target player

View file

@ -81,15 +81,14 @@ enum ServerZoneIpcType :
SocialRequestError = 0xF0AD, SocialRequestError = 0xF0AD,
CFRegistered = 0x029F, // updated 5.58h CFRegistered = 0x029F, // updated 5.58h
SocialRequestResponse = 0x373, // updated 6.48 SocialInviteResponse = 0x373, // updated 6.48
SocialMessage = 0x03CB, // updated 5.58h SocialInviteUpdate = 0x03CB, // updated 5.58h
SocialMessage2 = 0x01D7, // updated 5.58h SocialInviteResult = 0x01D7, // updated 5.58h
CancelAllianceForming = 0xF0C6, // updated 4.2 CancelAllianceForming = 0xF0C6, // updated 4.2
LogMessage = 0x316, // updated 6.48 LogMessage = 0x316, // updated 6.48
Chat = 0x0325, // updated 6.58 hotfix 2 Chat = 0x0325, // updated 6.58 hotfix 2
PartyChat = 0x0065,
WorldVisitList = 0xF0FE, // added 4.5 WorldVisitList = 0xF0FE, // added 4.5
@ -164,7 +163,7 @@ enum ServerZoneIpcType :
SomeCustomiseChangePacketProbably = 0x00CD, // added 5.18 SomeCustomiseChangePacketProbably = 0x00CD, // added 5.18
PartyList = 0x16f, // updated 6.48 PartyList = 0x16f, // updated 6.48
PartyMessage = 0x336, // updated 6.48 PartyUpdate = 0x336, // updated 6.48
HateRank = 0x2A7, // updated 6.58 hotfix 2 HateRank = 0x2A7, // updated 6.58 hotfix 2
HateList = 0x26B, // updated 6.58 hotfix 2 HateList = 0x26B, // updated 6.58 hotfix 2
ObjectSpawn = 0x03B8, // updated 6.58 hotfix 2 ObjectSpawn = 0x03B8, // updated 6.58 hotfix 2
@ -352,15 +351,15 @@ enum ClientZoneIpcType :
CancelLogout = 0x01e3, // updated 6.31h CancelLogout = 0x01e3, // updated 6.31h
CFDutyInfoHandler = 0xF078, // updated 4.2 CFDutyInfoHandler = 0xF078, // updated 4.2
SocialReqSendHandler = 0x00D7, // updated 5.58h SocialInviteHandler = 0x00D7, // updated 5.58h
SocialResponseHandler = 0x023B, // updated 5.58h SocialReplyHandler = 0x023B, // updated 5.58h
CreateCrossWorldLS = 0x9999, // updated 5.58h CreateCrossWorldLS = 0x9999, // updated 5.58h
ChatHandler = 0x246, // updated 6.58 hotfix 2 ChatHandler = 0x246, // updated 6.58 hotfix 2
PartyChatHandler = 0x0065, PartySetLeaderHandler = 0x036C, // updated 5.58h PartyChangeLeaderHandler = 0x036C, // updated 5.58h
LeavePartyHandler = 0x019D, // updated 5.58h PartyLeaveHandler = 0x019D, // updated 5.58h
KickPartyMemberHandler = 0x0262, // updated 5.58h PartyKickHandler = 0x0262, // updated 5.58h
DisbandPartyHandler = 0x0276, // updated 5.58h PartyDisbandHandler = 0x0276, // updated 5.58h
SocialListHandler = 0x10B, // updated 6.58 hotfix 2 SocialListHandler = 0x10B, // updated 6.58 hotfix 2
SetSearchInfoHandler = 0x01A0, // updated 6.58 hotfix 2 SetSearchInfoHandler = 0x01A0, // updated 6.58 hotfix 2
@ -451,6 +450,7 @@ enum ServerChatIpcType :
uint16_t uint16_t
{ {
Tell = 0x0064, // updated for sb Tell = 0x0064, // updated for sb
ChannelChat = 0x0065,
PublicContentTell = 0xF0FB, // added 4.5, this is used when receiving a /tell in PublicContent instances such as Eureka or Bozja (prepended F conflicts with TradeReturnEventHandler 6.38) PublicContentTell = 0xF0FB, // added 4.5, this is used when receiving a /tell in PublicContent instances such as Eureka or Bozja (prepended F conflicts with TradeReturnEventHandler 6.38)
TellErrNotFound = 0x0066, TellErrNotFound = 0x0066,
@ -464,6 +464,7 @@ enum ClientChatIpcType :
uint16_t uint16_t
{ {
TellReq = 0x0064, 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 PublicContentTellReq = 0x0326, // updated 5.35 hotfix, this is used when sending a /tell in PublicContent instances such as Eureka or Bozja
}; };

View file

@ -207,10 +207,10 @@ struct FFXIVIpcChatHandler :
/* 001A */ char message[1012]; /* 001A */ char message[1012];
}; };
struct FFXIVIpcPartyChatHandler : struct FFXIVIpcChannelChatHandler :
FFXIVIpcBasePacket< ChatHandler > FFXIVIpcBasePacket< ChannelChatReq >
{ {
uint64_t unknown; uint64_t channelId;
char message[1024]; char message[1024];
}; };
@ -362,8 +362,8 @@ struct FFXIVIpcWorldInteractionHandler :
Common::FFXIVARR_POSITION3 position; Common::FFXIVARR_POSITION3 position;
}; };
struct FFXIVIpcSocialReqSendHandler : struct FFXIVIpcSocialInviteHandler :
FFXIVIpcBasePacket< SocialReqSendHandler > FFXIVIpcBasePacket< SocialInviteHandler >
{ {
uint64_t unknown; uint64_t unknown;
uint8_t p1; uint8_t p1;
@ -373,8 +373,8 @@ struct FFXIVIpcSocialReqSendHandler :
uint8_t padding[5]; uint8_t padding[5];
}; };
struct FFXIVIpcSocialResponseHandler : struct FFXIVIpcSocialReplyHandler :
FFXIVIpcBasePacket< SocialResponseHandler > FFXIVIpcBasePacket< SocialReplyHandler >
{ {
uint64_t contentId; uint64_t contentId;
uint8_t p1; uint8_t p1;
@ -384,8 +384,8 @@ struct FFXIVIpcSocialResponseHandler :
uint32_t unknown; uint32_t unknown;
}; };
struct FFXIVIpcPartySetLeaderHandler : struct FFXIVIpcPartyChangeLeaderHandler :
FFXIVIpcBasePacket< PartySetLeaderHandler > FFXIVIpcBasePacket< PartyChangeLeaderHandler >
{ {
uint64_t contentId; uint64_t contentId;
uint8_t p1; uint8_t p1;
@ -394,14 +394,14 @@ struct FFXIVIpcPartySetLeaderHandler :
uint8_t padding[6]; uint8_t padding[6];
}; };
struct FFXIVIpcLeavePartyHandler : struct FFXIVIpcPartyLeaveHandler :
FFXIVIpcBasePacket< LeavePartyHandler > FFXIVIpcBasePacket< PartyLeaveHandler >
{ {
uint64_t empty; uint64_t empty;
}; };
struct FFXIVIpcKickPartyMemberHander : struct FFXIVIpcPartyKickHandler :
FFXIVIpcBasePacket< KickPartyMemberHandler > FFXIVIpcBasePacket< PartyKickHandler >
{ {
uint64_t contentId; uint64_t contentId;
uint8_t p1; uint8_t p1;
@ -410,8 +410,8 @@ struct FFXIVIpcKickPartyMemberHander :
uint8_t padding[6]; uint8_t padding[6];
}; };
struct FFXIVIpcDisbandPartyHandler : struct FFXIVIpcPartyDisbandHandler :
FFXIVIpcBasePacket< DisbandPartyHandler > FFXIVIpcBasePacket< PartyDisbandHandler >
{ {
uint64_t empty; uint64_t empty;
}; };

View file

@ -47,19 +47,6 @@ namespace Sapphire::Network::Packets::Server
uint8_t unknown[12]; //possibly padding? uint8_t unknown[12]; //possibly padding?
}; };
struct FFXIVIpcPartyChat : FFXIVIpcBasePacket< PartyChat >
{
uint64_t unknown;
uint64_t contentId;
uint32_t charaId;
uint8_t u1;
uint8_t u2;
uint8_t u3;
char name[32];
char message[1024];
uint8_t padding;
};
struct FFXIVIpcChatBanned : FFXIVIpcBasePacket< ChatBanned > struct FFXIVIpcChatBanned : FFXIVIpcBasePacket< ChatBanned >
{ {
uint8_t padding[4]; // I was not sure reinterpreting ZST is valid behavior in C++. uint8_t padding[4]; // I was not sure reinterpreting ZST is valid behavior in C++.
@ -2183,7 +2170,7 @@ namespace Sapphire::Network::Packets::Server
uint32_t param7; uint32_t param7;
}; };
struct FFXIVIpcSocialMessage : FFXIVIpcBasePacket< SocialMessage > struct FFXIVIpcSocialInviteUpdate : FFXIVIpcBasePacket< SocialInviteUpdate >
{ {
uint64_t contentId; uint64_t contentId;
uint32_t expireTime; uint32_t expireTime;
@ -2192,12 +2179,12 @@ namespace Sapphire::Network::Packets::Server
uint8_t socialType; uint8_t socialType;
uint8_t padding; uint8_t padding;
uint8_t type; uint8_t type;
uint8_t unknown4; uint8_t gender;
char name[32]; char name[32];
uint8_t padding2[6]; uint8_t padding2[6];
}; };
struct FFXIVIpcSocialMessage2 : FFXIVIpcBasePacket< SocialMessage2 > struct FFXIVIpcSocialInviteResult : FFXIVIpcBasePacket< SocialInviteResult >
{ {
uint64_t contentId; uint64_t contentId;
uint32_t unknown3; uint32_t unknown3;
@ -2208,40 +2195,42 @@ namespace Sapphire::Network::Packets::Server
char name[32]; char name[32];
}; };
struct FFXIVIpcSocialRequestResponse : FFXIVIpcBasePacket< SocialRequestResponse > struct FFXIVIpcSocialInviteResponse : FFXIVIpcBasePacket< SocialInviteResponse >
{ {
uint64_t contentId; uint64_t contentId;
uint32_t unknown3; uint32_t unknown3;
uint8_t u1AlwaysOne; uint8_t socialType;
uint8_t response; uint8_t response;
uint8_t u2AlwaysOne; uint8_t gender;
char name[32]; char name[32];
uint8_t padding; 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 FFXIVIpcPartyList : FFXIVIpcBasePacket< PartyList >
{ {
struct PartyMember member[8];
{
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];
uint64_t partyId; uint64_t partyId;
uint64_t channelId; uint64_t channelId;
uint8_t leaderIndex; uint8_t leaderIndex;
@ -2250,16 +2239,16 @@ namespace Sapphire::Network::Packets::Server
uint32_t padding2; uint32_t padding2;
}; };
struct FFXIVIpcPartyMessage : FFXIVIpcBasePacket< PartyMessage > struct FFXIVIpcPartyUpdate : FFXIVIpcBasePacket< PartyUpdate >
{ {
uint64_t leaderContentId; uint64_t executeContentId;
uint64_t memberContentId; uint64_t targetContentId;
uint8_t u1; uint8_t executeGender;
uint8_t u2; uint8_t targetGender;
uint16_t type; uint16_t updateStatus;
uint8_t partySize; // ? uint8_t partySize;
char leaderName[32]; char executeName[32];
char memberName[32]; char targetName[32];
uint8_t padding[3]; uint8_t padding[3];
}; };

View file

@ -67,4 +67,6 @@ void EventItemAction::execute()
void EventItemAction::start() void EventItemAction::start()
{ {
m_startTime = Common::Util::getTimeMs(); m_startTime = Common::Util::getTimeMs();
if( !hasCastTime() )
execute();
} }

View file

@ -85,6 +85,7 @@ Sapphire::Entity::Player::Player() :
m_onEnterEventDone( false ), m_onEnterEventDone( false ),
m_falling( false ), m_falling( false ),
m_pQueuedAction( nullptr ), m_pQueuedAction( nullptr ),
m_partyId( 0 ),
m_cfNotifiedContent( 0 ) m_cfNotifiedContent( 0 )
{ {
m_id = 0; m_id = 0;
@ -134,7 +135,8 @@ uint32_t Sapphire::Entity::Player::getMaxHp()
uint32_t Sapphire::Entity::Player::getMaxMp() 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 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 ) void Sapphire::Entity::Player::setOnlineStatusMask( uint64_t status )
{ {
m_onlineStatus = 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 uint64_t Sapphire::Entity::Player::getOnlineStatusMask() const
@ -243,6 +249,47 @@ uint64_t Sapphire::Entity::Player::getOnlineStatusMask() const
return m_onlineStatus; 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 ) 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() ); auto preparePacket = makeZonePacket< FFXIVIpcPrepareZoning >( getId() );
@ -513,14 +560,15 @@ bool Sapphire::Entity::Player::exitInstance()
{ {
auto& teriMgr = Common::Service< TerritoryMgr >::ref(); auto& teriMgr = Common::Service< TerritoryMgr >::ref();
auto d = getCurrentTerritory()->getAsDirector(); auto d = getCurrentTerritory()->getAsInstanceContent();
if( d && d->getContentFinderConditionId() > 0 ) if( d && d->getContentFinderConditionId() > 0 )
{ {
// shows correct name when leaving dungeon
auto p = makeZonePacket< FFXIVDirectorUnk4 >( getId() ); auto p = makeZonePacket< FFXIVDirectorUnk4 >( getId() );
p->data().param[0] = d->getDirectorId(); p->data().param[0] = d->getDirectorId();
p->data().param[1] = 1534; p->data().param[1] = 1534;
p->data().param[2] = 1; p->data().param[2] = 1;
p->data().param[3] = d->getContentFinderConditionId(); p->data().param[3] = d->getContentId();
queuePacket( p ); queuePacket( p );
prepareZoning( 0, 1, 1, 0, 0, 1, 9 ); prepareZoning( 0, 1, 1, 0, 0, 1, 9 );
@ -1080,7 +1128,6 @@ bool Sapphire::Entity::Player::hasStateFlag( Common::PlayerStateFlag flag ) cons
void Sapphire::Entity::Player::setStateFlag( Common::PlayerStateFlag flag ) void Sapphire::Entity::Player::setStateFlag( Common::PlayerStateFlag flag )
{ {
auto prevOnlineStatus = getOnlineStatus();
int32_t iFlag = static_cast< uint32_t >( flag ); int32_t iFlag = static_cast< uint32_t >( flag );
uint16_t index; uint16_t index;
@ -1089,13 +1136,6 @@ void Sapphire::Entity::Player::setStateFlag( Common::PlayerStateFlag flag )
m_stateFlags[ index ] |= value; m_stateFlags[ index ] |= value;
sendStateFlags(); 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 ) void Sapphire::Entity::Player::setStateFlags( std::vector< Common::PlayerStateFlag > flags )
@ -1116,8 +1156,6 @@ void Sapphire::Entity::Player::unsetStateFlag( Common::PlayerStateFlag flag )
if( !hasStateFlag( flag ) ) if( !hasStateFlag( flag ) )
return; return;
auto prevOnlineStatus = getOnlineStatus();
int32_t iFlag = static_cast< uint32_t >( flag ); int32_t iFlag = static_cast< uint32_t >( flag );
uint16_t index; uint16_t index;
@ -1126,11 +1164,6 @@ void Sapphire::Entity::Player::unsetStateFlag( Common::PlayerStateFlag flag )
m_stateFlags[ index ] ^= value; m_stateFlags[ index ] ^= value;
sendStateFlags(); 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 ) void Sapphire::Entity::Player::update( uint64_t tickCount )
@ -1356,6 +1389,14 @@ void Sapphire::Entity::Player::queuePacket( Network::Packets::FFXIVPacketBasePtr
} }
void Sapphire::Entity::Player::queuePacket( std::vector< Network::Packets::FFXIVPacketBasePtr > packets )
{
for( auto& packet : packets )
{
queuePacket( packet );
}
}
void Sapphire::Entity::Player::queueChatPacket( Network::Packets::FFXIVPacketBasePtr pPacket ) void Sapphire::Entity::Player::queueChatPacket( Network::Packets::FFXIVPacketBasePtr pPacket )
{ {
auto& serverMgr = Common::Service< World::ServerMgr >::ref(); auto& serverMgr = Common::Service< World::ServerMgr >::ref();
@ -2334,6 +2375,16 @@ bool Sapphire::Entity::Player::checkAction()
return true; 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 ) std::vector< Sapphire::Entity::ShopBuyBackEntry >& Sapphire::Entity::Player::getBuyBackListForShop( uint32_t shopId )
{ {
return m_shopBuyBackMap[ shopId ]; return m_shopBuyBackMap[ shopId ];

View file

@ -340,7 +340,7 @@ namespace Sapphire::Entity
void equipSoulCrystal( ItemPtr pItem, bool updateClass ); void equipSoulCrystal( ItemPtr pItem, bool updateClass );
/*! unequip a soul crystal, returning to the base class*/ /*! unequip a soul crystal, returning to the base class*/
void unequipSoulCrystal( ItemPtr pItem ); void unequipSoulCrystal();
/*! get player ilvl */ /*! get player ilvl */
uint16_t getItemLevel() const; uint16_t getItemLevel() const;
@ -543,6 +543,11 @@ namespace Sapphire::Entity
/*! returns the current online status */ /*! returns the current online status */
uint64_t getOnlineStatusMask() const; 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 ) */ /*! perform a teleport of a specified type ( teleport,return,aethernet ) */
void teleport( uint16_t aetheryteId, uint8_t type = 1 ); void teleport( uint16_t aetheryteId, uint8_t type = 1 );
@ -769,6 +774,7 @@ namespace Sapphire::Entity
/*! queue a packet for the player */ /*! queue a packet for the player */
void queuePacket( Network::Packets::FFXIVPacketBasePtr pPacket ); void queuePacket( Network::Packets::FFXIVPacketBasePtr pPacket );
void queuePacket( std::vector< Network::Packets::FFXIVPacketBasePtr > packets );
/*! queue a char connection packet for the player */ /*! queue a char connection packet for the player */
void queueChatPacket( Network::Packets::FFXIVPacketBasePtr pPacket ); void queueChatPacket( Network::Packets::FFXIVPacketBasePtr pPacket );
@ -1028,6 +1034,9 @@ namespace Sapphire::Entity
void updateHuntingLog( uint16_t id ); void updateHuntingLog( uint16_t id );
uint64_t getPartyId() const;
void setPartyId( uint64_t partyId );
World::SessionPtr getSession(); World::SessionPtr getSession();
uint64_t m_lastMoveTime; uint64_t m_lastMoveTime;
@ -1181,6 +1190,8 @@ namespace Sapphire::Entity
Common::Util::SpawnIndexAllocator< uint8_t > m_objSpawnIndexAllocator; Common::Util::SpawnIndexAllocator< uint8_t > m_objSpawnIndexAllocator;
Common::Util::SpawnIndexAllocator< uint8_t > m_actorSpawnIndexAllocator; Common::Util::SpawnIndexAllocator< uint8_t > m_actorSpawnIndexAllocator;
uint64_t m_partyId;
std::array< Common::HuntingLogEntry, 12 > m_huntingLogEntries; std::array< Common::HuntingLogEntry, 12 > m_huntingLogEntries;
std::unordered_map< uint32_t, std::vector< ShopBuyBackEntry > > m_shopBuyBackMap; std::unordered_map< uint32_t, std::vector< ShopBuyBackEntry > > m_shopBuyBackMap;
}; };

View file

@ -148,9 +148,9 @@ void Sapphire::Entity::Player::equipSoulCrystal( ItemPtr pItem, bool updateJob )
void Sapphire::Entity::Player::updateModels( GearSetSlot equipSlotId, const Sapphire::ItemPtr& pItem, bool updateClass ) void Sapphire::Entity::Player::updateModels( GearSetSlot equipSlotId, const Sapphire::ItemPtr& pItem, bool updateClass )
{ {
uint64_t model = pItem->getModelId1(); uint64_t model = pItem ? pItem->getModelId1() : 0;
uint64_t model2 = pItem->getModelId2(); uint64_t model2 = pItem ? pItem->getModelId2() : 0;
uint64_t stain = pItem->getStain(); uint64_t stain = pItem ? pItem->getStain() : 0;
switch( equipSlotId ) switch( equipSlotId )
{ {
@ -172,7 +172,10 @@ void Sapphire::Entity::Player::updateModels( GearSetSlot equipSlotId, const Sapp
break; break;
case SoulCrystal: case SoulCrystal:
equipSoulCrystal( pItem, updateClass ); if( pItem )
equipSoulCrystal( pItem, updateClass );
else
unequipSoulCrystal();
break; break;
case Waist: case Waist:
@ -265,15 +268,12 @@ void Sapphire::Entity::Player::unequipItem( Common::GearSetSlot equipSlotId, Ite
if( sendUpdate ) if( sendUpdate )
{ {
updateModels( equipSlotId, nullptr, true );
sendModel(); sendModel();
m_itemLevel = calculateEquippedGearItemLevel(); m_itemLevel = calculateEquippedGearItemLevel();
sendItemLevel(); sendItemLevel();
} }
if ( equipSlotId == SoulCrystal )
unequipSoulCrystal( pItem );
auto baseParams = pItem->getBaseParams(); auto baseParams = pItem->getBaseParams();
for( auto i = 0; i < 6; ++i ) for( auto i = 0; i < 6; ++i )
{ {
@ -293,7 +293,7 @@ void Sapphire::Entity::Player::unequipItem( Common::GearSetSlot equipSlotId, Ite
} }
} }
void Sapphire::Entity::Player::unequipSoulCrystal( ItemPtr pItem ) void Sapphire::Entity::Player::unequipSoulCrystal()
{ {
auto& exdData = Common::Service< Sapphire::Data::ExdDataGenerated >::ref(); auto& exdData = Common::Service< Sapphire::Data::ExdDataGenerated >::ref();
@ -547,38 +547,6 @@ void Sapphire::Entity::Player::writeInventory( InventoryType type )
db.execute( query ); db.execute( query );
} }
void Sapphire::Entity::Player::updateItemDb( Sapphire::ItemPtr pItem ) const
{
if( pItem->getUId() == 0 )
writeItemDb( pItem );
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
auto stmt = db.getPreparedStatement( Db::CHARA_ITEMGLOBAL_UP );
// todo: add more fields
stmt->setInt( 1, pItem->getStackSize() );
stmt->setInt( 2, pItem->getDurability() );
stmt->setInt( 3, pItem->getStain() );
stmt->setInt64( 4, pItem->getUId() );
db.directExecute( stmt );
}
void Sapphire::Entity::Player::deleteItemDb( Sapphire::ItemPtr item ) const
{
if( item->getUId() == 0 )
return;
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
auto stmt = db.getPreparedStatement( Db::CHARA_ITEMGLOBAL_DELETE );
stmt->setInt64( 1, item->getUId() );
db.directExecute( stmt );
}
bool Sapphire::Entity::Player::isObtainable( uint32_t catalogId, uint8_t quantity ) bool Sapphire::Entity::Player::isObtainable( uint32_t catalogId, uint8_t quantity )
{ {
return true; return true;
@ -748,7 +716,19 @@ Sapphire::Entity::Player::moveItem( uint16_t fromInventoryId, uint8_t fromSlotId
auto& itemMap = m_storageMap[ fromInventoryId ]->getItemMap(); auto& itemMap = m_storageMap[ fromInventoryId ]->getItemMap();
if( tmpItem == nullptr ) if( tmpItem == nullptr )
{
sendUrgent( "trying to move EMPTY item from [container{}, slot{}] to [container{}, slot{}], potential client desync, no action is performed.",
fromInventoryId, fromSlotId, toInventoryId, toSlot );
return; return;
}
if( auto target = m_storageMap[ toInventoryId ]->getItem( toSlot ) )
{
sendUrgent( "trying to move item from [container{}, slot{}] to NON-EMPTY [container{}, slot{}], potential client desync, swapItem is performed instead.",
fromInventoryId, fromSlotId, toInventoryId, toSlot );
swapItem( fromInventoryId, fromSlotId, toInventoryId, toSlot, sendUpdate );
return;
}
itemMap[ fromSlotId ].reset(); itemMap[ fromSlotId ].reset();
@ -885,8 +865,28 @@ void Sapphire::Entity::Player::swapItem( uint16_t fromInventoryId, uint8_t fromS
auto toItem = m_storageMap[ toInventoryId ]->getItem( toSlot ); auto toItem = m_storageMap[ toInventoryId ]->getItem( toSlot );
auto& itemMap = m_storageMap[ fromInventoryId ]->getItemMap(); auto& itemMap = m_storageMap[ fromInventoryId ]->getItemMap();
if( fromItem == nullptr || toItem == nullptr ) if( fromItem == nullptr && toItem == nullptr )
{
sendUrgent( "trying to swap TWO EMPTY ITEMS from [container{}, slot{}] to [container{}, slot{}], potential client desync, no action is performed.",
fromInventoryId, fromSlotId, toInventoryId, toSlot );
return; return;
}
if( fromItem != nullptr && toItem == nullptr )
{
sendUrgent( "trying to swap item from [container{}, slot{}] to EMPTY [container{}, slot{}], potential client desync, moveItem is performed instead.",
fromInventoryId, fromSlotId, toInventoryId, toSlot );
moveItem( fromInventoryId, fromSlotId, toInventoryId, toSlot, sendUpdate );
return;
}
if( fromItem == nullptr && toItem != nullptr )
{
sendUrgent( "trying to swap EMPTY item from [container{}, slot{}] to [container{}, slot{}], potential client desync, moveItem is performed instead.",
fromInventoryId, fromSlotId, toInventoryId, toSlot );
moveItem( toInventoryId, toSlot, fromInventoryId, fromSlotId, sendUpdate ); // we are moving the non-empty toSlot back to fromSlot.
return;
}
// An item is being moved from bag0-3 to equippment, meaning // An item is being moved from bag0-3 to equippment, meaning
// the swapped out item will be placed in the matching armory. // the swapped out item will be placed in the matching armory.

View file

@ -650,10 +650,13 @@ void Sapphire::Entity::Player::writeItemDb( Sapphire::ItemPtr pItem ) const
auto& itemMgr = Common::Service< World::Manager::ItemMgr >::ref(); auto& itemMgr = Common::Service< World::Manager::ItemMgr >::ref();
uint8_t flags = 0; uint8_t flags = 0;
if( pItem->isHq() )
flags |= Common::ItemFlag::FlagHq;
pItem->setUId( itemMgr.getNextUId() ); pItem->setUId( itemMgr.getNextUId() );
std::string sql = "INSERT INTO charaglobalitem ( CharacterId, itemId, catalogId, stack, flags ) VALUES ( " + std::string sql = "INSERT INTO charaglobalitem ( CharacterId, itemId, reservedFlag, catalogId, stack, flags ) VALUES ( " +
std::to_string( getId() ) + ", " + std::to_string( getId() ) + ", " +
std::to_string( pItem->getUId() ) + ", " + std::to_string( pItem->getUId() ) + ", " +
std::to_string( pItem->getReservedFlag() ) + ", " +
std::to_string( pItem->getId() ) + ", " + std::to_string( pItem->getId() ) + ", " +
std::to_string( pItem->getStackSize() ) + ", " + std::to_string( pItem->getStackSize() ) + ", " +
std::to_string( flags ) + ");"; std::to_string( flags ) + ");";
@ -661,6 +664,46 @@ void Sapphire::Entity::Player::writeItemDb( Sapphire::ItemPtr pItem ) const
} }
} }
void Sapphire::Entity::Player::updateItemDb( Sapphire::ItemPtr pItem ) const
{
if( pItem->getUId() == 0 )
{
writeItemDb( pItem );
return;
}
uint8_t flags = 0;
if( pItem->isHq() )
flags |= Common::ItemFlag::FlagHq;
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
auto stmt = db.getPreparedStatement( Db::CHARA_ITEMGLOBAL_UP );
// todo: add more fields
stmt->setInt( 1, pItem->getStackSize() );
stmt->setInt( 2, pItem->getDurability() );
stmt->setInt( 3, flags );
stmt->setInt( 4, pItem->getReservedFlag() );
stmt->setInt( 5, pItem->getStain() );
stmt->setInt64( 6, pItem->getUId() );
db.directExecute( stmt );
}
void Sapphire::Entity::Player::deleteItemDb( Sapphire::ItemPtr item ) const
{
if( item->getUId() == 0 )
return;
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
auto stmt = db.getPreparedStatement( Db::CHARA_ITEMGLOBAL_DELETE );
stmt->setInt64( 1, item->getUId() );
db.directExecute( stmt );
}
bool Sapphire::Entity::Player::loadInventory() bool Sapphire::Entity::Player::loadInventory()
{ {
auto& itemMgr = Common::Service< World::Manager::ItemMgr >::ref(); auto& itemMgr = Common::Service< World::Manager::ItemMgr >::ref();

View file

@ -0,0 +1,144 @@
#include <Network/PacketWrappers/ChannelChatPacket.h>
#include <Logging/Logger.h>
#include <Service.h>
#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 ];
}

View file

@ -0,0 +1,45 @@
#pragma once
#include <map>
#include <vector>
#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;
};
}

View file

@ -132,15 +132,16 @@ Sapphire::ItemPtr Sapphire::World::Manager::ItemMgr::loadItem( uint64_t uId )
try try
{ {
auto itemInfo = exdData.get< Sapphire::Data::Item >( itemRes->getUInt( 1 ) ); auto itemInfo = exdData.get< Sapphire::Data::Item >( itemRes->getUInt( 1 ) );
bool isHq = itemRes->getUInt( 3 ) == 1; bool isHq = itemRes->getUInt( 5 ) & Common::ItemFlag::FlagHq;
ItemPtr pItem = make_Item( uId, ItemPtr pItem = make_Item( uId,
itemRes->getUInt( 1 ), itemRes->getUInt( 1 ),
isHq ); isHq );
pItem->setStackSize( itemRes->getUInt( 2 ) ); pItem->setStackSize( itemRes->getUInt( 2 ) );
pItem->setStain( itemRes->getUInt16( 13 ) ); pItem->setReservedFlag( itemRes->getUInt( 3 ) );
pItem->setDurability( itemRes->getInt16( 6 ) ); pItem->setDurability( itemRes->getInt16( 6 ) );
pItem->setStain( itemRes->getUInt16( 13 ) );
return pItem; return pItem;
} }

View file

@ -0,0 +1,439 @@
#include <Common.h>
#include <Exd/ExdDataGenerated.h>
#include <Logging/Logger.h>
#include <Service.h>
#include <Network/PacketDef/Zone/ServerZoneDef.h>
#include <Network/PacketContainer.h>
#include "Network/GameConnection.h"
#include "PartyMgr.h"
#include "ServerMgr.h"
#include "ChatChannelMgr.h"
#include "PlayerMgr.h"
#include "Session.h"
#include "Actor/Player.h"
#include "Network/PacketWrappers/PartyUpdatePacket.h"
using namespace Sapphire;
using namespace Sapphire::World::Manager;
using namespace Sapphire::Network::Packets;
using namespace Sapphire::Network::Packets::Server;
void PartyMgr::onJoin( Entity::Player& joiner, Entity::Player& inviter )
{
auto& server = Common::Service< World::ServerMgr >::ref();
auto& ccMgr = Common::Service< World::Manager::ChatChannelMgr >::ref();
auto& inviteePlayer = joiner;
auto& invitingPlayer = inviter;
if( inviteePlayer.getPartyId() != 0 )
{
Logger::error( "Player#{} already in a party, cannot be invited!!", inviteePlayer.getId() );
return;
}
uint64_t partyId;
// if there is no party yet, one has to be created
PartyPtr party;
if( inviteePlayer.getPartyId() == 0 && invitingPlayer.getPartyId() == 0 )
{
partyId = createParty();
party = getParty( partyId );
assert( party );
inviteePlayer.setPartyId( partyId );
inviteePlayer.addOnlineStatus( Common::OnlineStatus::PartyMember );
invitingPlayer.setPartyId( partyId );
invitingPlayer.addOnlineStatus( Common::OnlineStatus::PartyLeader );
ccMgr.addToChannel( party->ChatChannel, invitingPlayer );
ccMgr.addToChannel( party->ChatChannel, inviteePlayer );
party->MemberId.push_back( invitingPlayer.getId() );
party->MemberId.push_back( inviteePlayer.getId() );
party->PartyCount = 2;
party->LeaderId = invitingPlayer.getId();
}
else if( inviteePlayer.getPartyId() == 0 )
{
partyId = invitingPlayer.getPartyId();
party = getParty( partyId );
inviteePlayer.setPartyId( partyId );
inviteePlayer.addOnlineStatus( Common::OnlineStatus::PartyMember );
ccMgr.addToChannel( party->ChatChannel, inviteePlayer );
party->MemberId.push_back( inviteePlayer.getId() );
party->PartyCount++;
}
auto pcUpdateParty = makePartyUpdate( invitingPlayer, inviteePlayer, UpdateStatus::JOINED, party->PartyCount );
auto members = getPartyMembers( *party );
sendPartyUpdate( *party );
for( const auto& member : members )
{
member->queuePacket( pcUpdateParty );
}
}
void PartyMgr::onLeave( Sapphire::Entity::Player &leavingPlayer )
{
auto& server = Common::Service< World::ServerMgr >::ref();
auto party = getParty( leavingPlayer.getPartyId() );
assert( party );
auto leadingPlayer = getPartyLeader( *party );
assert( leadingPlayer );
if( !leadingPlayer )
return;
if( party->PartyCount == 2 )
{
onDisband( *leadingPlayer );
}
else
{
auto members = getPartyMembers( *party );
removeMember( *party, leavingPlayer.getAsPlayer() );
uint32_t newLeaderId = 0;
for( const auto& member : members )
{
if( member->getId() == leavingPlayer.getId() )
{
member->removeOnlineStatus( { Common::OnlineStatus::PartyMember,
Common::OnlineStatus::PartyLeader } );
leavingPlayer.queuePacket( makeZonePacket< FFXIVIpcPartyList >( leavingPlayer.getId() ) );
member->queuePacket( makePartyUpdate( leadingPlayer, nullptr, UpdateStatus::KICK_SELF, party->PartyCount ) );
}
else
{
if( leavingPlayer.getId() == party->LeaderId )
{
newLeaderId = party->MemberId[ 0 ];
auto pPlayer = server.getSession( newLeaderId )->getPlayer();
if( !pPlayer /*|| !pPlayer->isConnected() */)
continue;
pPlayer->addOnlineStatus( Common::OnlineStatus::PartyLeader );
member->queuePacket( makePartyUpdate( leavingPlayer.getAsPlayer(), pPlayer, UpdateStatus::LEAVELEADER_LEAVED_MEMBER, party->PartyCount ) );
}
else
{
member->queuePacket( makePartyUpdate( leavingPlayer.getAsPlayer(), nullptr, UpdateStatus::LEAVE_MEMBER, party->PartyCount ) );
}
}
}
if( newLeaderId != 0 )
party->LeaderId = newLeaderId;
party->PartyCount--;
sendPartyUpdate( *party );
}
}
void PartyMgr::onDisband( Entity::Player& disbandingPlayer )
{
auto& server = Common::Service< World::ServerMgr >::ref();
auto party = getParty( disbandingPlayer.getPartyId() );
assert( party );
auto members = getPartyMembers( *party );
for( const auto& member : members )
{
removeMember( *party, member );
member->removeOnlineStatus( { Common::OnlineStatus::PartyMember, Common::OnlineStatus::PartyLeader } );
member->queuePacket( { makePartyUpdate( disbandingPlayer, disbandingPlayer, UpdateStatus::DISBAND, party->PartyCount ), makeZonePacket< FFXIVIpcPartyList >( member->getId() ) } );
}
removeParty( party->PartyID );
}
void PartyMgr::onMoveZone( Sapphire::Entity::Player &movingPlayer )
{
if( movingPlayer.getPartyId() == 0 )
{
movingPlayer.queuePacket( makeZonePacket< FFXIVIpcPartyList >( movingPlayer.getId() ) );
return;
}
auto party = getParty( movingPlayer.getPartyId() );
assert( party );
sendPartyUpdate( *party );
}
void PartyMgr::onMemberDisconnect( Entity::Player& disconnectingPlayer )
{
if( disconnectingPlayer.getPartyId() == 0 )
return;
auto& server = Common::Service< World::ServerMgr >::ref();
auto party = getParty( disconnectingPlayer.getPartyId() );
assert( party );
auto members = getPartyMembers( *party );
auto pLeader = getPartyLeader( *party );
bool anyMembersOnline = false;
for( const auto& member : members )
{
if( member/*->isConnected()*/ )
{
anyMembersOnline = true;
break;
}
}
// if there are no party members online, destroy the party
if( !anyMembersOnline )
return onDisband( *pLeader );
for( const auto& member : members )
{
// TODO: 2nd argument here makes it automatically send passing leadership message
member->queuePacket( { makePartyUpdate( disconnectingPlayer, UpdateStatus::OFFLINE_MEMBER, party->PartyCount ), makeZonePacket< FFXIVIpcPartyList >( member->getId() ) } );
}
sendPartyUpdate( *party );
}
void PartyMgr::onMemberRejoin( Entity::Player& joiningPlayer )
{
auto party = getParty( joiningPlayer.getPartyId() );
assert( party );
// TODO: do we need a party update here? move zone handler already handles it
}
void PartyMgr::onKick( const std::string& kickPlayerName, Entity::Player& leader )
{
auto& server = Common::Service< World::ServerMgr >::ref();
auto& playerMgr = Common::Service< World::Manager::PlayerMgr >::ref();
auto party = getParty( leader.getPartyId() );
assert( party );
auto pLeader = getPartyLeader( *party );
auto members = getPartyMembers( *party );
auto pKickedPlayer = server.getSession( kickPlayerName )->getPlayer();
if( !pKickedPlayer )
{
Logger::error( "Target player for kicking not found (\"{t}\")", kickPlayerName );
return;
}
if( party->PartyCount == 2 )
{
onDisband( *pLeader );
}
else
{
for( const auto &member: members )
{
if( kickPlayerName == member->getName() )
{
removeMember( *party, member );
member->removeOnlineStatus( Common::OnlineStatus::PartyMember );
member->queuePacket( { makePartyUpdate( *pLeader, *member, UpdateStatus::KICK_SELF, party->PartyCount ),
makeZonePacket< FFXIVIpcPartyList >( member->getId() ) } );
}
else
{
member->queuePacket( makePartyUpdate( *pKickedPlayer, UpdateStatus::KICK_MEMBER, party->PartyCount ) );
}
}
party->PartyCount--;
sendPartyUpdate( *party );
}
}
void PartyMgr::onChangeLeader( const std::string& newLeaderName, Entity::Player& oldLeader )
{
auto& server = Common::Service< World::ServerMgr >::ref();
auto& playerMgr = Common::Service< World::Manager::PlayerMgr >::ref();
auto party = getParty( oldLeader.getPartyId() );
auto pNewLeader = server.getSession( newLeaderName )->getPlayer();
if( !pNewLeader )
{
Logger::error( "Target player for new leader not found (\"{t}\")", newLeaderName );
return;
}
for( auto memberId : party->MemberId )
{
if( memberId == pNewLeader->getId() )
{
pNewLeader->addOnlineStatus( Common::OnlineStatus::PartyLeader );
// this is not ideal, probably better to have a function which can add
// and remove at the same time so packets are only triggered once
oldLeader.addOnlineStatus( Common::OnlineStatus::PartyMember );
oldLeader.removeOnlineStatus( Common::OnlineStatus::PartyLeader );
party->LeaderId = pNewLeader->getId();
break;
}
}
auto members = getPartyMembers( *party );
for( auto& member : members )
{
auto pcUpdateParty = makePartyUpdate( oldLeader.getAsPlayer(), pNewLeader, UpdateStatus::CHANGELEADER, party->PartyCount );
member->queuePacket( pcUpdateParty );
}
sendPartyUpdate( *party );
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
uint64_t PartyMgr::createParty()
{
auto& chatChannelMgr = Common::Service< ChatChannelMgr >::ref();
auto party = std::make_shared< Party >();
party->PartyID = getNextPartyId();
party->ChatChannel = chatChannelMgr.createChatChannel( Common::ChatChannelType::PartyChat );
m_partyIdMap[ party->PartyID ] = party;
return party->PartyID;
}
uint64_t PartyMgr::getNextPartyId()
{
return ++m_maxPartyId;
}
PartyPtr PartyMgr::getParty( uint64_t partyId )
{
auto it = m_partyIdMap.find( partyId );
if( it != m_partyIdMap.end() )
return it->second;
return nullptr;
}
std::vector< Entity::PlayerPtr > PartyMgr::getPartyMembers( Party& party )
{
std::vector< Entity::PlayerPtr > members;
auto& server = Common::Service< World::ServerMgr >::ref();
for( auto& memberId : party.MemberId )
{
if( memberId == 0 )
continue;
auto pPlayer = server.getSession( memberId )->getPlayer();
members.push_back( pPlayer );
}
return members;
}
Entity::PlayerPtr PartyMgr::getPartyLeader( Party& party )
{
auto& server = Common::Service< World::ServerMgr >::ref();
if( party.LeaderId == 0 )
return nullptr;
auto pLeader = server.getSession( party.LeaderId )->getPlayer();
return pLeader;
}
void PartyMgr::sendPartyUpdate( Party& party )
{
auto& exdData = Common::Service< Data::ExdDataGenerated >::ref();
auto partyMembers = getPartyMembers( party );
std::vector< PartyMember > entries;
auto& server = Common::Service< World::ServerMgr >::ref();
for( const auto& member : partyMembers )
{
auto classJob = exdData.get< Data::ClassJob >( static_cast< uint8_t >( member->getClass() ) );
if( !classJob )
continue;
PartyMember memberEntry{};
memberEntry./*ParentEntityId*/u1 = Common::INVALID_GAME_OBJECT_ID;
memberEntry./*PetEntityId*/u2 = Common::INVALID_GAME_OBJECT_ID;
memberEntry.hp = member->getHp();
memberEntry.maxHp = member->getMaxHp();
memberEntry.mp = member->getMp();
memberEntry.maxMp = member->getMaxMp();
memberEntry.classId = static_cast< uint8_t >( member->getClass() );
memberEntry.level = member->getLevel();
//memberEntry.ObjType = 4; // 1 PC, 2 Buddy ??
memberEntry.zoneId = member->getTerritoryTypeId();
memberEntry./*Valid*/gposeSelectable = 1;
//memberEntry.Tp = member->getTp();
//memberEntry.Role = classJob->role;
entries.push_back( memberEntry );
}
for( const auto& pMember : partyMembers )
{
size_t idx = 0;
auto updatePartyPacket = makeZonePacket< FFXIVIpcPartyList >( partyMembers[ 0 ]->getId() );
auto& data = updatePartyPacket->data();
data.partyId = party.PartyID;
data.leaderIndex = getPartyLeaderIndex( party );
data.channelId = party.ChatChannel;
data.partySize = party.PartyCount;
for( const auto& member : partyMembers )
{
bool isConnected = /*member->isConnected()*/true;
// if player is online and in the same zone as current member in party, display more data in partylist
bool hasInfo = isConnected && member->getTerritoryTypeId() == pMember->getTerritoryTypeId();
if( hasInfo )
{
data.member[ idx ] = entries[ idx ];
}
data.member[ idx ].contentId = member->getContentId();
data.member[ idx ].charaId = member->getId();
strcpy( data.member[ idx ].name, member->getName().c_str() );
idx++;
}
pMember->queuePacket( updatePartyPacket );
}
}
void PartyMgr::removeParty( uint64_t partyId )
{
m_partyIdMap.erase( partyId );
}
int8_t PartyMgr::getPartyLeaderIndex( const Party &party )
{
size_t idx = 0;
for( const auto& memberId : party.MemberId )
{
if( memberId == party.LeaderId )
return static_cast< int8_t >( idx );
idx++;
}
return -1;
}
void PartyMgr::removeMember( Party &party, const Entity::PlayerPtr& pMember )
{
auto& ccMgr = Common::Service< World::Manager::ChatChannelMgr >::ref();
pMember->setPartyId( 0 );
ccMgr.removeFromChannel( party.ChatChannel, *pMember );
party.MemberId.erase( std::remove( party.MemberId.begin(), party.MemberId.end(), pMember->getId() ), party.MemberId.end() );
}

View file

@ -0,0 +1,88 @@
#pragma once
#include <cstdint>
#include <string>
#include <ForwardsZone.h>
#include <array>
#include <set>
#include <unordered_map>
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 );
};
}

View file

@ -6,6 +6,7 @@
#include <unordered_map> #include <unordered_map>
#include <Service.h> #include <Service.h>
#include "Manager/PartyMgr.h"
#include "Actor/Player.h" #include "Actor/Player.h"
@ -759,6 +760,9 @@ bool Sapphire::World::Manager::TerritoryMgr::movePlayer( TerritoryPtr pZone, Sap
pPlayer->sendZonePackets(); pPlayer->sendZonePackets();
auto& partyMgr = Common::Service< World::Manager::PartyMgr >::ref();
partyMgr.onMoveZone( *pPlayer );
return true; return true;
} }

View file

@ -143,7 +143,15 @@ Sapphire::Network::GameConnection::GameConnection( Sapphire::Network::HivePtr pH
setZoneHandler( ClientZoneIpcType::InventoryEquipRecommendedItems, "InventoryEquipRecommendedItemsHandler", &GameConnection::inventoryEquipRecommendedItemsHandler ); setZoneHandler( ClientZoneIpcType::InventoryEquipRecommendedItems, "InventoryEquipRecommendedItemsHandler", &GameConnection::inventoryEquipRecommendedItemsHandler );
setChatHandler( ClientChatIpcType::TellReq, "TellReq", &GameConnection::tellHandler ); 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; Sapphire::Network::GameConnection::~GameConnection() = default;

View file

@ -179,6 +179,8 @@ namespace Sapphire::Network
DECLARE_HANDLER( tellHandler ); DECLARE_HANDLER( tellHandler );
DECLARE_HANDLER( channelChatHandler );
DECLARE_HANDLER( reqPlaceHousingItem ); DECLARE_HANDLER( reqPlaceHousingItem );
DECLARE_HANDLER( reqMoveHousingItem ); DECLARE_HANDLER( reqMoveHousingItem );
@ -200,6 +202,14 @@ namespace Sapphire::Network
DECLARE_HANDLER( eventYieldHandler ); DECLARE_HANDLER( eventYieldHandler );
DECLARE_HANDLER( inventoryEquipRecommendedItemsHandler ); DECLARE_HANDLER( inventoryEquipRecommendedItemsHandler );
DECLARE_HANDLER( socialInviteHandler );
DECLARE_HANDLER( socialReplyHandler );
DECLARE_HANDLER( partyLeaveHandler );
DECLARE_HANDLER( partyDisbandHandler );
DECLARE_HANDLER( partyKickHandler );
DECLARE_HANDLER( partyChangeLeaderHandler );
}; };
} }

View file

@ -256,19 +256,12 @@ void Sapphire::Network::GameConnection::gm1Handler( const Packets::FFXIVARR_PACK
{ {
targetPlayer->setOnlineStatusMask( param1 ); targetPlayer->setOnlineStatusMask( param1 );
auto statusPacket = makeZonePacket< FFXIVIpcSetOnlineStatus >( player.getId() );
statusPacket->data().onlineStatusFlags = param1;
queueOutPacket( statusPacket );
auto searchInfoPacket = makeZonePacket< FFXIVIpcSetSearchInfo >( player.getId() ); auto searchInfoPacket = makeZonePacket< FFXIVIpcSetSearchInfo >( player.getId() );
searchInfoPacket->data().onlineStatusFlags = param1; searchInfoPacket->data().onlineStatusFlags = param1;
searchInfoPacket->data().selectRegion = targetPlayer->getSearchSelectRegion(); searchInfoPacket->data().selectRegion = targetPlayer->getSearchSelectRegion();
strcpy( searchInfoPacket->data().searchMessage, targetPlayer->getSearchMessage() ); strcpy( searchInfoPacket->data().searchMessage, targetPlayer->getSearchMessage() );
targetPlayer->queuePacket( searchInfoPacket ); 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 ); player.sendNotice( "Icon for {0} was set to {1}", targetPlayer->getName(), param1 );
break; break;
} }

View file

@ -47,6 +47,8 @@
#include "Manager/HousingMgr.h" #include "Manager/HousingMgr.h"
#include "Manager/RNGMgr.h" #include "Manager/RNGMgr.h"
#include "Manager/ItemMgr.h" #include "Manager/ItemMgr.h"
#include "Manager/PartyMgr.h"
#include "Manager/ChatChannelMgr.h"
#include "Action/Action.h" #include "Action/Action.h"
#include "Inventory/Item.h" #include "Inventory/Item.h"
@ -91,18 +93,12 @@ void Sapphire::Network::GameConnection::setSearchInfoHandler( const Packets::FFX
// mark player as new adventurer // mark player as new adventurer
player.setNewAdventurer( true ); player.setNewAdventurer( true );
auto statusPacket = makeZonePacket< FFXIVIpcSetOnlineStatus >( player.getId() );
statusPacket->data().onlineStatusFlags = status;
queueOutPacket( statusPacket );
auto searchInfoPacket = makeZonePacket< FFXIVIpcSetSearchInfo >( player.getId() ); auto searchInfoPacket = makeZonePacket< FFXIVIpcSetSearchInfo >( player.getId() );
searchInfoPacket->data().onlineStatusFlags = status; searchInfoPacket->data().onlineStatusFlags = status;
searchInfoPacket->data().selectRegion = player.getSearchSelectRegion(); searchInfoPacket->data().selectRegion = player.getSearchSelectRegion();
strcpy( searchInfoPacket->data().searchMessage, player.getSearchMessage() ); strcpy( searchInfoPacket->data().searchMessage, player.getSearchMessage() );
queueOutPacket( searchInfoPacket ); 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, 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 ); int32_t entrysizes = sizeof( listPacket->data().entries );
memset( listPacket->data().entries, 0, sizeof( listPacket->data().entries ) ); memset( listPacket->data().entries, 0, sizeof( listPacket->data().entries ) );
listPacket->data().entries[ 0 ].bytes[ 2 ] = player.getCurrentTerritory()->getTerritoryTypeId(); auto fillEntryAt = [ &listPacket ]( int i, Entity::PlayerPtr nextPlayer, bool isLeader )
listPacket->data().entries[ 0 ].bytes[ 3 ] = 0x80; {
listPacket->data().entries[ 0 ].bytes[ 4 ] = 0x02; listPacket->data().entries[ i ].bytes[ 2 ] = nextPlayer->getCurrentTerritory()->getTerritoryTypeId();
listPacket->data().entries[ 0 ].bytes[ 6 ] = 0x3B; listPacket->data().entries[ i ].bytes[ 3 ] = 0x80;
listPacket->data().entries[ 0 ].bytes[ 11 ] = 0x10; listPacket->data().entries[ i ].bytes[ 4 ] = 0x02;
listPacket->data().entries[ 0 ].classJob = static_cast< uint8_t >( player.getClass() ); listPacket->data().entries[ i ].bytes[ 6 ] = 0x3B;
listPacket->data().entries[ 0 ].contentId = player.getContentId(); listPacket->data().entries[ i ].bytes[ 8 ] = isLeader;
listPacket->data().entries[ 0 ].level = player.getLevel(); listPacket->data().entries[ i ].bytes[ 11 ] = 0x10;
listPacket->data().entries[ 0 ].zoneId = player.getCurrentTerritory()->getTerritoryTypeId(); listPacket->data().entries[ i ].classJob = static_cast< uint8_t >( nextPlayer->getClass() );
listPacket->data().entries[ 0 ].zoneId1 = 0x0100; listPacket->data().entries[ i ].contentId = nextPlayer->getContentId();
// TODO: no idea what this does listPacket->data().entries[ i ].level = nextPlayer->getLevel();
//listPacket.data().entries[0].one = 1; 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 // GC icon
listPacket->data().entries[ 0 ].bytes1[ 0 ] = 2; listPacket->data().entries[ i ].bytes1[ 0 ] = 2;
// client language J = 0, E = 1, D = 2, F = 3 // client language J = 0, E = 1, D = 2, F = 3
listPacket->data().entries[ 0 ].bytes1[ 1 ] = 1; listPacket->data().entries[ i ].bytes1[ 1 ] = 1;
// user language settings flag J = 1, E = 2, D = 4, F = 8 // user language settings flag J = 1, E = 2, D = 4, F = 8
listPacket->data().entries[ 0 ].bytes1[ 2 ] = 1 + 2; listPacket->data().entries[ i ].bytes1[ 2 ] = 1 + 2 + 4 + 8;
listPacket->data().entries[ 0 ].onlineStatusMask = player.getOnlineStatusMask(); 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 ); queueOutPacket( listPacket );
} }
else if( type == 2 ) else if( type == 2 )
{ // friend list { // friend list
@ -604,12 +627,24 @@ void Sapphire::Network::GameConnection::tellHandler( const Packets::FFXIVARR_PAC
if( player.isActingAsGm() ) if( player.isActingAsGm() )
{ {
tellPacket->data().flags |= TellFlags::GmTellMsg; tellPacket->data().flags |= ChatFromType::GmTellMsg;
} }
pTargetPlayer->queueChatPacket( tellPacket ); 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, void Sapphire::Network::GameConnection::performNoteHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket,
Entity::Player& player ) Entity::Player& player )
{ {

View file

@ -0,0 +1,73 @@
#include <Common.h>
#include <Network/CommonNetwork.h>
#include <Network/GamePacket.h>
#include <Network/PacketContainer.h>
#include <Service.h>
#include <Network/PacketDef/Zone/ClientZoneDef.h>
#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 );
}

View file

@ -0,0 +1,41 @@
#pragma once
#include "Forwards.h"
#include "Actor/Player.h"
#include <Network/GamePacket.h>
#include <Network/PacketDef/Chat/ServerChatDef.h>
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;
};
};
}

View file

@ -0,0 +1,127 @@
#include <Common.h>
#include <Network/CommonNetwork.h>
#include <Network/GamePacket.h>
#include <Logging/Logger.h>
#include <Network/PacketContainer.h>
#include <datReader/DatCategories/bg/LgbTypes.h>
#include <Network/PacketDef/Zone/ClientZoneDef.h>
#include <Service.h>
#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;
}
}
}

View file

@ -0,0 +1,83 @@
#pragma once
#include <Network/GamePacket.h>
#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... );
}
}

View file

@ -42,6 +42,8 @@
#include "Manager/NaviMgr.h" #include "Manager/NaviMgr.h"
#include "Manager/ActionMgr.h" #include "Manager/ActionMgr.h"
#include "Manager/MapMgr.h" #include "Manager/MapMgr.h"
#include "Manager/ChatChannelMgr.h"
#include "Manager/PartyMgr.h"
#include "Territory/InstanceObjectCache.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 ); 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" ); Logger::info( "LinkshellMgr: Caching linkshells" );
auto pLsMgr = std::make_shared< Manager::LinkshellMgr >(); auto pLsMgr = std::make_shared< Manager::LinkshellMgr >();
if( !pLsMgr->loadLinkshells() ) 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 pEventMgr = std::make_shared< Manager::EventMgr >();
auto pItemMgr = std::make_shared< Manager::ItemMgr >(); auto pItemMgr = std::make_shared< Manager::ItemMgr >();
auto pRNGMgr = std::make_shared< Manager::RNGMgr >(); auto pRNGMgr = std::make_shared< Manager::RNGMgr >();
auto pPartyMgr = std::make_shared< Manager::PartyMgr >();
Common::Service< DebugCommandMgr >::set( pDebugCom ); Common::Service< DebugCommandMgr >::set( pDebugCom );
Common::Service< Manager::PlayerMgr >::set( pPlayerMgr ); 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::EventMgr >::set( pEventMgr );
Common::Service< Manager::ItemMgr >::set( pItemMgr ); Common::Service< Manager::ItemMgr >::set( pItemMgr );
Common::Service< Manager::RNGMgr >::set( pRNGMgr ); Common::Service< Manager::RNGMgr >::set( pRNGMgr );
Common::Service< Manager::PartyMgr >::set( pPartyMgr );
Logger::info( "World server running on {0}:{1}", m_ip, m_port ); 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() ); Logger::info( "[{0}] Session removal", it->second->getId() );
it = m_sessionMapById.erase( it ); it = m_sessionMapById.erase( it );
removeSession( pPlayer->getContentId() );
removeSession( pPlayer->getName() ); removeSession( pPlayer->getName() );
continue; continue;
} }
@ -334,6 +342,7 @@ void Sapphire::World::ServerMgr::mainLoop()
// if( it->second.unique() ) // if( it->second.unique() )
{ {
it = m_sessionMapById.erase( it ); it = m_sessionMapById.erase( it );
removeSession( pPlayer->getContentId() );
removeSession( pPlayer->getName() ); removeSession( pPlayer->getName() );
} }
} }
@ -372,17 +381,13 @@ bool Sapphire::World::ServerMgr::createSession( uint32_t sessionId )
return false; return false;
} }
m_sessionMapByContentId[ newSession->getPlayer()->getContentId() ] = newSession;
m_sessionMapByName[ newSession->getPlayer()->getName() ] = newSession; m_sessionMapByName[ newSession->getPlayer()->getName() ] = newSession;
return true; return true;
} }
void Sapphire::World::ServerMgr::removeSession( uint32_t sessionId )
{
m_sessionMapById.erase( sessionId );
}
Sapphire::World::SessionPtr Sapphire::World::ServerMgr::getSession( uint32_t id ) Sapphire::World::SessionPtr Sapphire::World::ServerMgr::getSession( uint32_t id )
{ {
//std::lock_guard<std::mutex> lock( m_sessionMutex ); //std::lock_guard<std::mutex> lock( m_sessionMutex );
@ -394,6 +399,16 @@ Sapphire::World::SessionPtr Sapphire::World::ServerMgr::getSession( uint32_t id
return nullptr; 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 ) Sapphire::World::SessionPtr Sapphire::World::ServerMgr::getSession( const std::string& playerName )
{ {
//std::lock_guard<std::mutex> lock( m_sessionMutex ); //std::lock_guard<std::mutex> lock( m_sessionMutex );
@ -406,6 +421,16 @@ Sapphire::World::SessionPtr Sapphire::World::ServerMgr::getSession( const std::s
return nullptr; 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 ) void Sapphire::World::ServerMgr::removeSession( const std::string& playerName )
{ {
m_sessionMapByName.erase( playerName ); m_sessionMapByName.erase( playerName );

View file

@ -24,10 +24,8 @@ namespace Sapphire::World
bool createSession( uint32_t sessionId ); 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( uint32_t id );
World::SessionPtr getSession( uint64_t contentId );
World::SessionPtr getSession( const std::string& playerName ); World::SessionPtr getSession( const std::string& playerName );
size_t getSessionCount() const; size_t getSessionCount() const;
@ -66,11 +64,15 @@ namespace Sapphire::World
Sapphire::Common::Config::WorldConfig m_config; Sapphire::Common::Config::WorldConfig m_config;
std::map< uint32_t, SessionPtr > m_sessionMapById; std::map< uint32_t, SessionPtr > m_sessionMapById;
std::map< uint64_t, SessionPtr > m_sessionMapByContentId;
std::map< std::string, SessionPtr > m_sessionMapByName; std::map< std::string, SessionPtr > m_sessionMapByName;
std::map< uint32_t, std::string > m_playerNameMapById; std::map< uint32_t, std::string > m_playerNameMapById;
std::map< uint32_t, uint32_t > m_zones; std::map< uint32_t, uint32_t > m_zones;
std::map< std::string, Entity::BNpcTemplatePtr > m_bNpcTemplateMap; std::map< std::string, Entity::BNpcTemplatePtr > m_bNpcTemplateMap;
void removeSession( uint32_t sessionId );
void removeSession( uint64_t contentId );
void removeSession( const std::string& playerName );
}; };
} }

View file

@ -8,6 +8,9 @@
#include "Network/GameConnection.h" #include "Network/GameConnection.h"
#include "Actor/Player.h" #include "Actor/Player.h"
#include "Service.h"
#include "Manager/PartyMgr.h"
#include "Session.h" #include "Session.h"
namespace fs = std::filesystem; namespace fs = std::filesystem;
@ -73,6 +76,12 @@ void Sapphire::World::Session::close()
if( m_pPlayer ) if( m_pPlayer )
{ {
m_pPlayer->clearBuyBackMap(); 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 // do one last update to db
m_pPlayer->updateSql(); m_pPlayer->updateSql();
// reset the zone, so the zone handler knows to remove the actor // reset the zone, so the zone handler knows to remove the actor