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:
commit
5d3ae12a26
9 changed files with 455 additions and 162 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 );
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Reference in a new issue