1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-05-02 16:57:47 +00:00

Merge pull request #946 from collett8192/Sapphire5.58_action

5.58 action update
This commit is contained in:
Mordred 2023-08-01 15:57:17 +02:00 committed by GitHub
commit a42353a0a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 1740 additions and 322 deletions

View file

@ -67,11 +67,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,
@ -801,6 +810,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...
@ -1170,6 +1195,16 @@ namespace Sapphire::Common
RequireCorrectPositional = 2, 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 enum class AstCardType : uint8_t
{ {
None = 0, None = 0,

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

@ -73,15 +73,14 @@ namespace Sapphire::Network::Packets
SocialRequestError = 0xF0AD, SocialRequestError = 0xF0AD,
CFRegistered = 0x029F, // updated 5.58 hotfix CFRegistered = 0x029F, // updated 5.58 hotfix
SocialRequestResponse = 0x0082, // updated 5.58 hotfix SocialInviteResponse = 0x0082, // updated 5.58 hotfix
SocialMessage = 0x03CB, // updated 5.58 hotfix SocialInviteUpdate = 0x03CB, // updated 5.58 hotfix
SocialMessage2 = 0x01D7, // updated 5.58 hotfix SocialInviteResult = 0x01D7, // updated 5.58 hotfix
CancelAllianceForming = 0xF0C6, // updated 4.2 CancelAllianceForming = 0xF0C6, // updated 4.2
LogMessage = 0x0118, // updated 5.58 hotfix LogMessage = 0x0118, // updated 5.58 hotfix
Chat = 0x00FE, // updated 5.58 hotfix Chat = 0x00FE, // updated 5.58 hotfix
PartyChat = 0x0065,
WorldVisitList = 0xF0FE, // added 4.5 WorldVisitList = 0xF0FE, // added 4.5
@ -149,7 +148,7 @@ namespace Sapphire::Network::Packets
SomeCustomiseChangePacketProbably = 0x00CD, // added 5.18 SomeCustomiseChangePacketProbably = 0x00CD, // added 5.18
PartyList = 0x0349, // updated 5.58 hotfix PartyList = 0x0349, // updated 5.58 hotfix
PartyMessage = 0x00A4, // updated 5.58 hotfix PartyUpdate = 0x00A4, // updated 5.58 hotfix
HateRank = 0x0150, // updated 5.58 hotfix HateRank = 0x0150, // updated 5.58 hotfix
HateList = 0x0243, // updated 5.58 hotfix HateList = 0x0243, // updated 5.58 hotfix
ObjectSpawn = 0x0125, // updated 5.58 hotfix ObjectSpawn = 0x0125, // updated 5.58 hotfix
@ -333,16 +332,15 @@ namespace Sapphire::Network::Packets
CancelLogout = 0x01AC, // updated 5.58 hotfix CancelLogout = 0x01AC, // updated 5.58 hotfix
CFDutyInfoHandler = 0xF078, // updated 4.2 CFDutyInfoHandler = 0xF078, // updated 4.2
SocialReqSendHandler = 0x00D7, // updated 5.58 hotfix SocialInviteHandler = 0x00D7, // updated 5.58 hotfix
SocialResponseHandler = 0x023B, // updated 5.58 hotfix SocialReplyHandler = 0x023B, // updated 5.58 hotfix
CreateCrossWorldLS = 0x035D, // updated 5.58 hotfix CreateCrossWorldLS = 0x035D, // updated 5.58 hotfix
ChatHandler = 0x03B0, // updated 5.58 hotfix ChatHandler = 0x03B0, // updated 5.58 hotfix
PartyChatHandler = 0x0065, PartyChangeLeaderHandler = 0x036C, // updated 5.58 hotfix
PartySetLeaderHandler = 0x036C, // updated 5.58 hotfix PartyLeaveHandler = 0x019D, // updated 5.58 hotfix
LeavePartyHandler = 0x019D, // updated 5.58 hotfix PartyKickHandler = 0x0262, // updated 5.58 hotfix
KickPartyMemberHandler = 0x0262, // updated 5.58 hotfix PartyDisbandHandler = 0x0276, // updated 5.58 hotfix
DisbandPartyHandler = 0x0276, // updated 5.58 hotfix
SocialListHandler = 0x01CA, // updated 5.58 hotfix SocialListHandler = 0x01CA, // updated 5.58 hotfix
SetSearchInfoHandler = 0x01D4, // updated 5.58 hotfix SetSearchInfoHandler = 0x01D4, // updated 5.58 hotfix
@ -433,6 +431,7 @@ namespace Sapphire::Network::Packets
enum ServerChatIpcType : uint16_t enum ServerChatIpcType : uint16_t
{ {
Tell = 0x0064, // updated for sb 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 PublicContentTell = 0x00FB, // added 4.5, this is used when receiving a /tell in PublicContent instances such as Eureka or Bozja
TellErrNotFound = 0x0066, TellErrNotFound = 0x0066,
@ -445,6 +444,7 @@ namespace Sapphire::Network::Packets
enum ClientChatIpcType : uint16_t enum ClientChatIpcType : 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

@ -46,19 +46,6 @@ namespace Sapphire::Network::Packets::Server
char msg[1012]; 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 > 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++.
@ -2178,7 +2165,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;
@ -2187,12 +2174,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;
@ -2203,40 +2190,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;
@ -2245,16 +2234,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

@ -0,0 +1,46 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_DEVINE_BENISON = 1218;
class ActionDivineBenison7432 :
public ScriptAPI::ActionScript
{
public:
ActionDivineBenison7432() :
ScriptAPI::ActionScript( 7432 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pTarget = action.getHitChara();
if( pTarget )
{
float shieldValue = action.calcHealing( 500 ).first; // 500 ptc worth of shield
shieldValue = Math::CalcStats::applyHealingReceiveMultiplier( *pTarget, shieldValue );
auto oldEffect = pTarget->getStatusEffectById( STATUS_ID_DEVINE_BENISON );
if( !oldEffect.second || oldEffect.second->getEffectEntry().getRemainingShield() <= shieldValue )
{
World::Action::StatusEffectEntry effectEntry;
effectEntry.init( Common::StatusEffectType::Shield, shieldValue, 0, 0, 0 );
auto pNewEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_DEVINE_BENISON, action.getSourceChara(), pTarget, 15000, 3000 );
pNewEffect->replaceEffectEntry( effectEntry );
action.getEffectbuilder()->applyStatusEffect( pTarget, action.getSourceChara(), pNewEffect );
}
else
action.getEffectbuilder()->statusNoEffect( pTarget, STATUS_ID_DEVINE_BENISON );
}
}
};
EXPOSE_SCRIPT( ActionDivineBenison7432 );

View file

@ -512,8 +512,8 @@ void Action::Action::buildEffects()
if( !shouldHitThisTarget ) if( !shouldHitThisTarget )
continue; continue;
bool shouldTriggerActionBonus = true; bool isValidVictim = false;
if( m_lutEntry.damagePotency > 0 ) if( m_lutEntry.damagePotency > 0 && actor->getObjKind() != m_pSource->getObjKind() /* is NOT friendly target, this will do for now */ )
{ {
Common::AttackType attackType = static_cast< Common::AttackType >( m_actionData->attackType ); Common::AttackType attackType = static_cast< Common::AttackType >( m_actionData->attackType );
actor->onActionHostile( m_pSource ); actor->onActionHostile( m_pSource );
@ -539,7 +539,6 @@ void Action::Action::buildEffects()
if( actor->hasInvulnerableEffect() ) if( actor->hasInvulnerableEffect() )
{ {
dmg.first = 0; dmg.first = 0;
shouldTriggerActionBonus = false;
} }
if( dmg.first > 0 ) if( dmg.first > 0 )
@ -563,7 +562,6 @@ void Action::Action::buildEffects()
if( dodged ) if( dodged )
{ {
dmg.first = 0; dmg.first = 0;
shouldTriggerActionBonus = false;
} }
else else
{ {
@ -573,6 +571,7 @@ void Action::Action::buildEffects()
if( dmg.first > 0 ) if( dmg.first > 0 )
{ {
isValidVictim = true;
auto attackTypeEffect = m_actionData->attackType; auto attackTypeEffect = m_actionData->attackType;
if( attackTypeEffect == -1 ) if( attackTypeEffect == -1 )
{ {
@ -612,116 +611,117 @@ void Action::Action::buildEffects()
m_effectBuilder->invulnerable( actor ); m_effectBuilder->invulnerable( actor );
} }
} }
if( shouldTriggerActionBonus )
{
if( ( !isComboAction() || isCorrectCombo() ) )
{
if ( !m_actionData->preservesCombo ) // this matches retail packet, on all standalone actions even casts.
{
m_effectBuilder->startCombo( actor, getId() ); // this is on all targets hit
}
}
if( m_lutEntry.bonusEffect & Common::ActionBonusEffect::SelfHeal )
{
if( checkActionBonusRequirement() )
{
auto heal = calcHealing( m_lutEntry.getSelfHealPotency() );
heal.first = Math::CalcStats::applyHealingReceiveMultiplier( *m_pSource, heal.first );
m_effectBuilder->heal( actor, m_pSource, heal.first, heal.second, Common::ActionEffectResultFlag::EffectOnSource );
}
}
if( validVictimCounter == 0 ) // effects only apply once if aoe, on first valid target (can be single target action as well)
{
if( isCorrectCombo() )
m_effectBuilder->comboSucceed( actor );
if( m_isAutoAttack && player )
{
if( player->getClass() == Common::ClassJob::Paladin )
{
player->gaugePldSetOath( std::min( 100, player->gaugePldGetOath() + 5 ) );
}
}
if( m_lutEntry.bonusEffect & Common::ActionBonusEffect::GainMPPercentage )
{
if( checkActionBonusRequirement() )
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.getMPGainPercentage() / 100, Common::ActionEffectResultFlag::EffectOnSource );
}
if( ( m_lutEntry.bonusEffect & Common::ActionBonusEffect::GainJobResource ) && player )
{
if( checkActionBonusRequirement() )
{
switch( m_lutEntry.getAffectedJob() )
{
case Common::ClassJob::Marauder:
case Common::ClassJob::Warrior:
{
player->gaugeWarSetIb( std::min( 100, player->gaugeWarGetIb() + m_lutEntry.getJobResourceGain() ) );
break;
}
case Common::ClassJob::Darkknight:
{
player->gaugeDrkSetBlood( std::min( 100, player->gaugeDrkGetBlood() + m_lutEntry.getJobResourceGain() ) );
break;
}
case Common::ClassJob::Gunbreaker:
{
player->gaugeGnbSetAmmo( std::min( 2, player->gaugeGnbGetAmmo() + m_lutEntry.getJobResourceGain() ) );
break;
}
case Common::ClassJob::Samurai:
{
player->gaugeSamSetKenki( std::min( 100, player->gaugeSamGetKenki() + m_lutEntry.getJobResourceGain() ) );
break;
}
}
}
}
if( ( m_lutEntry.bonusEffect & Common::ActionBonusEffect::GainJobTimer ) && player )
{
if( checkActionBonusRequirement() )
{
switch( m_lutEntry.getAffectedJob() )
{
case Common::ClassJob::Darkknight:
{
player->gaugeDrkSetDarkSideTimer( std::min( 60000, player->gaugeDrkGetDarkSideTimer() + m_lutEntry.getJobTimerGain() ), true );
break;
}
case Common::ClassJob::Dragoon:
{
player->gaugeDrgSetDragonTimer( std::min( 30000, player->gaugeDrgGetDragonTimer() + m_lutEntry.getJobTimerGain() ), true );
break;
}
}
}
}
}
if( validVictimCounter == 0 )
firstValidVictim = actor;
validVictimCounter++;
}
} }
if( m_lutEntry.healPotency > 0 && actor->getObjKind() == m_pSource->getObjKind() /* is friendly target, this will do for now */) if( m_lutEntry.healPotency > 0 && actor->getObjKind() == m_pSource->getObjKind() /* is friendly target, this will do for now */)
{ {
isValidVictim = true;
auto heal = calcHealing( m_lutEntry.healPotency ); auto heal = calcHealing( m_lutEntry.healPotency );
heal.first = Math::CalcStats::applyHealingReceiveMultiplier( *actor, heal.first ); heal.first = Math::CalcStats::applyHealingReceiveMultiplier( *actor, heal.first );
m_effectBuilder->heal( actor, actor, heal.first, heal.second, Common::ActionEffectResultFlag::None, getExecutionDelay() + victimCounter * 100 ); m_effectBuilder->heal( actor, actor, heal.first, heal.second, Common::ActionEffectResultFlag::None, getExecutionDelay() + victimCounter * 100 );
} }
if( isValidVictim )
{
if( ( !isComboAction() || isCorrectCombo() ) )
{
if ( !m_actionData->preservesCombo ) // this matches retail packet, on all standalone actions even casts.
{
m_effectBuilder->startCombo( actor, getId() ); // this is on all targets hit
}
}
if( m_lutEntry.bonusEffect & Common::ActionBonusEffect::SelfHeal )
{
if( checkActionBonusRequirement() )
{
auto heal = calcHealing( m_lutEntry.getSelfHealPotency() );
heal.first = Math::CalcStats::applyHealingReceiveMultiplier( *m_pSource, heal.first );
m_effectBuilder->heal( actor, m_pSource, heal.first, heal.second, Common::ActionEffectResultFlag::EffectOnSource );
}
}
if( validVictimCounter == 0 ) // effects only apply once if aoe, on first valid target (can be single target action as well)
{
if( isCorrectCombo() )
m_effectBuilder->comboSucceed( actor );
if( m_isAutoAttack && player )
{
if( player->getClass() == Common::ClassJob::Paladin )
{
player->gaugePldSetOath( std::min( 100, player->gaugePldGetOath() + 5 ) );
}
}
if( m_lutEntry.bonusEffect & Common::ActionBonusEffect::GainMPPercentage )
{
if( checkActionBonusRequirement() )
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.getMPGainPercentage() / 100, Common::ActionEffectResultFlag::EffectOnSource );
}
if( ( m_lutEntry.bonusEffect & Common::ActionBonusEffect::GainJobResource ) && player )
{
if( checkActionBonusRequirement() )
{
switch( m_lutEntry.getAffectedJob() )
{
case Common::ClassJob::Marauder:
case Common::ClassJob::Warrior:
{
player->gaugeWarSetIb( std::min( 100, player->gaugeWarGetIb() + m_lutEntry.getJobResourceGain() ) );
break;
}
case Common::ClassJob::Darkknight:
{
player->gaugeDrkSetBlood( std::min( 100, player->gaugeDrkGetBlood() + m_lutEntry.getJobResourceGain() ) );
break;
}
case Common::ClassJob::Gunbreaker:
{
player->gaugeGnbSetAmmo( std::min( 2, player->gaugeGnbGetAmmo() + m_lutEntry.getJobResourceGain() ) );
break;
}
case Common::ClassJob::Samurai:
{
player->gaugeSamSetKenki( std::min( 100, player->gaugeSamGetKenki() + m_lutEntry.getJobResourceGain() ) );
break;
}
}
}
}
if( ( m_lutEntry.bonusEffect & Common::ActionBonusEffect::GainJobTimer ) && player )
{
if( checkActionBonusRequirement() )
{
switch( m_lutEntry.getAffectedJob() )
{
case Common::ClassJob::Darkknight:
{
player->gaugeDrkSetDarkSideTimer( std::min( 60000, player->gaugeDrkGetDarkSideTimer() + m_lutEntry.getJobTimerGain() ), true );
break;
}
case Common::ClassJob::Dragoon:
{
player->gaugeDrgSetDragonTimer( std::min( 30000, player->gaugeDrgGetDragonTimer() + m_lutEntry.getJobTimerGain() ), true );
break;
}
}
}
}
}
if( validVictimCounter == 0 )
firstValidVictim = actor;
validVictimCounter++;
}
if( m_lutEntry.targetStatus != 0 ) if( m_lutEntry.targetStatus != 0 )
{ {
if( shouldTriggerActionBonus || actor->getObjKind() == m_pSource->getObjKind() /* is friendly target, this will do for now */ ) if( isValidVictim || actor->getObjKind() == m_pSource->getObjKind() /* is friendly target, this will do for now */ )
{ {
if( !isComboAction() || isCorrectCombo() ) 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() ) else if( actor->hasInvulnerableEffect() )
{ {
@ -735,9 +735,9 @@ void Action::Action::buildEffects()
if( !isComboAction() || isCorrectCombo() ) if( !isComboAction() || isCorrectCombo() )
{ {
if( firstValidVictim ) 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 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() );
} }
} }
@ -1168,6 +1168,9 @@ void Action::Action::addDefaultActorFilters()
bool Action::Action::preFilterActor( Sapphire::Entity::Actor& actor ) const bool Action::Action::preFilterActor( Sapphire::Entity::Actor& actor ) const
{ {
if( m_castType == Common::CastType::SingleTarget ) // trust the target sent by client is valid
return true;
auto kind = actor.getObjKind(); auto kind = actor.getObjKind();
auto chara = actor.getAsChara(); auto chara = actor.getAsChara();
@ -1175,19 +1178,16 @@ bool Action::Action::preFilterActor( Sapphire::Entity::Actor& actor ) const
if( kind != ObjKind::BattleNpc && kind != ObjKind::Player ) if( kind != ObjKind::BattleNpc && kind != ObjKind::Player )
return false; return false;
if( m_lutEntry.damagePotency > 0 && chara->getId() == m_pSource->getId() ) // !m_canTargetSelf if( chara->isAlive() && m_lutEntry.healPotency > 0 && m_pSource->getObjKind() == chara->getObjKind() )
return false; return true;
if( ( m_lutEntry.damagePotency > 0 || m_lutEntry.healPotency > 0 ) && !chara->isAlive() ) // !m_canTargetDead not working for aoe
return false;
if( m_lutEntry.damagePotency > 0 && m_pSource->getObjKind() == chara->getObjKind() ) // !m_canTargetFriendly not working for aoe if( chara->isAlive() && m_lutEntry.damagePotency > 0 && m_pSource->getObjKind() != chara->getObjKind() )
return false; return true;
if( ( m_lutEntry.damagePotency == 0 && m_lutEntry.healPotency > 0 ) && m_pSource->getObjKind() != chara->getObjKind() ) // !m_canTargetHostile not working for aoe
return false;
return true; if( !chara->isAlive() && m_lutEntry.damagePotency == 0 && m_lutEntry.healPotency == 0 )
return true;
return false;
} }
std::vector< Sapphire::Entity::CharaPtr >& Action::Action::getHitCharas() std::vector< Sapphire::Entity::CharaPtr >& Action::Action::getHitCharas()
@ -1334,4 +1334,75 @@ uint64_t Action::Action::getExecutionDelay() const
{ {
// let's see how 3.x is going to do it // let's see how 3.x is going to do it
return 600; 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;
}
}
} }

View file

@ -176,6 +176,8 @@ namespace Sapphire::World::Action
bool hasValidLutEntry() const; 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; uint32_t m_id;
uint16_t m_sequence; uint16_t m_sequence;

View file

@ -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 uint32_t Sapphire::World::Action::ActionEntry::getRawBonusData() const
@ -429,3 +454,25 @@ uint16_t Sapphire::World::Action::ActionEntry::getDirectHitRateBonus() const
return bonusDataUInt16L; return bonusDataUInt16L;
return 0; 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;
}

View file

@ -40,9 +40,14 @@ namespace Sapphire::World::Action
uint8_t bonusDataByte4; uint8_t bonusDataByte4;
}; };
}; };
uint8_t selfStatusRefreshPolicy;
int32_t selfStatusRefreshValue;
uint8_t targetStatusRefreshPolicy;
int32_t targetStatusRefreshValue;
public: public:
ActionEntry() = default; 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 );
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; uint32_t getRawBonusData() const;
uint8_t getDamageFallOffPercentage() const; // as the result percentage for 2nd (or more) victims, not the percentage to subtract from 100% uint8_t getDamageFallOffPercentage() const; // as the result percentage for 2nd (or more) victims, not the percentage to subtract from 100%
uint16_t getSelfHealPotency() const; uint16_t getSelfHealPotency() const;
@ -52,6 +57,10 @@ namespace Sapphire::World::Action
uint16_t getJobTimerGain() const; uint16_t getJobTimerGain() const;
uint16_t getCritRateBonus() const; uint16_t getCritRateBonus() const;
uint16_t getDirectHitRateBonus() const; uint16_t getDirectHitRateBonus() const;
Common::StatusRefreshPolicy getSelfStatusRefreshPolicy( bool sameSource );
Common::StatusRefreshPolicy getTargetStatusRefreshPolicy( bool sameSource );
int32_t getSelfStatusRefreshValue();
int32_t getTargetStatusRefreshValue();
}; };
struct StatusEffectEntry struct StatusEffectEntry

View file

@ -135,14 +135,15 @@ ActionLut::Lut ActionLut::m_actionLut =
{ 31, { 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { 31, { 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
//Maim, メイム //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 //has bonus effect: GainJobResource, 169148416
//bonus effect requirement: RequireCorrectCombo //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, バーサク //Berserk, バーサク
//applies to self: Berserk, バーサク, duration 10000, param 0 //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, オーバーパワー //Overpower, オーバーパワー
//has damage: potency 130, combo potency 0, directional potency 0 //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 } }, { 46, { 140, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
//Storm's Path, シュトルムヴィント //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 //has bonus effect: 24, 336920826
//bonus effect requirement: RequireCorrectCombo //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, スリル・オブ・バトル //Thrill of Battle, スリル・オブ・バトル
//applies to targets: Thrill of Battle, スリル・オブ・バトル, duration 20000, param 0 //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 } }, { 43, { 0, 0, 0, 0, 409, 8000, 0, 88, 8000, 0, 0, 0, 0 } },
//Storm's Eye, シュトルムブレハ //Storm's Eye, シュトルムブレハ
//has damage: potency 100, combo potency 380, directional potency 0 //has damage: potency 100, combo potency 420, directional potency 0
//applies to self: Storm's Eye, シュトルムブレハ, duration 30000, param 0 //applies to self: Storm's Eye, シュトルムブレハ, duration 30000, param 0, ExtendOrApply, ExtendOrApply, 60000
//has bonus effect: GainJobResource, 169148416 //has bonus effect: GainJobResource, 169148416
//bonus effect requirement: RequireCorrectCombo //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, 原初の魂 //Inner Beast, 原初の魂
//has damage: potency 350, combo potency 0, directional potency 0 //has damage: potency 350, combo potency 0, directional potency 0
@ -191,7 +192,8 @@ ActionLut::Lut ActionLut::m_actionLut =
//Mythril Tempest, ミスリルテンペスト //Mythril Tempest, ミスリルテンペスト
//has damage: potency 100, combo potency 200, directional potency 0 //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, スチールサイクロン //Steel Cyclone, スチールサイクロン
//has damage: potency 220, combo potency 0, directional potency 0 //has damage: potency 220, combo potency 0, directional potency 0
@ -231,7 +233,8 @@ ActionLut::Lut ActionLut::m_actionLut =
//Inner Release, 原初の解放 //Inner Release, 原初の解放
//applies to self: Inner Release, 原初の解放, duration 10000, param 65436 //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, カオティックサイクロン //Chaotic Cyclone, カオティックサイクロン
//has damage: potency 400, combo potency 0, directional potency 0 //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 } }, { 16463, { 400, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 100 } },
//Nascent Flash, 原初の猛り //Nascent Flash, 原初の猛り
//applies to targets: Sleep, 睡眠, duration 30000, param 0 //applies to targets: Nascent Flash, 原初の猛り, duration 6000, param 0
{ 16464, { 0, 0, 0, 0, 0, 0, 0, 3, 30000, 0, 0, 0, 0 } }, { 16464, { 0, 0, 0, 0, 0, 0, 0, 1857, 6000, 0, 0, 0, 0 } },
//Inner Chaos, インナーカオス //Inner Chaos, インナーカオス
//has damage: potency 920, combo potency 0, directional potency 0 //has damage: potency 920, combo potency 0, directional potency 0
@ -1834,8 +1837,8 @@ ActionLut::Lut ActionLut::m_actionLut =
{ 7431, { 280, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { 7431, { 280, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
//Divine Benison, ディヴァインベニゾン //Divine Benison, ディヴァインベニゾン
//applies to targets: Divine Benison, ディヴァインベニゾン, duration 15000, param 0 //applies to targets: Divine Benison, ディヴァインベニゾン, duration 15000, param 0 >> moved to script
{ 7432, { 0, 0, 0, 0, 0, 0, 0, 1218, 15000, 0, 0, 0, 0 } }, { 7432, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
//Plenary Indulgence, インドゥルゲンティア //Plenary Indulgence, インドゥルゲンティア
//applies to targets: Confession, インドゥルゲンティア, duration 10000, param 0 //applies to targets: Confession, インドゥルゲンティア, duration 10000, param 0
@ -1860,8 +1863,8 @@ ActionLut::Lut ActionLut::m_actionLut =
{ 16534, { 0, 0, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }, { 16534, { 0, 0, 0, 300, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
//Temperance, テンパランス //Temperance, テンパランス
//applies to targets: Temperance, テンパランス:効果, duration 20000, param 0 //applies to targets: Temperance, テンパランス, duration 20000, param 0
{ 16536, { 0, 0, 0, 0, 0, 0, 0, 1873, 20000, 0, 0, 0, 0 } }, { 16536, { 0, 0, 0, 0, 0, 0, 0, 1872, 20000, 0, 0, 0, 0 } },
//Ruin, ルイン //Ruin, ルイン
//has damage: potency 160, combo potency 0, directional potency 0 //has damage: potency 160, combo potency 0, directional potency 0
@ -3557,4 +3560,7 @@ ActionLut::StatusEffectTable ActionLut::m_statusEffectTable =
//Walking Dead, ウォーキングデッド: CannotDie //Walking Dead, ウォーキングデッド: CannotDie
{ 811, { 19, 0, 0, 0, 0 } }, { 811, { 19, 0, 0, 0, 0 } },
//Divine Benison, ディヴァインベニゾン (scripted)
{ 1218, { 0, 0, 0, 0, 0 } },
}; };

View file

@ -99,17 +99,17 @@ void EffectBuilder::comboSucceed( Entity::CharaPtr& target )
moveToResultList( target, nextResult ); 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 ); 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 ); 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 ); EffectResultPtr nextResult = make_EffectResult( target, source, Common::Util::getTimeMs() + resultDelayMs );
nextResult->applyStatusEffect( pStatusEffect, statusToSource ); nextResult->applyStatusEffect( pStatusEffect, statusToSource, shouldReuse );
moveToResultList( target, nextResult ); moveToResultList( target, nextResult );
} }

View file

@ -36,8 +36,8 @@ namespace Sapphire::World::Action
void comboSucceed( Entity::CharaPtr& target ); 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, 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 ); 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 ); void statusNoEffect( Entity::CharaPtr& target, uint16_t statusId );

View file

@ -20,7 +20,8 @@ EffectResult::EffectResult( Entity::CharaPtr target, Entity::CharaPtr source, ui
m_param1( 0 ), m_param1( 0 ),
m_param2( 0 ), m_param2( 0 ),
m_flag( Common::ActionEffectResultFlag::None ), 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; 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_value = statusId;
m_statusDuration = duration; m_statusDuration = duration;
m_param2 = param; m_param2 = param;
m_flag = statusToSource ? Common::ActionEffectResultFlag::EffectOnSource : Common::ActionEffectResultFlag::None; m_flag = statusToSource ? Common::ActionEffectResultFlag::EffectOnSource : Common::ActionEffectResultFlag::None;
m_statusShouldReuse = shouldReuse;
m_type = statusToSource ? Common::ActionEffectType::ApplyStatusEffectSource : Common::ActionEffectType::ApplyStatusEffectTarget; 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_value = pStatusEffect->getId();
m_param2 = pStatusEffect->getParam(); m_param2 = pStatusEffect->getParam();
m_pPreBuiltStatusEffect = std::move( pStatusEffect ); m_pPreBuiltStatusEffect = std::move( pStatusEffect );
m_flag = statusToSource ? Common::ActionEffectResultFlag::EffectOnSource : Common::ActionEffectResultFlag::None; m_flag = statusToSource ? Common::ActionEffectResultFlag::EffectOnSource : Common::ActionEffectResultFlag::None;
m_statusShouldReuse = shouldReuse;
m_type = statusToSource ? Common::ActionEffectType::ApplyStatusEffectSource : Common::ActionEffectType::ApplyStatusEffectTarget; m_type = statusToSource ? Common::ActionEffectType::ApplyStatusEffectSource : Common::ActionEffectType::ApplyStatusEffectTarget;
} }
@ -224,7 +225,7 @@ void EffectResult::execute()
for( auto const& entry : applyTarget->getStatusEffectMap() ) for( auto const& entry : applyTarget->getStatusEffectMap() )
{ {
auto statusEffect = entry.second; auto statusEffect = entry.second;
if( statusEffect->getId() == m_value && statusEffect->getSrcActorId() == m_source->getId() ) if( statusEffect->getId() == m_value && m_statusShouldReuse )
{ {
if( m_pPreBuiltStatusEffect ) if( m_pPreBuiltStatusEffect )
{ {
@ -232,7 +233,7 @@ void EffectResult::execute()
} }
else else
{ {
statusEffect->refresh(); statusEffect->refresh( m_statusDuration );
} }
applyTarget->sendStatusEffectUpdate(); applyTarget->sendStatusEffectUpdate();
return; return;

View file

@ -26,8 +26,8 @@ namespace Sapphire::World::Action
void restoreMP( uint32_t amount, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None ); void restoreMP( uint32_t amount, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None );
void startCombo( uint16_t actionId ); void startCombo( uint16_t actionId );
void comboSucceed(); void comboSucceed();
void applyStatusEffect( uint16_t statusId, uint32_t duration, uint16_t param, 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 ); void applyStatusEffect( StatusEffect::StatusEffectPtr pStatusEffect, bool statusToSource = false, bool shouldReuse = true );
void statusNoEffect( uint16_t statusId ); void statusNoEffect( uint16_t statusId );
void mount( uint16_t mountId ); void mount( uint16_t mountId );
void provoke(); void provoke();
@ -61,6 +61,7 @@ namespace Sapphire::World::Action
Common::ActionEffectResultFlag m_flag; Common::ActionEffectResultFlag m_flag;
StatusEffect::StatusEffectPtr m_pPreBuiltStatusEffect; StatusEffect::StatusEffectPtr m_pPreBuiltStatusEffect;
bool m_statusShouldReuse;
}; };
} }

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

@ -83,6 +83,7 @@ Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX
m_pCurrentTerritory = std::move( pZone ); m_pCurrentTerritory = std::move( pZone );
m_spawnPos = m_pos; m_spawnPos = m_pos;
m_isMoving = false;
m_timeOfDeath = 0; m_timeOfDeath = 0;
m_targetId = Common::INVALID_GAME_OBJECT_ID64; 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; m_radius *= modelSkeleton->radius;
} }
// todo: is this actually good? m_naviTargetReachedDistance = m_radius;
//m_naviTargetReachedDistance = m_scale * 2.f;
m_naviTargetReachedDistance = 4.f;
calculateStats(); calculateStats();
auto& scriptMgr = Common::Service< Sapphire::Scripting::ScriptMgr >::ref(); auto& scriptMgr = Common::Service< Sapphire::Scripting::ScriptMgr >::ref();
@ -194,7 +193,7 @@ void Sapphire::Entity::BNpc::setState( BNpcState state )
m_state = 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(); auto pNaviProvider = m_pCurrentTerritory->getNaviProvider();
@ -208,14 +207,15 @@ bool Sapphire::Entity::BNpc::moveTo( const FFXIVARR_POSITION3& pos )
} }
auto pos1 = pNaviProvider->getMovePos( *this ); auto pos1 = pNaviProvider->getMovePos( *this );
if( Util::distance( pos1, pos ) < getRadius() + 3.f ) if( Util::distance( pos1, pos ) < getNaviTargetReachedDistance() + radius )
{ {
// Reached destination // Reached destination
face( pos ); face( pos );
setPos( pos1 ); setPos( pos1 );
sendPositionUpdate(); sendPositionUpdate();
pNaviProvider->updateAgentPosition( *this ); pNaviProvider->updateAgentPosition( *this );
m_isMoving = false;
return true; return true;
} }
@ -223,59 +223,38 @@ bool Sapphire::Entity::BNpc::moveTo( const FFXIVARR_POSITION3& pos )
face( pos ); face( pos );
setPos( pos1 ); setPos( pos1 );
sendPositionUpdate(); sendPositionUpdate();
m_isMoving = true;
return false; return false;
} }
bool Sapphire::Entity::BNpc::moveTo( const Entity::Chara& targetChara ) bool Sapphire::Entity::BNpc::moveTo( const Entity::Chara& targetChara )
{ {
return moveTo( targetChara.getPos(), targetChara.getRadius() );
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;
} }
void Sapphire::Entity::BNpc::stopMoving() void Sapphire::Entity::BNpc::stopMoving()
{ {
auto pNaviProvider = m_pCurrentTerritory->getNaviProvider(); auto pNaviProvider = m_pCurrentTerritory->getNaviProvider();
if( !pNaviProvider ) if( !pNaviProvider || !m_isMoving )
return; return;
sendPositionUpdate(); sendPositionUpdate();
pNaviProvider->updateAgentPosition( *this ); pNaviProvider->updateAgentPosition( *this );
m_isMoving = false;
} }
void Sapphire::Entity::BNpc::sendPositionUpdate() void Sapphire::Entity::BNpc::sendPositionUpdate()
{ {
uint8_t unk1 = 0x3a; uint8_t unk1 = 0x3a;
uint8_t animationType = 2; uint8_t animationType = 2;
uint16_t moveSpeed = MoveSpeed::Walk;
if( m_state == BNpcState::Combat || m_state == BNpcState::Retreat ) if( m_state == BNpcState::Combat || m_state == BNpcState::Retreat )
{
animationType = 0; 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 ); sendToInRangeSet( movePacket );
} }
@ -329,7 +308,7 @@ void Sapphire::Entity::BNpc::hateListAddOrUpdate( Sapphire::Entity::CharaPtr pCh
if( pChara->isPlayer() ) if( pChara->isPlayer() )
{ {
auto pPlayer = pChara->getAsPlayer(); auto pPlayer = pChara->getAsPlayer();
pPlayer->hateListAdd( getAsBNpc() ); pPlayer->onMobAggro( getAsBNpc() );
} }
auto& scriptMgr = Common::Service< Sapphire::Scripting::ScriptMgr >::ref(); auto& scriptMgr = Common::Service< Sapphire::Scripting::ScriptMgr >::ref();
scriptMgr.onBNpcHateListAdd( *this, *pChara ); scriptMgr.onBNpcHateListAdd( *this, *pChara );
@ -496,6 +475,7 @@ void Sapphire::Entity::BNpc::doDefaultBNpcUpdate( uint64_t tickCount )
m_roamPos = pNaviProvider->findRandomPositionInCircle( m_spawnPos, 5 ); m_roamPos = pNaviProvider->findRandomPositionInCircle( m_spawnPos, 5 );
m_state = BNpcState::Roaming; m_state = BNpcState::Roaming;
pNaviProvider->updateAgentParameters( *this );
} }
checkAggro(); checkAggro();
@ -527,9 +507,6 @@ void Sapphire::Entity::BNpc::doDefaultBNpcUpdate( uint64_t tickCount )
return; return;
} }
if( pNaviProvider->syncPosToChara( *this ) )
sendPositionUpdate();
auto distance = Util::distance( getPos().x, getPos().y, getPos().z, auto distance = Util::distance( getPos().x, getPos().y, getPos().z,
pHatedActor->getPos().x, pHatedActor->getPos().y, pHatedActor->getPos().z ); pHatedActor->getPos().x, pHatedActor->getPos().y, pHatedActor->getPos().z );
@ -543,7 +520,7 @@ void Sapphire::Entity::BNpc::doDefaultBNpcUpdate( uint64_t tickCount )
break; break;
} }
if( distance > ( getRadius() + pHatedActor->getRadius() ) ) if( distance > ( getNaviTargetReachedDistance() + pHatedActor->getRadius() ) )
{ {
if( hasFlag( Immobile ) ) if( hasFlag( Immobile ) )
break; break;
@ -556,6 +533,8 @@ void Sapphire::Entity::BNpc::doDefaultBNpcUpdate( uint64_t tickCount )
if( distance < ( getRadius() + pHatedActor->getRadius() + 3.f ) ) if( distance < ( getRadius() + pHatedActor->getRadius() + 3.f ) )
{ {
stopMoving();
if( !hasFlag( TurningDisabled ) && face( pHatedActor->getPos() ) ) if( !hasFlag( TurningDisabled ) && face( pHatedActor->getPos() ) )
sendPositionUpdate(); sendPositionUpdate();

View file

@ -75,7 +75,7 @@ namespace Sapphire::Entity
float getNaviTargetReachedDistance() const; float getNaviTargetReachedDistance() const;
// return true if it reached the position // 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 ); bool moveTo( const Entity::Chara& targetChara );
@ -149,6 +149,7 @@ namespace Sapphire::Entity
Common::FFXIVARR_POSITION3 m_spawnPos; Common::FFXIVARR_POSITION3 m_spawnPos;
Common::FFXIVARR_POSITION3 m_roamPos; Common::FFXIVARR_POSITION3 m_roamPos;
bool m_isMoving;
BNpcState m_state; BNpcState m_state;
std::set< std::shared_ptr< HateListEntry > > m_hateList; std::set< std::shared_ptr< HateListEntry > > m_hateList;

View file

@ -1056,7 +1056,7 @@ void Sapphire::Entity::Chara::onTick()
} }
} }
if( thisTickDmg != 0 ) if( thisTickDmg != 0 && isAlive() )
{ {
thisTickDmg = applyShieldProtection( thisTickDmg ); thisTickDmg = applyShieldProtection( thisTickDmg );
if( thisTickDmg > 0 ) if( thisTickDmg > 0 )
@ -1065,7 +1065,7 @@ void Sapphire::Entity::Chara::onTick()
static_cast< uint8_t >( ActionEffectType::Damage ), thisTickDmg ), true ); static_cast< uint8_t >( ActionEffectType::Damage ), thisTickDmg ), true );
} }
if( thisTickHeal != 0 ) if( thisTickHeal != 0 && isAlive() )
{ {
heal( thisTickHeal ); heal( thisTickHeal );
sendToInRangeSet( makeActorControl( getId(), HPFloatingText, 0, sendToInRangeSet( makeActorControl( getId(), HPFloatingText, 0,

View file

@ -87,6 +87,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;
@ -136,7 +137,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
@ -249,6 +251,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() );
@ -1201,13 +1244,13 @@ void Sapphire::Entity::Player::update( uint64_t tickCount )
auto chara = actor->getAsChara(); auto chara = actor->getAsChara();
// default autoattack range // default autoattack range
float range = 3.f + chara->getRadius(); float range = 3.f + chara->getRadius() + getRadius() * 0.5f;
// default autoattack range for ranged classes // default autoattack range for ranged classes
if( getClass() == ClassJob::Machinist || if( getClass() == ClassJob::Machinist ||
getClass() == ClassJob::Bard || getClass() == ClassJob::Bard ||
getClass() == ClassJob::Archer ) getClass() == ClassJob::Archer )
range = 25; range = 25.f + chara->getRadius() + getRadius() * 0.5f;
if( Util::distance( getPos().x, getPos().y, getPos().z, if( Util::distance( getPos().x, getPos().y, getPos().z,
@ -1434,6 +1477,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();
@ -1606,14 +1657,17 @@ void Sapphire::Entity::Player::sendHateList()
void Sapphire::Entity::Player::onMobAggro( BNpcPtr pBNpc ) void Sapphire::Entity::Player::onMobAggro( BNpcPtr pBNpc )
{ {
hateListAdd( pBNpc ); hateListAdd( pBNpc );
queuePacket( makeActorControl( getId(), ToggleAggro, 1 ) ); if( !hasStateFlag( Common::PlayerStateFlag::InCombat ) )
setStateFlag( Common::PlayerStateFlag::InCombat ); {
queuePacket( makeActorControl( getId(), ToggleAggro, 1 ) );
setStateFlag( Common::PlayerStateFlag::InCombat );
}
} }
void Sapphire::Entity::Player::onMobDeaggro( BNpcPtr pBNpc ) void Sapphire::Entity::Player::onMobDeaggro( BNpcPtr pBNpc )
{ {
hateListRemove( pBNpc ); hateListRemove( pBNpc );
if( m_actorIdTohateSlotMap.empty() ) if( m_actorIdTohateSlotMap.empty() && hasStateFlag( Common::PlayerStateFlag::InCombat ) )
{ {
queuePacket( makeActorControl( getId(), ToggleAggro ) ); queuePacket( makeActorControl( getId(), ToggleAggro ) );
unsetStateFlag( Common::PlayerStateFlag::InCombat ); unsetStateFlag( Common::PlayerStateFlag::InCombat );
@ -2401,6 +2455,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

@ -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 );
@ -1070,6 +1076,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;
@ -1223,6 +1232,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

@ -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

@ -0,0 +1,436 @@
#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 )
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

@ -24,9 +24,9 @@ Sapphire::World::Navi::NaviProvider::NaviProvider( const std::string& internalNa
m_internalName( internalName ) m_internalName( internalName )
{ {
// Set defaults // Set defaults
m_polyFindRange[ 0 ] = 10; m_polyFindRange[ 0 ] = 20;
m_polyFindRange[ 1 ] = 20; m_polyFindRange[ 1 ] = 20;
m_polyFindRange[ 2 ] = 10; m_polyFindRange[ 2 ] = 20;
} }
bool Sapphire::World::Navi::NaviProvider::init() bool Sapphire::World::Navi::NaviProvider::init()
@ -355,7 +355,7 @@ std::vector< Sapphire::Common::FFXIVARR_POSITION3 >
// iterPos[ 0 ], iterPos[ 1 ], iterPos[ 2 ], // iterPos[ 0 ], iterPos[ 1 ], iterPos[ 2 ],
// targetPos[ 0 ], targetPos[ 1 ], targetPos[ 2 ] ); // targetPos[ 0 ], targetPos[ 1 ], targetPos[ 2 ] );
const float STEP_SIZE = 1.2f; const float STEP_SIZE = 0.5f;
const float SLOP = 0.15f; const float SLOP = 0.15f;
int32_t numSmoothPath = 0; int32_t numSmoothPath = 0;
@ -573,7 +573,8 @@ int32_t Sapphire::World::Navi::NaviProvider::addAgent( Entity::Chara& chara )
std::memset( &params, 0, sizeof( params ) ); std::memset( &params, 0, sizeof( params ) );
params.height = 3.f; params.height = 3.f;
params.maxAcceleration = 25.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.radius = ( chara.getRadius() ) * 0.75f;
params.collisionQueryRange = params.radius * 12.0f; params.collisionQueryRange = params.radius * 12.0f;
params.pathOptimizationRange = params.radius * 20.0f; params.pathOptimizationRange = params.radius * 20.0f;
@ -589,9 +590,10 @@ void Sapphire::World::Navi::NaviProvider::updateAgentParameters( Entity::BNpc& b
std::memset( &params, 0, sizeof( params ) ); std::memset( &params, 0, sizeof( params ) );
params.height = 3.f; params.height = 3.f;
params.maxAcceleration = 25.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 ) 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.radius = ( bnpc.getRadius() ) * 0.75f;
params.collisionQueryRange = params.radius * 12.0f; params.collisionQueryRange = params.radius * 12.0f;
params.pathOptimizationRange = params.radius * 20.0f; params.pathOptimizationRange = params.radius * 20.0f;

View file

@ -141,7 +141,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

@ -177,6 +177,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 );
@ -198,6 +200,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

@ -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"
@ -438,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
@ -598,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

View file

@ -341,6 +341,12 @@ void Sapphire::StatusEffect::StatusEffect::refresh()
applyStatus(); applyStatus();
} }
void Sapphire::StatusEffect::StatusEffect::refresh( uint32_t newDuration )
{
m_duration = newDuration;
refresh();
}
void Sapphire::StatusEffect::StatusEffect::refresh( Sapphire::World::Action::StatusEffectEntry newEntry ) void Sapphire::StatusEffect::StatusEffect::refresh( Sapphire::World::Action::StatusEffectEntry newEntry )
{ {
m_effectEntry = newEntry; m_effectEntry = newEntry;

View file

@ -68,6 +68,7 @@ public:
void markToRemove(); void markToRemove();
void refresh(); void refresh();
void refresh( uint32_t newDuration );
void refresh( Sapphire::World::Action::StatusEffectEntry newEntry ); void refresh( Sapphire::World::Action::StatusEffectEntry newEntry );
private: private: