mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-05-25 11:07:45 +00:00
Generic action handler update with status support.
This commit is contained in:
parent
38392be3bf
commit
896ce73175
20 changed files with 3088 additions and 1711 deletions
|
@ -160,7 +160,7 @@ namespace Sapphire::Network::Packets
|
||||||
PlayerStateFlags = 0x02C6, // updated 5.18
|
PlayerStateFlags = 0x02C6, // updated 5.18
|
||||||
PlayerClassInfo = 0x01B0, // updated 5.18
|
PlayerClassInfo = 0x01B0, // updated 5.18
|
||||||
|
|
||||||
ModelEquip = 0x0170, // updated 5.11
|
ModelEquip = 0x02E6, // updated 5.18
|
||||||
Examine = 0x0366, // updated 5.18
|
Examine = 0x0366, // updated 5.18
|
||||||
CharaNameReq = 0x0116, // updated 5.18
|
CharaNameReq = 0x0116, // updated 5.18
|
||||||
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
#include <ScriptObject.h>
|
|
||||||
#include <Actor/Player.h>
|
|
||||||
#include <Action/Action.h>
|
|
||||||
|
|
||||||
class ActionSprint3 :
|
|
||||||
public Sapphire::ScriptAPI::ActionScript
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ActionSprint3() :
|
|
||||||
Sapphire::ScriptAPI::ActionScript( 3 )
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void onExecute( Sapphire::World::Action::Action& action ) override
|
|
||||||
{
|
|
||||||
auto sourceChara = action.getSourceChara();
|
|
||||||
|
|
||||||
if( !sourceChara->isPlayer() )
|
|
||||||
return;
|
|
||||||
|
|
||||||
action.getEffectbuilder()->applyStatusEffect( sourceChara, 50, 30 );
|
|
||||||
|
|
||||||
sourceChara->getAsPlayer()->addStatusEffectByIdIfNotExist( 50, 20000, *sourceChara, 30 );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
EXPOSE_SCRIPT( ActionSprint3 );
|
|
|
@ -406,49 +406,12 @@ void Action::Action::execute()
|
||||||
|
|
||||||
std::pair< uint32_t, Common::ActionHitSeverityType > Action::Action::calcDamage( uint32_t potency )
|
std::pair< uint32_t, Common::ActionHitSeverityType > Action::Action::calcDamage( uint32_t potency )
|
||||||
{
|
{
|
||||||
// todo: what do for npcs?
|
return Math::CalcStats::calcActionDamage( *m_pSource, *this, potency, Math::CalcStats::getWeaponDamage( *m_pSource ) );
|
||||||
auto wepDmg = 1.f;
|
|
||||||
|
|
||||||
if( auto player = m_pSource->getAsPlayer() )
|
|
||||||
{
|
|
||||||
auto item = player->getEquippedWeapon();
|
|
||||||
assert( item );
|
|
||||||
|
|
||||||
auto role = player->getRole();
|
|
||||||
if( role == Common::Role::RangedMagical || role == Common::Role::Healer )
|
|
||||||
{
|
|
||||||
wepDmg = item->getMagicalDmg();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wepDmg = item->getPhysicalDmg();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math::CalcStats::calcActionDamage( *m_pSource, potency, wepDmg );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair< uint32_t, Common::ActionHitSeverityType > Action::Action::calcHealing( uint32_t potency )
|
std::pair< uint32_t, Common::ActionHitSeverityType > Action::Action::calcHealing( uint32_t potency )
|
||||||
{
|
{
|
||||||
auto wepDmg = 1.f;
|
return Math::CalcStats::calcActionHealing( *m_pSource, *this, potency, Math::CalcStats::getWeaponDamage( *m_pSource ) );
|
||||||
|
|
||||||
if( auto player = m_pSource->getAsPlayer() )
|
|
||||||
{
|
|
||||||
auto item = player->getEquippedWeapon();
|
|
||||||
assert( item );
|
|
||||||
|
|
||||||
auto role = player->getRole();
|
|
||||||
if( role == Common::Role::RangedMagical || role == Common::Role::Healer )
|
|
||||||
{
|
|
||||||
wepDmg = item->getMagicalDmg();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wepDmg = item->getPhysicalDmg();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math::CalcStats::calcActionHealing( *m_pSource, potency, wepDmg );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Action::Action::buildEffects()
|
void Action::Action::buildEffects()
|
||||||
|
@ -468,19 +431,22 @@ void Action::Action::buildEffects()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( !hasLutEntry || m_hitActors.empty() )
|
if( !hasLutEntry ) // this is just "if ( weCanNotUseGenericActionHandler )" in case we start to expand it.
|
||||||
{
|
{
|
||||||
// send any effect packet added by script or an empty one just to play animation for other players
|
// send any effect packet added by script or an empty one just to play animation for other players
|
||||||
m_effectBuilder->buildAndSendPackets();
|
m_effectBuilder->buildAndSendPackets();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// no script exists but we have a valid lut entry
|
// we have a valid lut entry
|
||||||
if( auto player = getSourceChara()->getAsPlayer() )
|
if( auto player = getSourceChara()->getAsPlayer() )
|
||||||
{
|
{
|
||||||
player->sendDebug( "Hit target: pot: {} (c: {}, f: {}, r: {}), heal pot: {}, mpp: {}",
|
player->sendDebug( "type: {}, dpot: {} (dcpot: {}, ddpot: {}), hpot: {}, shpot: {}, ss: {}, ts: {}, gmp: {}",
|
||||||
m_lutEntry.potency, m_lutEntry.comboPotency, m_lutEntry.flankPotency, m_lutEntry.rearPotency,
|
m_actionData->attackType,
|
||||||
m_lutEntry.curePotency, m_lutEntry.restoreMPPercentage );
|
m_lutEntry.damagePotency, m_lutEntry.damageComboPotency, m_lutEntry.damageDirectionalPotency,
|
||||||
|
m_lutEntry.healPotency, m_lutEntry.selfHealPotency,
|
||||||
|
m_lutEntry.selfStatus, m_lutEntry.targetStatus,
|
||||||
|
m_lutEntry.gainMPPercentage );
|
||||||
}
|
}
|
||||||
|
|
||||||
// when aoe, these effects are in the target whatever is hit first
|
// when aoe, these effects are in the target whatever is hit first
|
||||||
|
@ -489,9 +455,10 @@ void Action::Action::buildEffects()
|
||||||
|
|
||||||
for( auto& actor : m_hitActors )
|
for( auto& actor : m_hitActors )
|
||||||
{
|
{
|
||||||
if( m_lutEntry.potency > 0 )
|
if( m_lutEntry.damagePotency > 0 )
|
||||||
{
|
{
|
||||||
auto dmg = calcDamage( isCorrectCombo() ? m_lutEntry.comboPotency : m_lutEntry.potency );
|
auto dmg = calcDamage( isCorrectCombo() ? m_lutEntry.damageComboPotency : m_lutEntry.damagePotency );
|
||||||
|
dmg.first = Math::CalcStats::applyDamageReceiveMultiplier( *actor, dmg.first, m_actionData->attackType );
|
||||||
m_effectBuilder->damage( actor, actor, dmg.first, dmg.second );
|
m_effectBuilder->damage( actor, actor, dmg.first, dmg.second );
|
||||||
|
|
||||||
if( dmg.first > 0 )
|
if( dmg.first > 0 )
|
||||||
|
@ -505,15 +472,16 @@ void Action::Action::buildEffects()
|
||||||
|
|
||||||
if( !isComboAction() || isCorrectCombo() )
|
if( !isComboAction() || isCorrectCombo() )
|
||||||
{
|
{
|
||||||
if( m_lutEntry.curePotency > 0 ) // actions with self heal
|
if( m_lutEntry.selfHealPotency > 0 ) // actions with self heal
|
||||||
{
|
{
|
||||||
auto heal = calcHealing( m_lutEntry.curePotency );
|
auto heal = calcHealing( m_lutEntry.selfHealPotency );
|
||||||
|
heal.first = Math::CalcStats::applyHealingReceiveMultiplier( *m_pSource, heal.first, 0 );
|
||||||
m_effectBuilder->heal( actor, m_pSource, heal.first, heal.second, Common::ActionEffectResultFlag::EffectOnSource );
|
m_effectBuilder->heal( actor, m_pSource, heal.first, heal.second, Common::ActionEffectResultFlag::EffectOnSource );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP )
|
if( m_lutEntry.gainMPPercentage > 0 && shouldRestoreMP )
|
||||||
{
|
{
|
||||||
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
|
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.gainMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
|
||||||
shouldRestoreMP = false;
|
shouldRestoreMP = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,24 +491,35 @@ void Action::Action::buildEffects()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if( m_lutEntry.curePotency > 0 )
|
else if( m_lutEntry.healPotency > 0 )
|
||||||
{
|
{
|
||||||
auto heal = calcHealing( m_lutEntry.curePotency );
|
auto heal = calcHealing( m_lutEntry.healPotency );
|
||||||
|
heal.first = Math::CalcStats::applyHealingReceiveMultiplier( *actor, heal.first, 0 );
|
||||||
m_effectBuilder->heal( actor, actor, heal.first, heal.second );
|
m_effectBuilder->heal( actor, actor, heal.first, heal.second );
|
||||||
|
|
||||||
if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP )
|
if( m_lutEntry.gainMPPercentage > 0 && shouldRestoreMP )
|
||||||
{
|
{
|
||||||
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
|
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.gainMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
|
||||||
shouldRestoreMP = false;
|
shouldRestoreMP = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP )
|
else if( m_lutEntry.gainMPPercentage > 0 && shouldRestoreMP )
|
||||||
{
|
{
|
||||||
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
|
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.gainMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
|
||||||
shouldRestoreMP = false;
|
shouldRestoreMP = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( m_lutEntry.targetStatus != 0 )
|
||||||
|
{
|
||||||
|
m_effectBuilder->applyStatusEffect( actor, m_pSource, m_lutEntry.targetStatus, m_lutEntry.targetStatusDuration, m_lutEntry.targetStatusParam );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( m_lutEntry.selfStatus != 0 )
|
||||||
|
{
|
||||||
|
m_effectBuilder->applyStatusEffect( m_pSource, m_pSource, m_lutEntry.selfStatus, m_lutEntry.selfStatusDuration, m_lutEntry.selfStatusParam );
|
||||||
|
}
|
||||||
|
|
||||||
m_effectBuilder->buildAndSendPackets();
|
m_effectBuilder->buildAndSendPackets();
|
||||||
|
|
||||||
// at this point we're done with it and no longer need it
|
// at this point we're done with it and no longer need it
|
||||||
|
@ -565,7 +544,7 @@ bool Action::Action::playerPreCheck( Entity::Player& player )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// npc actions/non player actions
|
// npc actions/non player actions
|
||||||
if( m_actionData->classJob == -1 )
|
if( m_actionData->classJob == -1 && !m_actionData->isRoleAction )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if( player.getLevel() < m_actionData->classJobLevel )
|
if( player.getLevel() < m_actionData->classJobLevel )
|
||||||
|
@ -574,7 +553,7 @@ bool Action::Action::playerPreCheck( Entity::Player& player )
|
||||||
auto currentClass = player.getClass();
|
auto currentClass = player.getClass();
|
||||||
auto actionClass = static_cast< Common::ClassJob >( m_actionData->classJob );
|
auto actionClass = static_cast< Common::ClassJob >( m_actionData->classJob );
|
||||||
|
|
||||||
if( actionClass != Common::ClassJob::Adventurer && currentClass != actionClass )
|
if( actionClass != Common::ClassJob::Adventurer && currentClass != actionClass && !m_actionData->isRoleAction )
|
||||||
{
|
{
|
||||||
// check if not a base class action
|
// check if not a base class action
|
||||||
auto exdData = m_pFw->get< Data::ExdDataGenerated >();
|
auto exdData = m_pFw->get< Data::ExdDataGenerated >();
|
||||||
|
@ -777,18 +756,18 @@ bool Action::Action::preFilterActor( Sapphire::Entity::Actor& actor ) const
|
||||||
if( kind != ObjKind::BattleNpc && kind != ObjKind::Player )
|
if( kind != ObjKind::BattleNpc && kind != ObjKind::Player )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if( !m_canTargetSelf && chara->getId() == m_pSource->getId() )
|
if( m_lutEntry.damagePotency > 0 && chara->getId() == m_pSource->getId() ) // !m_canTargetSelf
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if( ( m_lutEntry.potency > 0 || m_lutEntry.curePotency > 0 ) && !chara->isAlive() ) // !m_canTargetDead not working for aoe
|
if( ( m_lutEntry.damagePotency > 0 || m_lutEntry.healPotency > 0 ) && !chara->isAlive() ) // !m_canTargetDead not working for aoe
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if( m_lutEntry.potency > 0 && m_pSource->getObjKind() == chara->getObjKind() ) // !m_canTargetFriendly not working for aoe
|
if( m_lutEntry.damagePotency > 0 && m_pSource->getObjKind() == chara->getObjKind() ) // !m_canTargetFriendly not working for aoe
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if( ( m_lutEntry.potency == 0 && m_lutEntry.curePotency > 0 ) && m_pSource->getObjKind() != chara->getObjKind() ) // !m_canTargetHostile not working for aoe
|
if( ( m_lutEntry.damagePotency == 0 && m_lutEntry.healPotency > 0 ) && m_pSource->getObjKind() != chara->getObjKind() ) // !m_canTargetHostile not working for aoe
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -809,11 +788,40 @@ Sapphire::Entity::CharaPtr Action::Action::getHitChara()
|
||||||
|
|
||||||
bool Action::Action::hasValidLutEntry() const
|
bool Action::Action::hasValidLutEntry() const
|
||||||
{
|
{
|
||||||
return m_lutEntry.potency != 0 || m_lutEntry.comboPotency != 0 || m_lutEntry.flankPotency != 0 || m_lutEntry.frontPotency != 0 ||
|
return m_lutEntry.damagePotency != 0 || m_lutEntry.healPotency != 0 || m_lutEntry.selfHealPotency != 0 || m_lutEntry.selfStatus != 0 ||
|
||||||
m_lutEntry.rearPotency != 0 || m_lutEntry.curePotency != 0 || m_lutEntry.restoreMPPercentage != 0;
|
m_lutEntry.targetStatus != 0 || m_lutEntry.gainMPPercentage != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Action::EffectBuilderPtr Action::Action::getEffectbuilder()
|
Action::EffectBuilderPtr Action::Action::getEffectbuilder()
|
||||||
{
|
{
|
||||||
return m_effectBuilder;
|
return m_effectBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
Data::ActionPtr Action::Action::getActionData() const
|
||||||
|
{
|
||||||
|
return m_actionData;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Action::Action::isPhysical() const
|
||||||
|
{
|
||||||
|
return isAttackTypePhysical( m_actionData->attackType );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Action::Action::isMagical() const
|
||||||
|
{
|
||||||
|
return isAttackTypeMagical( m_actionData->attackType );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Action::Action::isAttackTypePhysical( int8_t attackType )
|
||||||
|
{
|
||||||
|
return attackType == -1 ||
|
||||||
|
attackType == 1 ||
|
||||||
|
attackType == 2 ||
|
||||||
|
attackType == 3 ||
|
||||||
|
attackType == 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Action::Action::isAttackTypeMagical( int8_t attackType )
|
||||||
|
{
|
||||||
|
return attackType == 5;
|
||||||
}
|
}
|
|
@ -118,6 +118,14 @@ namespace Sapphire::World::Action
|
||||||
*/
|
*/
|
||||||
Entity::CharaPtr getHitChara();
|
Entity::CharaPtr getHitChara();
|
||||||
|
|
||||||
|
Data::ActionPtr getActionData() const;
|
||||||
|
|
||||||
|
bool isPhysical() const;
|
||||||
|
bool isMagical() const;
|
||||||
|
|
||||||
|
static bool isAttackTypePhysical( int8_t attackType );
|
||||||
|
static bool isAttackTypeMagical( int8_t attackType );
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Starts the cast. Finishes it immediately if there is no cast time (weaponskills).
|
* @brief Starts the cast. Finishes it immediately if there is no cast time (weaponskills).
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -12,9 +12,9 @@ bool ActionLut::validEntryExists( uint16_t actionId )
|
||||||
|
|
||||||
const auto& entry = it->second;
|
const auto& entry = it->second;
|
||||||
|
|
||||||
// if all of the fields are 0, it's not 'valid' due to parse error or no useful data in the tooltip
|
// if all of these fields are 0, it's not 'valid' due to parse error or no useful data
|
||||||
return entry.potency != 0 || entry.comboPotency != 0 || entry.flankPotency != 0 || entry.frontPotency != 0 ||
|
return entry.damagePotency != 0 || entry.healPotency != 0 || entry.selfHealPotency != 0 || entry.selfStatus != 0 ||
|
||||||
entry.rearPotency != 0 || entry.curePotency != 0;
|
entry.targetStatus != 0 || entry.gainMPPercentage != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ActionEntry& ActionLut::getEntry( uint16_t actionId )
|
const ActionEntry& ActionLut::getEntry( uint16_t actionId )
|
||||||
|
@ -23,5 +23,26 @@ const ActionEntry& ActionLut::getEntry( uint16_t actionId )
|
||||||
|
|
||||||
assert( it != m_actionLut.end() );
|
assert( it != m_actionLut.end() );
|
||||||
|
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActionLut::validStatusEffectExists( uint16_t statusId )
|
||||||
|
{
|
||||||
|
auto it = m_statusEffectTable.find( statusId );
|
||||||
|
|
||||||
|
if( it == m_statusEffectTable.end() )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const auto& entry = it->second;
|
||||||
|
|
||||||
|
return entry.effectType != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatusEffectEntry& ActionLut::getStatusEffectEntry( uint16_t statusId )
|
||||||
|
{
|
||||||
|
auto it = m_statusEffectTable.find( statusId );
|
||||||
|
|
||||||
|
assert( it != m_statusEffectTable.end() );
|
||||||
|
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
|
@ -7,24 +7,60 @@ namespace Sapphire::World::Action
|
||||||
{
|
{
|
||||||
struct ActionEntry
|
struct ActionEntry
|
||||||
{
|
{
|
||||||
uint16_t potency;
|
uint16_t damagePotency;
|
||||||
uint16_t comboPotency;
|
uint16_t damageComboPotency;
|
||||||
uint16_t flankPotency;
|
uint16_t damageDirectionalPotency;
|
||||||
uint16_t frontPotency;
|
uint16_t healPotency;
|
||||||
uint16_t rearPotency;
|
uint16_t selfHealPotency;
|
||||||
uint16_t curePotency;
|
uint16_t selfStatus;
|
||||||
uint16_t restoreMPPercentage;
|
uint32_t selfStatusDuration;
|
||||||
|
uint16_t selfStatusParam;
|
||||||
|
uint16_t targetStatus;
|
||||||
|
uint32_t targetStatusDuration;
|
||||||
|
uint16_t targetStatusParam;
|
||||||
|
uint16_t gainMPPercentage;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint32_t EffectTypeInvalid = 0;
|
||||||
|
const uint32_t EffectTypeDamageMultiplier = 1;
|
||||||
|
const uint32_t EffectTypeDamageReceiveMultiplier = 2;
|
||||||
|
const uint32_t EffectTypeHot = 3;
|
||||||
|
const uint32_t EffectTypeDot = 4;
|
||||||
|
const uint32_t EffectTypeHealReceiveMultiplier = 5;
|
||||||
|
const uint32_t EffectTypeHealCastMultiplier = 6;
|
||||||
|
const uint32_t EffectTypeCritDHRateBonus = 7;
|
||||||
|
|
||||||
|
const uint8_t EffectActionTypeFilterPhysical = 1;
|
||||||
|
const uint8_t EffectActionTypeFilterMagical = 2;
|
||||||
|
const uint8_t EffectActionTypeFilterAll = 255;
|
||||||
|
|
||||||
|
const uint8_t EffectCritDHBonusFilterDamage = 1;
|
||||||
|
const uint8_t EffectCritDHBonusFilterHeal = 2;
|
||||||
|
const uint8_t EffectCritDHBonusFilterAll = 255;
|
||||||
|
|
||||||
|
struct StatusEffectEntry
|
||||||
|
{
|
||||||
|
uint32_t effectType;
|
||||||
|
int32_t effectValue1;
|
||||||
|
int32_t effectValue2;
|
||||||
|
int32_t effectValue3;
|
||||||
|
int32_t effectValue4;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ActionLut
|
class ActionLut
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using Lut = std::unordered_map< uint16_t, ActionEntry >;
|
using Lut = std::unordered_map< uint16_t, ActionEntry >;
|
||||||
|
using StatusEffectTable = std::unordered_map< uint16_t, StatusEffectEntry >;
|
||||||
|
|
||||||
static bool validEntryExists( uint16_t actionId );
|
static bool validEntryExists( uint16_t actionId );
|
||||||
static const ActionEntry& getEntry( uint16_t actionId );
|
static const ActionEntry& getEntry( uint16_t actionId );
|
||||||
|
|
||||||
|
static bool validStatusEffectExists( uint16_t statusId );
|
||||||
|
static const StatusEffectEntry& getStatusEffectEntry( uint16_t statusId );
|
||||||
|
|
||||||
static Lut m_actionLut;
|
static Lut m_actionLut;
|
||||||
|
static StatusEffectTable m_statusEffectTable;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -51,43 +51,43 @@ void EffectBuilder::moveToResultList( Entity::CharaPtr& chara, EffectResultPtr r
|
||||||
|
|
||||||
void EffectBuilder::heal( Entity::CharaPtr& effectTarget, Entity::CharaPtr& healingTarget, uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag )
|
void EffectBuilder::heal( Entity::CharaPtr& effectTarget, Entity::CharaPtr& healingTarget, uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag )
|
||||||
{
|
{
|
||||||
EffectResultPtr nextResult = make_EffectResult( healingTarget, getResultDelayMs() );
|
EffectResultPtr nextResult = make_EffectResult( healingTarget, nullptr, getResultDelayMs() );
|
||||||
nextResult->heal( amount, severity, flag );
|
nextResult->heal( amount, severity, flag );
|
||||||
moveToResultList( effectTarget, nextResult );
|
moveToResultList( effectTarget, nextResult );
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectBuilder::restoreMP( Entity::CharaPtr& target, Entity::CharaPtr& restoringTarget, uint32_t amount, Common::ActionEffectResultFlag flag )
|
void EffectBuilder::restoreMP( Entity::CharaPtr& target, Entity::CharaPtr& restoringTarget, uint32_t amount, Common::ActionEffectResultFlag flag )
|
||||||
{
|
{
|
||||||
EffectResultPtr nextResult = make_EffectResult( restoringTarget, getResultDelayMs() ); // restore mp source actor
|
EffectResultPtr nextResult = make_EffectResult( restoringTarget, nullptr, getResultDelayMs() ); // restore mp source actor
|
||||||
nextResult->restoreMP( amount, flag );
|
nextResult->restoreMP( amount, flag );
|
||||||
moveToResultList( target, nextResult );
|
moveToResultList( target, nextResult );
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectBuilder::damage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag )
|
void EffectBuilder::damage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag )
|
||||||
{
|
{
|
||||||
EffectResultPtr nextResult = make_EffectResult( damagingTarget, getResultDelayMs() );
|
EffectResultPtr nextResult = make_EffectResult( damagingTarget, nullptr, getResultDelayMs() );
|
||||||
nextResult->damage( amount, severity, flag );
|
nextResult->damage( amount, severity, flag );
|
||||||
moveToResultList( effectTarget, nextResult );
|
moveToResultList( effectTarget, nextResult );
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectBuilder::startCombo( Entity::CharaPtr& target, uint16_t actionId )
|
void EffectBuilder::startCombo( Entity::CharaPtr& target, uint16_t actionId )
|
||||||
{
|
{
|
||||||
EffectResultPtr nextResult = make_EffectResult( target, 0 );
|
EffectResultPtr nextResult = make_EffectResult( target, nullptr, 0 );
|
||||||
nextResult->startCombo( actionId );
|
nextResult->startCombo( actionId );
|
||||||
moveToResultList( target, nextResult );
|
moveToResultList( target, nextResult );
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectBuilder::comboSucceed( Entity::CharaPtr& target )
|
void EffectBuilder::comboSucceed( Entity::CharaPtr& target )
|
||||||
{
|
{
|
||||||
EffectResultPtr nextResult = make_EffectResult( target, 0 );
|
EffectResultPtr nextResult = make_EffectResult( target, nullptr, 0 );
|
||||||
nextResult->comboSucceed();
|
nextResult->comboSucceed();
|
||||||
moveToResultList( target, nextResult );
|
moveToResultList( target, nextResult );
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectBuilder::applyStatusEffect( Entity::CharaPtr& target, uint16_t statusId, uint8_t param )
|
void EffectBuilder::applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, uint16_t statusId, uint32_t duration, uint8_t param )
|
||||||
{
|
{
|
||||||
EffectResultPtr nextResult = make_EffectResult( target, 0 );
|
EffectResultPtr nextResult = make_EffectResult( target, source, 0 );
|
||||||
nextResult->applyStatusEffect( statusId, param );
|
nextResult->applyStatusEffect( statusId, duration, param );
|
||||||
moveToResultList( target, nextResult );
|
moveToResultList( target, nextResult );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace Sapphire::World::Action
|
||||||
|
|
||||||
void comboSucceed( Entity::CharaPtr& target );
|
void comboSucceed( Entity::CharaPtr& target );
|
||||||
|
|
||||||
void applyStatusEffect( Entity::CharaPtr& target, uint16_t statusId, uint8_t param );
|
void applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, uint16_t statusId, uint32_t duration, uint8_t param );
|
||||||
|
|
||||||
void buildAndSendPackets();
|
void buildAndSendPackets();
|
||||||
|
|
||||||
|
|
|
@ -8,19 +8,26 @@ using namespace Sapphire;
|
||||||
using namespace Sapphire::World::Action;
|
using namespace Sapphire::World::Action;
|
||||||
|
|
||||||
|
|
||||||
EffectResult::EffectResult( Entity::CharaPtr target, uint64_t runAfter ) :
|
EffectResult::EffectResult( Entity::CharaPtr target, Entity::CharaPtr source, uint64_t runAfter ) :
|
||||||
m_target( std::move( target ) ),
|
m_target( std::move( target ) ),
|
||||||
|
m_source( std::move( source ) ),
|
||||||
m_delayMs( runAfter ),
|
m_delayMs( runAfter ),
|
||||||
|
m_type( Common::ActionEffectType::Nothing ),
|
||||||
m_value( 0 ),
|
m_value( 0 ),
|
||||||
|
m_value2( 0 ),
|
||||||
m_param0( 0 ),
|
m_param0( 0 ),
|
||||||
m_param1( 0 ),
|
m_param1( 0 ),
|
||||||
m_type( Common::ActionEffectType::Nothing ),
|
|
||||||
m_param2( 0 ),
|
m_param2( 0 ),
|
||||||
m_flag( Common::ActionEffectResultFlag::None )
|
m_flag( Common::ActionEffectResultFlag::None )
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Entity::CharaPtr EffectResult::getSource() const
|
||||||
|
{
|
||||||
|
return m_source;
|
||||||
|
}
|
||||||
|
|
||||||
Entity::CharaPtr EffectResult::getTarget() const
|
Entity::CharaPtr EffectResult::getTarget() const
|
||||||
{
|
{
|
||||||
return m_target;
|
return m_target;
|
||||||
|
@ -76,9 +83,10 @@ void EffectResult::comboSucceed()
|
||||||
m_type = Common::ActionEffectType::ComboSucceed;
|
m_type = Common::ActionEffectType::ComboSucceed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectResult::applyStatusEffect( uint16_t statusId, uint8_t param )
|
void EffectResult::applyStatusEffect( uint16_t statusId, uint32_t duration, uint8_t param )
|
||||||
{
|
{
|
||||||
m_value = statusId;
|
m_value = statusId;
|
||||||
|
m_value2 = duration;
|
||||||
m_param2 = param;
|
m_param2 = param;
|
||||||
|
|
||||||
m_type = Common::ActionEffectType::ApplyStatusEffect;
|
m_type = Common::ActionEffectType::ApplyStatusEffect;
|
||||||
|
@ -121,6 +129,12 @@ void EffectResult::execute()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Common::ActionEffectType::ApplyStatusEffect:
|
||||||
|
{
|
||||||
|
m_target->addStatusEffectById( m_value, m_value2, *m_source, m_param2 );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,15 +13,16 @@ namespace Sapphire::World::Action
|
||||||
class EffectResult
|
class EffectResult
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit EffectResult( Entity::CharaPtr target, uint64_t delayMs );
|
explicit EffectResult( Entity::CharaPtr target, Entity::CharaPtr source, uint64_t delayMs );
|
||||||
|
|
||||||
void damage( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None );
|
void damage( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None );
|
||||||
void heal( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None );
|
void heal( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None );
|
||||||
void restoreMP( uint32_t amount, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None );
|
void restoreMP( uint32_t amount, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None );
|
||||||
void startCombo( uint16_t actionId );
|
void startCombo( uint16_t actionId );
|
||||||
void comboSucceed();
|
void comboSucceed();
|
||||||
void applyStatusEffect( uint16_t statusId, uint8_t param );
|
void applyStatusEffect( uint16_t statusId, uint32_t duration, uint8_t param );
|
||||||
|
|
||||||
|
Entity::CharaPtr getSource() const;
|
||||||
Entity::CharaPtr getTarget() const;
|
Entity::CharaPtr getTarget() const;
|
||||||
|
|
||||||
uint32_t getValue() const;
|
uint32_t getValue() const;
|
||||||
|
@ -35,6 +36,7 @@ namespace Sapphire::World::Action
|
||||||
private:
|
private:
|
||||||
uint64_t m_delayMs;
|
uint64_t m_delayMs;
|
||||||
|
|
||||||
|
Entity::CharaPtr m_source;
|
||||||
Entity::CharaPtr m_target;
|
Entity::CharaPtr m_target;
|
||||||
|
|
||||||
Common::ActionEffectType m_type;
|
Common::ActionEffectType m_type;
|
||||||
|
@ -44,6 +46,7 @@ namespace Sapphire::World::Action
|
||||||
uint8_t m_param2;
|
uint8_t m_param2;
|
||||||
|
|
||||||
uint32_t m_value;
|
uint32_t m_value;
|
||||||
|
uint32_t m_value2;
|
||||||
Common::ActionEffectResultFlag m_flag;
|
Common::ActionEffectResultFlag m_flag;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -693,7 +693,7 @@ void Sapphire::Entity::BNpc::autoAttack( CharaPtr pTarget )
|
||||||
|
|
||||||
auto pRNGMgr = m_pFw->get< World::Manager::RNGMgr >();
|
auto pRNGMgr = m_pFw->get< World::Manager::RNGMgr >();
|
||||||
auto damage = Math::CalcStats::calcAutoAttackDamage( *this );
|
auto damage = Math::CalcStats::calcAutoAttackDamage( *this );
|
||||||
|
damage.first = Math::CalcStats::applyDamageReceiveMultiplier( *pTarget, damage.first, -1 );
|
||||||
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 );
|
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 );
|
||||||
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
|
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
|
||||||
Common::EffectEntry effectEntry{};
|
Common::EffectEntry effectEntry{};
|
||||||
|
|
|
@ -354,6 +354,8 @@ bool Sapphire::Entity::Chara::checkAction()
|
||||||
|
|
||||||
void Sapphire::Entity::Chara::update( uint64_t tickCount )
|
void Sapphire::Entity::Chara::update( uint64_t tickCount )
|
||||||
{
|
{
|
||||||
|
updateStatusEffects();
|
||||||
|
|
||||||
if( std::difftime( static_cast< time_t >( tickCount ), m_lastTickTime ) > 3000 )
|
if( std::difftime( static_cast< time_t >( tickCount ), m_lastTickTime ) > 3000 )
|
||||||
{
|
{
|
||||||
onTick();
|
onTick();
|
||||||
|
@ -388,8 +390,6 @@ uint8_t Sapphire::Entity::Chara::getLevel() const
|
||||||
Let an actor take damage and perform necessary steps
|
Let an actor take damage and perform necessary steps
|
||||||
according to resulting hp, propagates new hp value to players
|
according to resulting hp, propagates new hp value to players
|
||||||
in range
|
in range
|
||||||
TODO: eventually this needs to distinguish between physical and
|
|
||||||
magical dmg and take status effects into account
|
|
||||||
|
|
||||||
\param amount of damage to be taken
|
\param amount of damage to be taken
|
||||||
*/
|
*/
|
||||||
|
@ -502,6 +502,7 @@ void Sapphire::Entity::Chara::autoAttack( CharaPtr pTarget )
|
||||||
srand( static_cast< uint32_t >( tick ) );
|
srand( static_cast< uint32_t >( tick ) );
|
||||||
|
|
||||||
auto damage = static_cast< uint16_t >( 10 + rand() % 12 );
|
auto damage = static_cast< uint16_t >( 10 + rand() % 12 );
|
||||||
|
damage = Math::CalcStats::applyDamageReceiveMultiplier( *pTarget, damage, -1 );
|
||||||
|
|
||||||
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 );
|
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 );
|
||||||
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
|
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
|
||||||
|
@ -557,6 +558,9 @@ void Sapphire::Entity::Chara::addStatusEffect( StatusEffect::StatusEffectPtr pEf
|
||||||
/*! \param StatusEffectPtr to be applied to the actor */
|
/*! \param StatusEffectPtr to be applied to the actor */
|
||||||
void Sapphire::Entity::Chara::addStatusEffectById( uint32_t id, int32_t duration, Entity::Chara& source, uint16_t param )
|
void Sapphire::Entity::Chara::addStatusEffectById( uint32_t id, int32_t duration, Entity::Chara& source, uint16_t param )
|
||||||
{
|
{
|
||||||
|
if( hasStatusEffect( id ) ) // todo: check if we want to refresh it or discard and keep the old one
|
||||||
|
removeSingleStatusEffectById( id, false );
|
||||||
|
|
||||||
auto effect = StatusEffect::make_StatusEffect( id, source.getAsChara(), getAsChara(), duration, 3000, m_pFw );
|
auto effect = StatusEffect::make_StatusEffect( id, source.getAsChara(), getAsChara(), duration, 3000, m_pFw );
|
||||||
effect->setParam( param );
|
effect->setParam( param );
|
||||||
addStatusEffect( effect );
|
addStatusEffect( effect );
|
||||||
|
@ -593,19 +597,19 @@ void Sapphire::Entity::Chara::statusEffectFreeSlot( uint8_t slotId )
|
||||||
m_statusEffectFreeSlotQueue.push( slotId );
|
m_statusEffectFreeSlotQueue.push( slotId );
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sapphire::Entity::Chara::removeSingleStatusEffectById( uint32_t id )
|
void Sapphire::Entity::Chara::removeSingleStatusEffectById( uint32_t id, bool sendPacket )
|
||||||
{
|
{
|
||||||
for( auto effectIt : m_statusEffectMap )
|
for( auto effectIt : m_statusEffectMap )
|
||||||
{
|
{
|
||||||
if( effectIt.second->getId() == id )
|
if( effectIt.second->getId() == id )
|
||||||
{
|
{
|
||||||
removeStatusEffect( effectIt.first );
|
removeStatusEffect( effectIt.first, sendPacket );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sapphire::Entity::Chara::removeStatusEffect( uint8_t effectSlotId )
|
void Sapphire::Entity::Chara::removeStatusEffect( uint8_t effectSlotId, bool sendPacket )
|
||||||
{
|
{
|
||||||
auto pEffectIt = m_statusEffectMap.find( effectSlotId );
|
auto pEffectIt = m_statusEffectMap.find( effectSlotId );
|
||||||
if( pEffectIt == m_statusEffectMap.end() )
|
if( pEffectIt == m_statusEffectMap.end() )
|
||||||
|
@ -616,7 +620,8 @@ void Sapphire::Entity::Chara::removeStatusEffect( uint8_t effectSlotId )
|
||||||
auto pEffect = pEffectIt->second;
|
auto pEffect = pEffectIt->second;
|
||||||
pEffect->removeStatus();
|
pEffect->removeStatus();
|
||||||
|
|
||||||
sendToInRangeSet( makeActorControl( getId(), StatusEffectLose, pEffect->getId() ), isPlayer() );
|
if( sendPacket )
|
||||||
|
sendToInRangeSet( makeActorControl( getId(), StatusEffectLose, pEffect->getId() ), isPlayer() );
|
||||||
|
|
||||||
m_statusEffectMap.erase( effectSlotId );
|
m_statusEffectMap.erase( effectSlotId );
|
||||||
|
|
||||||
|
@ -694,10 +699,10 @@ void Sapphire::Entity::Chara::updateStatusEffects()
|
||||||
uint32_t duration = effect->getDuration();
|
uint32_t duration = effect->getDuration();
|
||||||
uint32_t tickRate = effect->getTickRate();
|
uint32_t tickRate = effect->getTickRate();
|
||||||
|
|
||||||
if( ( currentTimeMs - startTime ) > duration )
|
if( duration > 0 && ( currentTimeMs - startTime ) > duration )
|
||||||
{
|
{
|
||||||
// remove status effect
|
// remove status effect
|
||||||
removeStatusEffect( effectIndex );
|
removeStatusEffect( effectIndex, true );
|
||||||
// break because removing invalidates iterators
|
// break because removing invalidates iterators
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -733,20 +738,28 @@ void Sapphire::Entity::Chara::updateStatusEffects()
|
||||||
{
|
{
|
||||||
takeDamage( thisTickDmg );
|
takeDamage( thisTickDmg );
|
||||||
sendToInRangeSet( makeActorControl( getId(), HPFloatingText, 0,
|
sendToInRangeSet( makeActorControl( getId(), HPFloatingText, 0,
|
||||||
static_cast< uint8_t >( ActionEffectType::Damage ), thisTickDmg ) );
|
static_cast< uint8_t >( ActionEffectType::Damage ), thisTickDmg ), true );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( thisTickHeal != 0 )
|
if( thisTickHeal != 0 )
|
||||||
{
|
{
|
||||||
heal( thisTickDmg );
|
heal( thisTickHeal );
|
||||||
sendToInRangeSet( makeActorControl( getId(), HPFloatingText, 0,
|
sendToInRangeSet( makeActorControl( getId(), HPFloatingText, 0,
|
||||||
static_cast< uint8_t >( ActionEffectType::Heal ), thisTickHeal ) );
|
static_cast< uint8_t >( ActionEffectType::Heal ), thisTickHeal ), true );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Sapphire::Entity::Chara::hasStatusEffect( uint32_t id )
|
bool Sapphire::Entity::Chara::hasStatusEffect( uint32_t id )
|
||||||
{
|
{
|
||||||
return m_statusEffectMap.find( id ) != m_statusEffectMap.end();
|
//return m_statusEffectMap.find( id ) != m_statusEffectMap.end();
|
||||||
|
for( auto effectIt : m_statusEffectMap )
|
||||||
|
{
|
||||||
|
if( effectIt.second->getId() == id )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t Sapphire::Entity::Chara::getLastUpdateTime() const
|
int64_t Sapphire::Entity::Chara::getLastUpdateTime() const
|
||||||
|
|
|
@ -146,9 +146,9 @@ namespace Sapphire::Entity
|
||||||
/// Status effect functions
|
/// Status effect functions
|
||||||
void addStatusEffect( StatusEffect::StatusEffectPtr pEffect );
|
void addStatusEffect( StatusEffect::StatusEffectPtr pEffect );
|
||||||
|
|
||||||
void removeStatusEffect( uint8_t effectSlotId );
|
void removeStatusEffect( uint8_t effectSlotId, bool sendPacket );
|
||||||
|
|
||||||
void removeSingleStatusEffectById( uint32_t id );
|
void removeSingleStatusEffectById( uint32_t id, bool sendPacket );
|
||||||
|
|
||||||
void updateStatusEffects();
|
void updateStatusEffects();
|
||||||
|
|
||||||
|
|
|
@ -1100,8 +1100,6 @@ void Sapphire::Entity::Player::update( uint64_t tickCount )
|
||||||
if( !isAlive() )
|
if( !isAlive() )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
updateStatusEffects();
|
|
||||||
|
|
||||||
m_lastUpdate = tickCount;
|
m_lastUpdate = tickCount;
|
||||||
|
|
||||||
if( !checkAction() )
|
if( !checkAction() )
|
||||||
|
@ -1578,6 +1576,7 @@ void Sapphire::Entity::Player::autoAttack( CharaPtr pTarget )
|
||||||
auto variation = static_cast< uint32_t >( pRNGMgr->getRandGenerator< float >( 0, 3 ).next() );
|
auto variation = static_cast< uint32_t >( pRNGMgr->getRandGenerator< float >( 0, 3 ).next() );
|
||||||
|
|
||||||
auto damage = Math::CalcStats::calcAutoAttackDamage( *this );
|
auto damage = Math::CalcStats::calcAutoAttackDamage( *this );
|
||||||
|
damage.first = Math::CalcStats::applyDamageReceiveMultiplier( *pTarget, damage.first, -1 );
|
||||||
|
|
||||||
if( getClass() == ClassJob::Machinist || getClass() == ClassJob::Bard || getClass() == ClassJob::Archer )
|
if( getClass() == ClassJob::Machinist || getClass() == ClassJob::Bard || getClass() == ClassJob::Archer )
|
||||||
{
|
{
|
||||||
|
@ -2182,4 +2181,4 @@ bool Sapphire::Entity::Player::checkAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,10 @@
|
||||||
|
|
||||||
#include "Inventory/Item.h"
|
#include "Inventory/Item.h"
|
||||||
|
|
||||||
|
#include "StatusEffect/StatusEffect.h"
|
||||||
|
|
||||||
|
#include "Action/Action.h"
|
||||||
|
|
||||||
#include "CalcStats.h"
|
#include "CalcStats.h"
|
||||||
#include "Framework.h"
|
#include "Framework.h"
|
||||||
|
|
||||||
|
@ -190,7 +194,7 @@ float CalcStats::blockProbability( const Chara& chara )
|
||||||
return std::floor( ( 30 * blockRate ) / levelVal + 10 );
|
return std::floor( ( 30 * blockRate ) / levelVal + 10 );
|
||||||
}
|
}
|
||||||
|
|
||||||
float CalcStats::directHitProbability( const Chara& chara )
|
float CalcStats::directHitProbability( const Chara& chara, uint8_t filterType )
|
||||||
{
|
{
|
||||||
const auto& baseStats = chara.getStats();
|
const auto& baseStats = chara.getStats();
|
||||||
auto level = chara.getLevel();
|
auto level = chara.getLevel();
|
||||||
|
@ -200,10 +204,23 @@ float CalcStats::directHitProbability( const Chara& chara )
|
||||||
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
||||||
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
|
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
|
||||||
|
|
||||||
return std::floor( 550.f * ( dhRate - subVal ) / divVal ) / 10.f;
|
auto result = std::floor( 550.f * ( dhRate - subVal ) / divVal ) / 10.f;
|
||||||
|
|
||||||
|
for( auto const& entry : chara.getStatusEffectMap() )
|
||||||
|
{
|
||||||
|
auto status = entry.second;
|
||||||
|
auto effectEntry = status->getEffectEntry();
|
||||||
|
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeCritDHRateBonus )
|
||||||
|
continue;
|
||||||
|
if( effectEntry.effectValue1 & filterType )
|
||||||
|
{
|
||||||
|
result += effectEntry.effectValue3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
float CalcStats::criticalHitProbability( const Chara& chara )
|
float CalcStats::criticalHitProbability( const Chara& chara, uint8_t filterType )
|
||||||
{
|
{
|
||||||
const auto& baseStats = chara.getStats();
|
const auto& baseStats = chara.getStats();
|
||||||
auto level = chara.getLevel();
|
auto level = chara.getLevel();
|
||||||
|
@ -213,7 +230,20 @@ float CalcStats::criticalHitProbability( const Chara& chara )
|
||||||
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
|
||||||
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
|
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
|
||||||
|
|
||||||
return std::floor( 200.f * ( chRate - subVal ) / divVal + 50.f ) / 10.f;
|
auto result = std::floor( 200.f * ( chRate - subVal ) / divVal + 50.f ) / 10.f;
|
||||||
|
|
||||||
|
for( auto const& entry : chara.getStatusEffectMap() )
|
||||||
|
{
|
||||||
|
auto status = entry.second;
|
||||||
|
auto effectEntry = status->getEffectEntry();
|
||||||
|
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeCritDHRateBonus )
|
||||||
|
continue;
|
||||||
|
if( effectEntry.effectValue1 & filterType )
|
||||||
|
{
|
||||||
|
result += effectEntry.effectValue2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -328,6 +358,29 @@ float CalcStats::healingMagicPower( const Sapphire::Entity::Chara& chara )
|
||||||
return calcAttackPower( chara, chara.getStatValue( Common::BaseParam::HealingMagicPotency ) );
|
return calcAttackPower( chara, chara.getStatValue( Common::BaseParam::HealingMagicPotency ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float CalcStats::getWeaponDamage( Sapphire::Entity::Chara& chara )
|
||||||
|
{
|
||||||
|
auto wepDmg = chara.getLevel();
|
||||||
|
|
||||||
|
if( auto player = chara.getAsPlayer() )
|
||||||
|
{
|
||||||
|
auto item = player->getEquippedWeapon();
|
||||||
|
assert( item );
|
||||||
|
|
||||||
|
auto role = player->getRole();
|
||||||
|
if( role == Common::Role::RangedMagical || role == Common::Role::Healer )
|
||||||
|
{
|
||||||
|
wepDmg = item->getMagicalDmg();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wepDmg = item->getPhysicalDmg();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wepDmg;
|
||||||
|
}
|
||||||
|
|
||||||
float CalcStats::determination( const Sapphire::Entity::Chara& chara )
|
float CalcStats::determination( const Sapphire::Entity::Chara& chara )
|
||||||
{
|
{
|
||||||
auto level = chara.getLevel();
|
auto level = chara.getLevel();
|
||||||
|
@ -464,13 +517,13 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcAutoA
|
||||||
|
|
||||||
factor = std::floor( factor * speed( chara ) );
|
factor = std::floor( factor * speed( chara ) );
|
||||||
|
|
||||||
if( criticalHitProbability( chara ) > range100( rng ) )
|
if( criticalHitProbability( chara, Sapphire::World::Action::EffectCritDHBonusFilterDamage ) > range100( rng ) )
|
||||||
{
|
{
|
||||||
factor *= criticalHitBonus( chara );
|
factor *= criticalHitBonus( chara );
|
||||||
hitType = Sapphire::Common::ActionHitSeverityType::CritDamage;
|
hitType = Sapphire::Common::ActionHitSeverityType::CritDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( directHitProbability( chara ) > range100( rng ) )
|
if( directHitProbability( chara, Sapphire::World::Action::EffectCritDHBonusFilterDamage ) > range100( rng ) )
|
||||||
{
|
{
|
||||||
factor *= 1.25f;
|
factor *= 1.25f;
|
||||||
hitType = hitType == Sapphire::Common::ActionHitSeverityType::CritDamage ?
|
hitType = hitType == Sapphire::Common::ActionHitSeverityType::CritDamage ?
|
||||||
|
@ -480,7 +533,17 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcAutoA
|
||||||
|
|
||||||
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f );
|
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f );
|
||||||
|
|
||||||
// todo: buffs
|
for( auto const& entry : chara.getStatusEffectMap() )
|
||||||
|
{
|
||||||
|
auto status = entry.second;
|
||||||
|
auto effectEntry = status->getEffectEntry();
|
||||||
|
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeDamageMultiplier )
|
||||||
|
continue;
|
||||||
|
if( effectEntry.effectValue1 & Sapphire::World::Action::EffectActionTypeFilterPhysical )
|
||||||
|
{
|
||||||
|
factor *= 1.0f + ( effectEntry.effectValue2 / 100.0f );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constexpr auto format = "auto attack: pot: {} aa: {} ap: {} det: {} ten: {} = {}";
|
constexpr auto format = "auto attack: pot: {} aa: {} ap: {} det: {} ten: {} = {}";
|
||||||
|
|
||||||
|
@ -496,10 +559,10 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcAutoA
|
||||||
return std::pair( factor, hitType );
|
return std::pair( factor, hitType );
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActionDamage( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg )
|
float CalcStats::calcDamageBaseOnPotency( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg )
|
||||||
{
|
{
|
||||||
// D = ⌊ f(pot) × f(wd) × f(ap) × f(det) × f(tnc) × traits ⌋
|
// 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... ⌋
|
// × f(chr) ⌋ × f(dhr) ⌋ × rand[ 0.95, 1.05 ] ⌋ buff_1 ⌋ × buff_1 ⌋ × buff... ⌋
|
||||||
|
|
||||||
auto pot = potency( static_cast< uint16_t >( ptc ) );
|
auto pot = potency( static_cast< uint16_t >( ptc ) );
|
||||||
auto wd = weaponDamage( chara, wepDmg );
|
auto wd = weaponDamage( chara, wepDmg );
|
||||||
|
@ -511,15 +574,49 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActio
|
||||||
ten = tenacity( chara );
|
ten = tenacity( chara );
|
||||||
|
|
||||||
auto factor = std::floor( pot * wd * ap * det * ten );
|
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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
float CalcStats::calcHealBaseOnPotency( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg )
|
||||||
|
{
|
||||||
|
// reused damage formula just for testing
|
||||||
|
auto pot = potency( static_cast< uint16_t >( ptc ) );
|
||||||
|
auto wd = weaponDamage( chara, wepDmg );
|
||||||
|
auto ap = getPrimaryAttackPower( chara );
|
||||||
|
auto det = determination( chara );
|
||||||
|
|
||||||
|
auto factor = std::floor( pot * wd * ap * det );
|
||||||
|
|
||||||
|
constexpr auto format = "heal: pot: {} ({}) wd: {} ({}) ap: {} det: {} = {}";
|
||||||
|
|
||||||
|
if( auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer() )
|
||||||
|
{
|
||||||
|
player->sendDebug( format, pot, ptc, wd, wepDmg, ap, det, factor );
|
||||||
|
}
|
||||||
|
|
||||||
|
return factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActionDamage( const Sapphire::Entity::Chara& chara, const Sapphire::World::Action::Action& action, uint32_t ptc, float wepDmg )
|
||||||
|
{
|
||||||
|
auto factor =calcDamageBaseOnPotency( chara, ptc, wepDmg );
|
||||||
Sapphire::Common::ActionHitSeverityType hitType = Sapphire::Common::ActionHitSeverityType::NormalDamage;
|
Sapphire::Common::ActionHitSeverityType hitType = Sapphire::Common::ActionHitSeverityType::NormalDamage;
|
||||||
|
|
||||||
if( criticalHitProbability( chara ) > range100( rng ) )
|
if( criticalHitProbability( chara, Sapphire::World::Action::EffectCritDHBonusFilterDamage ) > range100( rng ) )
|
||||||
{
|
{
|
||||||
factor *= criticalHitBonus( chara );
|
factor *= criticalHitBonus( chara );
|
||||||
hitType = Sapphire::Common::ActionHitSeverityType::CritDamage;
|
hitType = Sapphire::Common::ActionHitSeverityType::CritDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( directHitProbability( chara ) > range100( rng ) )
|
if( directHitProbability( chara, Sapphire::World::Action::EffectCritDHBonusFilterDamage ) > range100( rng ) )
|
||||||
{
|
{
|
||||||
factor *= 1.25f;
|
factor *= 1.25f;
|
||||||
hitType = hitType == Sapphire::Common::ActionHitSeverityType::CritDamage ?
|
hitType = hitType == Sapphire::Common::ActionHitSeverityType::CritDamage ?
|
||||||
|
@ -529,29 +626,65 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActio
|
||||||
|
|
||||||
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f );
|
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f );
|
||||||
|
|
||||||
// todo: buffs
|
for( auto const& entry : chara.getStatusEffectMap() )
|
||||||
|
|
||||||
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 );
|
auto status = entry.second;
|
||||||
}
|
auto effectEntry = status->getEffectEntry();
|
||||||
else
|
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeDamageMultiplier )
|
||||||
{
|
continue;
|
||||||
Logger::debug( format, pot, ptc, wd, wepDmg, ap, det, ten, factor );
|
uint8_t actionType = action.isPhysical() ? Sapphire::World::Action::EffectActionTypeFilterPhysical :
|
||||||
|
( action.isMagical() ? Sapphire::World::Action::EffectActionTypeFilterMagical : 0 );
|
||||||
|
if( effectEntry.effectValue1 & actionType )
|
||||||
|
{
|
||||||
|
factor *= 1.0f + ( effectEntry.effectValue2 / 100.0f );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::pair( factor, hitType );
|
return std::pair( factor, hitType );
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActionHealing( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg )
|
float CalcStats::applyDamageReceiveMultiplier( const Sapphire::Entity::Chara& chara, float originalDamage, int8_t attackType )
|
||||||
{
|
{
|
||||||
// lol just for testing
|
float damage = originalDamage;
|
||||||
auto factor = std::floor( ptc * ( wepDmg / 10.0f ) + ptc );
|
for( auto const& entry : chara.getStatusEffectMap() )
|
||||||
|
{
|
||||||
|
auto status = entry.second;
|
||||||
|
auto effectEntry = status->getEffectEntry();
|
||||||
|
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeDamageReceiveMultiplier )
|
||||||
|
continue;
|
||||||
|
uint8_t actionType = 0;
|
||||||
|
if( World::Action::Action::isAttackTypePhysical( attackType ) )
|
||||||
|
actionType = Sapphire::World::Action::EffectActionTypeFilterPhysical;
|
||||||
|
else if( World::Action::Action::isAttackTypeMagical( attackType ) )
|
||||||
|
actionType = Sapphire::World::Action::EffectActionTypeFilterMagical;
|
||||||
|
if( effectEntry.effectValue1 & actionType )
|
||||||
|
{
|
||||||
|
damage *= ( 1.0f + ( effectEntry.effectValue2 / 100.0f ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return damage;
|
||||||
|
}
|
||||||
|
|
||||||
|
float CalcStats::applyHealingReceiveMultiplier( const Sapphire::Entity::Chara& chara, float originalHeal, int8_t healType )
|
||||||
|
{
|
||||||
|
float heal = originalHeal;
|
||||||
|
for( auto const& entry : chara.getStatusEffectMap() )
|
||||||
|
{
|
||||||
|
auto status = entry.second;
|
||||||
|
auto effectEntry = status->getEffectEntry();
|
||||||
|
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeHealReceiveMultiplier )
|
||||||
|
continue;
|
||||||
|
heal *= ( 1.0f + ( effectEntry.effectValue2 / 100.0f ) );
|
||||||
|
}
|
||||||
|
return heal;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActionHealing( const Sapphire::Entity::Chara& chara, const Sapphire::World::Action::Action& action, uint32_t ptc, float wepDmg )
|
||||||
|
{
|
||||||
|
auto factor = calcHealBaseOnPotency( chara, ptc, wepDmg );
|
||||||
Sapphire::Common::ActionHitSeverityType hitType = Sapphire::Common::ActionHitSeverityType::NormalHeal;
|
Sapphire::Common::ActionHitSeverityType hitType = Sapphire::Common::ActionHitSeverityType::NormalHeal;
|
||||||
|
|
||||||
if( criticalHitProbability( chara ) > range100( rng ) )
|
if( criticalHitProbability( chara, Sapphire::World::Action::EffectCritDHBonusFilterHeal ) > range100( rng ) )
|
||||||
{
|
{
|
||||||
factor *= criticalHitBonus( chara );
|
factor *= criticalHitBonus( chara );
|
||||||
hitType = Sapphire::Common::ActionHitSeverityType::CritHeal;
|
hitType = Sapphire::Common::ActionHitSeverityType::CritHeal;
|
||||||
|
@ -559,6 +692,19 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActio
|
||||||
|
|
||||||
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f );
|
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f );
|
||||||
|
|
||||||
|
for( auto const& entry : chara.getStatusEffectMap() )
|
||||||
|
{
|
||||||
|
auto status = entry.second;
|
||||||
|
auto effectEntry = status->getEffectEntry();
|
||||||
|
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeHealCastMultiplier )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if( static_cast< Common::ActionCategory >( action.getActionData()->actionCategory ) == Common::ActionCategory::Spell ) // must be a "cast"
|
||||||
|
{
|
||||||
|
factor *= 1.0f + ( effectEntry.effectValue2 / 100.0f );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return std::pair( factor, hitType );
|
return std::pair( factor, hitType );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,12 +26,12 @@ namespace Sapphire::Math
|
||||||
/*!
|
/*!
|
||||||
* @brief Calculates the probability of a direct hit happening
|
* @brief Calculates the probability of a direct hit happening
|
||||||
*/
|
*/
|
||||||
static float directHitProbability( const Sapphire::Entity::Chara& chara );
|
static float directHitProbability( const Sapphire::Entity::Chara& chara, uint8_t filterType );
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Calculates the probability of a critical hit happening
|
* @brief Calculates the probability of a critical hit happening
|
||||||
*/
|
*/
|
||||||
static float criticalHitProbability( const Sapphire::Entity::Chara& chara );
|
static float criticalHitProbability( const Sapphire::Entity::Chara& chara, uint8_t filterType );
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Calculates the contribution of potency to damage output.
|
* @brief Calculates the contribution of potency to damage output.
|
||||||
|
@ -64,6 +64,8 @@ namespace Sapphire::Math
|
||||||
|
|
||||||
static float healingMagicPower( const Sapphire::Entity::Chara& chara );
|
static float healingMagicPower( const Sapphire::Entity::Chara& chara );
|
||||||
|
|
||||||
|
static float getWeaponDamage( Sapphire::Entity::Chara& chara );
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Calculates determinations contribution to damage and healing output.
|
* @brief Calculates determinations contribution to damage and healing output.
|
||||||
*
|
*
|
||||||
|
@ -129,13 +131,25 @@ namespace Sapphire::Math
|
||||||
|
|
||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
|
|
||||||
|
static float calcDamageBaseOnPotency( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg );
|
||||||
|
|
||||||
|
static float calcHealBaseOnPotency( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg );
|
||||||
|
|
||||||
static std::pair< float, Common::ActionHitSeverityType > calcAutoAttackDamage( const Sapphire::Entity::Chara& chara );
|
static std::pair< float, Common::ActionHitSeverityType > calcAutoAttackDamage( const Sapphire::Entity::Chara& chara );
|
||||||
|
|
||||||
static std::pair< float, Common::ActionHitSeverityType > calcActionDamage( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg );
|
static std::pair< float, Common::ActionHitSeverityType > calcActionDamage( const Sapphire::Entity::Chara& chara, const Sapphire::World::Action::Action& action, uint32_t ptc, float wepDmg );
|
||||||
|
|
||||||
static std::pair< float, Common::ActionHitSeverityType > calcActionHealing( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg );
|
static float applyDamageReceiveMultiplier( const Sapphire::Entity::Chara& chara, float originalDamage, int8_t attackType );
|
||||||
|
|
||||||
|
static float applyHealingReceiveMultiplier( const Sapphire::Entity::Chara& chara, float originalHeal, int8_t healType );
|
||||||
|
|
||||||
|
static std::pair< float, Common::ActionHitSeverityType > calcActionHealing( const Sapphire::Entity::Chara& chara, const Sapphire::World::Action::Action& action, uint32_t ptc, float wepDmg );
|
||||||
|
|
||||||
static uint32_t primaryStatValue( const Sapphire::Entity::Chara& chara );
|
static uint32_t primaryStatValue( const Sapphire::Entity::Chara& chara );
|
||||||
|
|
||||||
|
static std::random_device dev;
|
||||||
|
static std::mt19937 rng;
|
||||||
|
static std::uniform_int_distribution< std::mt19937::result_type > range100;
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -144,10 +158,6 @@ namespace Sapphire::Math
|
||||||
* @param attackPower The magic/physical attack power value.
|
* @param attackPower The magic/physical attack power value.
|
||||||
*/
|
*/
|
||||||
static float calcAttackPower( const Sapphire::Entity::Chara& chara, uint32_t attackPower );
|
static float calcAttackPower( const Sapphire::Entity::Chara& chara, uint32_t attackPower );
|
||||||
|
|
||||||
static std::random_device dev;
|
|
||||||
static std::mt19937 rng;
|
|
||||||
static std::uniform_int_distribution< std::mt19937::result_type > range100;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ void Sapphire::Network::GameConnection::clientTriggerHandler( FrameworkPtr pFw,
|
||||||
case ClientTriggerType::RemoveStatusEffect: // Remove status (clicking it off)
|
case ClientTriggerType::RemoveStatusEffect: // Remove status (clicking it off)
|
||||||
{
|
{
|
||||||
// todo: check if status can be removed by client from exd
|
// todo: check if status can be removed by client from exd
|
||||||
player.removeSingleStatusEffectById( static_cast< uint32_t >( param1 ) );
|
player.removeSingleStatusEffectById( static_cast< uint32_t >( param1 ), true );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ClientTriggerType::CastCancel: // Cancel cast
|
case ClientTriggerType::CastCancel: // Cancel cast
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
#include "Script/ScriptMgr.h"
|
#include "Script/ScriptMgr.h"
|
||||||
|
|
||||||
|
#include "Math/CalcStats.h"
|
||||||
|
|
||||||
#include "StatusEffect.h"
|
#include "StatusEffect.h"
|
||||||
#include "Framework.h"
|
#include "Framework.h"
|
||||||
|
|
||||||
|
@ -26,7 +28,10 @@ Sapphire::StatusEffect::StatusEffect::StatusEffect( uint32_t id, Entity::CharaPt
|
||||||
m_startTime( 0 ),
|
m_startTime( 0 ),
|
||||||
m_tickRate( tickRate ),
|
m_tickRate( tickRate ),
|
||||||
m_lastTick( 0 ),
|
m_lastTick( 0 ),
|
||||||
m_pFw( pFw )
|
m_pFw( pFw ),
|
||||||
|
m_cachedHotOrDotValue( 0 ),
|
||||||
|
m_cachedSourceCrit( 0 ),
|
||||||
|
m_cachedSourceCritBonus( 0 )
|
||||||
{
|
{
|
||||||
auto pExdData = m_pFw->get< Data::ExdDataGenerated >();
|
auto pExdData = m_pFw->get< Data::ExdDataGenerated >();
|
||||||
auto entry = pExdData->get< Sapphire::Data::Status >( id );
|
auto entry = pExdData->get< Sapphire::Data::Status >( id );
|
||||||
|
@ -41,6 +46,11 @@ Sapphire::StatusEffect::StatusEffect::StatusEffect( uint32_t id, Entity::CharaPt
|
||||||
Util::eraseAll( m_name, '-' );
|
Util::eraseAll( m_name, '-' );
|
||||||
Util::eraseAll( m_name, '(' );
|
Util::eraseAll( m_name, '(' );
|
||||||
Util::eraseAll( m_name, ')' );
|
Util::eraseAll( m_name, ')' );
|
||||||
|
|
||||||
|
if( Sapphire::World::Action::ActionLut::validStatusEffectExists( id ) )
|
||||||
|
m_effectEntry = Sapphire::World::Action::ActionLut::getStatusEffectEntry( id );
|
||||||
|
else
|
||||||
|
m_effectEntry.effectType = Sapphire::World::Action::EffectTypeInvalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,7 +66,30 @@ void Sapphire::StatusEffect::StatusEffect::registerTickEffect( uint8_t type, uin
|
||||||
std::pair< uint8_t, uint32_t > Sapphire::StatusEffect::StatusEffect::getTickEffect()
|
std::pair< uint8_t, uint32_t > Sapphire::StatusEffect::StatusEffect::getTickEffect()
|
||||||
{
|
{
|
||||||
auto thisTick = m_currTickEffect;
|
auto thisTick = m_currTickEffect;
|
||||||
m_currTickEffect = std::make_pair( 0, 0 );
|
if( m_effectEntry.effectType == Sapphire::World::Action::EffectTypeDot )
|
||||||
|
{
|
||||||
|
auto value = m_cachedHotOrDotValue;
|
||||||
|
if( m_cachedSourceCrit > Sapphire::Math::CalcStats::range100( Sapphire::Math::CalcStats::rng ) )
|
||||||
|
{
|
||||||
|
value *= m_cachedSourceCritBonus;
|
||||||
|
}
|
||||||
|
value *= 1.0f + ( ( Sapphire::Math::CalcStats::range100( Sapphire::Math::CalcStats::rng ) - 50.0f ) / 1000.0f );
|
||||||
|
m_currTickEffect = std::make_pair( 1, value );
|
||||||
|
}
|
||||||
|
else if( m_effectEntry.effectType == Sapphire::World::Action::EffectTypeHot )
|
||||||
|
{
|
||||||
|
auto value = m_cachedHotOrDotValue;
|
||||||
|
if( m_cachedSourceCrit > Sapphire::Math::CalcStats::range100( Sapphire::Math::CalcStats::rng ) )
|
||||||
|
{
|
||||||
|
value *= m_cachedSourceCritBonus;
|
||||||
|
}
|
||||||
|
value *= 1.0f + ( ( Sapphire::Math::CalcStats::range100( Sapphire::Math::CalcStats::rng ) - 50.0f ) / 1000.0f );
|
||||||
|
m_currTickEffect = std::make_pair( 2, value );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_currTickEffect = std::make_pair( 0, 0 );
|
||||||
|
}
|
||||||
return thisTick;
|
return thisTick;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,23 +120,49 @@ void Sapphire::StatusEffect::StatusEffect::applyStatus()
|
||||||
m_startTime = Util::getTimeMs();
|
m_startTime = Util::getTimeMs();
|
||||||
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
|
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
|
||||||
|
|
||||||
// this is only right when an action is being used by the player
|
if( m_effectEntry.effectType == Sapphire::World::Action::EffectTypeDot )
|
||||||
// else you probably need to use an actorcontrol
|
{
|
||||||
|
auto wepDmg = Sapphire::Math::CalcStats::getWeaponDamage( *m_sourceActor );
|
||||||
|
auto damage = Sapphire::Math::CalcStats::calcDamageBaseOnPotency( *m_sourceActor, m_effectEntry.effectValue2, wepDmg );
|
||||||
|
|
||||||
//GamePacketNew< FFXIVIpcEffect > effectPacket( m_sourceActor->getId() );
|
for( auto const& entry : m_sourceActor->getStatusEffectMap() )
|
||||||
//effectPacket.data().targetId = m_sourceActor->getId();
|
{
|
||||||
//effectPacket.data().actionAnimationId = 3;
|
auto status = entry.second;
|
||||||
//effectPacket.data().unknown_3 = 1;
|
auto effectEntry = status->getEffectEntry();
|
||||||
//effectPacket.data().actionTextId = 3;
|
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeDamageMultiplier )
|
||||||
//effectPacket.data().unknown_5 = 1;
|
continue;
|
||||||
//effectPacket.data().unknown_6 = 321;
|
if( effectEntry.effectValue1 & m_effectEntry.effectValue1 )
|
||||||
//effectPacket.data().rotation = ( uint16_t ) ( 0x8000 * ( ( m_sourceActor->getPos().getR() + 3.1415926 ) ) / 3.1415926 );
|
{
|
||||||
//effectPacket.data().effectTargetId = m_sourceActor->getId();
|
damage *= 1.0f + ( effectEntry.effectValue2 / 100.0f );
|
||||||
//effectPacket.data().effects[4].unknown_1 = 17;
|
}
|
||||||
//effectPacket.data().effects[4].bonusPercent = 30;
|
}
|
||||||
//effectPacket.data().effects[4].param1 = m_id;
|
|
||||||
//effectPacket.data().effects[4].unknown_5 = 0x80;
|
m_cachedHotOrDotValue = Sapphire::Math::CalcStats::applyDamageReceiveMultiplier( *m_targetActor, damage,
|
||||||
//m_sourceActor->sendToInRangeSet( effectPacket, true );
|
m_effectEntry.effectValue1 == Sapphire::World::Action::EffectActionTypeFilterPhysical ? -1 :
|
||||||
|
( m_effectEntry.effectValue1 == Sapphire::World::Action::EffectActionTypeFilterMagical ? 5 : -128 ) );
|
||||||
|
m_cachedSourceCrit = Sapphire::Math::CalcStats::criticalHitProbability( *m_sourceActor, Sapphire::World::Action::EffectCritDHBonusFilterDamage );
|
||||||
|
m_cachedSourceCritBonus = Sapphire::Math::CalcStats::criticalHitBonus( *m_sourceActor );
|
||||||
|
}
|
||||||
|
else if( m_effectEntry.effectType == Sapphire::World::Action::EffectTypeHot )
|
||||||
|
{
|
||||||
|
auto wepDmg = Sapphire::Math::CalcStats::getWeaponDamage( *m_sourceActor );
|
||||||
|
auto heal = Sapphire::Math::CalcStats::calcHealBaseOnPotency( *m_sourceActor, m_effectEntry.effectValue2, wepDmg );
|
||||||
|
|
||||||
|
if( m_effectEntry.effectValue1 == 0 ) // this value is always 0 atm, if statement here just in case there is a hot that isn't a "cast"
|
||||||
|
{
|
||||||
|
for( auto const& entry : m_sourceActor->getStatusEffectMap() )
|
||||||
|
{
|
||||||
|
auto status = entry.second;
|
||||||
|
auto effectEntry = status->getEffectEntry();
|
||||||
|
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeHealCastMultiplier )
|
||||||
|
continue;
|
||||||
|
heal *= 1.0f + ( effectEntry.effectValue2 / 100.0f );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_cachedHotOrDotValue = Sapphire::Math::CalcStats::applyHealingReceiveMultiplier( *m_targetActor, heal, m_effectEntry.effectValue1 );
|
||||||
|
m_cachedSourceCrit = Sapphire::Math::CalcStats::criticalHitProbability( *m_sourceActor, Sapphire::World::Action::EffectCritDHBonusFilterHeal );
|
||||||
|
m_cachedSourceCritBonus = Sapphire::Math::CalcStats::criticalHitBonus( *m_sourceActor );
|
||||||
|
}
|
||||||
|
|
||||||
pScriptMgr->onStatusReceive( m_targetActor, m_id );
|
pScriptMgr->onStatusReceive( m_targetActor, m_id );
|
||||||
}
|
}
|
||||||
|
@ -153,3 +212,8 @@ const std::string& Sapphire::StatusEffect::StatusEffect::getName() const
|
||||||
{
|
{
|
||||||
return m_name;
|
return m_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Sapphire::World::Action::StatusEffectEntry& Sapphire::StatusEffect::StatusEffect::getEffectEntry() const
|
||||||
|
{
|
||||||
|
return m_effectEntry;
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include "Forwards.h"
|
#include "Forwards.h"
|
||||||
|
|
||||||
|
#include "Action/ActionLut.h"
|
||||||
|
|
||||||
namespace Sapphire {
|
namespace Sapphire {
|
||||||
namespace StatusEffect {
|
namespace StatusEffect {
|
||||||
|
|
||||||
|
@ -47,6 +49,8 @@ public:
|
||||||
|
|
||||||
const std::string& getName() const;
|
const std::string& getName() const;
|
||||||
|
|
||||||
|
const Sapphire::World::Action::StatusEffectEntry& getEffectEntry() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t m_id;
|
uint32_t m_id;
|
||||||
Entity::CharaPtr m_sourceActor;
|
Entity::CharaPtr m_sourceActor;
|
||||||
|
@ -59,7 +63,10 @@ private:
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
std::pair< uint8_t, uint32_t > m_currTickEffect;
|
std::pair< uint8_t, uint32_t > m_currTickEffect;
|
||||||
FrameworkPtr m_pFw;
|
FrameworkPtr m_pFw;
|
||||||
|
Sapphire::World::Action::StatusEffectEntry m_effectEntry;
|
||||||
|
uint32_t m_cachedHotOrDotValue;
|
||||||
|
float m_cachedSourceCrit;
|
||||||
|
float m_cachedSourceCritBonus;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue