mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-04-25 05:57:45 +00:00
516 lines
No EOL
16 KiB
C++
516 lines
No EOL
16 KiB
C++
#include <cmath>
|
||
|
||
#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"
|
||
|
||
using namespace Sapphire::Math;
|
||
using namespace Sapphire::Entity;
|
||
|
||
const int levelTable[81][6] =
|
||
{
|
||
// MAIN,SUB,DIV,HP,ELMT,THREAT
|
||
{ 1, 1, 1, 1, 1, 1 },
|
||
{ 20, 56, 56, 0, 52, 2 },
|
||
{ 21, 57, 57, 0, 54, 2 },
|
||
{ 22, 60, 60, 0, 56, 3 },
|
||
{ 24, 62, 62, 0, 58, 3 },
|
||
{ 26, 65, 65, 0, 60, 3 },
|
||
{ 27, 68, 68, 0, 62, 3 },
|
||
{ 29, 70, 70, 0, 64, 4 },
|
||
{ 31, 73, 73, 0, 66, 4 },
|
||
{ 33, 76, 76, 0, 68, 4 },
|
||
{ 35, 78, 78, 0, 70, 5 },
|
||
{ 36, 82, 82, 0, 73, 5 },
|
||
{ 38, 85, 85, 0, 75, 5 },
|
||
{ 41, 89, 89, 0, 78, 6 },
|
||
{ 44, 93, 93, 0, 81, 6 },
|
||
{ 46, 96, 96, 0, 84, 7 },
|
||
{ 49, 100, 100, 0, 86, 7 },
|
||
{ 52, 104, 104, 0, 89, 8 },
|
||
{ 54, 109, 109, 0, 93, 9 },
|
||
{ 57, 113, 113, 0, 95, 9 },
|
||
{ 60, 116, 116, 0, 98, 10 },
|
||
{ 63, 122, 122, 0, 102, 10 },
|
||
{ 67, 127, 127, 0, 105, 11 },
|
||
{ 71, 133, 133, 0, 109, 12 },
|
||
{ 74, 138, 138, 0, 113, 13 },
|
||
{ 78, 144, 144, 0, 117, 14 },
|
||
{ 81, 150, 150, 0, 121, 15 },
|
||
{ 85, 155, 155, 0, 125, 16 },
|
||
{ 89, 162, 162, 0, 129, 17 },
|
||
{ 92, 168, 168, 0, 133, 18 },
|
||
{ 97, 173, 173, 0, 137, 19 },
|
||
{ 101, 181, 181, 0, 143, 20 },
|
||
{ 106, 188, 188, 0, 148, 22 },
|
||
{ 110, 194, 194, 0, 153, 23 },
|
||
{ 115, 202, 202, 0, 159, 25 },
|
||
{ 119, 209, 209, 0, 165, 27 },
|
||
{ 124, 215, 215, 0, 170, 29 },
|
||
{ 128, 223, 223, 0, 176, 31 },
|
||
{ 134, 229, 229, 0, 181, 33 },
|
||
{ 139, 236, 236, 0, 186, 35 },
|
||
{ 144, 244, 244, 0, 192, 38 },
|
||
{ 150, 253, 253, 0, 200, 40 },
|
||
{ 155, 263, 263, 0, 207, 43 },
|
||
{ 161, 272, 272, 0, 215, 46 },
|
||
{ 166, 283, 283, 0, 223, 49 },
|
||
{ 171, 292, 292, 0, 231, 52 },
|
||
{ 177, 302, 302, 0, 238, 55 },
|
||
{ 183, 311, 311, 0, 246, 58 },
|
||
{ 189, 322, 322, 0, 254, 62 },
|
||
{ 196, 331, 331, 0, 261, 66 },
|
||
{ 202, 341, 341, 1700, 269, 70 },
|
||
{ 204, 342, 393, 1774, 270, 84 },
|
||
{ 205, 344, 444, 1851, 271, 99 },
|
||
{ 207, 345, 496, 1931, 273, 113 },
|
||
{ 209, 346, 548, 2015, 274, 128 },
|
||
{ 210, 347, 600, 2102, 275, 142 },
|
||
{ 212, 349, 651, 2194, 276, 157 },
|
||
{ 214, 350, 703, 2289, 278, 171 },
|
||
{ 215, 351, 755, 2388, 279, 186 },
|
||
{ 217, 352, 806, 2492, 280, 200 },
|
||
{ 218, 354, 858, 2600, 282, 215 },
|
||
{ 224, 355, 941, 2700, 283, 232 },
|
||
{ 228, 356, 1032, 2800, 284, 250 },
|
||
{ 236, 357, 1133, 2900, 286, 269 },
|
||
{ 244, 358, 1243, 3000, 287, 290 },
|
||
{ 252, 359, 1364, 3100, 288, 313 },
|
||
{ 260, 360, 1497, 3200, 290, 337 },
|
||
{ 268, 361, 1643, 3300, 292, 363 },
|
||
{ 276, 362, 1802, 3400, 293, 392 },
|
||
{ 284, 363, 1978, 3500, 294, 422 },
|
||
{ 292, 364, 2170, 3600, 295, 455 },
|
||
|
||
// todo: add proper shbr values - hp/elmt/threat
|
||
// sub/div added from http://theoryjerks.akhmorning.com/resources/levelmods/
|
||
{ 296, 365, 2263, 3600, 466, 466 },
|
||
{ 300, 366, 2360, 3600, 295, 466 },
|
||
{ 305, 367, 2461, 3600, 295, 466 },
|
||
{ 310, 368, 2566, 3600, 295, 466 },
|
||
{ 315, 370, 2676, 3600, 295, 466 },
|
||
{ 320, 372, 2790, 3600, 295, 466 },
|
||
{ 325, 374, 2910, 3600, 295, 466 },
|
||
{ 330, 376, 3034, 3600, 295, 466 },
|
||
{ 335, 378, 3164, 3600, 295, 466 },
|
||
{ 340, 380, 3300, 3600, 569, 569 },
|
||
};
|
||
|
||
/*
|
||
Class used for battle-related formulas and calculations.
|
||
Big thanks to the Theoryjerks group!
|
||
|
||
NOTE:
|
||
Formulas here shouldn't be considered final. It's possible that the formula it was based on is correct but
|
||
wasn't implemented correctly here, or approximated things due to limited knowledge of how things work in retail.
|
||
It's also possible that we're using formulas that were correct for previous patches, but not the current version.
|
||
|
||
TODO:
|
||
|
||
Base HP val modifier. I can only find values for levels 50~70.
|
||
Dereferencing the actor (Player right now) for stats seem meh, perhaps consider a structure purely for stats?
|
||
Reduce repeated code (more specifically the data we pull from exd)
|
||
*/
|
||
|
||
// 3 Versions. SB and HW are linear, ARR is polynomial.
|
||
// Originally from Player.cpp, calculateStats().
|
||
|
||
float CalcStats::calculateBaseStat( const Chara& chara )
|
||
{
|
||
float base = 0.0f;
|
||
uint8_t level = chara.getLevel();
|
||
|
||
if( level > Common::MAX_PLAYER_LEVEL )
|
||
level = Common::MAX_PLAYER_LEVEL;
|
||
|
||
return static_cast< float >( levelTable[level][2] );
|
||
}
|
||
|
||
// Leggerless' HP Formula
|
||
// ROUNDDOWN(JobModHP * (BaseHP / 100)) + ROUNDDOWN(VitHPMod / 100 * (VIT - BaseDET))
|
||
|
||
uint32_t CalcStats::calculateMaxHp( PlayerPtr pPlayer, Sapphire::FrameworkPtr pFw )
|
||
{
|
||
auto pExdData = pFw->get< Data::ExdDataGenerated >();
|
||
// TODO: Replace ApproxBaseHP with something that can get us an accurate BaseHP.
|
||
// Is there any way to pull reliable BaseHP without having to manually use a pet for every level, and using the values from a table?
|
||
// More info here: https://docs.google.com/spreadsheets/d/1de06KGT0cNRUvyiXNmjNgcNvzBCCQku7jte5QxEQRbs/edit?usp=sharing
|
||
|
||
auto classInfo = pExdData->get< Sapphire::Data::ClassJob >( static_cast< uint8_t >( pPlayer->getClass() ) );
|
||
auto paramGrowthInfo = pExdData->get< Sapphire::Data::ParamGrow >( pPlayer->getLevel() );
|
||
|
||
if( !classInfo || !paramGrowthInfo )
|
||
return 0;
|
||
|
||
uint8_t level = pPlayer->getLevel();
|
||
|
||
auto vitMod = pPlayer->getBonusStat( Common::BaseParam::Vitality );
|
||
float baseStat = calculateBaseStat( *pPlayer );
|
||
uint16_t vitStat = static_cast< uint16_t >( pPlayer->getStats().vit ) + static_cast< uint16_t >( vitMod );
|
||
uint16_t hpMod = paramGrowthInfo->hpModifier;
|
||
uint16_t jobModHp = classInfo->modifierHitPoints;
|
||
float approxBaseHp = 0.0f; // Read above
|
||
|
||
// These values are not precise.
|
||
|
||
if( level >= 60 )
|
||
approxBaseHp = static_cast< float >( 2600 + ( level - 60 ) * 100 );
|
||
else if( level >= 50 )
|
||
approxBaseHp = 1700 + ( ( level - 50 ) * ( 1700 * 1.04325f ) );
|
||
else
|
||
approxBaseHp = paramGrowthInfo->mpModifier * 0.7667f;
|
||
|
||
uint16_t result = static_cast< uint16_t >( floor( jobModHp * ( approxBaseHp / 100.0f ) ) +
|
||
floor( hpMod / 100.0f * ( vitStat - baseStat ) ) );
|
||
|
||
return result;
|
||
}
|
||
|
||
float CalcStats::blockProbability( const Chara& chara )
|
||
{
|
||
auto level = chara.getLevel();
|
||
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 );
|
||
}
|
||
|
||
float CalcStats::directHitProbability( const Chara& chara )
|
||
{
|
||
const auto& baseStats = chara.getStats();
|
||
auto level = chara.getLevel();
|
||
|
||
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 ] );
|
||
|
||
return std::floor( 550.f * ( dhRate - subVal ) / divVal ) / 10.f;
|
||
}
|
||
|
||
float CalcStats::criticalHitProbability( const Chara& chara )
|
||
{
|
||
const auto& baseStats = chara.getStats();
|
||
auto level = chara.getLevel();
|
||
|
||
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 ] );
|
||
|
||
return std::floor( 200.f * ( chRate - subVal ) / divVal + 50.f ) / 10.f;
|
||
}
|
||
|
||
|
||
float CalcStats::potency( uint16_t potency )
|
||
{
|
||
return potency / 100.f;
|
||
}
|
||
|
||
float CalcStats::autoAttackPotency( const Sapphire::Entity::Chara& chara )
|
||
{
|
||
uint32_t aaPotency = AUTO_ATTACK_POTENCY;
|
||
|
||
if( chara.getRole() == Common::Role::RangedPhysical )
|
||
{
|
||
aaPotency = RANGED_AUTO_ATTACK_POTENCY;
|
||
}
|
||
|
||
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();
|
||
|
||
switch( chara.getPrimaryStat() )
|
||
{
|
||
case Common::BaseParam::Mind:
|
||
{
|
||
return healingMagicPower( chara );
|
||
}
|
||
case Common::BaseParam::Intelligence:
|
||
{
|
||
return magicAttackPower( chara );
|
||
}
|
||
|
||
default:
|
||
{
|
||
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();
|
||
|
||
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 * ( chara.getStatValue( Common::BaseParam::Determination ) - mainVal ) / divVal + 1000.f ) / 1000.f;
|
||
}
|
||
|
||
float CalcStats::tenacity( const Sapphire::Entity::Chara& chara )
|
||
{
|
||
auto level = chara.getLevel();
|
||
|
||
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 * ( chara.getStatValue( Common::BaseParam::Tenacity ) - subVal ) / divVal + 1000.f ) / 1000.f;
|
||
}
|
||
|
||
float CalcStats::speed( const Sapphire::Entity::Chara& chara )
|
||
{
|
||
auto level = chara.getLevel();
|
||
|
||
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
|
||
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
||
|
||
uint32_t speedVal = 0;
|
||
|
||
// check whether we use spellspeed or skillspeed
|
||
switch( chara.getPrimaryStat() )
|
||
{
|
||
case Common::BaseParam::Intelligence:
|
||
case Common::BaseParam::Mind:
|
||
speedVal = chara.getStatValue( Common::BaseParam::SpellSpeed );
|
||
break;
|
||
|
||
default:
|
||
speedVal = chara.getStatValue( Common::BaseParam::SkillSpeed );
|
||
}
|
||
|
||
return std::floor( 130.f * ( speedVal - subVal ) / divVal + 1000.f ) / 1000.f;
|
||
}
|
||
|
||
float CalcStats::criticalHitBonus( const Sapphire::Entity::Chara& chara )
|
||
{
|
||
auto level = chara.getLevel();
|
||
|
||
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 * ( chara.getStatValue( Common::BaseParam::CriticalHit ) - subVal ) / divVal + 1400.f ) / 1000.f;
|
||
}
|
||
|
||
float CalcStats::physicalDefence( const Sapphire::Entity::Chara& chara )
|
||
{
|
||
auto level = chara.getLevel();
|
||
|
||
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
||
|
||
return std::floor( 15.f * chara.getStatValue( Common::BaseParam::Defense ) ) / 100.f;
|
||
}
|
||
|
||
float CalcStats::magicDefence( const Sapphire::Entity::Chara& chara )
|
||
{
|
||
auto level = chara.getLevel();
|
||
|
||
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
||
|
||
return std::floor( 15.f * chara.getStatValue( Common::BaseParam::MagicDefense ) ) / 100.f;
|
||
}
|
||
|
||
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 )
|
||
{
|
||
return std::floor( 100.f * ( chara.getStatValue( Common::BaseParam::HealingMagicPotency ) - 292.f ) / 264.f + 100.f ) / 100.f;
|
||
}
|
||
|
||
float CalcStats::calcAutoAttackDamage( 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 = 1.f;
|
||
if( chara.getRole() == Common::Role::Tank )
|
||
ten = tenacity( chara );
|
||
|
||
// todo: everything after tenacity
|
||
auto factor = std::floor( pot * aa * ap * det * ten );
|
||
|
||
constexpr auto format = "auto attack: pot: {} aa: {} ap: {} det: {} ten: {} = {}";
|
||
|
||
if( auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer() )
|
||
{
|
||
player->sendDebug( format, pot, aa, ap, det, ten, factor );
|
||
}
|
||
else
|
||
{
|
||
Logger::debug( format, pot, aa, ap, det, ten, factor );
|
||
}
|
||
|
||
// 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;
|
||
}
|
||
|
||
float CalcStats::calcActionDamage( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg )
|
||
{
|
||
// D = ⌊ f(pot) × f(wd) × f(ap) × f(det) × f(tnc) × traits ⌋
|
||
// × f(chr) ⌋ × f(dhr) ⌋ × rand[ 0.95, 1.05 ] ⌋ buff_1 ⌋ × buff_1 ⌋ × buff... ⌋
|
||
|
||
auto pot = potency( static_cast< uint16_t >( ptc ) );
|
||
auto wd = weaponDamage( chara, wepDmg );
|
||
auto ap = getPrimaryAttackPower( chara );
|
||
auto det = determination( chara );
|
||
|
||
auto ten = 1.f;
|
||
if( chara.getRole() == Common::Role::Tank )
|
||
ten = tenacity( chara );
|
||
|
||
auto factor = std::floor( pot * wd * ap * det * ten );
|
||
|
||
constexpr auto format = "dmg: pot: {} ({}) wd: {} ({}) ap: {} det: {} ten: {} = {}";
|
||
|
||
if( auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer() )
|
||
{
|
||
player->sendDebug( format, pot, ptc, wd, wepDmg, ap, det, ten, factor );
|
||
}
|
||
else
|
||
{
|
||
Logger::debug( format, pot, ptc, wd, wepDmg, ap, det, ten, factor );
|
||
}
|
||
|
||
// todo: the rest
|
||
|
||
return factor;
|
||
}
|
||
|
||
uint32_t CalcStats::primaryStatValue( const Sapphire::Entity::Chara& chara )
|
||
{
|
||
return chara.getStatValue( chara.getPrimaryStat() );
|
||
} |