2017-08-28 16:13:23 -03:00
|
|
|
#include <cmath>
|
2017-08-20 02:46:06 -03:00
|
|
|
|
2018-03-06 22:22:19 +01:00
|
|
|
#include <Exd/ExdDataGenerated.h>
|
|
|
|
#include <Common.h>
|
2018-03-02 07:22:25 -03:00
|
|
|
|
2018-02-20 22:46:44 +01:00
|
|
|
#include "Actor/Chara.h"
|
2018-03-06 00:10:36 +01:00
|
|
|
|
2017-12-08 15:38:25 +01:00
|
|
|
#include "Actor/Player.h"
|
2017-11-21 03:19:08 -02:00
|
|
|
|
|
|
|
#include "CalcStats.h"
|
2018-03-02 07:22:25 -03:00
|
|
|
#include "Framework.h"
|
2017-11-21 03:19:08 -02:00
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
using namespace Sapphire::Math;
|
|
|
|
using namespace Sapphire::Entity;
|
2017-08-20 02:46:06 -03:00
|
|
|
|
2019-03-23 23:12:41 +11:00
|
|
|
const int levelTable[71][7] =
|
2019-03-20 15:22:40 +01:00
|
|
|
{
|
|
|
|
// PIE, MP, MAIN,SUB,DIV,HP,ELMT,THREAT
|
2019-03-23 23:12:41 +11:00
|
|
|
{ 1, 1, 1, 1, 1, 1, 1 },
|
2019-03-20 21:29:34 +01:00
|
|
|
{ 50, 104, 20, 56, 56, 0, 52 },
|
|
|
|
{ 55, 114, 21, 57, 57, 0, 54 },
|
|
|
|
{ 60, 123, 22, 60, 60, 0, 56 },
|
|
|
|
{ 65, 133, 24, 62, 62, 0, 58 },
|
|
|
|
{ 70, 142, 26, 65, 65, 0, 60 },
|
|
|
|
{ 75, 152, 27, 68, 68, 0, 62 },
|
|
|
|
{ 80, 161, 29, 70, 70, 0, 64 },
|
|
|
|
{ 85, 171, 31, 73, 73, 0, 66 },
|
|
|
|
{ 90, 180, 33, 76, 76, 0, 68 },
|
|
|
|
{ 95, 190, 35, 78, 78, 0, 70 },
|
|
|
|
{ 100, 209, 36, 82, 82, 0, 73 },
|
|
|
|
{ 105, 228, 38, 85, 85, 0, 75 },
|
|
|
|
{ 110, 247, 41, 89, 89, 0, 78 },
|
|
|
|
{ 115, 266, 44, 93, 93, 0, 81 },
|
|
|
|
{ 120, 285, 46, 96, 96, 0, 84 },
|
|
|
|
{ 125, 304, 49, 100, 100, 0, 86 },
|
|
|
|
{ 130, 323, 52, 104, 104, 0, 89 },
|
|
|
|
{ 135, 342, 54, 109, 109, 0, 93 },
|
|
|
|
{ 140, 361, 57, 113, 113, 0, 95 },
|
|
|
|
{ 145, 380, 60, 116, 116, 0, 98 },
|
|
|
|
{ 150, 418, 63, 122, 122, 0, 102 },
|
|
|
|
{ 155, 456, 67, 127, 127, 0, 105 },
|
|
|
|
{ 160, 494, 71, 133, 133, 0, 109 },
|
|
|
|
{ 165, 532, 74, 138, 138, 0, 113 },
|
|
|
|
{ 170, 570, 78, 144, 144, 0, 117 },
|
|
|
|
{ 175, 608, 81, 150, 150, 0, 121 },
|
|
|
|
{ 180, 646, 85, 155, 155, 0, 125 },
|
|
|
|
{ 185, 684, 89, 162, 162, 0, 129 },
|
|
|
|
{ 190, 722, 92, 168, 168, 0, 133 },
|
|
|
|
{ 195, 760, 97, 173, 173, 0, 137 },
|
|
|
|
{ 200, 826, 101, 181, 181, 0, 143 },
|
|
|
|
{ 205, 893, 106, 188, 188, 0, 148 },
|
|
|
|
{ 210, 959, 110, 194, 194, 0, 153 },
|
|
|
|
{ 215, 1026, 115, 202, 202, 0, 159 },
|
|
|
|
{ 220, 1092, 119, 209, 209, 0, 165 },
|
|
|
|
{ 225, 1159, 124, 215, 215, 0, 170 },
|
|
|
|
{ 230, 1225, 128, 223, 223, 0, 176 },
|
|
|
|
{ 235, 1292, 134, 229, 229, 0, 181 },
|
|
|
|
{ 240, 1358, 139, 236, 236, 0, 186 },
|
|
|
|
{ 245, 1425, 144, 244, 244, 0, 192 },
|
|
|
|
{ 250, 1548, 150, 253, 253, 0, 200 },
|
|
|
|
{ 255, 1672, 155, 263, 263, 0, 207 },
|
|
|
|
{ 260, 1795, 161, 272, 272, 0, 215 },
|
|
|
|
{ 265, 1919, 166, 283, 283, 0, 223 },
|
|
|
|
{ 270, 2042, 171, 292, 292, 0, 231 },
|
|
|
|
{ 275, 2166, 177, 302, 302, 0, 238 },
|
|
|
|
{ 280, 2289, 183, 311, 311, 0, 246 },
|
|
|
|
{ 285, 2413, 189, 322, 322, 0, 254 },
|
|
|
|
{ 290, 2536, 196, 331, 331, 0, 261 },
|
|
|
|
{ 300, 2660, 202, 341, 341, 1700, 269 },
|
|
|
|
{ 315, 3000, 204, 342, 393, 1774, 270 },
|
|
|
|
{ 330, 3380, 205, 344, 444, 1851, 271 },
|
|
|
|
{ 360, 3810, 207, 345, 496, 1931, 273 },
|
|
|
|
{ 390, 4300, 209, 346, 548, 2015, 274 },
|
|
|
|
{ 420, 4850, 210, 347, 600, 2102, 275 },
|
|
|
|
{ 450, 5470, 212, 349, 651, 2194, 276 },
|
|
|
|
{ 480, 6170, 214, 350, 703, 2289, 278 },
|
|
|
|
{ 510, 6950, 215, 351, 755, 2388, 279 },
|
|
|
|
{ 540, 7840, 217, 352, 806, 2492, 280 },
|
|
|
|
{ 620, 8840, 218, 354, 858, 2600, 282 },
|
|
|
|
{ 650, 8980, 224, 355, 941, 2700, 283 },
|
|
|
|
{ 680, 9150, 228, 356, 1032, 2800, 284 },
|
|
|
|
{ 710, 9350, 236, 357, 1133, 2900, 286 },
|
|
|
|
{ 740, 9590, 244, 358, 1243, 3000, 287 },
|
|
|
|
{ 770, 9870, 252, 359, 1364, 3100, 288 },
|
|
|
|
{ 800, 10190, 260, 360, 1497, 3200, 290 },
|
|
|
|
{ 830, 10560, 268, 361, 1643, 3300, 292 },
|
|
|
|
{ 860, 10980, 276, 362, 1802, 3400, 293 },
|
|
|
|
{ 890, 11450, 284, 363, 1978, 3500, 294 },
|
|
|
|
{ 890, 12000, 292, 364, 2170, 3600, 295 }
|
2019-03-20 15:22:40 +01:00
|
|
|
};
|
2019-03-20 21:29:34 +01:00
|
|
|
|
2017-08-20 02:46:06 -03:00
|
|
|
/*
|
2017-08-20 19:20:37 -03:00
|
|
|
Class used for battle-related formulas and calculations.
|
2017-08-20 02:46:06 -03:00
|
|
|
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:
|
|
|
|
|
2017-08-20 19:20:37 -03:00
|
|
|
Base HP val modifier. I can only find values for levels 50~70.
|
2017-11-21 03:19:08 -02:00
|
|
|
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)
|
2017-08-20 02:46:06 -03:00
|
|
|
*/
|
|
|
|
|
2017-09-07 22:09:30 -03:00
|
|
|
// 3 Versions. SB and HW are linear, ARR is polynomial.
|
2017-08-20 19:27:06 -03:00
|
|
|
// Originally from Player.cpp, calculateStats().
|
2017-08-20 02:46:06 -03:00
|
|
|
|
2017-11-21 03:19:08 -02:00
|
|
|
float CalcStats::calculateBaseStat( PlayerPtr pPlayer )
|
2017-08-20 19:27:06 -03:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
float base = 0.0f;
|
|
|
|
uint8_t level = pPlayer->getLevel();
|
|
|
|
|
2019-03-20 21:29:34 +01:00
|
|
|
if( level > 70 )
|
|
|
|
level = 70;
|
|
|
|
|
|
|
|
return static_cast< float >( levelTable[level][2] );
|
2017-08-20 02:46:06 -03:00
|
|
|
}
|
|
|
|
|
2017-08-20 19:27:06 -03:00
|
|
|
// Leggerless' HP Formula
|
2017-08-20 19:20:37 -03:00
|
|
|
// ROUNDDOWN(JobModHP * (BaseHP / 100)) + ROUNDDOWN(VitHPMod / 100 * (VIT - BaseDET))
|
|
|
|
|
2018-12-29 00:53:52 +01:00
|
|
|
uint32_t CalcStats::calculateMaxHp( PlayerPtr pPlayer, Sapphire::FrameworkPtr pFw )
|
2017-08-20 02:46:06 -03:00
|
|
|
{
|
2018-12-29 00:53:52 +01:00
|
|
|
auto pExdData = pFw->get< Data::ExdDataGenerated >();
|
2018-08-29 21:40:59 +02:00
|
|
|
// 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
|
2017-08-20 19:20:37 -03:00
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
auto classInfo = pExdData->get< Sapphire::Data::ClassJob >( static_cast< uint8_t >( pPlayer->getClass() ) );
|
|
|
|
auto paramGrowthInfo = pExdData->get< Sapphire::Data::ParamGrow >( pPlayer->getLevel() );
|
2017-08-23 00:38:54 -03:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( !classInfo || !paramGrowthInfo )
|
|
|
|
return 0;
|
2017-08-23 00:38:54 -03:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
uint8_t level = pPlayer->getLevel();
|
2017-08-20 19:20:37 -03:00
|
|
|
|
2019-03-21 18:53:32 +01:00
|
|
|
auto vitMod = pPlayer->getBonusStat( Common::BaseParam::Vitality );
|
2018-08-29 21:40:59 +02:00
|
|
|
float baseStat = calculateBaseStat( pPlayer );
|
2019-03-21 18:53:32 +01:00
|
|
|
uint16_t vitStat = pPlayer->getStats().vit + static_cast< uint16_t >( vitMod );
|
2018-08-29 21:40:59 +02:00
|
|
|
uint16_t hpMod = paramGrowthInfo->hpModifier;
|
|
|
|
uint16_t jobModHp = classInfo->modifierHitPoints;
|
|
|
|
float approxBaseHp = 0.0f; // Read above
|
2017-08-20 19:20:37 -03:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// These values are not precise.
|
2017-08-20 19:20:37 -03:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
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;
|
2017-08-20 19:20:37 -03:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
uint16_t result = static_cast< uint16_t >( floor( jobModHp * ( approxBaseHp / 100.0f ) ) +
|
|
|
|
floor( hpMod / 100.0f * ( vitStat - baseStat ) ) );
|
|
|
|
|
|
|
|
return result;
|
2017-08-20 19:20:37 -03:00
|
|
|
}
|
2017-08-20 02:46:06 -03:00
|
|
|
|
2017-08-20 19:27:06 -03:00
|
|
|
// Leggerless' MP Formula
|
2017-08-20 19:20:37 -03:00
|
|
|
// ROUNDDOWN(((ROUNDDOWN(((PIE - BaseDET) * PieMPMod/100),0) + BaseMP) * JobModMP / 100),0)
|
2017-08-20 02:46:06 -03:00
|
|
|
|
2018-12-29 00:53:52 +01:00
|
|
|
uint32_t CalcStats::calculateMaxMp( PlayerPtr pPlayer, Sapphire::FrameworkPtr pFw )
|
2017-08-20 19:20:37 -03:00
|
|
|
{
|
2018-12-29 00:53:52 +01:00
|
|
|
auto pExdData = pFw->get< Data::ExdDataGenerated >();
|
2018-11-29 16:55:48 +01:00
|
|
|
auto classInfo = pExdData->get< Sapphire::Data::ClassJob >( static_cast< uint8_t >( pPlayer->getClass() ) );
|
|
|
|
auto paramGrowthInfo = pExdData->get< Sapphire::Data::ParamGrow >( pPlayer->getLevel() );
|
2017-08-20 19:20:37 -03:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( !classInfo || !paramGrowthInfo )
|
|
|
|
return 0;
|
2017-08-23 00:38:54 -03:00
|
|
|
|
2019-03-21 18:53:32 +01:00
|
|
|
auto pieMod = pPlayer->getBonusStat( Common::BaseParam::Piety );
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
float baseStat = calculateBaseStat( pPlayer );
|
2019-03-21 18:53:32 +01:00
|
|
|
uint16_t piety = pPlayer->getStats().pie + pieMod;
|
2018-08-29 21:40:59 +02:00
|
|
|
uint16_t pietyScalar = paramGrowthInfo->mpModifier;
|
|
|
|
uint16_t jobModMp = classInfo->modifierManaPoints;
|
|
|
|
uint16_t baseMp = paramGrowthInfo->mpModifier;
|
2017-08-20 19:20:37 -03:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
uint16_t result = static_cast< uint16_t >( floor( floor( piety - baseStat ) * ( pietyScalar / 100 ) + baseMp ) *
|
|
|
|
jobModMp / 100 );
|
2017-08-20 19:20:37 -03:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
return result;
|
2017-12-08 15:38:25 +01:00
|
|
|
}
|
2019-03-22 13:26:34 +01:00
|
|
|
|
2019-03-23 11:48:49 +11:00
|
|
|
uint16_t CalcStats::calculateMpCost( const Sapphire::Entity::Chara& chara, uint16_t baseCost )
|
|
|
|
{
|
|
|
|
auto level = chara.getLevel();
|
|
|
|
|
|
|
|
// each level range is 1-10, 11-20, 21-30, ... therefore:
|
|
|
|
// level 50 should be in the 4th group, not the 5t
|
|
|
|
// dividing by 10 on the border will break this unless we subtract 1
|
2019-03-23 11:53:21 +11:00
|
|
|
auto levelGroup = std::max< uint8_t >( level - 1, 1 ) / 10;
|
2019-03-23 11:48:49 +11:00
|
|
|
|
|
|
|
float cost = baseCost;
|
|
|
|
|
|
|
|
// thanks to andrew for helping me figure this shit out
|
|
|
|
// played with this some more and it seems to be accurate for everything i've tried
|
|
|
|
switch( levelGroup )
|
|
|
|
{
|
|
|
|
// level 1-10
|
|
|
|
case 0:
|
|
|
|
{
|
|
|
|
// r^2 = 0.9999
|
|
|
|
cost = 0.0952f * level + 0.9467f;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-03-23 11:53:21 +11:00
|
|
|
// level 11-20
|
2019-03-23 11:48:49 +11:00
|
|
|
case 1:
|
|
|
|
{
|
|
|
|
// r^2 = 1
|
|
|
|
cost = 0.19f * level;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-03-23 11:53:21 +11:00
|
|
|
// level 21-30
|
2019-03-23 11:48:49 +11:00
|
|
|
case 2:
|
|
|
|
{
|
|
|
|
// r^2 = 1
|
|
|
|
cost = 0.38f * level - 3.8f;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-03-23 11:53:21 +11:00
|
|
|
// level 31-40
|
2019-03-23 11:48:49 +11:00
|
|
|
case 3:
|
|
|
|
{
|
|
|
|
// r^2 = 1
|
|
|
|
cost = 0.6652f * level - 12.358f;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-03-23 11:53:21 +11:00
|
|
|
// level 41-50
|
2019-03-23 11:48:49 +11:00
|
|
|
case 4:
|
|
|
|
{
|
|
|
|
// r^2 = 1
|
|
|
|
cost = 1.2352f * level - 35.159f;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-03-23 11:53:21 +11:00
|
|
|
// level 51-60
|
2019-03-23 11:48:49 +11:00
|
|
|
case 5:
|
|
|
|
{
|
|
|
|
// r^2 = 1
|
|
|
|
cost = 0.0654f * std::exp( 0.1201f * level );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-03-23 11:53:21 +11:00
|
|
|
// level 61-70
|
2019-03-23 11:48:49 +11:00
|
|
|
case 6:
|
|
|
|
{
|
|
|
|
// r^2 = 0.9998
|
|
|
|
cost = 0.2313f * ( level * level ) - 26.98f * level + 875.21f;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return static_cast< uint16_t >( std::round( cost * baseCost ) );
|
|
|
|
}
|
|
|
|
|
2019-03-22 22:02:37 +01:00
|
|
|
float CalcStats::pBlk( const Chara& chara )
|
2019-03-22 13:26:34 +01:00
|
|
|
{
|
|
|
|
auto level = chara.getLevel();
|
|
|
|
float blockRate = static_cast< float >( chara.getBonusStat( Common::BaseParam::BlockRate ) );
|
2019-03-23 11:48:49 +11:00
|
|
|
float levelVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
2019-03-22 13:26:34 +01:00
|
|
|
|
|
|
|
return std::floor( ( 30 * blockRate ) / levelVal + 10 );
|
|
|
|
}
|
|
|
|
|
2019-03-22 22:02:37 +01:00
|
|
|
float CalcStats::pDhr( const Chara& chara )
|
2019-03-22 13:26:34 +01:00
|
|
|
{
|
|
|
|
const auto& baseStats = chara.getStats();
|
|
|
|
auto level = chara.getLevel();
|
|
|
|
|
|
|
|
float dhRate = static_cast< float >( chara.getBonusStat( Common::BaseParam::DirectHitRate ) ) +
|
|
|
|
baseStats.accuracy;
|
|
|
|
|
2019-03-23 11:48:49 +11:00
|
|
|
float divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
|
|
|
float subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
|
2019-03-22 13:26:34 +01:00
|
|
|
|
|
|
|
return std::floor( 550.f * ( dhRate - subVal ) / divVal ) / 10.f;
|
|
|
|
}
|
|
|
|
|
2019-03-22 22:02:37 +01:00
|
|
|
float CalcStats::pChr( const Chara& chara )
|
2019-03-22 13:26:34 +01:00
|
|
|
{
|
|
|
|
const auto& baseStats = chara.getStats();
|
|
|
|
auto level = chara.getLevel();
|
|
|
|
|
|
|
|
float chRate = static_cast< float >( chara.getBonusStat( Common::BaseParam::CriticalHit ) ) +
|
|
|
|
baseStats.critHitRate;
|
|
|
|
|
2019-03-23 11:48:49 +11:00
|
|
|
float divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
|
|
|
float subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
|
2019-03-22 13:26:34 +01:00
|
|
|
|
|
|
|
return std::floor( 200.f * ( chRate - subVal ) / divVal + 50.f ) / 10.f;
|
|
|
|
}
|