1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-25 14:07:46 +00:00

Merge branch 'stat_calc' into develop

This commit is contained in:
NotAdam 2019-05-29 21:35:07 +10:00
commit ed3e44afcc
11 changed files with 455 additions and 134 deletions

View file

@ -1114,6 +1114,7 @@ Sapphire::Data::ClassJob::ClassJob( uint32_t row_id, Sapphire::Data::ExdDataGene
classJobParent = exdData->getField< uint8_t >( row, 26 ); classJobParent = exdData->getField< uint8_t >( row, 26 );
nameEnglish = exdData->getField< std::string >( row, 27 ); nameEnglish = exdData->getField< std::string >( row, 27 );
itemStartingWeapon = exdData->getField< int32_t >( row, 28 ); itemStartingWeapon = exdData->getField< int32_t >( row, 28 );
primaryStat = exdData->getField< uint8_t >( row, 33 );
limitBreak1 = exdData->getField< uint16_t >( row, 34 ); limitBreak1 = exdData->getField< uint16_t >( row, 34 );
limitBreak2 = exdData->getField< uint16_t >( row, 35 ); limitBreak2 = exdData->getField< uint16_t >( row, 35 );
limitBreak3 = exdData->getField< uint16_t >( row, 36 ); limitBreak3 = exdData->getField< uint16_t >( row, 36 );

View file

@ -1485,6 +1485,7 @@ struct ClassJob
uint8_t classJobParent; uint8_t classJobParent;
std::string nameEnglish; std::string nameEnglish;
int32_t itemStartingWeapon; int32_t itemStartingWeapon;
uint8_t primaryStat;
uint16_t limitBreak1; uint16_t limitBreak1;
uint16_t limitBreak2; uint16_t limitBreak2;
uint16_t limitBreak3; uint16_t limitBreak3;

View file

@ -22,18 +22,22 @@
#include "Network/PacketWrappers/MoveActorPacket.h" #include "Network/PacketWrappers/MoveActorPacket.h"
#include "Navi/NaviProvider.h" #include "Navi/NaviProvider.h"
#include "Math/CalcBattle.h"
#include "Math/CalcStats.h"
#include "StatusEffect/StatusEffect.h" #include "StatusEffect/StatusEffect.h"
#include "ServerMgr.h" #include "ServerMgr.h"
#include "Session.h" #include "Session.h"
#include "Math/CalcBattle.h"
#include "Chara.h" #include "Chara.h"
#include "Player.h" #include "Player.h"
#include "BNpc.h" #include "BNpc.h"
#include "BNpcTemplate.h" #include "BNpcTemplate.h"
#include "Manager/TerritoryMgr.h"
#include "Common.h" #include "Common.h"
#include "Framework.h" #include "Framework.h"
#include <Logging/Logger.h>
#include <Manager/TerritoryMgr.h>
#include <Manager/NaviMgr.h> #include <Manager/NaviMgr.h>
#include <Manager/TerritoryMgr.h> #include <Manager/TerritoryMgr.h>
#include <Manager/RNGMgr.h> #include <Manager/RNGMgr.h>
@ -72,7 +76,9 @@ Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX
m_levelId = 0; m_levelId = 0;
m_flags = 0; m_flags = 0;
m_pCurrentZone = pZone; m_class = ClassJob::Adventurer;
m_pCurrentZone = std::move( pZone );
m_spawnPos = m_pos; m_spawnPos = m_pos;
@ -112,6 +118,8 @@ Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX
// todo: is this actually good? // todo: is this actually good?
//m_naviTargetReachedDistance = m_scale * 2.f; //m_naviTargetReachedDistance = m_scale * 2.f;
m_naviTargetReachedDistance = 4.f; m_naviTargetReachedDistance = 4.f;
calculateStats();
} }
Sapphire::Entity::BNpc::~BNpc() = default; Sapphire::Entity::BNpc::~BNpc() = default;
@ -262,7 +270,7 @@ void Sapphire::Entity::BNpc::sendPositionUpdate()
void Sapphire::Entity::BNpc::hateListClear() void Sapphire::Entity::BNpc::hateListClear()
{ {
auto it = m_hateList.begin(); auto it = m_hateList.begin();
for( auto listEntry : m_hateList ) for( auto& listEntry : m_hateList )
{ {
if( isInRangeSet( listEntry->m_pChara ) ) if( isInRangeSet( listEntry->m_pChara ) )
deaggro( listEntry->m_pChara ); deaggro( listEntry->m_pChara );
@ -684,7 +692,7 @@ void Sapphire::Entity::BNpc::autoAttack( CharaPtr pTarget )
srand( static_cast< uint32_t >( tick ) ); srand( static_cast< uint32_t >( tick ) );
auto pRNGMgr = m_pFw->get< World::Manager::RNGMgr >(); auto pRNGMgr = m_pFw->get< World::Manager::RNGMgr >();
auto damage = static_cast< uint16_t >( pRNGMgr->getRandGenerator< float >( m_level, m_level + m_level * 1.5f ).next() ); auto damage = Math::CalcStats::calculateAutoAttackDamage( *this );
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 ); auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 );
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) ); effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
@ -700,4 +708,38 @@ void Sapphire::Entity::BNpc::autoAttack( CharaPtr pTarget )
pTarget->takeDamage( damage ); pTarget->takeDamage( damage );
} }
}
void Sapphire::Entity::BNpc::calculateStats()
{
uint8_t level = getLevel();
uint8_t job = static_cast< uint8_t >( getClass() );
auto pExdData = m_pFw->get< Data::ExdDataGenerated >();
auto classInfo = pExdData->get< Sapphire::Data::ClassJob >( job );
auto paramGrowthInfo = pExdData->get< Sapphire::Data::ParamGrow >( level );
float base = Math::CalcStats::calculateBaseStat( *this );
m_baseStats.str = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->modifierStrength ) / 100 ) );
m_baseStats.dex = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->modifierDexterity ) / 100 ) );
m_baseStats.vit = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->modifierVitality ) / 100 ) );
m_baseStats.inte = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->modifierIntelligence ) / 100 ) );
m_baseStats.mnd = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->modifierMind ) / 100 ) );
m_baseStats.pie = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->modifierPiety ) / 100 ) );
m_baseStats.determination = static_cast< uint32_t >( base );
m_baseStats.pie = static_cast< uint32_t >( base );
m_baseStats.skillSpeed = paramGrowthInfo->baseSpeed;
m_baseStats.spellSpeed = paramGrowthInfo->baseSpeed;
m_baseStats.accuracy = paramGrowthInfo->baseSpeed;
m_baseStats.critHitRate = paramGrowthInfo->baseSpeed;
m_baseStats.attackPotMagic = paramGrowthInfo->baseSpeed;
m_baseStats.healingPotMagic = paramGrowthInfo->baseSpeed;
m_baseStats.tenacity = paramGrowthInfo->baseSpeed;
m_baseStats.attack = m_baseStats.str;
m_baseStats.attackPotMagic = m_baseStats.inte;
m_baseStats.healingPotMagic = m_baseStats.mnd;
} }

View file

@ -118,6 +118,8 @@ namespace Sapphire::Entity
bool hasFlag( uint32_t flag ) const; bool hasFlag( uint32_t flag ) const;
void setFlag( uint32_t flags ); void setFlag( uint32_t flags );
void calculateStats() override;
private: private:
uint32_t m_bNpcBaseId; uint32_t m_bNpcBaseId;
uint32_t m_bNpcNameId; uint32_t m_bNpcNameId;

View file

@ -21,7 +21,7 @@
#include "Action/Action.h" #include "Action/Action.h"
#include "ServerMgr.h" #include "ServerMgr.h"
#include "Session.h" #include "Session.h"
#include "Math/CalcBattle.h" #include "Math/CalcStats.h"
#include "Chara.h" #include "Chara.h"
#include "Player.h" #include "Player.h"
#include "Manager/TerritoryMgr.h" #include "Manager/TerritoryMgr.h"
@ -719,4 +719,150 @@ void Sapphire::Entity::Chara::setAgentId( uint32_t agentId )
float Sapphire::Entity::Chara::getRadius() const float Sapphire::Entity::Chara::getRadius() const
{ {
return m_radius; return m_radius;
}
Sapphire::Common::BaseParam Sapphire::Entity::Chara::getPrimaryStat() const
{
auto exdData = m_pFw->get< Data::ExdDataGenerated >();
assert( exdData );
auto classJob = exdData->get< Data::ClassJob >( static_cast< uint16_t >( getClass() ) );
assert( classJob );
return static_cast< Sapphire::Common::BaseParam >( classJob->primaryStat );
}
uint32_t Sapphire::Entity::Chara::getStatValue( Sapphire::Common::BaseParam baseParam ) const
{
uint32_t value = 0;
switch( baseParam )
{
case Common::BaseParam::Strength:
{
value = m_baseStats.str;
break;
}
case Common::BaseParam::Dexterity:
{
value = m_baseStats.dex;
break;
}
case Common::BaseParam::Vitality:
{
value = m_baseStats.vit;
break;
}
case Common::BaseParam::Intelligence:
{
value = m_baseStats.inte;
break;
}
case Common::BaseParam::Mind:
{
value = m_baseStats.mnd;
break;
}
case Common::BaseParam::Piety:
{
value = m_baseStats.pie;
break;
}
case Common::BaseParam::Determination:
{
value = m_baseStats.determination;
break;
}
case Common::BaseParam::HP:
{
value = m_baseStats.max_hp;
break;
}
case Common::BaseParam::MP:
{
value = m_baseStats.max_mp;
break;
}
case Common::BaseParam::AttackPower:
{
auto primaryStat = getPrimaryStat();
// everything else uses str for atk power except for brd/rogue/etc who use dex
if( primaryStat == Common::BaseParam::Dexterity )
{
return getStatValue( primaryStat );
}
return getStatValue( Common::BaseParam::Strength );
}
case Common::BaseParam::AttackMagicPotency:
{
value = m_baseStats.attackPotMagic;
break;
}
case Common::BaseParam::HealingMagicPotency:
{
value = m_baseStats.healingPotMagic;
break;
}
case Common::BaseParam::SkillSpeed:
{
value = m_baseStats.skillSpeed;
break;
}
case Common::BaseParam::SpellSpeed:
{
value = m_baseStats.spellSpeed;
break;
}
case Common::BaseParam::CriticalHit:
{
value = m_baseStats.critHitRate;
break;
}
case Common::BaseParam::Defense:
{
value = m_baseStats.defense;
break;
}
case Common::BaseParam::MagicDefense:
{
value = m_baseStats.magicDefense;
break;
}
case Common::BaseParam::Tenacity:
{
value = m_baseStats.tenacity;
break;
}
// todo: not sure if this is right?
case Common::BaseParam::DirectHitRate:
{
value = m_baseStats.accuracy;
break;
}
default:
break;
}
return value + getBonusStat( baseParam );
} }

View file

@ -191,6 +191,8 @@ namespace Sapphire::Entity
ActorStats getStats() const; ActorStats getStats() const;
uint32_t getStatValue( Common::BaseParam baseParam ) const;
uint32_t getHp() const; uint32_t getHp() const;
uint32_t getHpPercent() const; uint32_t getHpPercent() const;
@ -280,6 +282,8 @@ namespace Sapphire::Entity
float getRadius() const; float getRadius() const;
Common::BaseParam getPrimaryStat() const;
}; };
} }

View file

@ -256,7 +256,7 @@ void Sapphire::Entity::Player::calculateStats()
auto tribeInfo = pExdData->get< Sapphire::Data::Tribe >( tribe ); auto tribeInfo = pExdData->get< Sapphire::Data::Tribe >( tribe );
auto paramGrowthInfo = pExdData->get< Sapphire::Data::ParamGrow >( level ); auto paramGrowthInfo = pExdData->get< Sapphire::Data::ParamGrow >( level );
float base = Math::CalcStats::calculateBaseStat( getAsPlayer() ); float base = Math::CalcStats::calculateBaseStat( *this );
m_baseStats.str = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->modifierStrength ) / 100 ) + m_baseStats.str = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->modifierStrength ) / 100 ) +
tribeInfo->sTR ); tribeInfo->sTR );
@ -313,28 +313,28 @@ void Sapphire::Entity::Player::sendStats()
{ {
auto statPacket = makeZonePacket< FFXIVIpcPlayerStats >( getId() ); auto statPacket = makeZonePacket< FFXIVIpcPlayerStats >( getId() );
statPacket->data().strength = m_baseStats.str + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Strength ) ]; statPacket->data().strength = getStatValue( Common::BaseParam::Strength );
statPacket->data().dexterity = m_baseStats.dex + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Dexterity ) ]; statPacket->data().dexterity = getStatValue( Common::BaseParam::Dexterity );
statPacket->data().vitality = m_baseStats.vit + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Vitality ) ]; statPacket->data().vitality = getStatValue( Common::BaseParam::Vitality );
statPacket->data().intelligence = m_baseStats.inte + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Intelligence ) ]; statPacket->data().intelligence = getStatValue( Common::BaseParam::Intelligence );
statPacket->data().mind = m_baseStats.mnd + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Mind ) ]; statPacket->data().mind = getStatValue( Common::BaseParam::Mind );
statPacket->data().piety = m_baseStats.pie + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Piety ) ]; statPacket->data().piety = getStatValue( Common::BaseParam::Piety );
statPacket->data().determination = m_baseStats.determination + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Determination ) ]; statPacket->data().determination = getStatValue( Common::BaseParam::Determination );
statPacket->data().hp = m_baseStats.max_hp + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::HP ) ]; statPacket->data().hp = getStatValue( Common::BaseParam::HP );
statPacket->data().mp = m_baseStats.max_mp + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::MP ) ]; statPacket->data().mp = getStatValue( Common::BaseParam::MP );
statPacket->data().accuracy = m_baseStats.accuracy; statPacket->data().accuracy = m_baseStats.accuracy;
statPacket->data().attack = m_baseStats.attack + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::AttackPower ) ]; statPacket->data().attack = getStatValue( Common::BaseParam::AttackPower );
statPacket->data().attackMagicPotency = m_baseStats.attackPotMagic + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::AttackMagicPotency ) ]; statPacket->data().attackMagicPotency = getStatValue( Common::BaseParam::AttackMagicPotency );
statPacket->data().healingMagicPotency = m_baseStats.healingPotMagic + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::HealingMagicPotency ) ]; statPacket->data().healingMagicPotency = getStatValue( Common::BaseParam::HealingMagicPotency );
statPacket->data().skillSpeed = m_baseStats.skillSpeed + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::SkillSpeed ) ]; statPacket->data().skillSpeed = getStatValue( Common::BaseParam::SkillSpeed );
statPacket->data().spellSpeed = m_baseStats.spellSpeed + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::SpellSpeed ) ]; statPacket->data().spellSpeed = getStatValue( Common::BaseParam::SpellSpeed );
statPacket->data().spellSpeed1 = m_baseStats.spellSpeed + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::SpellSpeed ) ]; statPacket->data().spellSpeed1 = getStatValue( Common::BaseParam::SpellSpeed );
statPacket->data().spellSpeedMod = 100; statPacket->data().spellSpeedMod = 100;
statPacket->data().criticalHitRate = m_baseStats.critHitRate + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::CriticalHit ) ]; statPacket->data().criticalHitRate = getStatValue( Common::BaseParam::CriticalHit );
statPacket->data().defense = m_baseStats.defense + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Defense ) ]; statPacket->data().defense = getStatValue( Common::BaseParam::Defense );
statPacket->data().magicDefense = m_baseStats.magicDefense + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::MagicDefense ) ]; statPacket->data().magicDefense = getStatValue( Common::BaseParam::MagicDefense );
statPacket->data().tenacity = m_baseStats.tenacity + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Tenacity ) ]; statPacket->data().tenacity = getStatValue( Common::BaseParam::Tenacity );
queuePacket( statPacket ); queuePacket( statPacket );
} }
@ -1105,9 +1105,10 @@ void Sapphire::Entity::Player::update( uint64_t tickCount )
{ {
if( actor->getId() == m_targetId && actor->getAsChara()->isAlive() && mainWeap ) if( actor->getId() == m_targetId && actor->getAsChara()->isAlive() && mainWeap )
{ {
auto chara = actor->getAsChara();
// default autoattack range // default autoattack range
// TODO make this dependant on bnpc size float range = 3.f + chara->getRadius();
uint32_t range = 7;
// default autoattack range for ranged classes // default autoattack range for ranged classes
if( getClass() == ClassJob::Machinist || if( getClass() == ClassJob::Machinist ||
@ -1566,8 +1567,7 @@ void Sapphire::Entity::Player::autoAttack( CharaPtr pTarget )
auto pRNGMgr = m_pFw->get< World::Manager::RNGMgr >(); auto pRNGMgr = m_pFw->get< World::Manager::RNGMgr >();
auto variation = static_cast< uint32_t >( pRNGMgr->getRandGenerator< float >( 0, 3 ).next() ); auto variation = static_cast< uint32_t >( pRNGMgr->getRandGenerator< float >( 0, 3 ).next() );
auto damage = static_cast< uint32_t >( pRNGMgr->getRandGenerator< float >( static_cast< uint32_t > ( getLevel() * 1.5f ), auto damage = Math::CalcStats::calculateAutoAttackDamage( *this );
getLevel() + static_cast< uint32_t >( mainWeap->getAutoAttackDmg() * 2 ) ).next() );
if( getClass() == ClassJob::Machinist || getClass() == ClassJob::Bard || getClass() == ClassJob::Archer ) if( getClass() == ClassJob::Machinist || getClass() == ClassJob::Bard || getClass() == ClassJob::Archer )
{ {
@ -2124,3 +2124,13 @@ Sapphire::World::SessionPtr Sapphire::Entity::Player::getSession()
return m_pSession; return m_pSession;
} }
void Sapphire::Entity::Player::setActiveLand( uint8_t land, uint8_t ward )
{
m_activeLand.plot = land;
m_activeLand.ward = ward;
}
Sapphire::Common::ActiveLand Sapphire::Entity::Player::getActiveLand() const
{
return m_activeLand;
}

View file

@ -933,6 +933,8 @@ namespace Sapphire::Entity
/*! calculate and return player ilvl based off equipped gear */ /*! calculate and return player ilvl based off equipped gear */
uint16_t calculateEquippedGearItemLevel(); uint16_t calculateEquippedGearItemLevel();
ItemPtr getEquippedWeapon();
/*! return the current amount of currency of type */ /*! return the current amount of currency of type */
uint32_t getCurrency( Common::CurrencyType type ); uint32_t getCurrency( Common::CurrencyType type );
@ -964,6 +966,8 @@ namespace Sapphire::Entity
Sapphire::ItemPtr dropInventoryItem( Common::InventoryType type, uint16_t slotId ); Sapphire::ItemPtr dropInventoryItem( Common::InventoryType type, uint16_t slotId );
//////////////////////////////////////////////////////////////////////////////////////////////////////
Common::HuntingLogEntry& getHuntingLogEntry( uint8_t index ); Common::HuntingLogEntry& getHuntingLogEntry( uint8_t index );
void sendHuntingLog(); void sendHuntingLog();
@ -974,8 +978,6 @@ namespace Sapphire::Entity
World::SessionPtr getSession(); World::SessionPtr getSession();
//////////////////////////////////////////////////////////////////////////////////////////////////////
uint64_t m_lastMoveTime; uint64_t m_lastMoveTime;
uint8_t m_lastMoveflag; uint8_t m_lastMoveflag;
bool m_falling; bool m_falling;

View file

@ -831,17 +831,6 @@ void Sapphire::Entity::Player::discardItem( uint16_t fromInventoryId, uint8_t fr
queuePacket( invTransFinPacket ); queuePacket( invTransFinPacket );
} }
void Sapphire::Entity::Player::setActiveLand( uint8_t land, uint8_t ward )
{
m_activeLand.plot = land;
m_activeLand.ward = ward;
}
Sapphire::Common::ActiveLand Sapphire::Entity::Player::getActiveLand() const
{
return m_activeLand;
}
uint16_t Sapphire::Entity::Player::calculateEquippedGearItemLevel() uint16_t Sapphire::Entity::Player::calculateEquippedGearItemLevel()
{ {
uint32_t iLvlResult = 0; uint32_t iLvlResult = 0;
@ -854,7 +843,7 @@ uint16_t Sapphire::Entity::Player::calculateEquippedGearItemLevel()
{ {
auto currItem = it->second; auto currItem = it->second;
if( currItem ) if( currItem && currItem->getCategory() != Common::ItemUICategory::SoulCrystal )
{ {
iLvlResult += currItem->getItemLevel(); iLvlResult += currItem->getItemLevel();
@ -871,6 +860,10 @@ uint16_t Sapphire::Entity::Player::calculateEquippedGearItemLevel()
return static_cast< uint16_t >( std::min( static_cast< int32_t >( iLvlResult / 13 ), 9999 ) ); return static_cast< uint16_t >( std::min( static_cast< int32_t >( iLvlResult / 13 ), 9999 ) );
} }
Sapphire::ItemPtr Sapphire::Entity::Player::getEquippedWeapon()
{
return m_storageMap[ GearSet0 ]->getItem( GearSetSlot::MainHand );
}
uint8_t Sapphire::Entity::Player::getFreeSlotsInBags() uint8_t Sapphire::Entity::Player::getFreeSlotsInBags()
{ {

View file

@ -2,11 +2,13 @@
#include <Exd/ExdDataGenerated.h> #include <Exd/ExdDataGenerated.h>
#include <Common.h> #include <Common.h>
#include <Logging/Logger.h>
#include "Actor/Chara.h" #include "Actor/Chara.h"
#include "Actor/Player.h" #include "Actor/Player.h"
#include "Inventory/Item.h"
#include "CalcStats.h" #include "CalcStats.h"
#include "Framework.h" #include "Framework.h"
@ -108,10 +110,10 @@ const int levelTable[71][7] =
// 3 Versions. SB and HW are linear, ARR is polynomial. // 3 Versions. SB and HW are linear, ARR is polynomial.
// Originally from Player.cpp, calculateStats(). // Originally from Player.cpp, calculateStats().
float CalcStats::calculateBaseStat( PlayerPtr pPlayer ) float CalcStats::calculateBaseStat( const Chara& chara )
{ {
float base = 0.0f; float base = 0.0f;
uint8_t level = pPlayer->getLevel(); uint8_t level = chara.getLevel();
if( level > 70 ) if( level > 70 )
level = 70; level = 70;
@ -138,7 +140,7 @@ uint32_t CalcStats::calculateMaxHp( PlayerPtr pPlayer, Sapphire::FrameworkPtr pF
uint8_t level = pPlayer->getLevel(); uint8_t level = pPlayer->getLevel();
auto vitMod = pPlayer->getBonusStat( Common::BaseParam::Vitality ); auto vitMod = pPlayer->getBonusStat( Common::BaseParam::Vitality );
float baseStat = calculateBaseStat( pPlayer ); float baseStat = calculateBaseStat( *pPlayer );
uint16_t vitStat = pPlayer->getStats().vit + static_cast< uint16_t >( vitMod ); uint16_t vitStat = pPlayer->getStats().vit + static_cast< uint16_t >( vitMod );
uint16_t hpMod = paramGrowthInfo->hpModifier; uint16_t hpMod = paramGrowthInfo->hpModifier;
uint16_t jobModHp = classInfo->modifierHitPoints; uint16_t jobModHp = classInfo->modifierHitPoints;
@ -173,7 +175,7 @@ uint32_t CalcStats::calculateMaxMp( PlayerPtr pPlayer, Sapphire::FrameworkPtr pF
auto pieMod = pPlayer->getBonusStat( Common::BaseParam::Piety ); auto pieMod = pPlayer->getBonusStat( Common::BaseParam::Piety );
float baseStat = calculateBaseStat( pPlayer ); float baseStat = calculateBaseStat( *pPlayer );
uint16_t piety = pPlayer->getStats().pie + pieMod; uint16_t piety = pPlayer->getStats().pie + pieMod;
uint16_t pietyScalar = paramGrowthInfo->mpModifier; uint16_t pietyScalar = paramGrowthInfo->mpModifier;
uint16_t jobModMp = classInfo->modifierManaPoints; uint16_t jobModMp = classInfo->modifierManaPoints;
@ -268,7 +270,7 @@ uint16_t CalcStats::calculateMpCost( const Sapphire::Entity::Chara& chara, uint1
float CalcStats::blockProbability( const Chara& chara ) float CalcStats::blockProbability( const Chara& chara )
{ {
auto level = chara.getLevel(); auto level = chara.getLevel();
auto blockRate = static_cast< float >( chara.getBonusStat( Common::BaseParam::BlockRate ) ); auto blockRate = static_cast< float >( chara.getStatValue( Common::BaseParam::BlockRate ) );
auto levelVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] ); auto levelVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
return std::floor( ( 30 * blockRate ) / levelVal + 10 ); return std::floor( ( 30 * blockRate ) / levelVal + 10 );
@ -279,8 +281,7 @@ float CalcStats::directHitProbability( const Chara& chara )
const auto& baseStats = chara.getStats(); const auto& baseStats = chara.getStats();
auto level = chara.getLevel(); auto level = chara.getLevel();
float dhRate = static_cast< float >( chara.getBonusStat( Common::BaseParam::DirectHitRate ) ) + float dhRate = chara.getStatValue( Common::BaseParam::DirectHitRate );
baseStats.accuracy;
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] ); auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] ); auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
@ -293,8 +294,7 @@ float CalcStats::criticalHitProbability( const Chara& chara )
const auto& baseStats = chara.getStats(); const auto& baseStats = chara.getStats();
auto level = chara.getLevel(); auto level = chara.getLevel();
float chRate = static_cast< float >( chara.getBonusStat( Common::BaseParam::CriticalHit ) ) + float chRate = chara.getStatValue( Common::BaseParam::CriticalHit );
baseStats.critHitRate;
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] ); auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] ); auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
@ -308,77 +308,142 @@ float CalcStats::potency( uint16_t potency )
return potency / 100.f; return potency / 100.f;
} }
//float CalcStats::weaponDamage( const Sapphire::Entity::Chara& chara, float weaponDamage, bool isMagicDamage ) float CalcStats::autoAttackPotency( const Sapphire::Entity::Chara& chara )
//{
// 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; uint32_t aaPotency = AUTO_ATTACK_POTENCY;
// check if ranged class
switch( chara.getClass() )
{
case Common::ClassJob::Machinist:
case Common::ClassJob::Bard:
case Common::ClassJob::Archer:
aaPotency = RANGED_AUTO_ATTACK_POTENCY;
default:
break;
}
float autoAttackDelay = 2.5f;
// fetch actual auto attack delay if its a player
if( chara.isPlayer() )
{
// todo: ew
auto pPlayer = const_cast< Entity::Chara& >( chara ).getAsPlayer();
assert( pPlayer );
auto pItem = pPlayer->getEquippedWeapon();
assert( pItem );
autoAttackDelay = pItem->getDelay() / 1000.f;
}
// factors in f(PTC) in order to not lose precision
return std::floor( aaPotency / 3.f * autoAttackDelay ) / 100.f;
} }
float CalcStats::magicAttackPower( const Sapphire::Entity::Chara& chara ) float CalcStats::weaponDamage( const Sapphire::Entity::Chara& chara, float weaponDamage )
{
const auto& baseStats = chara.getStats();
auto level = chara.getLevel();
auto mainVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] );
uint32_t jobAttribute = 1;
switch( chara.getPrimaryStat() )
{
case Common::BaseParam::Intelligence:
{
jobAttribute = baseStats.healingPotMagic;
break;
}
case Common::BaseParam::Mind:
{
jobAttribute = baseStats.attackPotMagic;
break;
}
default:
{
jobAttribute = baseStats.attack;
break;
}
}
return std::floor( ( ( mainVal * jobAttribute ) / 1000.f ) + weaponDamage );
}
float CalcStats::calcAttackPower( const Sapphire::Entity::Chara& chara, uint32_t attackPower )
{
auto level = chara.getLevel();
auto mainVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] );
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
// todo: not sure if its ( ap - mv ) / mv or ( ap - mv ) / dv
return std::floor( ( 125.f * ( attackPower - mainVal ) / divVal ) + 100.f ) / 100.f;
}
float CalcStats::getPrimaryAttackPower( const Sapphire::Entity::Chara& chara )
{ {
const auto& baseStats = chara.getStats(); const auto& baseStats = chara.getStats();
return calcAttackPower( baseStats.attackPotMagic ); switch( chara.getPrimaryStat() )
} {
case Common::BaseParam::Mind:
{
return healingMagicPower( chara );
}
case Common::BaseParam::Intelligence:
{
return magicAttackPower( chara );
}
float CalcStats::healingMagicPower( const Sapphire::Entity::Chara& chara ) default:
{ {
const auto& baseStats = chara.getStats(); return attackPower( chara );
}
return calcAttackPower( baseStats.healingPotMagic ); }
} }
float CalcStats::attackPower( const Sapphire::Entity::Chara& chara ) float CalcStats::attackPower( const Sapphire::Entity::Chara& chara )
{ {
const auto& baseStats = chara.getStats(); return calcAttackPower( chara, chara.getStatValue( Common::BaseParam::AttackPower ) );
}
return calcAttackPower( baseStats.attack ); float CalcStats::magicAttackPower( const Sapphire::Entity::Chara& chara )
{
return calcAttackPower( chara, chara.getStatValue( Common::BaseParam::AttackMagicPotency ) );
}
float CalcStats::healingMagicPower( const Sapphire::Entity::Chara& chara )
{
return calcAttackPower( chara, chara.getStatValue( Common::BaseParam::HealingMagicPotency ) );
} }
float CalcStats::determination( const Sapphire::Entity::Chara& chara ) float CalcStats::determination( const Sapphire::Entity::Chara& chara )
{ {
auto level = chara.getLevel(); auto level = chara.getLevel();
const auto& baseStats = chara.getStats();
auto mainVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] ); auto mainVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] );
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] ); auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
return std::floor( 130.f * ( baseStats.determination - mainVal ) / divVal + 1000.f ) / 1000.f; return std::floor( 130.f * ( chara.getStatValue( Common::BaseParam::Determination ) - mainVal ) / divVal + 1000.f ) / 1000.f;
} }
float CalcStats::tenacity( const Sapphire::Entity::Chara& chara ) float CalcStats::tenacity( const Sapphire::Entity::Chara& chara )
{ {
auto level = chara.getLevel(); auto level = chara.getLevel();
const auto& baseStats = chara.getStats();
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] ); auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] ); auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
return std::floor( 100.f * ( baseStats.tenacity - subVal ) / divVal + 1000.f ) / 1000.f; return std::floor( 100.f * ( chara.getStatValue( Common::BaseParam::Tenacity ) - subVal ) / divVal + 1000.f ) / 1000.f;
} }
float CalcStats::speed( const Sapphire::Entity::Chara& chara ) float CalcStats::speed( const Sapphire::Entity::Chara& chara )
{ {
auto level = chara.getLevel(); auto level = chara.getLevel();
const auto& baseStats = chara.getStats();
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] ); auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] ); auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
@ -386,23 +451,15 @@ float CalcStats::speed( const Sapphire::Entity::Chara& chara )
uint32_t speedVal = 0; uint32_t speedVal = 0;
// check whether we use spellspeed or skillspeed // check whether we use spellspeed or skillspeed
// todo: this is kinda shitty though switch( chara.getPrimaryStat() )
switch( chara.getClass() )
{ {
case Common::ClassJob::Arcanist: case Common::BaseParam::Intelligence:
case Common::ClassJob::Astrologian: case Common::BaseParam::Mind:
case Common::ClassJob::Whitemage: speedVal = chara.getStatValue( Common::BaseParam::SpellSpeed );
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; break;
default: default:
speedVal = baseStats.skillSpeed; speedVal = chara.getStatValue( Common::BaseParam::SkillSpeed );
} }
return std::floor( 130.f * ( speedVal - subVal ) / divVal + 1000.f ) / 1000.f; return std::floor( 130.f * ( speedVal - subVal ) / divVal + 1000.f ) / 1000.f;
@ -411,42 +468,104 @@ float CalcStats::speed( const Sapphire::Entity::Chara& chara )
float CalcStats::criticalHitBonus( const Sapphire::Entity::Chara& chara ) float CalcStats::criticalHitBonus( const Sapphire::Entity::Chara& chara )
{ {
auto level = chara.getLevel(); auto level = chara.getLevel();
const auto& baseStats = chara.getStats();
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] ); auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] ); auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
return std::floor( 200.f * ( baseStats.critHitRate - subVal ) / divVal + 1400.f ) / 1000.f; return std::floor( 200.f * ( chara.getStatValue( Common::BaseParam::CriticalHit ) - subVal ) / divVal + 1400.f ) / 1000.f;
} }
float CalcStats::physicalDefence( const Sapphire::Entity::Chara& chara ) float CalcStats::physicalDefence( const Sapphire::Entity::Chara& chara )
{ {
auto level = chara.getLevel(); auto level = chara.getLevel();
const auto& baseStats = chara.getStats();
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] ); auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
return std::floor( 15.f * baseStats.defense ) / 100.f; return std::floor( 15.f * chara.getStatValue( Common::BaseParam::Defense ) ) / 100.f;
} }
float CalcStats::magicDefence( const Sapphire::Entity::Chara& chara ) float CalcStats::magicDefence( const Sapphire::Entity::Chara& chara )
{ {
auto level = chara.getLevel(); auto level = chara.getLevel();
const auto& baseStats = chara.getStats();
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] ); auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
return std::floor( 15.f * baseStats.magicDefense ) / 100.f; return std::floor( 15.f * chara.getStatValue( Common::BaseParam::MagicDefense ) ) / 100.f;
} }
//float CalcStats::blockStrength( const Sapphire::Entity::Chara& chara ) float CalcStats::blockStrength( const Sapphire::Entity::Chara& chara )
//{ {
// auto level = chara.getLevel();
//} auto blockStrength = static_cast< float >( chara.getBonusStat( Common::BaseParam::BlockStrength ) );
auto levelVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
return std::floor( ( 30 * blockStrength ) / levelVal + 10 ) / 100.f;
}
float CalcStats::autoAttack( const Sapphire::Entity::Chara& chara )
{
// todo: default values for NPCs, not sure what we should have here
float autoAttackDelay = 2.f;
float weaponDamage = 10.f;
// fetch actual auto attack delay if its a player
if( chara.isPlayer() )
{
// todo: ew
auto pPlayer = const_cast< Entity::Chara& >( chara ).getAsPlayer();
assert( pPlayer );
auto pItem = pPlayer->getEquippedWeapon();
assert( pItem );
autoAttackDelay = pItem->getDelay() / 1000.f;
weaponDamage = pItem->getWeaponDmg();
}
auto level = chara.getLevel();
auto mainVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] );
auto innerCalc = std::floor( ( mainVal * primaryStatValue( chara ) / 1000.f ) + weaponDamage );
return std::floor( innerCalc * ( autoAttackDelay / 3.f ) );
}
float CalcStats::healingMagicPotency( const Sapphire::Entity::Chara& chara ) float CalcStats::healingMagicPotency( const Sapphire::Entity::Chara& chara )
{ {
const auto& baseStats = chara.getStats(); return std::floor( 100.f * ( chara.getStatValue( Common::BaseParam::HealingMagicPotency ) - 292.f ) / 264.f + 100.f ) / 100.f;
}
return std::floor( 100.f * ( baseStats.healingPotMagic - 292.f ) / 264.f + 100.f ) / 100.f; float CalcStats::calculateAutoAttackDamage( const Sapphire::Entity::Chara& chara )
{
// D = ⌊ f(ptc) × f(aa) × f(ap) × f(det) × f(tnc) × traits ⌋ × f(ss) ⌋ ×
// f(chr) ⌋ × f(dhr) ⌋ × rand[ 0.95, 1.05 ] ⌋ × buff_1 ⌋ × buff... ⌋
auto pot = autoAttackPotency( chara );
auto aa = autoAttack( chara );
auto ap = getPrimaryAttackPower( chara );
auto det = determination( chara );
auto ten = tenacity( chara );
Logger::debug( "auto attack: pot: {} aa: {} ap: {} det: {} ten: {}", pot, aa, ap, det, ten );
auto factor = std::floor( pot * aa * ap * det * ten );
// todo: traits
factor = std::floor( factor * speed( chara ) );
// todo: surely this aint right?
//factor = std::floor( factor * criticalHitProbability( chara ) );
//factor = std::floor( factor * directHitProbability( chara ) );
// todo: random 0.95 - 1.05 factor
// todo: buffs
return factor;
}
uint32_t CalcStats::primaryStatValue( const Sapphire::Entity::Chara& chara )
{
return chara.getStatValue( chara.getPrimaryStat() );
} }

View file

@ -10,7 +10,10 @@ namespace Sapphire::Math
class CalcStats class CalcStats
{ {
public: public:
static float calculateBaseStat( Sapphire::Entity::PlayerPtr pPlayer ); static const uint32_t AUTO_ATTACK_POTENCY = 110;
static const uint32_t RANGED_AUTO_ATTACK_POTENCY = 100;
static float calculateBaseStat( const Entity::Chara& chara );
static uint32_t calculateMaxMp( Sapphire::Entity::PlayerPtr pPlayer, FrameworkPtr pFw ); static uint32_t calculateMaxMp( Sapphire::Entity::PlayerPtr pPlayer, FrameworkPtr pFw );
@ -47,36 +50,28 @@ namespace Sapphire::Math
*/ */
static float potency( uint16_t potency ); static float potency( uint16_t potency );
static float autoAttackPotency( const Sapphire::Entity::Chara& chara );
/*! /*!
* @brief Weapon damage is the contribution the weapon's damage rating * @brief Weapon damage is the contribution the weapon's damage rating
* *
* @param chara The source/casting character. * @param chara The source/casting character.
* @param weaponDamage the weapons physical or magic damage * @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 ); static float weaponDamage( const Sapphire::Entity::Chara& chara, float weaponDamage );
/*! /*!
* @brief Calculates the contribution of physical attack power to damage dealt * @brief Calculates the contribution of attack power to damage dealt with consideration for the primary stat
* @todo Only works at level 70 * @todo Only works at level 70
* *
* @param chara The source/casting character. * @param chara The source/casting character.
*/ */
static float getPrimaryAttackPower( const Sapphire::Entity::Chara& chara );
static float attackPower( const Sapphire::Entity::Chara& chara ); 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 ); 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 ); static float healingMagicPower( const Sapphire::Entity::Chara& chara );
/*! /*!
@ -131,6 +126,8 @@ namespace Sapphire::Math
*/ */
static float blockStrength( const Sapphire::Entity::Chara& chara ); static float blockStrength( const Sapphire::Entity::Chara& chara );
static float autoAttack( const Sapphire::Entity::Chara& chara );
/*! /*!
* @brief Calculates the multiplier that healing magic potency affects healing output * @brief Calculates the multiplier that healing magic potency affects healing output
* *
@ -140,16 +137,20 @@ namespace Sapphire::Math
*/ */
static float healingMagicPotency( const Sapphire::Entity::Chara& chara ); static float healingMagicPotency( const Sapphire::Entity::Chara& chara );
private: ////////////////////////////////////////////
static uint32_t getPrimaryClassJobAttribute( const Sapphire::Entity::Chara& chara ); static float calculateAutoAttackDamage( const Sapphire::Entity::Chara& chara );
static uint32_t primaryStatValue( const Sapphire::Entity::Chara& chara );
private:
/*! /*!
* @brief Has the main attack power calculation allowing for de-duplication of functions. * @brief Has the main attack power calculation allowing for de-duplication of functions.
* *
* @param attackPower The magic/physical attack power value. * @param attackPower The magic/physical attack power value.
*/ */
static float calcAttackPower( uint32_t attackPower ); static float calcAttackPower( const Sapphire::Entity::Chara& chara, uint32_t attackPower );
}; };