2017-08-28 16:13:23 -03:00
|
|
|
|
#include <cmath>
|
2017-08-20 02:46:06 -03:00
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
|
#include <Exd/ExdData.h>
|
2018-03-06 22:22:19 +01:00
|
|
|
|
#include <Common.h>
|
2019-04-24 23:25:07 +10:00
|
|
|
|
#include <Logging/Logger.h>
|
2020-03-01 01:00:57 +11:00
|
|
|
|
#include <Service.h>
|
2018-03-02 07:22:25 -03:00
|
|
|
|
|
2018-02-20 22:46:44 +01:00
|
|
|
|
#include "Actor/Chara.h"
|
2017-12-08 15:38:25 +01:00
|
|
|
|
#include "Actor/Player.h"
|
2022-01-03 13:24:33 +01:00
|
|
|
|
#include "Util/UtilMath.h"
|
2017-11-21 03:19:08 -02:00
|
|
|
|
|
2019-04-24 23:25:07 +10:00
|
|
|
|
#include "Inventory/Item.h"
|
|
|
|
|
|
2017-11-21 03:19:08 -02:00
|
|
|
|
#include "CalcStats.h"
|
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
|
#include "Manager/PlayerMgr.h"
|
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
|
using namespace Sapphire::Math;
|
|
|
|
|
using namespace Sapphire::Entity;
|
2021-11-27 00:53:57 +01:00
|
|
|
|
using namespace Sapphire::World::Manager;
|
2017-08-20 02:46:06 -03:00
|
|
|
|
|
2021-12-14 23:05:58 +01:00
|
|
|
|
const int levelTable[61][6] =
|
|
|
|
|
{
|
2019-07-06 14:51:58 +10:00
|
|
|
|
// MAIN,SUB,DIV,HP,ELMT,THREAT
|
2019-07-26 23:18:17 +10:00
|
|
|
|
{ 1, 1, 1, 1, 1, 1 },
|
2020-01-05 17:41:38 +09:00
|
|
|
|
{ 20, 56, 56, 86, 52, 2 },
|
|
|
|
|
{ 21, 57, 57, 101, 54, 2 },
|
|
|
|
|
{ 22, 60, 60, 109, 56, 3 },
|
|
|
|
|
{ 24, 62, 62, 116, 58, 3 },
|
|
|
|
|
{ 26, 65, 65, 123, 60, 3 },
|
|
|
|
|
{ 27, 68, 68, 131, 62, 3 },
|
|
|
|
|
{ 29, 70, 70, 138, 64, 4 },
|
|
|
|
|
{ 31, 73, 73, 145, 66, 4 },
|
|
|
|
|
{ 33, 76, 76, 153, 68, 4 },
|
|
|
|
|
{ 35, 78, 78, 160, 70, 5 },
|
|
|
|
|
{ 36, 82, 82, 174, 73, 5 },
|
|
|
|
|
{ 38, 85, 85, 188, 75, 5 },
|
|
|
|
|
{ 41, 89, 89, 202, 78, 6 },
|
|
|
|
|
{ 44, 93, 93, 216, 81, 6 },
|
|
|
|
|
{ 46, 96, 96, 230, 84, 7 },
|
|
|
|
|
{ 49, 100, 100, 244, 86, 7 },
|
|
|
|
|
{ 52, 104, 104, 258, 89, 8 },
|
|
|
|
|
{ 54, 109, 109, 272, 93, 9 },
|
|
|
|
|
{ 57, 113, 113, 286, 95, 9 },
|
|
|
|
|
{ 60, 116, 116, 300, 98, 10 },
|
|
|
|
|
{ 63, 122, 122, 333, 102, 10 },
|
|
|
|
|
{ 67, 127, 127, 366, 105, 11 },
|
|
|
|
|
{ 71, 133, 133, 399, 109, 12 },
|
|
|
|
|
{ 74, 138, 138, 432, 113, 13 },
|
|
|
|
|
{ 78, 144, 144, 465, 117, 14 },
|
|
|
|
|
{ 81, 150, 150, 498, 121, 15 },
|
|
|
|
|
{ 85, 155, 155, 531, 125, 16 },
|
|
|
|
|
{ 89, 162, 162, 564, 129, 17 },
|
|
|
|
|
{ 92, 168, 168, 597, 133, 18 },
|
|
|
|
|
{ 97, 173, 173, 630, 137, 19 },
|
|
|
|
|
{ 101, 181, 181, 669, 143, 20 },
|
|
|
|
|
{ 106, 188, 188, 708, 148, 22 },
|
|
|
|
|
{ 110, 194, 194, 747, 153, 23 },
|
|
|
|
|
{ 115, 202, 202, 786, 159, 25 },
|
|
|
|
|
{ 119, 209, 209, 825, 165, 27 },
|
|
|
|
|
{ 124, 215, 215, 864, 170, 29 },
|
|
|
|
|
{ 128, 223, 223, 903, 176, 31 },
|
|
|
|
|
{ 134, 229, 229, 942, 181, 33 },
|
|
|
|
|
{ 139, 236, 236, 981, 186, 35 },
|
|
|
|
|
{ 144, 244, 244, 1020, 192, 38 },
|
|
|
|
|
{ 150, 253, 253, 1088, 200, 40 },
|
|
|
|
|
{ 155, 263, 263, 1156, 207, 43 },
|
|
|
|
|
{ 161, 272, 272, 1224, 215, 46 },
|
|
|
|
|
{ 166, 283, 283, 1292, 223, 49 },
|
|
|
|
|
{ 171, 292, 292, 1360, 231, 52 },
|
|
|
|
|
{ 177, 302, 302, 1428, 238, 55 },
|
|
|
|
|
{ 183, 311, 311, 1496, 246, 58 },
|
|
|
|
|
{ 189, 322, 322, 1564, 254, 62 },
|
|
|
|
|
{ 196, 331, 331, 1632, 261, 66 },
|
2019-07-26 23:18:17 +10:00
|
|
|
|
{ 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 },
|
2019-03-20 15:22:40 +01:00
|
|
|
|
};
|
2019-03-20 21:29:34 +01:00
|
|
|
|
|
2020-01-07 19:08:13 +09:00
|
|
|
|
std::random_device CalcStats::dev;
|
|
|
|
|
std::mt19937 CalcStats::rng( dev() );
|
|
|
|
|
std::uniform_int_distribution< std::mt19937::result_type > CalcStats::range100( 0, 99 );
|
|
|
|
|
|
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
|
|
|
|
|
2019-04-24 23:25:07 +10:00
|
|
|
|
float CalcStats::calculateBaseStat( const Chara& chara )
|
2017-08-20 19:27:06 -03:00
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
|
float base = 0.0f;
|
2019-04-24 23:25:07 +10:00
|
|
|
|
uint8_t level = chara.getLevel();
|
2018-08-29 21:40:59 +02:00
|
|
|
|
|
2019-06-30 19:51:18 +10:00
|
|
|
|
if( level > Common::MAX_PLAYER_LEVEL )
|
|
|
|
|
level = Common::MAX_PLAYER_LEVEL;
|
2019-03-20 21:29:34 +01:00
|
|
|
|
|
2020-01-05 17:41:38 +09:00
|
|
|
|
return static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] );
|
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))
|
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
|
uint32_t CalcStats::calculateMaxHp( Player& player )
|
2017-08-20 02:46:06 -03:00
|
|
|
|
{
|
2021-11-27 00:53:57 +01:00
|
|
|
|
auto& exdData = Common::Service< Data::ExdData >::ref();
|
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
|
|
|
|
|
2022-01-27 21:24:54 +01:00
|
|
|
|
auto classInfo = exdData.getRow< Excel::ClassJob >( static_cast< uint8_t >( player.getClass() ) );
|
|
|
|
|
auto paramGrowthInfo = exdData.getRow< Excel::ParamGrow >( player.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
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
|
uint8_t level = player.getLevel();
|
2017-08-20 19:20:37 -03:00
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
|
auto vitMod = player.getBonusStat( Common::BaseParam::Vitality );
|
|
|
|
|
float baseStat = calculateBaseStat( player );
|
|
|
|
|
uint16_t vitStat = static_cast< uint16_t >( player.getStatValue( Common::BaseParam::Vitality ) ) + static_cast< uint16_t >( vitMod );
|
|
|
|
|
uint16_t hpMod = paramGrowthInfo->data().ParamBase;
|
|
|
|
|
uint16_t jobModHp = classInfo->data().Hp;
|
2018-08-29 21:40:59 +02:00
|
|
|
|
float approxBaseHp = 0.0f; // Read above
|
2017-08-20 19:20:37 -03:00
|
|
|
|
|
2020-01-05 17:41:38 +09:00
|
|
|
|
approxBaseHp = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::HP ] );
|
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
|
|
|
|
|
2021-12-30 13:57:08 +01:00
|
|
|
|
uint32_t CalcStats::calculateMaxHp( Chara& chara )
|
|
|
|
|
{
|
|
|
|
|
auto& exdData = Common::Service< Data::ExdData >::ref();
|
|
|
|
|
// 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
|
|
|
|
|
|
2022-01-27 21:24:54 +01:00
|
|
|
|
auto classInfo = exdData.getRow< Excel::ClassJob >( static_cast< uint8_t >( chara.getClass() ) );
|
|
|
|
|
auto paramGrowthInfo = exdData.getRow< Excel::ParamGrow >( chara.getLevel() );
|
2021-12-30 13:57:08 +01:00
|
|
|
|
|
|
|
|
|
if( !classInfo || !paramGrowthInfo )
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
uint8_t level = chara.getLevel();
|
|
|
|
|
|
|
|
|
|
auto vitMod = chara.getBonusStat( Common::BaseParam::Vitality );
|
|
|
|
|
float baseStat = calculateBaseStat( chara );
|
|
|
|
|
uint16_t vitStat = static_cast< uint16_t >( chara.getStatValue( Common::BaseParam::Vitality ) ) + static_cast< uint16_t >( vitMod );
|
|
|
|
|
uint16_t hpMod = paramGrowthInfo->data().ParamBase;
|
|
|
|
|
uint16_t jobModHp = classInfo->data().Hp;
|
|
|
|
|
float approxBaseHp = 0.0f; // Read above
|
|
|
|
|
|
|
|
|
|
approxBaseHp = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::HP ] );
|
|
|
|
|
|
|
|
|
|
uint16_t result = static_cast< uint16_t >( floor( jobModHp * ( approxBaseHp / 100.0f ) ) +
|
|
|
|
|
floor( hpMod / 100.0f * ( vitStat - baseStat ) ) );
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-14 23:05:58 +01:00
|
|
|
|
uint32_t CalcStats::calculateMaxMp( Player& player )
|
|
|
|
|
{
|
|
|
|
|
auto& exdData = Common::Service< Data::ExdData >::ref();
|
|
|
|
|
|
2022-01-27 21:24:54 +01:00
|
|
|
|
auto classInfo = exdData.getRow< Excel::ClassJob >( static_cast< uint8_t >( player.getClass() ) );
|
|
|
|
|
auto paramGrowthInfo = exdData.getRow< Excel::ParamGrow >( player.getLevel() );
|
2021-12-14 23:05:58 +01:00
|
|
|
|
|
|
|
|
|
if( !classInfo || !paramGrowthInfo )
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
float baseStat = calculateBaseStat( player );
|
|
|
|
|
uint16_t piety = player.getStats()[ static_cast< uint32_t >( Common::BaseParam::Piety ) ];
|
|
|
|
|
uint16_t pietyScalar = paramGrowthInfo->data().ParamBase;
|
|
|
|
|
uint16_t jobModMp = classInfo->data().Mp;
|
|
|
|
|
uint16_t baseMp = paramGrowthInfo->data().Mp;
|
|
|
|
|
|
|
|
|
|
uint16_t result = static_cast< uint16_t >( std::floor( floor( piety - baseStat ) * ( pietyScalar / 100 ) + baseMp ) *
|
|
|
|
|
jobModMp / 100 );
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
auto levelGroup = std::max< uint8_t >( level - 1, 1 ) / 10;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// level 11-20
|
|
|
|
|
case 1:
|
|
|
|
|
{
|
|
|
|
|
// r^2 = 1
|
|
|
|
|
cost = 0.19f * level;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// level 21-30
|
|
|
|
|
case 2:
|
|
|
|
|
{
|
|
|
|
|
// r^2 = 1
|
|
|
|
|
cost = 0.38f * level - 3.8f;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// level 31-40
|
|
|
|
|
case 3:
|
|
|
|
|
{
|
|
|
|
|
// r^2 = 1
|
|
|
|
|
cost = 0.6652f * level - 12.358f;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// level 41-50
|
|
|
|
|
case 4:
|
|
|
|
|
{
|
|
|
|
|
// r^2 = 1
|
|
|
|
|
cost = 1.2352f * level - 35.159f;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// level 51-60
|
|
|
|
|
case 5:
|
|
|
|
|
{
|
|
|
|
|
// r^2 = 1
|
|
|
|
|
cost = 0.0654f * std::exp( 0.1201f * level );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return static_cast< uint16_t >( std::round( cost * baseCost ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-03-24 14:25:00 +11:00
|
|
|
|
float CalcStats::blockProbability( const Chara& chara )
|
2019-03-22 13:26:34 +01:00
|
|
|
|
{
|
|
|
|
|
auto level = chara.getLevel();
|
2019-04-25 21:57:41 +10:00
|
|
|
|
auto blockRate = static_cast< float >( chara.getStatValue( Common::BaseParam::BlockRate ) );
|
2019-03-24 14:25:00 +11:00
|
|
|
|
auto 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-24 14:25:00 +11:00
|
|
|
|
float CalcStats::directHitProbability( const Chara& chara )
|
2019-03-22 13:26:34 +01:00
|
|
|
|
{
|
|
|
|
|
const auto& baseStats = chara.getStats();
|
|
|
|
|
auto level = chara.getLevel();
|
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
|
auto dhRate = chara.getStatValueFloat( Common::BaseParam::Accuracy );
|
2019-03-22 13:26:34 +01:00
|
|
|
|
|
2019-03-24 14:25:00 +11:00
|
|
|
|
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
|
|
|
|
auto 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-24 14:25:00 +11:00
|
|
|
|
float CalcStats::criticalHitProbability( const Chara& chara )
|
2019-03-22 13:26:34 +01:00
|
|
|
|
{
|
|
|
|
|
const auto& baseStats = chara.getStats();
|
|
|
|
|
auto level = chara.getLevel();
|
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
|
auto chRate = chara.getStatValueFloat( Common::BaseParam::CriticalHit );
|
2019-03-22 13:26:34 +01:00
|
|
|
|
|
2019-03-24 14:25:00 +11:00
|
|
|
|
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
|
|
|
|
auto 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;
|
|
|
|
|
}
|
2019-03-24 14:25:00 +11:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float CalcStats::potency( uint16_t potency )
|
|
|
|
|
{
|
2022-01-02 23:31:02 +01:00
|
|
|
|
return static_cast< float >( potency ) / 100.f;
|
2019-03-24 14:25:00 +11:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-11 13:16:34 +10:00
|
|
|
|
float CalcStats::autoAttackPotency( const Sapphire::Entity::Chara& chara )
|
|
|
|
|
{
|
|
|
|
|
uint32_t aaPotency = AUTO_ATTACK_POTENCY;
|
|
|
|
|
|
2019-07-27 00:37:40 +10:00
|
|
|
|
if( chara.getRole() == Common::Role::RangedPhysical )
|
2019-05-11 13:16:34 +10:00
|
|
|
|
{
|
2019-07-27 00:37:40 +10:00
|
|
|
|
aaPotency = RANGED_AUTO_ATTACK_POTENCY;
|
2019-05-11 13:16:34 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2021-12-30 13:57:08 +01:00
|
|
|
|
//return std::floor( aaPotency / 3.f * autoAttackDelay ) / 100.f;
|
2022-01-02 23:31:02 +01:00
|
|
|
|
return aaPotency / 100.f;
|
2019-05-11 13:16:34 +10:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-24 23:25:07 +10:00
|
|
|
|
float CalcStats::weaponDamage( const Sapphire::Entity::Chara& chara, float weaponDamage )
|
2019-03-24 14:25:00 +11:00
|
|
|
|
{
|
|
|
|
|
const auto& baseStats = chara.getStats();
|
2019-04-24 23:25:07 +10:00
|
|
|
|
auto level = chara.getLevel();
|
|
|
|
|
|
|
|
|
|
auto mainVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] );
|
|
|
|
|
|
2022-01-02 23:31:02 +01:00
|
|
|
|
uint32_t jobMod = 1;
|
|
|
|
|
|
|
|
|
|
auto& exdData = Common::Service< Data::ExdData >::ref();
|
2022-01-27 21:24:54 +01:00
|
|
|
|
auto classInfo = exdData.getRow< Excel::ClassJob >( static_cast< uint8_t >( chara.getClass() ) );
|
2022-01-02 23:31:02 +01:00
|
|
|
|
|
|
|
|
|
if( !classInfo )
|
|
|
|
|
return 0.f;
|
2019-03-24 14:25:00 +11:00
|
|
|
|
|
2019-04-24 23:25:07 +10:00
|
|
|
|
switch( chara.getPrimaryStat() )
|
|
|
|
|
{
|
|
|
|
|
case Common::BaseParam::Intelligence:
|
|
|
|
|
{
|
2022-01-02 23:31:02 +01:00
|
|
|
|
jobMod = classInfo->data().INT_;
|
2019-04-24 23:25:07 +10:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Common::BaseParam::Mind:
|
|
|
|
|
{
|
2022-01-02 23:31:02 +01:00
|
|
|
|
jobMod = classInfo->data().MND;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Common::BaseParam::Strength:
|
|
|
|
|
{
|
|
|
|
|
jobMod = classInfo->data().STR;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Common::BaseParam::Dexterity:
|
|
|
|
|
{
|
|
|
|
|
jobMod = classInfo->data().DEX;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Common::BaseParam::Vitality:
|
|
|
|
|
{
|
|
|
|
|
jobMod = classInfo->data().VIT;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Common::BaseParam::Piety:
|
|
|
|
|
{
|
|
|
|
|
jobMod = classInfo->data().PIE;
|
2019-04-24 23:25:07 +10:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
{
|
2022-01-02 23:31:02 +01:00
|
|
|
|
jobMod = 100;
|
2019-04-24 23:25:07 +10:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-02 23:31:02 +01:00
|
|
|
|
return ( std::floor( mainVal * jobMod / 1000.f ) + weaponDamage );
|
2019-03-24 14:25:00 +11:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-24 23:47:35 +10:00
|
|
|
|
float CalcStats::calcAttackPower( const Sapphire::Entity::Chara& chara, uint32_t attackPower )
|
2019-03-24 14:25:00 +11:00
|
|
|
|
{
|
2019-04-24 23:47:35 +10:00
|
|
|
|
auto level = chara.getLevel();
|
|
|
|
|
auto mainVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] );
|
|
|
|
|
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
|
|
|
|
|
2022-01-03 13:24:33 +01:00
|
|
|
|
return Common::Util::trunc( static_cast< float >( attackPower ) / divVal + ( 1.0f - mainVal / divVal ), 2 );
|
2019-03-24 14:25:00 +11:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-25 21:57:41 +10:00
|
|
|
|
float CalcStats::getPrimaryAttackPower( const Sapphire::Entity::Chara& chara )
|
2019-03-24 14:25:00 +11:00
|
|
|
|
{
|
|
|
|
|
const auto& baseStats = chara.getStats();
|
|
|
|
|
|
2019-04-24 23:25:07 +10:00
|
|
|
|
switch( chara.getPrimaryStat() )
|
|
|
|
|
{
|
|
|
|
|
case Common::BaseParam::Mind:
|
|
|
|
|
{
|
2019-04-25 21:57:41 +10:00
|
|
|
|
return healingMagicPower( chara );
|
2019-04-24 23:25:07 +10:00
|
|
|
|
}
|
|
|
|
|
case Common::BaseParam::Intelligence:
|
|
|
|
|
{
|
2019-04-25 21:57:41 +10:00
|
|
|
|
return magicAttackPower( chara );
|
2019-04-24 23:25:07 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
{
|
2019-04-25 21:57:41 +10:00
|
|
|
|
return attackPower( chara );
|
2019-04-24 23:25:07 +10:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-03-24 14:25:00 +11:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-25 21:57:41 +10:00
|
|
|
|
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 ) );
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-24 14:25:00 +11:00
|
|
|
|
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 ] );
|
|
|
|
|
|
2022-01-03 13:24:33 +01:00
|
|
|
|
return Common::Util::trunc( 1.0f + ( chara.getStatValue( Common::BaseParam::Determination ) - mainVal ) / divVal, 3 );
|
2019-03-24 14:25:00 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 ] );
|
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
|
return std::floor( 100.f * ( chara.getStatValue( Common::BaseParam::Parry ) - subVal ) / divVal + 1000.f ) / 1000.f;
|
2019-03-24 14:25:00 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2019-04-24 23:25:07 +10:00
|
|
|
|
switch( chara.getPrimaryStat() )
|
2019-03-24 14:25:00 +11:00
|
|
|
|
{
|
2019-04-24 23:25:07 +10:00
|
|
|
|
case Common::BaseParam::Intelligence:
|
|
|
|
|
case Common::BaseParam::Mind:
|
2019-04-25 21:57:41 +10:00
|
|
|
|
speedVal = chara.getStatValue( Common::BaseParam::SpellSpeed );
|
2019-03-24 14:25:00 +11:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
2019-04-25 21:57:41 +10:00
|
|
|
|
speedVal = chara.getStatValue( Common::BaseParam::SkillSpeed );
|
2019-03-24 14:25:00 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 ] );
|
|
|
|
|
|
2019-04-25 21:57:41 +10:00
|
|
|
|
return std::floor( 200.f * ( chara.getStatValue( Common::BaseParam::CriticalHit ) - subVal ) / divVal + 1400.f ) / 1000.f;
|
2019-03-24 16:04:04 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float CalcStats::physicalDefence( const Sapphire::Entity::Chara& chara )
|
|
|
|
|
{
|
|
|
|
|
auto level = chara.getLevel();
|
|
|
|
|
|
|
|
|
|
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
|
|
|
|
|
2019-04-25 21:57:41 +10:00
|
|
|
|
return std::floor( 15.f * chara.getStatValue( Common::BaseParam::Defense ) ) / 100.f;
|
2019-03-24 16:04:04 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float CalcStats::magicDefence( const Sapphire::Entity::Chara& chara )
|
|
|
|
|
{
|
|
|
|
|
auto level = chara.getLevel();
|
|
|
|
|
|
|
|
|
|
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
|
|
|
|
|
2019-04-25 21:57:41 +10:00
|
|
|
|
return std::floor( 15.f * chara.getStatValue( Common::BaseParam::MagicDefense ) ) / 100.f;
|
2019-03-24 16:04:04 +11:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-24 23:25:07 +10:00
|
|
|
|
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;
|
2022-01-02 23:31:02 +01:00
|
|
|
|
float dmg = 10.f;
|
2019-04-24 23:25:07 +10:00
|
|
|
|
|
|
|
|
|
// 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;
|
2022-01-02 23:31:02 +01:00
|
|
|
|
dmg = pItem->getWeaponDmg();
|
2019-04-24 23:25:07 +10:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto level = chara.getLevel();
|
|
|
|
|
auto mainVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] );
|
|
|
|
|
|
2022-01-02 23:31:02 +01:00
|
|
|
|
auto innerCalc = weaponDamage( chara, dmg );
|
2019-04-24 23:25:07 +10:00
|
|
|
|
|
|
|
|
|
return std::floor( innerCalc * ( autoAttackDelay / 3.f ) );
|
|
|
|
|
}
|
2019-03-24 16:04:04 +11:00
|
|
|
|
|
|
|
|
|
float CalcStats::healingMagicPotency( const Sapphire::Entity::Chara& chara )
|
|
|
|
|
{
|
2019-04-25 21:57:41 +10:00
|
|
|
|
return std::floor( 100.f * ( chara.getStatValue( Common::BaseParam::HealingMagicPotency ) - 292.f ) / 264.f + 100.f ) / 100.f;
|
2019-04-24 23:25:07 +10:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-07 19:08:13 +09:00
|
|
|
|
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcAutoAttackDamage( const Sapphire::Entity::Chara& chara )
|
2019-04-24 23:25:07 +10:00
|
|
|
|
{
|
|
|
|
|
// 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... ⌋
|
|
|
|
|
|
2019-05-11 13:16:34 +10:00
|
|
|
|
auto pot = autoAttackPotency( chara );
|
2019-04-24 23:25:07 +10:00
|
|
|
|
auto aa = autoAttack( chara );
|
2019-04-25 21:57:41 +10:00
|
|
|
|
auto ap = getPrimaryAttackPower( chara );
|
2019-04-24 23:25:07 +10:00
|
|
|
|
auto det = determination( chara );
|
|
|
|
|
|
|
|
|
|
|
2019-07-27 00:37:40 +10:00
|
|
|
|
// todo: everything after tenacity
|
2022-01-03 13:24:33 +01:00
|
|
|
|
auto factor = Common::Util::trunc( pot * aa * ap * det, 0 );
|
2020-01-07 19:08:13 +09:00
|
|
|
|
Sapphire::Common::ActionHitSeverityType hitType = Sapphire::Common::ActionHitSeverityType::NormalDamage;
|
|
|
|
|
|
|
|
|
|
// todo: traits
|
|
|
|
|
|
|
|
|
|
factor = std::floor( factor * speed( chara ) );
|
|
|
|
|
|
|
|
|
|
if( criticalHitProbability( chara ) > range100( rng ) )
|
|
|
|
|
{
|
|
|
|
|
factor *= criticalHitBonus( chara );
|
|
|
|
|
hitType = Sapphire::Common::ActionHitSeverityType::CritDamage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( directHitProbability( chara ) > range100( rng ) )
|
|
|
|
|
{
|
|
|
|
|
factor *= 1.25f;
|
|
|
|
|
hitType = hitType == Sapphire::Common::ActionHitSeverityType::CritDamage ?
|
|
|
|
|
Sapphire::Common::ActionHitSeverityType::CritDirectHitDamage :
|
|
|
|
|
Sapphire::Common::ActionHitSeverityType::DirectHitDamage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f );
|
|
|
|
|
|
|
|
|
|
// todo: buffs
|
2019-04-24 23:25:07 +10:00
|
|
|
|
|
2019-07-27 00:37:40 +10:00
|
|
|
|
constexpr auto format = "auto attack: pot: {} aa: {} ap: {} det: {} ten: {} = {}";
|
|
|
|
|
|
|
|
|
|
if( auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer() )
|
|
|
|
|
{
|
2021-12-30 13:57:08 +01:00
|
|
|
|
PlayerMgr::sendDebug( *player, format, pot, aa, ap, det, 1, factor );
|
2019-07-27 00:37:40 +10:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-11-27 00:53:57 +01:00
|
|
|
|
// Logger::debug( format, pot, aa, ap, det, ten, factor );
|
2019-07-27 00:37:40 +10:00
|
|
|
|
}
|
2019-07-27 13:59:35 +10:00
|
|
|
|
|
2022-01-02 22:32:17 +01:00
|
|
|
|
return std::pair( factor, hitType );
|
2019-04-24 23:25:07 +10:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-07 19:08:13 +09:00
|
|
|
|
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActionDamage( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg )
|
2019-07-27 00:37:40 +10:00
|
|
|
|
{
|
|
|
|
|
// 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... ⌋
|
|
|
|
|
|
2019-12-26 11:09:15 +03:00
|
|
|
|
auto pot = potency( static_cast< uint16_t >( ptc ) );
|
2019-07-27 00:37:40 +10:00
|
|
|
|
auto wd = weaponDamage( chara, wepDmg );
|
|
|
|
|
auto ap = getPrimaryAttackPower( chara );
|
|
|
|
|
auto det = determination( chara );
|
|
|
|
|
|
2022-01-03 13:24:33 +01:00
|
|
|
|
auto factor = Common::Util::trunc( pot * wd * ap * det, 0 );
|
2020-01-07 19:08:13 +09:00
|
|
|
|
Sapphire::Common::ActionHitSeverityType hitType = Sapphire::Common::ActionHitSeverityType::NormalDamage;
|
|
|
|
|
|
|
|
|
|
if( criticalHitProbability( chara ) > range100( rng ) )
|
|
|
|
|
{
|
|
|
|
|
factor *= criticalHitBonus( chara );
|
|
|
|
|
hitType = Sapphire::Common::ActionHitSeverityType::CritDamage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( directHitProbability( chara ) > range100( rng ) )
|
|
|
|
|
{
|
|
|
|
|
factor *= 1.25f;
|
|
|
|
|
hitType = hitType == Sapphire::Common::ActionHitSeverityType::CritDamage ?
|
|
|
|
|
Sapphire::Common::ActionHitSeverityType::CritDirectHitDamage :
|
|
|
|
|
Sapphire::Common::ActionHitSeverityType::DirectHitDamage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f );
|
|
|
|
|
|
|
|
|
|
// todo: buffs
|
2019-07-27 00:37:40 +10:00
|
|
|
|
|
2022-01-03 13:24:33 +01:00
|
|
|
|
constexpr auto format = "dmg: pot: {} ({}) wd: {} ({}) ap: {} det: {} = {}";
|
2019-07-27 00:37:40 +10:00
|
|
|
|
|
|
|
|
|
if( auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer() )
|
|
|
|
|
{
|
2022-01-03 13:24:33 +01:00
|
|
|
|
PlayerMgr::sendDebug( *player, format, pot, ptc, wd, wepDmg, ap, det, factor );
|
2019-07-27 00:37:40 +10:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2022-01-03 13:24:33 +01:00
|
|
|
|
Logger::debug( format, pot, ptc, wd, wepDmg, ap, det, factor );
|
2019-07-27 00:37:40 +10:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-07 19:08:13 +09:00
|
|
|
|
return std::pair( factor, hitType );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActionHealing( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg )
|
|
|
|
|
{
|
|
|
|
|
// lol just for testing
|
2020-01-07 19:46:07 +09:00
|
|
|
|
auto factor = std::floor( ptc * ( wepDmg / 10.0f ) + ptc );
|
2020-01-07 19:08:13 +09:00
|
|
|
|
Sapphire::Common::ActionHitSeverityType hitType = Sapphire::Common::ActionHitSeverityType::NormalHeal;
|
|
|
|
|
|
|
|
|
|
if( criticalHitProbability( chara ) > range100( rng ) )
|
|
|
|
|
{
|
|
|
|
|
factor *= criticalHitBonus( chara );
|
|
|
|
|
hitType = Sapphire::Common::ActionHitSeverityType::CritHeal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f );
|
2019-07-27 00:37:40 +10:00
|
|
|
|
|
2020-01-07 19:08:13 +09:00
|
|
|
|
return std::pair( factor, hitType );
|
2019-07-27 00:37:40 +10:00
|
|
|
|
}
|
|
|
|
|
|
2019-04-24 23:25:07 +10:00
|
|
|
|
uint32_t CalcStats::primaryStatValue( const Sapphire::Entity::Chara& chara )
|
|
|
|
|
{
|
2019-04-25 21:57:41 +10:00
|
|
|
|
return chara.getStatValue( chara.getPrimaryStat() );
|
2019-03-24 14:25:00 +11:00
|
|
|
|
}
|