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 393ee19f..548db5d8 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 ); @@ -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() ) ); @@ -700,4 +708,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 3da8d362..d2eac841 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" @@ -719,4 +719,150 @@ 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 ); +} + +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 ); } \ No newline at end of file diff --git a/src/world/Actor/Chara.h b/src/world/Actor/Chara.h index d882a18a..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; @@ -280,6 +282,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 3a96b640..362dfa0e 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 ); @@ -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; +} 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..77677c64 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; @@ -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() { diff --git a/src/world/Math/CalcStats.cpp b/src/world/Math/CalcStats.cpp index 859cedec..b455b48a 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; @@ -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 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(); - 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 ) -{ - const auto& baseStats = chara.getStats(); - - return calcAttackPower( baseStats.healingPotMagic ); + default: + { + 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 * ( 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() ); } \ No newline at end of file diff --git a/src/world/Math/CalcStats.h b/src/world/Math/CalcStats.h index 6aab838d..92402124 100644 --- a/src/world/Math/CalcStats.h +++ b/src/world/Math/CalcStats.h @@ -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 ); + };