From 4de66b10645cf8ea978f72ec4d78dcef64c36d27 Mon Sep 17 00:00:00 2001 From: NotAdam Date: Wed, 24 Apr 2019 23:25:07 +1000 Subject: [PATCH 1/7] autoattack damage stat calc funcs, bnpc stat calculation --- src/common/Exd/ExdDataGenerated.cpp | 1 + src/common/Exd/ExdDataGenerated.h | 1 + src/world/Actor/BNpc.cpp | 54 +++++++- src/world/Actor/BNpc.h | 2 + src/world/Actor/Chara.cpp | 11 ++ src/world/Actor/Chara.h | 2 + src/world/Actor/Player.cpp | 15 +- src/world/Actor/Player.h | 6 +- src/world/Actor/PlayerInventory.cpp | 15 +- src/world/Math/CalcStats.cpp | 207 +++++++++++++++++++++------- src/world/Math/CalcStats.h | 34 ++--- 11 files changed, 253 insertions(+), 95 deletions(-) diff --git a/src/common/Exd/ExdDataGenerated.cpp b/src/common/Exd/ExdDataGenerated.cpp index c7485692..fa51b627 100644 --- a/src/common/Exd/ExdDataGenerated.cpp +++ b/src/common/Exd/ExdDataGenerated.cpp @@ -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 ); diff --git a/src/common/Exd/ExdDataGenerated.h b/src/common/Exd/ExdDataGenerated.h index 97c6122b..bcbba9b2 100644 --- a/src/common/Exd/ExdDataGenerated.h +++ b/src/common/Exd/ExdDataGenerated.h @@ -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; diff --git a/src/world/Actor/BNpc.cpp b/src/world/Actor/BNpc.cpp index 841ff2b9..9f7bd41f 100644 --- a/src/world/Actor/BNpc.cpp +++ b/src/world/Actor/BNpc.cpp @@ -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 + +#include #include #include #include @@ -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 ); @@ -681,7 +689,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() ) ); @@ -696,4 +704,38 @@ void Sapphire::Entity::BNpc::autoAttack( CharaPtr pTarget ) 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; } \ No newline at end of file diff --git a/src/world/Actor/BNpc.h b/src/world/Actor/BNpc.h index 6a38ecfa..8f14e024 100644 --- a/src/world/Actor/BNpc.h +++ b/src/world/Actor/BNpc.h @@ -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; diff --git a/src/world/Actor/Chara.cpp b/src/world/Actor/Chara.cpp index b4e9303d..968698b0 100644 --- a/src/world/Actor/Chara.cpp +++ b/src/world/Actor/Chara.cpp @@ -718,4 +718,15 @@ void Sapphire::Entity::Chara::setAgentId( uint32_t agentId ) 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 ); } \ No newline at end of file diff --git a/src/world/Actor/Chara.h b/src/world/Actor/Chara.h index d882a18a..4e2d8e9b 100644 --- a/src/world/Actor/Chara.h +++ b/src/world/Actor/Chara.h @@ -280,6 +280,8 @@ namespace Sapphire::Entity float getRadius() const; + Common::BaseParam getPrimaryStat() const; + }; } diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index f6d32053..1547a860 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -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 ); @@ -1559,8 +1559,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 ) { @@ -2116,3 +2115,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; +} diff --git a/src/world/Actor/Player.h b/src/world/Actor/Player.h index b7becacb..3fbf9f88 100644 --- a/src/world/Actor/Player.h +++ b/src/world/Actor/Player.h @@ -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; diff --git a/src/world/Actor/PlayerInventory.cpp b/src/world/Actor/PlayerInventory.cpp index 97fec07b..3d25ad4f 100644 --- a/src/world/Actor/PlayerInventory.cpp +++ b/src/world/Actor/PlayerInventory.cpp @@ -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; @@ -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() { diff --git a/src/world/Math/CalcStats.cpp b/src/world/Math/CalcStats.cpp index 859cedec..99c536c8 100644 --- a/src/world/Math/CalcStats.cpp +++ b/src/world/Math/CalcStats.cpp @@ -2,11 +2,13 @@ #include #include +#include #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; @@ -308,49 +310,67 @@ 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 -//} +float CalcStats::weaponDamage( const Sapphire::Entity::Chara& chara, float weaponDamage ) +{ + const auto& baseStats = chara.getStats(); + auto level = chara.getLevel(); -// todo: this is all retarded, needs to be per weapon and etcetc -//uint32_t CalcStats::getPrimaryClassJobAttribute( const Sapphire::Entity::Chara& chara ) -//{ -// -//} + 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( 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 ); + // todo: this is wrong + if( chara.isBattleNpc() ) + return calcAttackPower( baseStats.attack ); + + switch( chara.getPrimaryStat() ) + { + case Common::BaseParam::Mind: + { + return calcAttackPower( baseStats.healingPotMagic ); + } + case Common::BaseParam::Intelligence: + { + return calcAttackPower( baseStats.attackPotMagic ); + } + + default: + { + return calcAttackPower( baseStats.attack ); + } + } } float CalcStats::determination( const Sapphire::Entity::Chara& chara ) @@ -386,18 +406,10 @@ 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: + case Common::BaseParam::Intelligence: + case Common::BaseParam::Mind: speedVal = baseStats.spellSpeed; break; @@ -439,14 +451,105 @@ float CalcStats::magicDefence( const Sapphire::Entity::Chara& chara ) return std::floor( 15.f * baseStats.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; +} + +//////// + +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 = potency( AUTO_ATTACK_POTENCY ); + auto aa = autoAttack( chara ); + //auto ap = attackPower( chara ); + auto ap = 1.f; + auto det = determination( chara ); + auto ten = tenacity( chara ); + + Logger::info( "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 ) +{ + const auto& baseStats = chara.getStats(); + + switch( chara.getPrimaryStat() ) + { + default: + return 1; + + case Common::BaseParam::Intelligence: + return baseStats.inte; + + case Common::BaseParam::Mind: + return baseStats.mnd; + + case Common::BaseParam::Strength: + return baseStats.str; + + case Common::BaseParam::Vitality: + return baseStats.vit; + + case Common::BaseParam::Dexterity: + return baseStats.dex; + } } \ No newline at end of file diff --git a/src/world/Math/CalcStats.h b/src/world/Math/CalcStats.h index 6aab838d..11d9fac1 100644 --- a/src/world/Math/CalcStats.h +++ b/src/world/Math/CalcStats.h @@ -10,7 +10,9 @@ namespace Sapphire::Math class CalcStats { public: - static float calculateBaseStat( Sapphire::Entity::PlayerPtr pPlayer ); + static const uint32_t AUTO_ATTACK_POTENCY = 100; + + static float calculateBaseStat( const Entity::Chara& chara ); static uint32_t calculateMaxMp( Sapphire::Entity::PlayerPtr pPlayer, FrameworkPtr pFw ); @@ -52,33 +54,17 @@ namespace Sapphire::Math * * @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 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. * @@ -131,6 +117,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,9 +128,11 @@ 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 ); + + private: /*! * @brief Has the main attack power calculation allowing for de-duplication of functions. @@ -151,6 +141,8 @@ namespace Sapphire::Math */ static float calcAttackPower( uint32_t attackPower ); + static uint32_t primaryStatValue( const Sapphire::Entity::Chara& chara ); + }; } From c851a4a88b84cf10703119753e2e8abb94fb3aa1 Mon Sep 17 00:00:00 2001 From: NotAdam Date: Wed, 24 Apr 2019 23:35:26 +1000 Subject: [PATCH 2/7] slightly better stat calc for bnpcs --- src/world/Math/CalcStats.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/world/Math/CalcStats.cpp b/src/world/Math/CalcStats.cpp index 99c536c8..eda56f55 100644 --- a/src/world/Math/CalcStats.cpp +++ b/src/world/Math/CalcStats.cpp @@ -495,8 +495,6 @@ float CalcStats::healingMagicPotency( const Sapphire::Entity::Chara& chara ) 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) ⌋ × @@ -534,8 +532,9 @@ uint32_t CalcStats::primaryStatValue( const Sapphire::Entity::Chara& chara ) switch( chara.getPrimaryStat() ) { + case Common::BaseParam::Strength: default: - return 1; + return baseStats.str; case Common::BaseParam::Intelligence: return baseStats.inte; @@ -543,9 +542,6 @@ uint32_t CalcStats::primaryStatValue( const Sapphire::Entity::Chara& chara ) case Common::BaseParam::Mind: return baseStats.mnd; - case Common::BaseParam::Strength: - return baseStats.str; - case Common::BaseParam::Vitality: return baseStats.vit; From fc9f1e6673dc6fb3a8246b4b889a28719d775f6d Mon Sep 17 00:00:00 2001 From: NotAdam Date: Wed, 24 Apr 2019 23:47:35 +1000 Subject: [PATCH 3/7] fix attack power calculation not using level value --- src/world/Math/CalcStats.cpp | 19 +++++++++++-------- src/world/Math/CalcStats.h | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/world/Math/CalcStats.cpp b/src/world/Math/CalcStats.cpp index eda56f55..1134ffa0 100644 --- a/src/world/Math/CalcStats.cpp +++ b/src/world/Math/CalcStats.cpp @@ -342,9 +342,13 @@ float CalcStats::weaponDamage( const Sapphire::Entity::Chara& chara, float weapo return std::floor( ( ( mainVal * jobAttribute ) / 1000.f ) + weaponDamage ); } -float CalcStats::calcAttackPower( uint32_t attackPower ) +float CalcStats::calcAttackPower( const Sapphire::Entity::Chara& chara, uint32_t attackPower ) { - return std::floor( ( 125.f * ( attackPower - 292.f ) / 292.f ) + 100.f ) / 100.f; + auto level = chara.getLevel(); + auto mainVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] ); + auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] ); + + return std::floor( ( 125.f * ( attackPower - mainVal ) / divVal ) + 100.f ) / 100.f; } float CalcStats::attackPower( const Sapphire::Entity::Chara& chara ) @@ -353,22 +357,22 @@ float CalcStats::attackPower( const Sapphire::Entity::Chara& chara ) // todo: this is wrong if( chara.isBattleNpc() ) - return calcAttackPower( baseStats.attack ); + return calcAttackPower( chara, baseStats.attack ); switch( chara.getPrimaryStat() ) { case Common::BaseParam::Mind: { - return calcAttackPower( baseStats.healingPotMagic ); + return calcAttackPower( chara, baseStats.healingPotMagic ); } case Common::BaseParam::Intelligence: { - return calcAttackPower( baseStats.attackPotMagic ); + return calcAttackPower( chara, baseStats.attackPotMagic ); } default: { - return calcAttackPower( baseStats.attack ); + return calcAttackPower( chara, baseStats.attack ); } } } @@ -502,8 +506,7 @@ float CalcStats::calculateAutoAttackDamage( const Sapphire::Entity::Chara& chara auto pot = potency( AUTO_ATTACK_POTENCY ); auto aa = autoAttack( chara ); - //auto ap = attackPower( chara ); - auto ap = 1.f; + auto ap = attackPower( chara ); auto det = determination( chara ); auto ten = tenacity( chara ); diff --git a/src/world/Math/CalcStats.h b/src/world/Math/CalcStats.h index 11d9fac1..70859de6 100644 --- a/src/world/Math/CalcStats.h +++ b/src/world/Math/CalcStats.h @@ -139,7 +139,7 @@ namespace Sapphire::Math * * @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 ); static uint32_t primaryStatValue( const Sapphire::Entity::Chara& chara ); From 9a26a24d31d9c8096e7433904edf175c2463e007 Mon Sep 17 00:00:00 2001 From: NotAdam Date: Thu, 25 Apr 2019 18:26:35 +1000 Subject: [PATCH 4/7] ignore soulstones in ilvl calculation --- src/world/Actor/PlayerInventory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/world/Actor/PlayerInventory.cpp b/src/world/Actor/PlayerInventory.cpp index 3d25ad4f..77677c64 100644 --- a/src/world/Actor/PlayerInventory.cpp +++ b/src/world/Actor/PlayerInventory.cpp @@ -843,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(); From 0005b672a7d662df87d9a967675b0388eaa0209b Mon Sep 17 00:00:00 2001 From: NotAdam Date: Thu, 25 Apr 2019 21:57:41 +1000 Subject: [PATCH 5/7] cleanup stat calculation code, slightly better working aa dmg calc --- src/world/Actor/Chara.cpp | 130 ++++++++++++++++++++++++++++++++++- src/world/Actor/Chara.h | 2 + src/world/Actor/Player.cpp | 38 +++++----- src/world/Math/CalcStats.cpp | 85 +++++++++-------------- src/world/Math/CalcStats.h | 8 ++- 5 files changed, 191 insertions(+), 72 deletions(-) diff --git a/src/world/Actor/Chara.cpp b/src/world/Actor/Chara.cpp index 968698b0..779c3afd 100644 --- a/src/world/Actor/Chara.cpp +++ b/src/world/Actor/Chara.cpp @@ -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" @@ -729,4 +729,132 @@ Sapphire::Common::BaseParam Sapphire::Entity::Chara::getPrimaryStat() const 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: + { + value = m_baseStats.attack; + break; + } + + 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 ); } \ No newline at end of file diff --git a/src/world/Actor/Chara.h b/src/world/Actor/Chara.h index 4e2d8e9b..5679b2cb 100644 --- a/src/world/Actor/Chara.h +++ b/src/world/Actor/Chara.h @@ -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; diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index 1547a860..32852714 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -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 ); } diff --git a/src/world/Math/CalcStats.cpp b/src/world/Math/CalcStats.cpp index 1134ffa0..1542e2fd 100644 --- a/src/world/Math/CalcStats.cpp +++ b/src/world/Math/CalcStats.cpp @@ -270,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 ); @@ -281,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 ] ); @@ -295,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 ] ); @@ -348,61 +346,70 @@ float CalcStats::calcAttackPower( const Sapphire::Entity::Chara& chara, uint32_t 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::attackPower( const Sapphire::Entity::Chara& chara ) +float CalcStats::getPrimaryAttackPower( const Sapphire::Entity::Chara& chara ) { const auto& baseStats = chara.getStats(); - // todo: this is wrong - if( chara.isBattleNpc() ) - return calcAttackPower( chara, baseStats.attack ); - switch( chara.getPrimaryStat() ) { case Common::BaseParam::Mind: { - return calcAttackPower( chara, baseStats.healingPotMagic ); + return healingMagicPower( chara ); } case Common::BaseParam::Intelligence: { - return calcAttackPower( chara, baseStats.attackPotMagic ); + return magicAttackPower( chara ); } default: { - return calcAttackPower( chara, baseStats.attack ); + return attackPower( chara ); } } } +float CalcStats::attackPower( const Sapphire::Entity::Chara& chara ) +{ + return calcAttackPower( chara, chara.getStatValue( Common::BaseParam::AttackPower ) ); +} + +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 ] ); @@ -414,11 +421,11 @@ float CalcStats::speed( const Sapphire::Entity::Chara& chara ) { case Common::BaseParam::Intelligence: case Common::BaseParam::Mind: - speedVal = baseStats.spellSpeed; + 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; @@ -427,32 +434,29 @@ 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 ) @@ -494,9 +498,7 @@ float CalcStats::autoAttack( 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; + 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 ) @@ -506,11 +508,11 @@ float CalcStats::calculateAutoAttackDamage( const Sapphire::Entity::Chara& chara auto pot = potency( AUTO_ATTACK_POTENCY ); auto aa = autoAttack( chara ); - auto ap = attackPower( chara ); + auto ap = getPrimaryAttackPower( chara ); auto det = determination( chara ); auto ten = tenacity( chara ); - Logger::info( "auto attack: pot: {} aa: {} ap: {} det: {} ten: {}", pot, aa, ap, det, ten ); + Logger::debug( "auto attack: pot: {} aa: {} ap: {} det: {} ten: {}", pot, aa, ap, det, ten ); auto factor = std::floor( pot * aa * ap * det * ten ); @@ -531,24 +533,5 @@ float CalcStats::calculateAutoAttackDamage( const Sapphire::Entity::Chara& chara uint32_t CalcStats::primaryStatValue( const Sapphire::Entity::Chara& chara ) { - const auto& baseStats = chara.getStats(); - - switch( chara.getPrimaryStat() ) - { - case Common::BaseParam::Strength: - default: - return baseStats.str; - - case Common::BaseParam::Intelligence: - return baseStats.inte; - - case Common::BaseParam::Mind: - return baseStats.mnd; - - case Common::BaseParam::Vitality: - return baseStats.vit; - - case Common::BaseParam::Dexterity: - return baseStats.dex; - } + return chara.getStatValue( chara.getPrimaryStat() ); } \ No newline at end of file diff --git a/src/world/Math/CalcStats.h b/src/world/Math/CalcStats.h index 70859de6..5f2b4060 100644 --- a/src/world/Math/CalcStats.h +++ b/src/world/Math/CalcStats.h @@ -63,8 +63,14 @@ namespace Sapphire::Math * * @param chara The source/casting character. */ + static float getPrimaryAttackPower( const Sapphire::Entity::Chara& chara ); + static float attackPower( const Sapphire::Entity::Chara& chara ); + static float magicAttackPower( const Sapphire::Entity::Chara& chara ); + + static float healingMagicPower( const Sapphire::Entity::Chara& chara ); + /*! * @brief Calculates determinations contribution to damage and healing output. * @@ -132,6 +138,7 @@ namespace Sapphire::Math static float calculateAutoAttackDamage( const Sapphire::Entity::Chara& chara ); + static uint32_t primaryStatValue( const Sapphire::Entity::Chara& chara ); private: /*! @@ -141,7 +148,6 @@ namespace Sapphire::Math */ static float calcAttackPower( const Sapphire::Entity::Chara& chara, uint32_t attackPower ); - static uint32_t primaryStatValue( const Sapphire::Entity::Chara& chara ); }; From a0d596f40fa1b1c739624c0f361b290ab4e0f4b5 Mon Sep 17 00:00:00 2001 From: NotAdam Date: Thu, 25 Apr 2019 22:45:23 +1000 Subject: [PATCH 6/7] fix autoattack range --- src/world/Actor/Chara.cpp | 4 ++-- src/world/Actor/Player.cpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/world/Actor/Chara.cpp b/src/world/Actor/Chara.cpp index 779c3afd..bb328c33 100644 --- a/src/world/Actor/Chara.cpp +++ b/src/world/Actor/Chara.cpp @@ -793,8 +793,8 @@ uint32_t Sapphire::Entity::Chara::getStatValue( Sapphire::Common::BaseParam base case Common::BaseParam::AttackPower: { - value = m_baseStats.attack; - break; + // todo: think this is right... not sure + return getStatValue( Common::BaseParam::Strength ); } case Common::BaseParam::AttackMagicPotency: diff --git a/src/world/Actor/Player.cpp b/src/world/Actor/Player.cpp index 32852714..bae8edb5 100644 --- a/src/world/Actor/Player.cpp +++ b/src/world/Actor/Player.cpp @@ -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 || From 3468a149f43c67980209028c1b7bc4abe8be52b0 Mon Sep 17 00:00:00 2001 From: NotAdam Date: Sat, 11 May 2019 13:16:34 +1000 Subject: [PATCH 7/7] correct autoattack potency calculation, fix attack power calc --- src/world/Actor/Chara.cpp | 9 ++++++++- src/world/Math/CalcStats.cpp | 38 ++++++++++++++++++++++++++++++++++-- src/world/Math/CalcStats.h | 5 ++++- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/world/Actor/Chara.cpp b/src/world/Actor/Chara.cpp index bb328c33..445eac47 100644 --- a/src/world/Actor/Chara.cpp +++ b/src/world/Actor/Chara.cpp @@ -793,7 +793,14 @@ uint32_t Sapphire::Entity::Chara::getStatValue( Sapphire::Common::BaseParam base case Common::BaseParam::AttackPower: { - // todo: think this is right... not sure + 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 ); } diff --git a/src/world/Math/CalcStats.cpp b/src/world/Math/CalcStats.cpp index 1542e2fd..b455b48a 100644 --- a/src/world/Math/CalcStats.cpp +++ b/src/world/Math/CalcStats.cpp @@ -308,6 +308,40 @@ float CalcStats::potency( uint16_t potency ) return potency / 100.f; } +float CalcStats::autoAttackPotency( const Sapphire::Entity::Chara& chara ) +{ + 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::weaponDamage( const Sapphire::Entity::Chara& chara, float weaponDamage ) { const auto& baseStats = chara.getStats(); @@ -491,7 +525,7 @@ float CalcStats::autoAttack( const Sapphire::Entity::Chara& chara ) auto level = chara.getLevel(); auto mainVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] ); - auto innerCalc = std::floor( ( ( mainVal * primaryStatValue( chara ) ) / 1000.f ) + weaponDamage ); + auto innerCalc = std::floor( ( mainVal * primaryStatValue( chara ) / 1000.f ) + weaponDamage ); return std::floor( innerCalc * ( autoAttackDelay / 3.f ) ); } @@ -506,7 +540,7 @@ 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 = potency( AUTO_ATTACK_POTENCY ); + auto pot = autoAttackPotency( chara ); auto aa = autoAttack( chara ); auto ap = getPrimaryAttackPower( chara ); auto det = determination( chara ); diff --git a/src/world/Math/CalcStats.h b/src/world/Math/CalcStats.h index 5f2b4060..92402124 100644 --- a/src/world/Math/CalcStats.h +++ b/src/world/Math/CalcStats.h @@ -10,7 +10,8 @@ namespace Sapphire::Math class CalcStats { public: - static const uint32_t AUTO_ATTACK_POTENCY = 100; + static const uint32_t AUTO_ATTACK_POTENCY = 110; + static const uint32_t RANGED_AUTO_ATTACK_POTENCY = 100; static float calculateBaseStat( const Entity::Chara& chara ); @@ -49,6 +50,8 @@ 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 *