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 );
nameEnglish = exdData->getField< std::string >( row, 27 );
itemStartingWeapon = exdData->getField< int32_t >( row, 28 );
primaryStat = exdData->getField< uint8_t >( row, 33 );
limitBreak1 = exdData->getField< uint16_t >( row, 34 );
limitBreak2 = exdData->getField< uint16_t >( row, 35 );
limitBreak3 = exdData->getField< uint16_t >( row, 36 );

View file

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

View file

@ -22,18 +22,22 @@
#include "Network/PacketWrappers/MoveActorPacket.h"
#include "Navi/NaviProvider.h"
#include "Math/CalcBattle.h"
#include "Math/CalcStats.h"
#include "StatusEffect/StatusEffect.h"
#include "ServerMgr.h"
#include "Session.h"
#include "Math/CalcBattle.h"
#include "Chara.h"
#include "Player.h"
#include "BNpc.h"
#include "BNpcTemplate.h"
#include "Manager/TerritoryMgr.h"
#include "Common.h"
#include "Framework.h"
#include <Logging/Logger.h>
#include <Manager/TerritoryMgr.h>
#include <Manager/NaviMgr.h>
#include <Manager/TerritoryMgr.h>
#include <Manager/RNGMgr.h>
@ -72,7 +76,9 @@ Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX
m_levelId = 0;
m_flags = 0;
m_pCurrentZone = pZone;
m_class = ClassJob::Adventurer;
m_pCurrentZone = std::move( pZone );
m_spawnPos = m_pos;
@ -112,6 +118,8 @@ Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX
// todo: is this actually good?
//m_naviTargetReachedDistance = m_scale * 2.f;
m_naviTargetReachedDistance = 4.f;
calculateStats();
}
Sapphire::Entity::BNpc::~BNpc() = default;
@ -262,7 +270,7 @@ void Sapphire::Entity::BNpc::sendPositionUpdate()
void Sapphire::Entity::BNpc::hateListClear()
{
auto it = m_hateList.begin();
for( auto listEntry : m_hateList )
for( auto& listEntry : m_hateList )
{
if( isInRangeSet( listEntry->m_pChara ) )
deaggro( listEntry->m_pChara );
@ -684,7 +692,7 @@ void Sapphire::Entity::BNpc::autoAttack( CharaPtr pTarget )
srand( static_cast< uint32_t >( tick ) );
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 );
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
@ -701,3 +709,37 @@ void Sapphire::Entity::BNpc::autoAttack( CharaPtr pTarget )
}
}
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;
void setFlag( uint32_t flags );
void calculateStats() override;
private:
uint32_t m_bNpcBaseId;
uint32_t m_bNpcNameId;

View file

@ -21,7 +21,7 @@
#include "Action/Action.h"
#include "ServerMgr.h"
#include "Session.h"
#include "Math/CalcBattle.h"
#include "Math/CalcStats.h"
#include "Chara.h"
#include "Player.h"
#include "Manager/TerritoryMgr.h"
@ -720,3 +720,149 @@ float Sapphire::Entity::Chara::getRadius() const
{
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;
uint32_t getStatValue( Common::BaseParam baseParam ) const;
uint32_t getHp() const;
uint32_t getHpPercent() const;
@ -280,6 +282,8 @@ namespace Sapphire::Entity
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 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 ) +
tribeInfo->sTR );
@ -313,28 +313,28 @@ void Sapphire::Entity::Player::sendStats()
{
auto statPacket = makeZonePacket< FFXIVIpcPlayerStats >( getId() );
statPacket->data().strength = m_baseStats.str + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Strength ) ];
statPacket->data().dexterity = m_baseStats.dex + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Dexterity ) ];
statPacket->data().vitality = m_baseStats.vit + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Vitality ) ];
statPacket->data().intelligence = m_baseStats.inte + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Intelligence ) ];
statPacket->data().mind = m_baseStats.mnd + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Mind ) ];
statPacket->data().piety = m_baseStats.pie + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Piety ) ];
statPacket->data().determination = m_baseStats.determination + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Determination ) ];
statPacket->data().hp = m_baseStats.max_hp + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::HP ) ];
statPacket->data().mp = m_baseStats.max_mp + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::MP ) ];
statPacket->data().strength = getStatValue( Common::BaseParam::Strength );
statPacket->data().dexterity = getStatValue( Common::BaseParam::Dexterity );
statPacket->data().vitality = getStatValue( Common::BaseParam::Vitality );
statPacket->data().intelligence = getStatValue( Common::BaseParam::Intelligence );
statPacket->data().mind = getStatValue( Common::BaseParam::Mind );
statPacket->data().piety = getStatValue( Common::BaseParam::Piety );
statPacket->data().determination = getStatValue( Common::BaseParam::Determination );
statPacket->data().hp = getStatValue( Common::BaseParam::HP );
statPacket->data().mp = getStatValue( Common::BaseParam::MP );
statPacket->data().accuracy = m_baseStats.accuracy;
statPacket->data().attack = m_baseStats.attack + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::AttackPower ) ];
statPacket->data().attackMagicPotency = m_baseStats.attackPotMagic + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::AttackMagicPotency ) ];
statPacket->data().healingMagicPotency = m_baseStats.healingPotMagic + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::HealingMagicPotency ) ];
statPacket->data().skillSpeed = m_baseStats.skillSpeed + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::SkillSpeed ) ];
statPacket->data().spellSpeed = m_baseStats.spellSpeed + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::SpellSpeed ) ];
statPacket->data().spellSpeed1 = m_baseStats.spellSpeed + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::SpellSpeed ) ];
statPacket->data().attack = getStatValue( Common::BaseParam::AttackPower );
statPacket->data().attackMagicPotency = getStatValue( Common::BaseParam::AttackMagicPotency );
statPacket->data().healingMagicPotency = getStatValue( Common::BaseParam::HealingMagicPotency );
statPacket->data().skillSpeed = getStatValue( Common::BaseParam::SkillSpeed );
statPacket->data().spellSpeed = getStatValue( Common::BaseParam::SpellSpeed );
statPacket->data().spellSpeed1 = getStatValue( Common::BaseParam::SpellSpeed );
statPacket->data().spellSpeedMod = 100;
statPacket->data().criticalHitRate = m_baseStats.critHitRate + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::CriticalHit ) ];
statPacket->data().defense = m_baseStats.defense + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Defense ) ];
statPacket->data().magicDefense = m_baseStats.magicDefense + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::MagicDefense ) ];
statPacket->data().tenacity = m_baseStats.tenacity + m_bonusStats[ static_cast< uint8_t >( Common::BaseParam::Tenacity ) ];
statPacket->data().criticalHitRate = getStatValue( Common::BaseParam::CriticalHit );
statPacket->data().defense = getStatValue( Common::BaseParam::Defense );
statPacket->data().magicDefense = getStatValue( Common::BaseParam::MagicDefense );
statPacket->data().tenacity = getStatValue( Common::BaseParam::Tenacity );
queuePacket( statPacket );
}
@ -1105,9 +1105,10 @@ void Sapphire::Entity::Player::update( uint64_t tickCount )
{
if( actor->getId() == m_targetId && actor->getAsChara()->isAlive() && mainWeap )
{
auto chara = actor->getAsChara();
// default autoattack range
// TODO make this dependant on bnpc size
uint32_t range = 7;
float range = 3.f + chara->getRadius();
// default autoattack range for ranged classes
if( getClass() == ClassJob::Machinist ||
@ -1566,8 +1567,7 @@ void Sapphire::Entity::Player::autoAttack( CharaPtr pTarget )
auto pRNGMgr = m_pFw->get< World::Manager::RNGMgr >();
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 ),
getLevel() + static_cast< uint32_t >( mainWeap->getAutoAttackDmg() * 2 ) ).next() );
auto damage = Math::CalcStats::calculateAutoAttackDamage( *this );
if( getClass() == ClassJob::Machinist || getClass() == ClassJob::Bard || getClass() == ClassJob::Archer )
{
@ -2124,3 +2124,13 @@ Sapphire::World::SessionPtr Sapphire::Entity::Player::getSession()
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 */
uint16_t calculateEquippedGearItemLevel();
ItemPtr getEquippedWeapon();
/*! return the current amount of currency of type */
uint32_t getCurrency( Common::CurrencyType type );
@ -964,6 +966,8 @@ namespace Sapphire::Entity
Sapphire::ItemPtr dropInventoryItem( Common::InventoryType type, uint16_t slotId );
//////////////////////////////////////////////////////////////////////////////////////////////////////
Common::HuntingLogEntry& getHuntingLogEntry( uint8_t index );
void sendHuntingLog();
@ -974,8 +978,6 @@ namespace Sapphire::Entity
World::SessionPtr getSession();
//////////////////////////////////////////////////////////////////////////////////////////////////////
uint64_t m_lastMoveTime;
uint8_t m_lastMoveflag;
bool m_falling;

View file

@ -831,17 +831,6 @@ void Sapphire::Entity::Player::discardItem( uint16_t fromInventoryId, uint8_t fr
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()
{
uint32_t iLvlResult = 0;
@ -854,7 +843,7 @@ uint16_t Sapphire::Entity::Player::calculateEquippedGearItemLevel()
{
auto currItem = it->second;
if( currItem )
if( currItem && currItem->getCategory() != Common::ItemUICategory::SoulCrystal )
{
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 ) );
}
Sapphire::ItemPtr Sapphire::Entity::Player::getEquippedWeapon()
{
return m_storageMap[ GearSet0 ]->getItem( GearSetSlot::MainHand );
}
uint8_t Sapphire::Entity::Player::getFreeSlotsInBags()
{

View file

@ -2,11 +2,13 @@
#include <Exd/ExdDataGenerated.h>
#include <Common.h>
#include <Logging/Logger.h>
#include "Actor/Chara.h"
#include "Actor/Player.h"
#include "Inventory/Item.h"
#include "CalcStats.h"
#include "Framework.h"
@ -108,10 +110,10 @@ const int levelTable[71][7] =
// 3 Versions. SB and HW are linear, ARR is polynomial.
// Originally from Player.cpp, calculateStats().
float CalcStats::calculateBaseStat( PlayerPtr pPlayer )
float CalcStats::calculateBaseStat( const Chara& chara )
{
float base = 0.0f;
uint8_t level = pPlayer->getLevel();
uint8_t level = chara.getLevel();
if( level > 70 )
level = 70;
@ -138,7 +140,7 @@ uint32_t CalcStats::calculateMaxHp( PlayerPtr pPlayer, Sapphire::FrameworkPtr pF
uint8_t level = pPlayer->getLevel();
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 hpMod = paramGrowthInfo->hpModifier;
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 );
float baseStat = calculateBaseStat( pPlayer );
float baseStat = calculateBaseStat( *pPlayer );
uint16_t piety = pPlayer->getStats().pie + pieMod;
uint16_t pietyScalar = paramGrowthInfo->mpModifier;
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 )
{
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 ] );
return std::floor( ( 30 * blockRate ) / levelVal + 10 );
@ -279,8 +281,7 @@ float CalcStats::directHitProbability( const Chara& chara )
const auto& baseStats = chara.getStats();
auto level = chara.getLevel();
float dhRate = static_cast< float >( chara.getBonusStat( Common::BaseParam::DirectHitRate ) ) +
baseStats.accuracy;
float dhRate = chara.getStatValue( Common::BaseParam::DirectHitRate );
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
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();
auto level = chara.getLevel();
float chRate = static_cast< float >( chara.getBonusStat( Common::BaseParam::CriticalHit ) ) +
baseStats.critHitRate;
float chRate = chara.getStatValue( Common::BaseParam::CriticalHit );
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
@ -308,77 +308,142 @@ 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 )
float CalcStats::autoAttackPotency( const Sapphire::Entity::Chara& chara )
{
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 CalcStats::magicAttackPower( const Sapphire::Entity::Chara& chara )
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::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();
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 calcAttackPower( baseStats.healingPotMagic );
return attackPower( 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 )
{
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;
return std::floor( 130.f * ( chara.getStatValue( Common::BaseParam::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;
return std::floor( 100.f * ( chara.getStatValue( Common::BaseParam::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 ] );
@ -386,23 +451,15 @@ float CalcStats::speed( const Sapphire::Entity::Chara& chara )
uint32_t speedVal = 0;
// check whether we use spellspeed or skillspeed
// todo: this is kinda shitty though
switch( chara.getClass() )
switch( chara.getPrimaryStat() )
{
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;
case Common::BaseParam::Intelligence:
case Common::BaseParam::Mind:
speedVal = chara.getStatValue( Common::BaseParam::SpellSpeed );
break;
default:
speedVal = baseStats.skillSpeed;
speedVal = chara.getStatValue( Common::BaseParam::SkillSpeed );
}
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 )
{
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;
return std::floor( 200.f * ( chara.getStatValue( Common::BaseParam::CriticalHit ) - 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;
return std::floor( 15.f * chara.getStatValue( Common::BaseParam::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;
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 )
{
const auto& baseStats = chara.getStats();
return std::floor( 100.f * ( baseStats.healingPotMagic - 292.f ) / 264.f + 100.f ) / 100.f;
return std::floor( 100.f * ( chara.getStatValue( Common::BaseParam::HealingMagicPotency ) - 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
{
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 );
@ -47,36 +50,28 @@ namespace Sapphire::Math
*/
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
*
* @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 );
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
*
* @param chara The source/casting character.
*/
static float getPrimaryAttackPower( 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 );
/*!
* @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 );
/*!
@ -131,6 +126,8 @@ namespace Sapphire::Math
*/
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
*
@ -140,16 +137,20 @@ namespace Sapphire::Math
*/
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.
*
* @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 );
};