1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-05-25 19:17:45 +00:00

Generic action handler update with status support.

This commit is contained in:
collett 2020-01-10 21:24:35 +09:00
parent 38392be3bf
commit 896ce73175
20 changed files with 3088 additions and 1711 deletions

View file

@ -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

View file

@ -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 );

View file

@ -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,22 +491,33 @@ 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();
@ -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,16 +756,16 @@ 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;
}

View file

@ -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).
*/ */

View file

@ -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 )
@ -25,3 +25,24 @@ const ActionEntry& ActionLut::getEntry( uint16_t actionId )
return it->second; 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;
}

View file

@ -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

View file

@ -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 );
} }

View file

@ -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();

View file

@ -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;
} }

View file

@ -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;
}; };
} }

View file

@ -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{};

View file

@ -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

View file

@ -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();

View file

@ -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 )
{ {

View file

@ -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,7 +559,7 @@ 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... ⌋
@ -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 );
} }

View file

@ -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;
}; };
} }

View file

@ -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

View file

@ -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;
}

View file

@ -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;
}; };
} }