1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-26 06:27:45 +00:00

Merge pull request #65 from itsmaru/master

Basic battle-related calculation class; Cast interrupt;
This commit is contained in:
SapphireMordred 2017-08-21 00:31:52 +02:00 committed by GitHub
commit 488070a53f
19 changed files with 287 additions and 93 deletions

View file

@ -10,7 +10,7 @@ class skillDef_119Def
def onFinish( player, target )
{
player.handleScriptSkill( STD_DAMAGE, 119, 30, 0, target );
player.handleScriptSkill( STD_DAMAGE, 119, 140, 0, target );
}
};

View file

@ -1,5 +1,5 @@
// Skill Name: Sprint
// Skill ID: 3
// Skill Name: Cure
// Skill ID: 120
class skillDef_120Def
{
@ -10,7 +10,7 @@ class skillDef_120Def
def onFinish( player, target )
{
player.handleScriptSkill( STD_HEAL, 120, 1000, 0, target );
player.handleScriptSkill( STD_HEAL, 120, 450, 0, target );
}
};

View file

@ -1,5 +1,5 @@
// Skill Name: Sprint
// Skill ID: 3
// Skill Name: Aero
// Skill ID: 121
class skillDef_121Def
{

View file

@ -1,5 +1,5 @@
// Skill Name: Sprint
// Skill ID: 3
// Skill Name: Stone II
// Skill ID: 127
class skillDef_127Def
{
@ -10,7 +10,7 @@ class skillDef_127Def
def onFinish( player, target )
{
player.handleScriptSkill( STD_DAMAGE, 127, 1000, 0, target );
player.handleScriptSkill( STD_DAMAGE, 127, 200, 0, target );
}
};

View file

@ -0,0 +1,18 @@
// Skill Name: Repose
// Skill ID: 128
class skillDef_128Def
{
def skillDef_128Def()
{
}
def onFinish( player, target )
{
target.addStatusEffectByIdIfNotExist(3, 30000, 0);
}
};
GLOBAL skillDef_128 = skillDef_128Def();

View file

@ -0,0 +1,18 @@
// Skill Name: Bootshine
// Skill ID: 53
class skillDef_53Def
{
def skillDef_53Def()
{
}
def onFinish( player, target )
{
player.handleScriptSkill( STD_DAMAGE, 53, 140, 0, target );
}
};
GLOBAL skillDef_53 = skillDef_53Def();

View file

@ -1,5 +1,5 @@
// Skill Name: Sprint
// Skill ID: 3
// Skill Name: Return
// Skill ID: 6
class skillDef_6Def
{

View file

@ -253,16 +253,16 @@ namespace Core {
JOB_WARRIOR = 21, // warrior
JOB_DRAGON = 22, // dragoon
JOB_BARD = 23, // bard
JOB_WHITE = 24, // white mage
JOB_BLACK = 25, // black mage
JOB_WHITEMAGE = 24, // white mage
JOB_BLACKMAGE = 25, // black mage
CLASS_ARCANIST = 26, // arcanist
JOB_SUMMONER = 27, // summoner
JOB_SCHOLAR = 28, // scholar
CLASS_ROGUE = 29,
JOB_NINJA = 30,
JOB_MACHINIST = 31, // machinist
JOB_DARKKNIGHT = 32, // darknight
JOB_ASTROLOGIAN = 33, // astro
CLASS_ROGUE = 29, // rogue
JOB_NINJA = 30, // ninja
JOB_MACHINIST = 31, // machinist
JOB_DARKKNIGHT = 32, // darknight
JOB_ASTROLOGIAN = 33, // astro
JOB_SAMURAI = 34, // sam
JOB_REDMAGE = 35, // red mage

View file

@ -236,11 +236,11 @@ bool Core::Data::ExdData::loadParamGrowInfo()
uint32_t id = row.first;
info.level = id;
info.needed_exp = getField< int32_t >( fields, 0 );
info.piety_scalar = getField< uint16_t >( fields, 3 ); // 3
info.mp_mod = getField< uint16_t >( fields, 3 ); // 3
info.mp_const = getField< int32_t >( fields, 4 ); // 4
info.base_secondary = getField< int32_t >( fields, 5 );// 5
info.hp_mod = getField< uint16_t >( fields, 8 ); // 8
info.quest_exp_mod = getField< uint8_t >( fields, 7 ); // 7
info.hp_mod = getField< uint16_t >(fields, 8); // 8
m_paramGrowthInfoMap[id] = info;
@ -426,6 +426,7 @@ boost::shared_ptr< Core::Data::ItemInfo >
info->model_primary = getField< uint64_t >( row, 45 );
info->model_secondary = getField< uint64_t >( row, 46 );
info->physical_damage = getField< uint16_t >( row, 49 );
info->magical_damage = getField< uint16_t >( row, 50 );
info->delayMs = getField< uint16_t >( row, 51 );
info->is_unique = getField< int16_t >( row, 64 ) != 0 ? true : false;
info->is_untradeable = getField< uint8_t >( row, 65 ) != 0 ? true : false;

View file

@ -116,7 +116,7 @@ namespace Core {
uint32_t needed_exp;
int16_t hp_mod;
int32_t mp_const;
int16_t piety_scalar;
int16_t mp_mod;
int32_t base_secondary;
uint16_t quest_exp_mod;
};
@ -211,8 +211,9 @@ namespace Core {
uint64_t model_primary; //28
uint64_t model_secondary; //29
uint16_t physical_damage; //49
uint16_t magical_damage; //50
uint16_t delayMs; //51
uint32_t class_job_requirement; //58
uint16_t delayMs; //59
bool is_unique; //72
bool is_untradeable; //73
uint32_t class_job_index; //86

View file

@ -79,6 +79,12 @@ Core::Entity::Actor::Stance Core::Entity::Actor::getStance() const
return m_currentStance;
}
/*! \return actor stats */
Core::Entity::Actor::ActorStats Core::Entity::Actor::getStats() const
{
return m_baseStats;
}
/*! \return current HP */
uint32_t Core::Entity::Actor::getHp() const
{
@ -207,11 +213,8 @@ void Core::Entity::Actor::die()
// fire onDeath event
onDeath();
bool selfNeedsUpdate = false;
// if the actor is a player, the update needs to be send to himself too
if( isPlayer() )
selfNeedsUpdate = true;
bool selfNeedsUpdate = isPlayer();
sendToInRangeSet( ActorControlPacket142( m_id, SetStatus, static_cast< uint8_t>( ActorStatus::Dead ) ), selfNeedsUpdate );

View file

@ -55,6 +55,51 @@ public:
SMachine = 0x08
};
struct ActorStats
{
uint32_t max_mp = 0;
uint32_t max_hp = 0;
uint32_t str = 0;
uint32_t dex = 0;
uint32_t vit = 0;
uint32_t inte = 0;
uint32_t mnd = 0;
uint32_t pie = 0;
uint32_t parry = 0;
uint32_t attack = 0;
uint32_t defense = 0;
uint32_t accuracy = 0;
uint32_t spellSpeed = 0;
uint32_t magicDefense = 0;
uint32_t critHitRate = 0;
uint32_t resistSlash = 0;
uint32_t resistPierce = 0;
uint32_t resistBlunt = 0;
uint32_t attackPotMagic = 0;
uint32_t healingPotMagic = 0;
uint32_t determination = 0;
uint32_t skillSpeed = 0;
uint32_t resistSlow = 0;
uint32_t resistSilence = 0;
uint32_t resistBlind = 0;
uint32_t resistPoison = 0;
uint32_t resistStun = 0;
uint32_t resistSleep = 0;
uint32_t resistBind = 0;
uint32_t resistHeavy = 0;
uint32_t resistFire = 0;
uint32_t resistIce = 0;
uint32_t resistWind = 0;
uint32_t resistEarth = 0;
uint32_t resistLightning = 0;
uint32_t resistWater = 0;
} m_baseStats;
protected:
// TODO: The position class should probably be abolished and
// the FFXIV_POS struct used instead ( the functions in there
@ -106,51 +151,6 @@ protected:
/*! Container for status effects */
StatusEffect::StatusEffectContainerPtr m_pStatusEffectContainer;
struct
{
uint32_t max_mp = 0;
uint32_t max_hp = 0;
uint32_t str = 0;
uint32_t dex = 0;
uint32_t vit = 0;
uint32_t inte = 0;
uint32_t mnd = 0;
uint32_t pie = 0;
uint32_t parry = 0;
uint32_t attack = 0;
uint32_t defense = 0;
uint32_t accuracy = 0;
uint32_t spellSpeed = 0;
uint32_t magicDefense = 0;
uint32_t critHitRate = 0;
uint32_t resistSlash = 0;
uint32_t resistPierce = 0;
uint32_t resistBlunt = 0;
uint32_t attackPotMagic = 0;
uint32_t healingPotMagic = 0;
uint32_t determination = 0;
uint32_t skillSpeed = 0;
uint32_t resistSlow = 0;
uint32_t resistSilence = 0;
uint32_t resistBlind = 0;
uint32_t resistPoison = 0;
uint32_t resistStun = 0;
uint32_t resistSleep = 0;
uint32_t resistBind = 0;
uint32_t resistHeavy = 0;
uint32_t resistFire = 0;
uint32_t resistIce = 0;
uint32_t resistWind = 0;
uint32_t resistEarth = 0;
uint32_t resistLightning = 0;
uint32_t resistWater = 0;
} m_baseStats;
public:
Actor();
@ -189,6 +189,8 @@ public:
void setStance( Stance stance );
ActorStats getStats() const;
uint32_t getHp() const;
uint32_t getMp() const;

View file

@ -0,0 +1,107 @@
#include <src/servers/Server_Common/Exd/ExdData.h>
#include "CalcBattle.h"
#include "Actor.h"
#include "Player.h"
using namespace Core::Entity;
extern Core::Data::ExdData g_exdData;
/*
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.
Attack power (and healing power). Need more researchg on this.
Damage outgoing calculations. This includes auto-attacks, etc.
*/
// Don't know too much about this formula, but seems to work for some of the levels tested.
// Originally from Player.cpp, calculateStats().
uint32_t CalcBattle::calculateBaseStat( PlayerPtr pPlayer )
{
float base = 0.0f;
uint8_t level = pPlayer->getLevel();
if (level < 51)
base = static_cast<uint32_t>( 0.053f * ( level * level ) + (1.022f * level) - 0.907f + 20 );
else
base = static_cast<uint32_t>( 1.627f * level + 120.773f );
return base;
}
// Leggerless' HP Formula
// ROUNDDOWN(JobModHP * (BaseHP / 100)) + ROUNDDOWN(VitHPMod / 100 * (VIT - BaseDET))
uint32_t CalcBattle::calculateMaxHp( PlayerPtr pPlayer )
{
// TODO: Replace ApproxBaseHP with something that can get us a BaseHP reliably.
// Is there any way to pull BaseHP without having to manually use a pet for every level, and using the values from a table?
auto classInfoIt = g_exdData.m_classJobInfoMap.find( pPlayer->getClass() );
auto paramGrowthInfoIt = g_exdData.m_paramGrowthInfoMap.find( pPlayer->getLevel() );
float baseStat = calculateBaseStat( pPlayer );
uint16_t vit = pPlayer->getStats().vit;
uint16_t hp_mod = paramGrowthInfoIt->second.hp_mod;
uint16_t jobModHp = classInfoIt->second.mod_hp;
uint16_t approxBaseHp = 0; // Read above
// These values are not precise.
if ( pPlayer->getLevel() > 50 )
approxBaseHp = 0.1452f * paramGrowthInfoIt->second.mp_const + 1356.6f;
else
approxBaseHp = paramGrowthInfoIt->second.mp_const * 0.525f;
uint16_t result = floor( jobModHp * ( approxBaseHp / 100.0f ) ) + floor( hp_mod / 100.0f * ( vit - baseStat ) );
return result;
}
// Leggerless' MP Formula
// ROUNDDOWN(((ROUNDDOWN(((PIE - BaseDET) * PieMPMod/100),0) + BaseMP) * JobModMP / 100),0)
uint32_t CalcBattle::calculateMaxMp( PlayerPtr pPlayer )
{
auto classInfoIt = g_exdData.m_classJobInfoMap.find( pPlayer->getClass() );
auto paramGrowthInfoIt = g_exdData.m_paramGrowthInfoMap.find( pPlayer->getLevel() );
float baseStat = calculateBaseStat( pPlayer );
uint16_t piety = pPlayer->getStats().pie;
uint16_t piety_scalar = paramGrowthInfoIt->second.mp_mod;
uint16_t jobModMp = classInfoIt->second.mod_mpcpgp;
uint16_t baseMp = paramGrowthInfoIt->second.mp_const;
uint16_t result = floor( floor( piety - baseStat ) * ( piety_scalar / 100 ) + baseMp ) * jobModMp / 100;
return result;
}
uint32_t CalcBattle::calculateHealValue( PlayerPtr pPlayer, uint32_t potency )
{
auto classInfoIt = g_exdData.m_classJobInfoMap.find( pPlayer->getClass() );
auto paramGrowthInfoIt = g_exdData.m_paramGrowthInfoMap.find( pPlayer->getLevel() );
if ( classInfoIt == g_exdData.m_classJobInfoMap.end() ||
paramGrowthInfoIt == g_exdData.m_paramGrowthInfoMap.end())
return 0;
auto jobModVal = classInfoIt->second;
// consider 3% variation
return potency / 10;
}

View file

@ -0,0 +1,27 @@
#ifndef _CALCBATTLE_H
#define _CALCBATTLE_H
#include <src/servers/Server_Common/Common.h>
#include "Actor.h"
namespace Core {
namespace Entity {
class CalcBattle
{
public:
static uint32_t calculateBaseStat( PlayerPtr pPlayer );
static uint32_t calculateMaxMp( PlayerPtr pPlayer );
static uint32_t calculateMaxHp( PlayerPtr pPlayer );
static uint32_t calculateHealValue( PlayerPtr pPlayer, uint32_t potency );
private:
};
}
}
#endif

View file

@ -44,6 +44,7 @@
#include "src/servers/Server_Zone/Action/EventAction.h"
#include "src/servers/Server_Zone/Action/EventItemAction.h"
#include "src/servers/Server_Zone/Zone/ZonePosition.h"
#include "src/servers/Server_Zone/Actor/CalcBattle.h"
#include <boost/make_shared.hpp>
extern Core::Logger g_log;
@ -203,12 +204,7 @@ void Core::Entity::Player::calculateStats()
auto paramGrowthInfo = paramGrowthInfoIt->second;
// TODO: put formula somewhere else...
float base = 0.0f;
if( level < 51 )
base = 0.053f * ( level * level ) + ( 1.022f * level ) - 0.907f + 20;
else
base = 1.627f * level + 120.773f;
float base = CalcBattle::calculateBaseStat( getAsPlayer() );
m_baseStats.str = base * ( static_cast< float >( classInfo.mod_str ) / 100 ) + tribeInfo.mod_str;
m_baseStats.dex = base * ( static_cast< float >( classInfo.mod_dex ) / 100 ) + tribeInfo.mod_dex;
@ -224,15 +220,9 @@ void Core::Entity::Player::calculateStats()
m_baseStats.attackPotMagic = paramGrowthInfo.base_secondary;
m_baseStats.healingPotMagic = paramGrowthInfo.base_secondary;
m_baseStats.max_mp = floor(
floor(
( ( m_baseStats.pie - base ) * ( static_cast< float >( paramGrowthInfo.piety_scalar ) / 100 ) ) + paramGrowthInfo.mp_const ) * ( static_cast< float >( classInfo.mod_mpcpgp ) / 100 )
);
m_baseStats.max_mp = CalcBattle::calculateMaxMp( getAsPlayer() );
m_baseStats.max_hp = floor(
floor(
( ( m_baseStats.vit - base ) * ( ( static_cast< float >( paramGrowthInfo.piety_scalar ) ) / 100 ) ) + paramGrowthInfo.hp_mod ) * ( static_cast< float >( classInfo.mod_hp * 0.9f ) / 100 ) * 15
);
m_baseStats.max_hp = CalcBattle::calculateMaxHp( getAsPlayer() );
if( m_mp > m_baseStats.max_mp )
m_mp = m_baseStats.max_mp;
@ -1451,10 +1441,12 @@ void Core::Entity::Player::autoAttack( ActorPtr pTarget )
//uint64_t tick = Util::getTimeMs();
//srand(static_cast< uint32_t >(tick));
uint32_t damage = mainWeap->getAutoAttackDmg() + rand() % 12;
uint32_t damage = mainWeap->getAutoAttackDmg();
uint32_t variation = 0 + rand() % 3;
if( getClass() == 5 || getClass() == 23 || getClass() == 31 )
if (getClass() == JOB_MACHINIST ||
getClass() == JOB_BARD ||
getClass() == CLASS_ARCHER)
{
GamePacketNew< FFXIVIpcEffect, ServerZoneIpcType > effectPacket(getId());
effectPacket.data().targetId = pTarget->getId();
@ -1533,7 +1525,10 @@ void Core::Entity::Player::handleScriptSkill( uint32_t type, uint32_t actionId,
case Core::Common::HandleSkillType::StdHeal:
{
uint32_t calculatedHeal = CalcBattle::calculateHealValue( getAsPlayer(), param1 );
sendDebug( "STD_HEAL" );
GamePacketNew< FFXIVIpcEffect, ServerZoneIpcType > effectPacket( getId() );
effectPacket.data().targetId = pTarget.getId();
effectPacket.data().actionAnimationId = actionId;
@ -1543,14 +1538,14 @@ void Core::Entity::Player::handleScriptSkill( uint32_t type, uint32_t actionId,
effectPacket.data().numEffects = 1;
effectPacket.data().rotation = Math::Util::floatToUInt16Rot( getRotation() );
effectPacket.data().effectTarget = pTarget.getId();
effectPacket.data().effects[0].param1 = param1;
effectPacket.data().effects[0].param1 = calculatedHeal;
effectPacket.data().effects[0].unknown_1 = 4;
effectPacket.data().effects[0].unknown_2 = 1;
effectPacket.data().effects[0].unknown_3 = 7;
sendToInRangeSet( effectPacket, true );
pTarget.heal( param1 );
pTarget.heal( calculatedHeal );
break;
}

View file

@ -56,13 +56,13 @@ void Core::Entity::Player::equipWeapon( Core::ItemPtr pItem )
case ItemCategory::ThmWep:
case ItemCategory::Thm2Wep:
if( currentClass != ClassJob::CLASS_THAUMATURGE &&
currentClass != ClassJob::JOB_BLACK )
currentClass != ClassJob::JOB_BLACKMAGE )
setClassJob( ClassJob::CLASS_THAUMATURGE );
break;
case ItemCategory::CnjWep:
case ItemCategory::Cnj2Wep:
if( currentClass != ClassJob::CLASS_CONJURER &&
currentClass != ClassJob::JOB_WHITE )
currentClass != ClassJob::JOB_WHITEMAGE )
setClassJob( ClassJob::CLASS_CONJURER );
break;
case ItemCategory::ArnWep:

View file

@ -27,7 +27,9 @@ Core::Item::Item( uint64_t uId, uint32_t catalogId, uint64_t model1, uint64_t mo
auto itemInfo = g_exdData.getItemInfo( catalogId );
m_delayMs = itemInfo->delayMs;
m_physicalDmg = itemInfo->physical_damage;
m_autoAttackDmg = float( m_physicalDmg * m_delayMs ) / 3000;
m_magicalDmg = itemInfo->magical_damage;
m_weaponDmg = ( m_physicalDmg != 0 ) ? m_physicalDmg : m_magicalDmg;
m_autoAttackDmg = static_cast< float >( m_weaponDmg * m_delayMs ) / 3000;
}
Core::Item::~Item()
@ -50,6 +52,16 @@ uint16_t Core::Item::getPhysicalDmg() const
return m_physicalDmg;
}
uint16_t Core::Item::getMagicalDmg() const
{
return m_magicalDmg;
}
uint16_t Core::Item::getWeaponDmg() const
{
return m_weaponDmg;
}
uint32_t Core::Item::getId() const
{
return m_id;

View file

@ -44,6 +44,10 @@ public:
uint16_t getPhysicalDmg() const;
uint16_t getMagicalDmg() const;
uint16_t getWeaponDmg() const;
float getAutoAttackDmg() const;
@ -64,6 +68,8 @@ protected:
uint16_t m_delayMs;
uint16_t m_physicalDmg;
uint16_t m_magicalDmg;
uint16_t m_weaponDmg;
float m_autoAttackDmg;
};

View file

@ -106,7 +106,11 @@ void Core::Network::GameConnection::actionHandler( const Packets::GamePacket& in
pPlayer->changeTarget( targetId );
break;
}
case 0x69: // Cancel cast
{
pPlayer->getCurrentAction()->setInterrupted();
break;
}
case 0x133: // Update howtos seen
{
uint32_t howToId = static_cast< uint32_t >( param1 );