1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-26 14:37:44 +00:00

Merge pull request #535 from NotAdam/develop

more stat calc functions, combo length validation
This commit is contained in:
Mordred 2019-03-26 08:17:22 +01:00 committed by GitHub
commit 5d3ae12a26
9 changed files with 455 additions and 162 deletions

View file

@ -21,6 +21,13 @@ namespace Sapphire::Common
const int32_t INVALID_GAME_OBJECT_ID = 0xE0000000;
const uint64_t INVALID_GAME_OBJECT_ID64 = 0xE0000000;
/*!
* @brief The maximum length (in ms) of a combo before it is canceled/voided.
*
* The client has a combo timer of about 12 seconds, with a 0.5 second grace on top for latency considerations.
*/
const uint16_t MAX_COMBO_LENGTH = 12500;
struct FFXIVARR_POSITION3_U16
{
uint16_t x;

View file

@ -1,126 +1,126 @@
#include "CommonNetwork.h"
#include "GamePacketParser.h"
#include <string.h> // memcpy
using namespace Sapphire::Network::Packets;
PacketParseResult Sapphire::Network::Packets::getHeader( const std::vector< uint8_t >& buffer,
const uint32_t offset,
FFXIVARR_PACKET_HEADER& header )
{
const auto headerSize = sizeof( FFXIVARR_PACKET_HEADER );
// Check if we have enough bytes in the buffer.
auto remainingBytes = buffer.size() - offset;
if( remainingBytes < headerSize )
return Incomplete;
// Copy packet header.
memcpy( &header, buffer.data() + offset, headerSize );
if( !checkHeader( header ) )
return Malformed;
return Success;
}
PacketParseResult Sapphire::Network::Packets::getSegmentHeader( const std::vector< uint8_t >& buffer,
const uint32_t offset,
FFXIVARR_PACKET_SEGMENT_HEADER& header )
{
const auto headerSize = sizeof( FFXIVARR_PACKET_SEGMENT_HEADER );
// Check if we have enough bytes in the buffer.
auto remainingBytes = buffer.size() - offset;
if( remainingBytes < headerSize )
return Incomplete;
// Copy segment header
memcpy( &header, buffer.data() + offset, headerSize );
return Success;
}
PacketParseResult Sapphire::Network::Packets::getPackets( const std::vector< uint8_t >& buffer,
const uint32_t offset,
const FFXIVARR_PACKET_HEADER& packetHeader,
std::vector< FFXIVARR_PACKET_RAW >& packets )
{
// sanity check: check there's enough bytes in the buffer
const auto bytesExpected = packetHeader.size - sizeof( struct FFXIVARR_PACKET_HEADER );
if( buffer.size() - offset < bytesExpected )
return Incomplete;
// Loop each message
uint32_t count = 0;
uint32_t bytesProcessed = 0;
while( count < packetHeader.count )
{
FFXIVARR_PACKET_RAW rawPacket;
// Copy ipc packet message
const auto packetResult = getPacket( buffer, offset + bytesProcessed, rawPacket );
if( packetResult != Success )
return packetResult;
// NOTE: isn't rawPacket is allocated on stack?
// why is okay to do this?
packets.push_back( rawPacket );
// Add message size and count
bytesProcessed += rawPacket.segHdr.size;
count += 1;
}
// sanity check: check if we processed all bytes.
// this check can fail if size of messages don't add up to size reported from packet header.
if( bytesExpected != bytesProcessed )
return Malformed;
return Success;
}
PacketParseResult Sapphire::Network::Packets::getPacket( const std::vector< uint8_t >& buffer, const uint32_t offset,
FFXIVARR_PACKET_RAW& packet )
{
// Copy segment header
const auto headerResult = getSegmentHeader( buffer, offset, packet.segHdr );
if( headerResult != Success )
return headerResult;
// Check header sanity and it's size
if( !checkSegmentHeader( packet.segHdr ) )
return Malformed;
const auto dataOffset = offset + sizeof( struct FFXIVARR_PACKET_SEGMENT_HEADER );
const auto dataSize = packet.segHdr.size;
// Allocate data buffer and copy
packet.data.resize( dataSize );
memcpy( packet.data.data(), buffer.data() + dataOffset, dataSize );
return Success;
}
bool Sapphire::Network::Packets::checkHeader( const FFXIVARR_PACKET_HEADER& header )
{
// Max size of the packet is capped at 1MB for now.
if( header.size > 1 * 1024 * 1024 )
return false;
// Max number of message is capped at 255 for now.
if( header.count > 255 )
return false;
return true;
}
bool Sapphire::Network::Packets::checkSegmentHeader( const FFXIVARR_PACKET_SEGMENT_HEADER& header )
{
// Max size of individual message is capped at 256KB for now.
if( header.size > 256 * 1024 )
return false;
return true;
}
#include "CommonNetwork.h"
#include "GamePacketParser.h"
#include <string.h> // memcpy
using namespace Sapphire::Network::Packets;
PacketParseResult Sapphire::Network::Packets::getHeader( const std::vector< uint8_t >& buffer,
const uint32_t offset,
FFXIVARR_PACKET_HEADER& header )
{
const auto headerSize = sizeof( FFXIVARR_PACKET_HEADER );
// Check if we have enough bytes in the buffer.
auto remainingBytes = buffer.size() - offset;
if( remainingBytes < headerSize )
return Incomplete;
// Copy packet header.
memcpy( &header, buffer.data() + offset, headerSize );
if( !checkHeader( header ) )
return Malformed;
return Success;
}
PacketParseResult Sapphire::Network::Packets::getSegmentHeader( const std::vector< uint8_t >& buffer,
const uint32_t offset,
FFXIVARR_PACKET_SEGMENT_HEADER& header )
{
const auto headerSize = sizeof( FFXIVARR_PACKET_SEGMENT_HEADER );
// Check if we have enough bytes in the buffer.
auto remainingBytes = buffer.size() - offset;
if( remainingBytes < headerSize )
return Incomplete;
// Copy segment header
memcpy( &header, buffer.data() + offset, headerSize );
return Success;
}
PacketParseResult Sapphire::Network::Packets::getPackets( const std::vector< uint8_t >& buffer,
const uint32_t offset,
const FFXIVARR_PACKET_HEADER& packetHeader,
std::vector< FFXIVARR_PACKET_RAW >& packets )
{
// sanity check: check there's enough bytes in the buffer
const auto bytesExpected = packetHeader.size - sizeof( struct FFXIVARR_PACKET_HEADER );
if( buffer.size() - offset < bytesExpected )
return Incomplete;
// Loop each message
uint32_t count = 0;
uint32_t bytesProcessed = 0;
while( count < packetHeader.count )
{
FFXIVARR_PACKET_RAW rawPacket;
// Copy ipc packet message
const auto packetResult = getPacket( buffer, offset + bytesProcessed, rawPacket );
if( packetResult != Success )
return packetResult;
// NOTE: isn't rawPacket is allocated on stack?
// why is okay to do this?
packets.push_back( rawPacket );
// Add message size and count
bytesProcessed += rawPacket.segHdr.size;
count += 1;
}
// sanity check: check if we processed all bytes.
// this check can fail if size of messages don't add up to size reported from packet header.
if( bytesExpected != bytesProcessed )
return Malformed;
return Success;
}
PacketParseResult Sapphire::Network::Packets::getPacket( const std::vector< uint8_t >& buffer, const uint32_t offset,
FFXIVARR_PACKET_RAW& packet )
{
// Copy segment header
const auto headerResult = getSegmentHeader( buffer, offset, packet.segHdr );
if( headerResult != Success )
return headerResult;
// Check header sanity and it's size
if( !checkSegmentHeader( packet.segHdr ) )
return Malformed;
const auto dataOffset = offset + sizeof( struct FFXIVARR_PACKET_SEGMENT_HEADER );
const auto dataSize = packet.segHdr.size;
// Allocate data buffer and copy
packet.data.resize( dataSize );
memcpy( packet.data.data(), buffer.data() + dataOffset, dataSize );
return Success;
}
bool Sapphire::Network::Packets::checkHeader( const FFXIVARR_PACKET_HEADER& header )
{
// Max size of the packet is capped at 1MB for now.
if( header.size > 1 * 1024 * 1024 )
return false;
// Max number of message is capped at 255 for now.
if( header.count > 255 )
return false;
return true;
}
bool Sapphire::Network::Packets::checkSegmentHeader( const FFXIVARR_PACKET_SEGMENT_HEADER& header )
{
// Max size of individual message is capped at 256KB for now.
if( header.size > 256 * 1024 )
return false;
return true;
}

View file

@ -54,16 +54,13 @@ void Sapphire::Network::GameConnection::onDisconnect()
void Sapphire::Network::GameConnection::onRecv( std::vector< uint8_t >& buffer )
{
Packets::FFXIVARR_PACKET_HEADER packetHeader;
const auto headerResult = Packets::getHeader( buffer, 0, packetHeader );
m_packets.insert( std::end( m_packets ), std::begin( buffer ), std::end( buffer ) );
// This is assumed packet always start with valid FFXIVARR_PACKET_HEADER for now.
Packets::FFXIVARR_PACKET_HEADER packetHeader{};
const auto headerResult = Packets::getHeader( m_packets, 0, packetHeader );
if( headerResult == Incomplete )
{
Logger::info( "Dropping connection due to incomplete packet header." );
Logger::info( "FIXME: Packet message bounary is not implemented." );
disconnect();
return;
}
if( headerResult == Malformed )
{
@ -74,16 +71,11 @@ void Sapphire::Network::GameConnection::onRecv( std::vector< uint8_t >& buffer )
// Dissect packet list
std::vector< Packets::FFXIVARR_PACKET_RAW > packetList;
const auto packetResult = Packets::getPackets( buffer, sizeof( struct FFXIVARR_PACKET_HEADER ),
const auto packetResult = Packets::getPackets( m_packets, sizeof( struct FFXIVARR_PACKET_HEADER ),
packetHeader, packetList );
if( packetResult == Incomplete )
{
Logger::info( "Dropping connection due to incomplete packets." );
Logger::info( "FIXME: Packet message bounary is not implemented." );
disconnect();
return;
}
if( packetResult == Malformed )
{
@ -94,6 +86,7 @@ void Sapphire::Network::GameConnection::onRecv( std::vector< uint8_t >& buffer )
// Handle it
handlePackets( packetHeader, packetList );
m_packets.clear();
}
@ -243,15 +236,15 @@ void Sapphire::Network::GameConnection::enterWorld( FFXIVARR_PACKET_RAW& packet,
bool Sapphire::Network::GameConnection::sendServiceAccountList( FFXIVARR_PACKET_RAW& packet, uint32_t tmpId )
{
LobbySessionPtr pSession = g_serverLobby.getSession( ( char* ) &packet.data[ 0 ] + 0x20 );
LobbySessionPtr pSession = g_serverLobby.getSession( ( char* ) &packet.data[ 0 ] + 0x22 );
if( g_serverLobby.getConfig().allowNoSessionConnect && pSession == nullptr )
{
auto session = make_LobbySession();
session->setAccountID( 0 );
session->setSessionId( ( uint8_t* ) &packet.data[ 0 ] + 0x20 );
session->setSessionId( ( uint8_t* ) &packet.data[ 0 ] + 0x22 );
pSession = session;
Logger::info( "Allowed connection with no session: {0}", std::string( ( char* ) &packet.data[ 0 ] + 0x20 ) );
Logger::info( "Allowed connection with no session: {0}", std::string( ( char* ) &packet.data[ 0 ] + 0x22 ) );
}
if( pSession != nullptr )
@ -395,6 +388,8 @@ void Sapphire::Network::GameConnection::handleGamePacket( Packets::FFXIVARR_PACK
Logger::info( "OpCode [{0}]", *reinterpret_cast< uint16_t* >( &packet.data[ 2 ] ) );
Logger::info( Util::binaryToHexDump( packet.data.data(), packet.data.size() ) );
switch( *reinterpret_cast< uint16_t* >( &packet.data[ 2 ] ) )
{
case ClientVersionInfo:
@ -458,7 +453,7 @@ void Sapphire::Network::GameConnection::generateEncryptionKey( uint32_t key, con
m_baseKey[ 2 ] = 0x34;
m_baseKey[ 3 ] = 0x12;
memcpy( m_baseKey + 0x04, &key, 4 );
m_baseKey[ 8 ] = 0x30;
m_baseKey[ 8 ] = 0xC6;
m_baseKey[ 9 ] = 0x11;
memcpy( ( char* ) m_baseKey + 0x0C, keyPhrase.c_str(), keyPhrase.size() );
Sapphire::Util::md5( m_baseKey, m_encKey, 0x2C );

View file

@ -38,6 +38,7 @@ namespace Sapphire::Network
LockedQueue< Packets::GamePacketPtr > m_inQueue;
LockedQueue< Packets::GamePacketPtr > m_outQueue;
std::vector< uint8_t > m_packets;
public:
GameConnection( HivePtr pHive, AcceptorPtr pAcceptor, FrameworkPtr pFw );

View file

@ -43,6 +43,8 @@ Sapphire::Entity::Chara::Chara( ObjKind type, FrameworkPtr pFw ) :
m_lastTickTime = 0;
m_lastUpdate = 0;
m_bonusStats.fill( 0 );
// initialize the free slot queue
for( uint8_t i = 0; i < MAX_STATUS_EFFECTS; i++ )
{
@ -662,9 +664,18 @@ int64_t Sapphire::Entity::Chara::getLastUpdateTime() const
void Sapphire::Entity::Chara::setLastComboActionId( uint32_t actionId )
{
m_lastComboActionId = actionId;
m_lastComboActionTime = Util::getTimeMs();
}
uint32_t Sapphire::Entity::Chara::getLastComboActionId() const
{
// initially check for the time passed first, if it's more than the threshold just return 0 for the combo
// we can hide the implementation detail this way and it just works:tm: for anything that uses it
if( std::difftime( Util::getTimeMs(), m_lastComboActionTime ) > Common::MAX_COMBO_LENGTH )
{
return 0;
}
return m_lastComboActionId;
}

View file

@ -103,8 +103,17 @@ namespace Sapphire::Entity
uint64_t m_targetId;
/*! Ptr to a queued action */
Action::ActionPtr m_pCurrentAction;
/*! the id of the last combo action used (IgnoresCombo) */
/*!
* @brief the id of the last combo action used (IgnoresCombo)
*/
uint32_t m_lastComboActionId;
/*!
* @brief when the last combo action was used in ms
*/
uint64_t m_lastComboActionTime;
/*! Invincibility type */
Common::InvincibilityType m_invincibilityType;

View file

@ -265,16 +265,16 @@ uint16_t CalcStats::calculateMpCost( const Sapphire::Entity::Chara& chara, uint1
return static_cast< uint16_t >( std::round( cost * baseCost ) );
}
float CalcStats::pBlk( const Chara& chara )
float CalcStats::blockProbability( const Chara& chara )
{
auto level = chara.getLevel();
float blockRate = static_cast< float >( chara.getBonusStat( Common::BaseParam::BlockRate ) );
float levelVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
auto blockRate = static_cast< float >( chara.getBonusStat( Common::BaseParam::BlockRate ) );
auto levelVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
return std::floor( ( 30 * blockRate ) / levelVal + 10 );
}
float CalcStats::pDhr( const Chara& chara )
float CalcStats::directHitProbability( const Chara& chara )
{
const auto& baseStats = chara.getStats();
auto level = chara.getLevel();
@ -282,13 +282,13 @@ float CalcStats::pDhr( const Chara& chara )
float dhRate = static_cast< float >( chara.getBonusStat( Common::BaseParam::DirectHitRate ) ) +
baseStats.accuracy;
float divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
float subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
return std::floor( 550.f * ( dhRate - subVal ) / divVal ) / 10.f;
}
float CalcStats::pChr( const Chara& chara )
float CalcStats::criticalHitProbability( const Chara& chara )
{
const auto& baseStats = chara.getStats();
auto level = chara.getLevel();
@ -296,8 +296,157 @@ float CalcStats::pChr( const Chara& chara )
float chRate = static_cast< float >( chara.getBonusStat( Common::BaseParam::CriticalHit ) ) +
baseStats.critHitRate;
float divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
float subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
return std::floor( 200.f * ( chRate - subVal ) / divVal + 50.f ) / 10.f;
}
float CalcStats::potency( uint16_t potency )
{
return potency / 100.f;
}
//float CalcStats::weaponDamage( const Sapphire::Entity::Chara& chara, float weaponDamage, bool isMagicDamage )
//{
// const auto& baseStats = chara.getStats();
// auto level = chara.getLevel();
//
// auto mainVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] );
//
// float jobAttribute = 1.f;
//
// // todo: fix this
// return 1.f
//}
// todo: this is all retarded, needs to be per weapon and etcetc
//uint32_t CalcStats::getPrimaryClassJobAttribute( const Sapphire::Entity::Chara& chara )
//{
//
//}
float CalcStats::calcAttackPower( uint32_t attackPower )
{
return std::floor( ( 125.f * ( attackPower - 292.f ) / 292.f ) + 100.f ) / 100.f;
}
float CalcStats::magicAttackPower( const Sapphire::Entity::Chara& chara )
{
const auto& baseStats = chara.getStats();
return calcAttackPower( baseStats.attackPotMagic );
}
float CalcStats::healingMagicPower( const Sapphire::Entity::Chara& chara )
{
const auto& baseStats = chara.getStats();
return calcAttackPower( baseStats.healingPotMagic );
}
float CalcStats::attackPower( const Sapphire::Entity::Chara& chara )
{
const auto& baseStats = chara.getStats();
return calcAttackPower( baseStats.attack );
}
float CalcStats::determination( const Sapphire::Entity::Chara& chara )
{
auto level = chara.getLevel();
const auto& baseStats = chara.getStats();
auto mainVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] );
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
return std::floor( 130.f * ( baseStats.determination - mainVal ) / divVal + 1000.f ) / 1000.f;
}
float CalcStats::tenacity( const Sapphire::Entity::Chara& chara )
{
auto level = chara.getLevel();
const auto& baseStats = chara.getStats();
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
return std::floor( 100.f * ( baseStats.tenacity - subVal ) / divVal + 1000.f ) / 1000.f;
}
float CalcStats::speed( const Sapphire::Entity::Chara& chara )
{
auto level = chara.getLevel();
const auto& baseStats = chara.getStats();
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
uint32_t speedVal = 0;
// check whether we use spellspeed or skillspeed
// todo: this is kinda shitty though
switch( chara.getClass() )
{
case Common::ClassJob::Arcanist:
case Common::ClassJob::Astrologian:
case Common::ClassJob::Whitemage:
case Common::ClassJob::Redmage:
case Common::ClassJob::Bluemage:
case Common::ClassJob::Blackmage:
case Common::ClassJob::Summoner:
case Common::ClassJob::Scholar:
case Common::ClassJob::Thaumaturge:
speedVal = baseStats.spellSpeed;
break;
default:
speedVal = baseStats.skillSpeed;
}
return std::floor( 130.f * ( speedVal - subVal ) / divVal + 1000.f ) / 1000.f;
}
float CalcStats::criticalHitBonus( const Sapphire::Entity::Chara& chara )
{
auto level = chara.getLevel();
const auto& baseStats = chara.getStats();
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
return std::floor( 200.f * ( baseStats.critHitRate - subVal ) / divVal + 1400.f ) / 1000.f;
}
float CalcStats::physicalDefence( const Sapphire::Entity::Chara& chara )
{
auto level = chara.getLevel();
const auto& baseStats = chara.getStats();
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
return std::floor( 15.f * baseStats.defense ) / 100.f;
}
float CalcStats::magicDefence( const Sapphire::Entity::Chara& chara )
{
auto level = chara.getLevel();
const auto& baseStats = chara.getStats();
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
return std::floor( 15.f * baseStats.magicDefense ) / 100.f;
}
//float CalcStats::blockStrength( const Sapphire::Entity::Chara& chara )
//{
//
//}
float CalcStats::healingMagicPotency( const Sapphire::Entity::Chara& chara )
{
const auto& baseStats = chara.getStats();
return std::floor( 100.f * ( baseStats.healingPotMagic - 292.f ) / 264.f + 100.f ) / 100.f;
}

View file

@ -18,6 +18,7 @@ namespace Sapphire::Math
/*!
* @brief Calculates the MP cost of a spell given its base cost
*
* @param chara The Chara that is casting the action
* @param baseCost The action cost
* @return The total MP to be consumed by a successful cast
@ -26,24 +27,130 @@ namespace Sapphire::Math
/*!
* @brief Calculates the probability of a block happening
* @return
*/
static float pBlk( const Sapphire::Entity::Chara& );
static float blockProbability( const Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates the probability of a direct hit happening
* @return
*/
static float pDhr( const Sapphire::Entity::Chara& );
static float directHitProbability( const Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates the probability of a critical hit happening
* @return
*/
static float pChr( const Sapphire::Entity::Chara& );
static float criticalHitProbability( const Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates the contribution of potency to damage output.
*
* @param potency The action potency
*/
static float potency( uint16_t potency );
/*!
* @brief Weapon damage is the contribution the weapon's damage rating
*
* @param chara The source/casting character.
* @param weaponDamage the weapons physical or magic damage
* @param isMagicDamage true if the damage is magical, otherwise it's treated as physical damage
*/
static float weaponDamage( const Sapphire::Entity::Chara& chara, float weaponDamage, bool isMagicDamage );
/*!
* @brief Calculates the contribution of physical attack power to damage dealt
* @todo Only works at level 70
*
* @param chara The source/casting character.
*/
static float attackPower( const Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates the contribution of magical attack power to damage dealt
* @todo Only works at level 70
*
* @param chara The source/casting character.
*/
static float magicAttackPower( const Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates the contribution of healing magic power to healing dealt
*
* @param chara The source/casting character.
*/
static float healingMagicPower( const Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates determinations contribution to damage and healing output.
*
* @param chara The source/casting character.
* @return Returns a rational number rounded to 3 decimal places.
*/
static float determination( const Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates the tenacity contribution to damage, mitigation and healing.
*
* @param chara The source/casting character.
* @return Returns a rational number rounded to 3 decimal places.
*/
static float tenacity( const Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates the bonus granted by either spell speed or skill speed depending on the casters classjob.
*
* @param chara The source/casting character.
*/
static float speed( const Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates the amount of bonus damaged applied on a critical hit
* @note Called Critical Hit Rate in the TJ document but I think that name is a bit too ambiguous - f(CHR)
*
* @param chara The source/casting character.
*/
static float criticalHitBonus( const Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates how much damage you mitigate via physical defence
*
* @param chara The source/casting character.
*/
static float physicalDefence( const Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates how much damage you mitigate via magical defence
*
* @param chara The source/casting character.
*/
static float magicDefence( const Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates the percentage of damage that is mitigated on a successful block
*
* @param chara The source/casting character.
*/
static float blockStrength( const Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates the multiplier that healing magic potency affects healing output
*
* @todo Only works for level 70
*
* @param chara The source/casting character.
*/
static float healingMagicPotency( const Sapphire::Entity::Chara& chara );
private:
static uint32_t getPrimaryClassJobAttribute( const Sapphire::Entity::Chara& chara );
/*!
* @brief Has the main attack power calculation allowing for de-duplication of functions.
*
* @param attackPower The magic/physical attack power value.
*/
static float calcAttackPower( uint32_t attackPower );
};
}

View file

@ -154,22 +154,36 @@ void Sapphire::Network::GameConnection::onDisconnect()
void Sapphire::Network::GameConnection::onRecv( std::vector< uint8_t >& buffer )
{
m_packets.insert( std::end( m_packets ), std::begin( buffer ), std::end( buffer ) );
m_packets.insert( std::end( m_packets ), std::begin( buffer ), std::end( buffer ) );
// This is assumed packet always start with valid FFXIVARR_PACKET_HEADER for now.
Packets::FFXIVARR_PACKET_HEADER packetHeader{};
const auto headerResult = Packets::getHeader( m_packets, 0, packetHeader );
if( ( headerResult == Incomplete ) || ( headerResult == Malformed ) )
if( headerResult == Incomplete )
return;
if( headerResult == Malformed )
{
Logger::info( "Dropping connection due to malformed packet header." );
disconnect();
return;
}
// Dissect packet list
std::vector< Packets::FFXIVARR_PACKET_RAW > packetList;
const auto packetResult = Packets::getPackets( m_packets, sizeof( struct FFXIVARR_PACKET_HEADER ),
packetHeader, packetList );
if( ( packetResult == Incomplete ) || ( packetResult == Malformed ) )
if( packetResult == Incomplete )
return;
if( packetResult == Malformed )
{
Logger::info( "Dropping connection due to malformed packets." );
disconnect();
return;
}
// Handle it
handlePackets( packetHeader, packetList );
m_packets.clear();