1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-05-05 02:07:46 +00:00

Merge pull request #911 from collett8192/Sapphire5.58_with_action_pr

Sapphire 5.58 now with actions.
This commit is contained in:
Mordred 2023-03-06 22:58:39 +01:00 committed by GitHub
commit aca46ac8ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
72 changed files with 7853 additions and 2017 deletions

View file

@ -1103,6 +1103,73 @@ namespace Sapphire::Common
Gatherer Gatherer
}; };
enum class StatusEffectType : uint32_t
{
Invalid = 0,
DamageMultiplier = 1,
DamageReceiveMultiplier = 2,
Hot = 3,
Dot = 4,
HealReceiveMultiplier = 5,
HealCastMultiplier = 6,
CritDHRateBonus = 7,
DamageReceiveTrigger = 8,
DamageDealtTrigger = 9,
Shield = 10,
MPRestore = 11,
Haste = 12,
InstantCast = 13,
BlockParryRateBonus = 14,
MPRestorePerGCD = 15,
AlwaysCombo = 16,
PotencyMultiplier = 17,
};
enum class ActionTypeFilter : int32_t
{
Unknown = 0,
Physical = 1,
Magical = 2,
Slashing = 4,
Piercing = 8,
Blunt = 16,
All = 255
};
enum class CritDHBonusFilter : int32_t
{
None = 0,
Damage = 1,
Heal = 2,
All = 255,
};
enum class StatusEffectTriggerResult : int32_t
{
None = 0,
ReflectDamage = 1,
AbsorbHP = 2,
};
enum ActionBonusEffect : uint8_t
{
NoBonus = 0,
CritBonus = 1,
DHBonus = 2,
GainMPPercentage = 4,
GainJobResource = 8,
SelfHeal = 16,
DamageFallOff = 32,
GainJobTimer = 64,
};
enum ActionBonusEffectRequirement : uint8_t
{
NoRequirement = 0,
RequireCorrectCombo = 1,
RequireCorrectPositional = 2,
};
enum class AstCardType : uint8_t enum class AstCardType : uint8_t
{ {
None = 0, None = 0,
@ -1224,7 +1291,7 @@ namespace Sapphire::Common
{ {
uint8_t ammo; uint8_t ammo;
uint8_t unused; uint8_t unused;
uint16_t maxTimerDuration; uint16_t maxTimerDuration; // what is this?
uint8_t ammoComboStep; uint8_t ammoComboStep;
} gnb; } gnb;
struct struct

View file

@ -271,7 +271,7 @@ namespace Sapphire::Network::Packets
////////////////////////////////////////////////// //////////////////////////////////////////////////
DuelChallenge = 0x0277, // 4.2; this is responsible for opening the ui DuelChallenge = 0xF277, // 4.2; this is responsible for opening the ui
PerformNote = 0x0127, // updated 5.58 hotfix PerformNote = 0x0127, // updated 5.58 hotfix
PrepareZoning = 0x02AB, // updated 5.58 hotfix PrepareZoning = 0x02AB, // updated 5.58 hotfix

View file

@ -0,0 +1,33 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_STRAIGHT_SHOT_READY = 122;
class ActionHeavyShot97 :
public ScriptAPI::ActionScript
{
public:
ActionHeavyShot97() :
ScriptAPI::ActionScript( 97 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 && action.getSourceChara()->getLevel() >= 2 && Math::CalcStats::getRandomNumber0To100() < 20 )
{
auto pEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_STRAIGHT_SHOT_READY, action.getSourceChara(), action.getSourceChara(), 10000, 3000 );
action.getEffectbuilder()->applyStatusEffect( action.getSourceChara(), action.getSourceChara(), pEffect );
}
}
};
EXPOSE_SCRIPT( ActionHeavyShot97 );

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

@ -0,0 +1,27 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionBloodOfTheDragon3553 :
public ScriptAPI::ActionScript
{
public:
ActionBloodOfTheDragon3553() :
ScriptAPI::ActionScript( 3553 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
pPlayer->gaugeDrgSetDragonTimer( 30000, true );
pPlayer->gaugeDrgSetDragonState( Common::DrgState::BloodOfTheDragon );
}
};
EXPOSE_SCRIPT( ActionBloodOfTheDragon3553 );

View file

@ -0,0 +1,36 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_SHARPER_FANG_AND_CLAW = 802;
const uint16_t STATUS_ID_ENHANCED_WHEELING_THRUST = 803;
class ActionChaosThrust88 :
public ScriptAPI::ActionScript
{
public:
ActionChaosThrust88() :
ScriptAPI::ActionScript( 88 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
if( pPlayer->gaugeDrgGetDragonState( Common::DrgState::BloodOfTheDragon ) || pPlayer->gaugeDrgGetDragonState( Common::DrgState::LifeOfTheDragon ) )
{
auto pEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_ENHANCED_WHEELING_THRUST, action.getSourceChara(), action.getSourceChara(), 10000, 3000 );
action.getEffectbuilder()->applyStatusEffect( action.getSourceChara(), action.getSourceChara(), pEffect );
}
}
};
EXPOSE_SCRIPT( ActionChaosThrust88 );

View file

@ -0,0 +1,34 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionCoerthanTorment16477 :
public ScriptAPI::ActionScript
{
public:
ActionCoerthanTorment16477() :
ScriptAPI::ActionScript( 16477 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
if( pPlayer->getLastComboActionId() == 7397 )
{
if( pPlayer->gaugeDrgGetDragonState( Common::DrgState::BloodOfTheDragon ) )
{
uint16_t dragonTimer = pPlayer->gaugeDrgGetDragonTimer();
dragonTimer = std::min( 30000, dragonTimer + 10000 );
pPlayer->gaugeDrgSetDragonTimer( dragonTimer, true );
}
}
}
};
EXPOSE_SCRIPT( ActionCoerthanTorment16477 );

View file

@ -0,0 +1,57 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_SHARPER_FANG_AND_CLAW = 802;
const uint16_t STATUS_ID_ENHANCED_WHEELING_THRUST = 803;
const uint16_t STATUS_ID_RAIDEN_THRUST_READY = 1863;
class ActionFangAndClaw3554 :
public ScriptAPI::ActionScript
{
public:
ActionFangAndClaw3554() :
ScriptAPI::ActionScript( 3554 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto effectEntry = action.getSourceChara()->getStatusEffectById( STATUS_ID_SHARPER_FANG_AND_CLAW );
if( effectEntry.second )
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
action.getSourceChara()->removeStatusEffect( effectEntry.first );
if( pPlayer->gaugeDrgGetDragonState( Common::DrgState::BloodOfTheDragon ) )
{
uint16_t dragonTimer = pPlayer->gaugeDrgGetDragonTimer();
dragonTimer = std::min( 30000, dragonTimer + 10000 );
pPlayer->gaugeDrgSetDragonTimer( dragonTimer, true );
}
if( pPlayer->getLastComboActionId() == 84 )
{
auto pEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_ENHANCED_WHEELING_THRUST, action.getSourceChara(), action.getSourceChara(), 10000, 3000 );
action.getEffectbuilder()->applyStatusEffect( action.getSourceChara(), action.getSourceChara(), pEffect );
}
if( pPlayer->getLastComboActionId() == 3556 )
{
auto pEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_RAIDEN_THRUST_READY, action.getSourceChara(), action.getSourceChara(), 10000, 3000 );
action.getEffectbuilder()->applyStatusEffect( action.getSourceChara(), action.getSourceChara(), pEffect );
}
}
else
{
action.disableGenericHandler();
action.interrupt();
}
}
};
EXPOSE_SCRIPT( ActionFangAndClaw3554 );

View file

@ -0,0 +1,35 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_SHARPER_FANG_AND_CLAW = 802;
class ActionFullThrust84 :
public ScriptAPI::ActionScript
{
public:
ActionFullThrust84() :
ScriptAPI::ActionScript( 84 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
if( pPlayer->gaugeDrgGetDragonState( Common::DrgState::BloodOfTheDragon ) || pPlayer->gaugeDrgGetDragonState( Common::DrgState::LifeOfTheDragon ) )
{
auto pEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_SHARPER_FANG_AND_CLAW, action.getSourceChara(), action.getSourceChara(), 10000, 3000 );
action.getEffectbuilder()->applyStatusEffect( action.getSourceChara(), action.getSourceChara(), pEffect );
}
}
};
EXPOSE_SCRIPT( ActionFullThrust84 );

View file

@ -0,0 +1,42 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
class ActionGeirskogul3555 :
public ScriptAPI::ActionScript
{
public:
ActionGeirskogul3555() :
ScriptAPI::ActionScript( 3555 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
if( pPlayer->gaugeDrgGetDragonState( Common::DrgState::BloodOfTheDragon ) )
{
if( pPlayer->gaugeDrgGetEyes() == 2 )
{
pPlayer->gaugeDrgSetDragonState( Common::DrgState::LifeOfTheDragon );
pPlayer->gaugeDrgSetDragonTimer( 30000, true );
pPlayer->setVisualEffect( 34, true );
pPlayer->gaugeDrgSetEyes( 0 );
}
}
else
{
action.disableGenericHandler();
action.interrupt();
}
}
};
EXPOSE_SCRIPT( ActionGeirskogul3555 );

View file

@ -0,0 +1,35 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_DIVE_READY = 1243;
class ActionHighJump16478 :
public ScriptAPI::ActionScript
{
public:
ActionHighJump16478() :
ScriptAPI::ActionScript( 16478 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
if( pPlayer->gaugeDrgGetDragonState( Common::DrgState::BloodOfTheDragon ) || pPlayer->gaugeDrgGetDragonState( Common::DrgState::LifeOfTheDragon ) )
{
auto pEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_DIVE_READY, action.getSourceChara(), action.getSourceChara(), 15000, 3000 );
action.getEffectbuilder()->applyStatusEffect( action.getSourceChara(), action.getSourceChara(), pEffect );
}
}
};
EXPOSE_SCRIPT( ActionHighJump16478 );

View file

@ -0,0 +1,45 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_DIVE_READY = 1243;
class ActionMirageDive7399 :
public ScriptAPI::ActionScript
{
public:
ActionMirageDive7399() :
ScriptAPI::ActionScript( 7399 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto effectEntry = action.getSourceChara()->getStatusEffectById( STATUS_ID_DIVE_READY );
if( effectEntry.second )
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
action.getSourceChara()->removeStatusEffect( effectEntry.first );
if( pPlayer->gaugeDrgGetDragonState( Common::DrgState::BloodOfTheDragon ) || pPlayer->gaugeDrgGetDragonState( Common::DrgState::LifeOfTheDragon ) )
{
uint8_t eyes = pPlayer->gaugeDrgGetEyes();
eyes = std::min( 2, eyes + 1 );
pPlayer->gaugeDrgSetEyes( eyes );
}
}
else
{
action.disableGenericHandler();
action.interrupt();
}
}
};
EXPOSE_SCRIPT( ActionMirageDive7399 );

View file

@ -0,0 +1,40 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_RAIDEN_THRUST_READY = 1863;
class ActionRaidenThrust16479 :
public ScriptAPI::ActionScript
{
public:
ActionRaidenThrust16479() :
ScriptAPI::ActionScript( 16479 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto effectEntry = action.getSourceChara()->getStatusEffectById( STATUS_ID_RAIDEN_THRUST_READY );
if( effectEntry.second )
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
action.getSourceChara()->removeStatusEffect( effectEntry.first );
}
else
{
action.disableGenericHandler();
action.interrupt();
}
}
};
EXPOSE_SCRIPT( ActionRaidenThrust16479 );

View file

@ -0,0 +1,34 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionSonicThrust7397 :
public ScriptAPI::ActionScript
{
public:
ActionSonicThrust7397() :
ScriptAPI::ActionScript( 7397 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
if( pPlayer->getLastComboActionId() == 86 )
{
if( pPlayer->gaugeDrgGetDragonState( Common::DrgState::BloodOfTheDragon ) )
{
uint16_t dragonTimer = pPlayer->gaugeDrgGetDragonTimer();
dragonTimer = std::min( 30000, dragonTimer + 10000 );
pPlayer->gaugeDrgSetDragonTimer( dragonTimer, true );
}
}
}
};
EXPOSE_SCRIPT( ActionSonicThrust7397 );

View file

@ -0,0 +1,57 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_SHARPER_FANG_AND_CLAW = 802;
const uint16_t STATUS_ID_ENHANCED_WHEELING_THRUST = 803;
const uint16_t STATUS_ID_RAIDEN_THRUST_READY = 1863;
class ActionWheelingThrust3556 :
public ScriptAPI::ActionScript
{
public:
ActionWheelingThrust3556() :
ScriptAPI::ActionScript( 3556 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto effectEntry = action.getSourceChara()->getStatusEffectById( STATUS_ID_ENHANCED_WHEELING_THRUST );
if( effectEntry.second )
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
action.getSourceChara()->removeStatusEffect( effectEntry.first );
if( pPlayer->gaugeDrgGetDragonState( Common::DrgState::BloodOfTheDragon ) )
{
uint16_t dragonTimer = pPlayer->gaugeDrgGetDragonTimer();
dragonTimer = std::min( 30000, dragonTimer + 10000 );
pPlayer->gaugeDrgSetDragonTimer( dragonTimer, true );
}
if( pPlayer->getLastComboActionId() == 88 )
{
auto pEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_SHARPER_FANG_AND_CLAW, action.getSourceChara(), action.getSourceChara(), 10000, 3000 );
action.getEffectbuilder()->applyStatusEffect( action.getSourceChara(), action.getSourceChara(), pEffect );
}
if( pPlayer->getLastComboActionId() == 3554 )
{
auto pEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_RAIDEN_THRUST_READY, action.getSourceChara(), action.getSourceChara(), 10000, 3000 );
action.getEffectbuilder()->applyStatusEffect( action.getSourceChara(), action.getSourceChara(), pEffect );
}
}
else
{
action.disableGenericHandler();
action.interrupt();
}
}
};
EXPOSE_SCRIPT( ActionWheelingThrust3556 );

View file

@ -0,0 +1,39 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_THE_BLACKEST_NIGHT = 1178;
class ActionTheBlackestNight7393 :
public ScriptAPI::ActionScript
{
public:
ActionTheBlackestNight7393() :
ScriptAPI::ActionScript( 7393 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pTarget = action.getHitChara();
World::Action::StatusEffectEntry effectEntry;
effectEntry.init( Common::StatusEffectType::Shield, static_cast< int32_t >( ( 1.0 * pTarget->getMaxHp() ) * 0.25 ), 0, 0, 0 );
auto pNewEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_THE_BLACKEST_NIGHT, action.getSourceChara(), pTarget, 7000, 3000 );
pNewEffect->replaceEffectEntry( effectEntry );
action.getEffectbuilder()->applyStatusEffect( pTarget, action.getSourceChara(), pNewEffect );
}
void onStart( Sapphire::World::Action::Action& action ) override
{
action.disableGenericHandler();
}
};
EXPOSE_SCRIPT( ActionTheBlackestNight7393 );

View file

@ -0,0 +1,32 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionBloodfest16164 :
public ScriptAPI::ActionScript
{
public:
ActionBloodfest16164() :
ScriptAPI::ActionScript( 16164 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto chara = action.getHitChara();
if( chara )
{
auto source = action.getSourceChara();
auto player = source->getAsPlayer();
assert( player );
chara->onActionHostile( source );
player->gaugeGnbSetAmmo( 2 );
}
}
};
EXPOSE_SCRIPT( ActionBloodfest16164 );

View file

@ -0,0 +1,44 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_BRUTAL_SHELL = 1898;
class ActionBrutalShell16139 :
public ScriptAPI::ActionScript
{
public:
ActionBrutalShell16139() :
ScriptAPI::ActionScript( 16139 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 && action.isCorrectCombo() )
{
auto chara = action.getSourceChara();
auto heal = action.calcHealing( 150 );
heal.first = Math::CalcStats::applyHealingReceiveMultiplier( *chara, heal.first );
action.getEffectbuilder()->heal( chara, chara, heal.first, heal.second );
if( chara->getLevel() >= 52 && heal.first > 0 )
{
World::Action::StatusEffectEntry effectEntry;
effectEntry.init( Common::StatusEffectType::Shield, heal.first, 0, 0, 0 );
auto pNewEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_BRUTAL_SHELL, action.getSourceChara(), chara, 10000, 3000 );
pNewEffect->replaceEffectEntry( effectEntry );
action.getEffectbuilder()->applyStatusEffect( chara, chara, pNewEffect );
}
}
}
};
EXPOSE_SCRIPT( ActionBrutalShell16139 );

View file

@ -0,0 +1,41 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_READY_TO_RIP = 1842;
const uint16_t STATUS_ID_READY_TO_TEAR = 1843;
const uint16_t STATUS_ID_READY_TO_GOUGE = 1844;
class ActionGnashingFang16146 :
public ScriptAPI::ActionScript
{
public:
ActionGnashingFang16146() :
ScriptAPI::ActionScript( 16146 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 )
{
auto chara = action.getSourceChara();
auto player = chara->getAsPlayer();
assert( player );
player->removeSingleStatusEffectById( STATUS_ID_READY_TO_TEAR, false );
player->removeSingleStatusEffectById( STATUS_ID_READY_TO_GOUGE, false );
auto pNewEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_READY_TO_RIP, action.getSourceChara(), player, 10000, 3000 );
action.getEffectbuilder()->applyStatusEffect( chara, chara, pNewEffect, 0 );
player->gaugeGnbSetComboStep( 1 );
}
}
};
EXPOSE_SCRIPT( ActionGnashingFang16146 );

View file

@ -0,0 +1,41 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_READY_TO_RIP = 1842;
const uint16_t STATUS_ID_READY_TO_TEAR = 1843;
const uint16_t STATUS_ID_READY_TO_GOUGE = 1844;
class ActionSavageClaw16147 :
public ScriptAPI::ActionScript
{
public:
ActionSavageClaw16147() :
ScriptAPI::ActionScript( 16147 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 )
{
auto chara = action.getSourceChara();
auto player = chara->getAsPlayer();
assert( player );
player->removeSingleStatusEffectById( STATUS_ID_READY_TO_RIP, false );
player->removeSingleStatusEffectById( STATUS_ID_READY_TO_GOUGE, false );
auto pNewEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_READY_TO_TEAR, action.getSourceChara(), player, 10000, 3000 );
action.getEffectbuilder()->applyStatusEffect( chara, chara, pNewEffect, 0 );
player->gaugeGnbSetComboStep( 2 );
}
}
};
EXPOSE_SCRIPT( ActionSavageClaw16147 );

View file

@ -0,0 +1,41 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_READY_TO_RIP = 1842;
const uint16_t STATUS_ID_READY_TO_TEAR = 1843;
const uint16_t STATUS_ID_READY_TO_GOUGE = 1844;
class ActionWickedTalon16150 :
public ScriptAPI::ActionScript
{
public:
ActionWickedTalon16150() :
ScriptAPI::ActionScript( 16150 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 )
{
auto chara = action.getSourceChara();
auto player = chara->getAsPlayer();
assert( player );
player->removeSingleStatusEffectById( STATUS_ID_READY_TO_RIP, false );
player->removeSingleStatusEffectById( STATUS_ID_READY_TO_TEAR, false );
auto pNewEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_READY_TO_GOUGE, action.getSourceChara(), player, 10000, 3000 );
action.getEffectbuilder()->applyStatusEffect( chara, chara, pNewEffect, 0 );
player->gaugeGnbSetComboStep( 0 );
}
}
};
EXPOSE_SCRIPT( ActionWickedTalon16150 );

View file

@ -0,0 +1,29 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionProvoke7533 :
public ScriptAPI::ActionScript
{
public:
ActionProvoke7533() :
ScriptAPI::ActionScript( 7533 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto chara = action.getHitChara();
if( chara )
{
chara->onActionHostile( action.getSourceChara() );
action.getEffectbuilder()->provoke( chara );
}
}
};
EXPOSE_SCRIPT( ActionProvoke7533 );

View file

@ -0,0 +1,59 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
const uint16_t STATUS_ID_ENHANCED_ENPI = 1236;
class ActionEnpi7486 :
public ScriptAPI::ActionScript
{
public:
ActionEnpi7486() :
ScriptAPI::ActionScript( 7486 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 )
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
assert( pPlayer );
int kenki = pPlayer->gaugeSamGetKenki();
auto level = pPlayer->getLevel();
if( level >= 52 )
{
kenki += 5;
}
if( level >= 62 )
{
kenki += 5;
}
pPlayer->gaugeSamSetKenki( std::min( 100, kenki ) );
}
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto effectEntry = action.getSourceChara()->getStatusEffectById( STATUS_ID_ENHANCED_ENPI );
if( effectEntry.second )
{
action.getSourceChara()->removeStatusEffect( effectEntry.first );
}
}
void onBeforePreCheck( Sapphire::World::Action::Action& action ) override
{
auto effectEntry = action.getSourceChara()->getStatusEffectById( STATUS_ID_ENHANCED_ENPI );
if( effectEntry.second )
{
action.getActionEntry().damagePotency = 320;
}
}
};
EXPOSE_SCRIPT( ActionEnpi7486 );

View file

@ -0,0 +1,32 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionFuga7483 :
public ScriptAPI::ActionScript
{
public:
ActionFuga7483() :
ScriptAPI::ActionScript( 7483 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 )
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
assert( pPlayer );
if( pPlayer->getLevel() >= 62 )
{
pPlayer->gaugeSamSetKenki( std::min( 100, pPlayer->gaugeSamGetKenki() + 5 ) );
}
}
}
};
EXPOSE_SCRIPT( ActionFuga7483 );

View file

@ -0,0 +1,43 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionGekko7481 :
public ScriptAPI::ActionScript
{
public:
ActionGekko7481() :
ScriptAPI::ActionScript( 7481 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 && action.isCorrectCombo() )
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
assert( pPlayer );
int kenki = pPlayer->gaugeSamGetKenki();
auto level = pPlayer->getLevel();
if( /*positional &&*/ level >= 52 )
{
kenki += 5;
}
if( level >= 62 )
{
kenki += 5;
}
pPlayer->gaugeSamSetKenki( std::min( 100, kenki ) );
if( level >= 30 )
{
pPlayer->gaugeSamSetSen( Common::SamSen::Getsu, true );
}
}
}
};
EXPOSE_SCRIPT( ActionGekko7481 );

View file

@ -0,0 +1,33 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionHagakure7495 :
public ScriptAPI::ActionScript
{
public:
ActionHagakure7495() :
ScriptAPI::ActionScript( 7495 )
{
}
void onStart( Sapphire::World::Action::Action& action ) override
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
assert( pPlayer );
int kenki = pPlayer->gaugeSamGetKenki();
if( pPlayer->gaugeSamGetSen( Common::SamSen::Getsu ) )
kenki += 10;
if( pPlayer->gaugeSamGetSen( Common::SamSen::Setsu ) )
kenki += 10;
if( pPlayer->gaugeSamGetSen( Common::SamSen::Ka ) )
kenki += 10;
pPlayer->gaugeSamSetKenki( std::min( 100, kenki ) );
}
};
EXPOSE_SCRIPT( ActionHagakure7495 );

View file

@ -0,0 +1,32 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionHakaze7477 :
public ScriptAPI::ActionScript
{
public:
ActionHakaze7477() :
ScriptAPI::ActionScript( 7477 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 )
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
assert( pPlayer );
if( pPlayer->getLevel() >= 62 )
{
pPlayer->gaugeSamSetKenki( std::min( 100, pPlayer->gaugeSamGetKenki() + 5 ) );
}
}
}
};
EXPOSE_SCRIPT( ActionHakaze7477 );

View file

@ -0,0 +1,26 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionIkishoten16482 :
public ScriptAPI::ActionScript
{
public:
ActionIkishoten16482() :
ScriptAPI::ActionScript( 16482 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
assert( pPlayer );
pPlayer->gaugeSamSetKenki( std::min( 100, pPlayer->gaugeSamGetKenki() + 50 ) );
}
};
EXPOSE_SCRIPT( ActionIkishoten16482 );

View file

@ -0,0 +1,32 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionJinpu7478 :
public ScriptAPI::ActionScript
{
public:
ActionJinpu7478() :
ScriptAPI::ActionScript( 7478 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 && action.isCorrectCombo() )
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
assert( pPlayer );
if( pPlayer->getLevel() >= 62 )
{
pPlayer->gaugeSamSetKenki( std::min( 100, pPlayer->gaugeSamGetKenki() + 5 ) );
}
}
}
};
EXPOSE_SCRIPT( ActionJinpu7478 );

View file

@ -0,0 +1,43 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionKasha7482 :
public ScriptAPI::ActionScript
{
public:
ActionKasha7482() :
ScriptAPI::ActionScript( 7482 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 && action.isCorrectCombo() )
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
assert( pPlayer );
int kenki = pPlayer->gaugeSamGetKenki();
auto level = pPlayer->getLevel();
if( /*positional &&*/ level >= 52 )
{
kenki += 5;
}
if( level >= 62 )
{
kenki += 5;
}
pPlayer->gaugeSamSetKenki( std::min( 100, kenki ) );
if( level >= 40 )
{
pPlayer->gaugeSamSetSen( Common::SamSen::Ka, true );
}
}
}
};
EXPOSE_SCRIPT( ActionKasha7482 );

View file

@ -0,0 +1,43 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionMangetsu7484 :
public ScriptAPI::ActionScript
{
public:
ActionMangetsu7484() :
ScriptAPI::ActionScript( 7484 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 && action.isCorrectCombo() )
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
assert( pPlayer );
int kenki = pPlayer->gaugeSamGetKenki();
auto level = pPlayer->getLevel();
if( level >= 52 )
{
kenki += 5;
}
if( level >= 62 )
{
kenki += 5;
}
pPlayer->gaugeSamSetKenki( std::min( 100, kenki ) );
if( level >= 35 )
{
pPlayer->gaugeSamSetSen( Common::SamSen::Getsu, true );
}
}
}
};
EXPOSE_SCRIPT( ActionMangetsu7484 );

View file

@ -0,0 +1,43 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionOka7485 :
public ScriptAPI::ActionScript
{
public:
ActionOka7485() :
ScriptAPI::ActionScript( 7485 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 && action.isCorrectCombo() )
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
assert( pPlayer );
int kenki = pPlayer->gaugeSamGetKenki();
auto level = pPlayer->getLevel();
if( level >= 52 )
{
kenki += 5;
}
if( level >= 62 )
{
kenki += 5;
}
pPlayer->gaugeSamSetKenki( std::min( 100, kenki ) );
if( level >= 45 )
{
pPlayer->gaugeSamSetSen( Common::SamSen::Ka, true );
}
}
}
};
EXPOSE_SCRIPT( ActionOka7485 );

View file

@ -0,0 +1,32 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionShifu7479 :
public ScriptAPI::ActionScript
{
public:
ActionShifu7479() :
ScriptAPI::ActionScript( 7479 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 && action.isCorrectCombo() )
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
assert( pPlayer );
if( pPlayer->getLevel() >= 62 )
{
pPlayer->gaugeSamSetKenki( std::min( 100, pPlayer->gaugeSamGetKenki() + 5 ) );
}
}
}
};
EXPOSE_SCRIPT( ActionShifu7479 );

View file

@ -0,0 +1,43 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionYukikaze7480 :
public ScriptAPI::ActionScript
{
public:
ActionYukikaze7480() :
ScriptAPI::ActionScript( 7480 )
{
}
void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter ) override
{
if( validVictimCounter > 0 && action.isCorrectCombo() )
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
assert( pPlayer );
int kenki = pPlayer->gaugeSamGetKenki();
auto level = pPlayer->getLevel();
if( level >= 52 )
{
kenki += 10;
}
if( level >= 62 )
{
kenki += 5;
}
pPlayer->gaugeSamSetKenki( std::min( 100, kenki ) );
if( level >= 50 )
{
pPlayer->gaugeSamSetSen( Common::SamSen::Setsu, true );
}
}
}
};
EXPOSE_SCRIPT( ActionYukikaze7480 );

View file

@ -0,0 +1,81 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_GALVANIZE = 297;
const uint16_t STATUS_ID_CATALYZE = 1918;
class ActionAdloquium185 :
public ScriptAPI::ActionScript
{
public:
ActionAdloquium185() :
ScriptAPI::ActionScript( 185 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pTarget = action.getHitChara();
if( pTarget )
{
// still pull out the lut entry to get potency values etc.
auto lutEntry = action.getActionEntry();
// do healing part
auto heal = action.calcHealing( lutEntry.healPotency );
heal.first = Math::CalcStats::applyHealingReceiveMultiplier( *pTarget, heal.first );
action.getEffectbuilder()->heal( pTarget, pTarget, heal.first, heal.second );
float shieldValue = heal.first * 1.25f;
// apply new galvanize when not existing or larger than existing one
auto oldEffect = pTarget->getStatusEffectById( STATUS_ID_GALVANIZE );
if( !oldEffect.second || oldEffect.second->getEffectEntry().getRemainingShield() <= shieldValue )
{
World::Action::StatusEffectEntry effectEntry;
effectEntry.init( Common::StatusEffectType::Shield, shieldValue, 0, 0, 0 );
auto pNewEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_GALVANIZE, action.getSourceChara(), pTarget, 30000, 3000 );
pNewEffect->replaceEffectEntry( effectEntry );
action.getEffectbuilder()->applyStatusEffect( pTarget, action.getSourceChara(), pNewEffect );
}
else
action.getEffectbuilder()->statusNoEffect( pTarget, STATUS_ID_GALVANIZE );
if( heal.second == Common::ActionHitSeverityType::CritHeal )
{
// apply catalyze when crit, same rule as galvanize
oldEffect = pTarget->getStatusEffectById( STATUS_ID_CATALYZE );
if( !oldEffect.second || oldEffect.second->getEffectEntry().getRemainingShield() <= shieldValue )
{
World::Action::StatusEffectEntry effectEntry;
effectEntry.init( Common::StatusEffectType::Shield, shieldValue, 0, 0, 0 );
auto pNewEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_CATALYZE, action.getSourceChara(), pTarget, 30000, 3000 );
pNewEffect->replaceEffectEntry( effectEntry );
action.getEffectbuilder()->applyStatusEffect( pTarget, action.getSourceChara(), pNewEffect );
}
else
action.getEffectbuilder()->statusNoEffect( pTarget, STATUS_ID_CATALYZE );
}
}
}
void onStart( Sapphire::World::Action::Action& action ) override
{
/*
don't run generic action handler for this action.
for simpler actions like cure 1 we only want to apply the freecure proc in script,
and let the generic handler do the heal so we don't have to copy heal code into scripts,
unless in cases like adlo, the healing result matters so we have to.
*/
action.disableGenericHandler();
}
};
EXPOSE_SCRIPT( ActionAdloquium185 );

View file

@ -0,0 +1,36 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_NASCENT_CHAOS = 1897;
class ActionChaoticCyclone16463 :
public ScriptAPI::ActionScript
{
public:
ActionChaoticCyclone16463() :
ScriptAPI::ActionScript( 16463 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto effectEntry = action.getSourceChara()->getStatusEffectById( STATUS_ID_NASCENT_CHAOS );
if( effectEntry.second )
{
action.getSourceChara()->removeStatusEffect( effectEntry.first );
}
else
{
action.disableGenericHandler();
action.interrupt();
}
}
};
EXPOSE_SCRIPT( ActionChaoticCyclone16463 );

View file

@ -0,0 +1,36 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_NASCENT_CHAOS = 1897;
class ActionInfuriate52 :
public ScriptAPI::ActionScript
{
public:
ActionInfuriate52() :
ScriptAPI::ActionScript( 52 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
assert( pPlayer );
uint8_t ib = pPlayer->gaugeWarGetIb();
ib = std::min( 100, ib + 50 );
pPlayer->gaugeWarSetIb( ib );
if( pPlayer->getLevel() >= 72 )
{
auto pEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_NASCENT_CHAOS, action.getSourceChara(), action.getSourceChara(), 30000, 3000 );
action.getEffectbuilder()->applyStatusEffect( action.getSourceChara(), action.getSourceChara(), pEffect, 0 );
}
}
};
EXPOSE_SCRIPT( ActionInfuriate52 );

View file

@ -0,0 +1,36 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_NASCENT_CHAOS = 1897;
class ActionInnerChaos16465 :
public ScriptAPI::ActionScript
{
public:
ActionInnerChaos16465() :
ScriptAPI::ActionScript( 16465 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto effectEntry = action.getSourceChara()->getStatusEffectById( STATUS_ID_NASCENT_CHAOS );
if( effectEntry.second )
{
action.getSourceChara()->removeStatusEffect( effectEntry.first );
}
else
{
action.disableGenericHandler();
action.interrupt();
}
}
};
EXPOSE_SCRIPT( ActionInnerChaos16465 );

View file

@ -0,0 +1,28 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
using namespace Sapphire;
class ActionBenediction140 :
public ScriptAPI::ActionScript
{
public:
ActionBenediction140() :
ScriptAPI::ActionScript( 140 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pTarget = action.getHitChara();
if( pTarget )
{
action.getEffectbuilder()->heal( pTarget, pTarget, pTarget->getMaxHp() );
}
}
};
EXPOSE_SCRIPT( ActionBenediction140 );

View file

@ -0,0 +1,33 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_FREECURE = 155;
class ActionCure120 :
public ScriptAPI::ActionScript
{
public:
ActionCure120() :
ScriptAPI::ActionScript( 120 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
if( action.getSourceChara()->getLevel() >= 30 && Math::CalcStats::getRandomNumber0To100() < 15 )
{
auto pEffect = Sapphire::StatusEffect::make_StatusEffect( STATUS_ID_FREECURE, action.getSourceChara(), action.getSourceChara(), 15000, 3000 );
action.getEffectbuilder()->applyStatusEffect( action.getSourceChara(), action.getSourceChara(), pEffect );
}
}
};
EXPOSE_SCRIPT( ActionCure120 );

View file

@ -0,0 +1,42 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_FREECURE = 155;
class ActionCureII135 :
public ScriptAPI::ActionScript
{
public:
ActionCureII135() :
ScriptAPI::ActionScript( 135 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto effectEntry = action.getSourceChara()->getStatusEffectById( STATUS_ID_FREECURE );
if( effectEntry.second )
{
action.getSourceChara()->removeStatusEffect( effectEntry.first );
}
}
void onBeforePreCheck( Sapphire::World::Action::Action& action ) override
{
auto effectEntry = action.getSourceChara()->getStatusEffectById( STATUS_ID_FREECURE );
if( effectEntry.second )
{
action.setPrimaryCost( Common::ActionPrimaryCostType::None, 0 );
}
}
};
EXPOSE_SCRIPT( ActionCureII135 );

View file

@ -11,6 +11,8 @@
#include "Actor/Player.h" #include "Actor/Player.h"
#include "Actor/BNpc.h" #include "Actor/BNpc.h"
#include "Action/ActionLut.h"
#include "Territory/Territory.h" #include "Territory/Territory.h"
#include <Network/CommonActorControl.h> #include <Network/CommonActorControl.h>
@ -49,7 +51,10 @@ Action::Action::Action( Entity::CharaPtr caster, uint32_t actionId, uint16_t seq
m_targetId( 0 ), m_targetId( 0 ),
m_startTime( 0 ), m_startTime( 0 ),
m_interruptType( Common::ActionInterruptType::None ), m_interruptType( Common::ActionInterruptType::None ),
m_sequence( sequence ) m_sequence( sequence ),
m_isAutoAttack( false ),
m_disableGenericHandler( false ),
m_shouldAlwaysCombo( false )
{ {
} }
@ -75,6 +80,13 @@ bool Action::Action::init()
m_castTimeMs = static_cast< uint32_t >( m_actionData->cast100ms * 100 ); m_castTimeMs = static_cast< uint32_t >( m_actionData->cast100ms * 100 );
m_recastTimeMs = static_cast< uint32_t >( m_actionData->recast100ms * 100 ); m_recastTimeMs = static_cast< uint32_t >( m_actionData->recast100ms * 100 );
auto actionCategory = static_cast< Common::ActionCategory >( m_actionData->actionCategory );
if( actionCategory == Common::ActionCategory::Spell || actionCategory == Common::ActionCategory::Weaponskill )
{
m_castTimeMs = static_cast< uint32_t >( m_castTimeMs * ( m_pSource->getStatValue( Common::BaseParam::Haste ) / 100.0f ) );
m_recastTimeMs = static_cast< uint32_t >( m_recastTimeMs * ( m_pSource->getStatValue( Common::BaseParam::Haste ) / 100.0f ) );
}
m_cooldownGroup = m_actionData->cooldownGroup; m_cooldownGroup = m_actionData->cooldownGroup;
m_range = m_actionData->range; m_range = m_actionData->range;
m_effectRange = m_actionData->effectRange; m_effectRange = m_actionData->effectRange;
@ -107,6 +119,57 @@ bool Action::Action::init()
m_primaryCostType = static_cast< Common::ActionPrimaryCostType >( m_actionData->primaryCostType ); m_primaryCostType = static_cast< Common::ActionPrimaryCostType >( m_actionData->primaryCostType );
m_primaryCost = m_actionData->primaryCostValue; m_primaryCost = m_actionData->primaryCostValue;
if( m_primaryCostType != Common::ActionPrimaryCostType::None )
{
for( auto const& entry : m_pSource->getStatusEffectMap() )
{
if( entry.second->getParam() == 65436 ) // todo: decode this shit and figure out exact percentage to apply to primary cost, this magic number is 0%
{
/*
Since the client is displaying correctly without additional data, there should be a "primary primary cost type" defined for each class.
In the case of 65436, on whm, mp cost is removed, on drk, blood cost is removed but mp cost remains.
*/
auto affectedPrimaryCost = Common::ActionPrimaryCostType::MagicPoints;
switch( m_pSource->getClass() )
{
case Common::ClassJob::Marauder:
case Common::ClassJob::Warrior:
{
affectedPrimaryCost = Common::ActionPrimaryCostType::WARGauge;
break;
}
case Common::ClassJob::Darkknight:
{
affectedPrimaryCost = Common::ActionPrimaryCostType::DRKGauge;
break;
}
}
if( m_primaryCostType == affectedPrimaryCost )
{
setPrimaryCost( Common::ActionPrimaryCostType::None, 0 );
}
break;
}
}
}
if( auto player = m_pSource->getAsPlayer() )
{
switch( player->getClass() )
{
case Common::ClassJob::Darkknight:
{
if( m_primaryCostType == Common::ActionPrimaryCostType::MagicPoints && player->gaugeDrkGetDarkArts() )
{
setPrimaryCost( Common::ActionPrimaryCostType::None, 0 );
player->gaugeDrkSetDarkArts( false );
}
break;
}
}
}
/*if( !m_actionData->targetArea ) /*if( !m_actionData->targetArea )
{ {
// override pos to target position // override pos to target position
@ -134,6 +197,8 @@ bool Action::Action::init()
addDefaultActorFilters(); addDefaultActorFilters();
m_effectBuilder->setAnimationLock( getAnimationLock() );
return true; return true;
} }
@ -172,11 +237,6 @@ Common::ActionInterruptType Action::Action::getInterruptType() const
return m_interruptType; return m_interruptType;
} }
void Action::Action::setInterrupted( Common::ActionInterruptType type )
{
m_interruptType = type;
}
uint32_t Action::Action::getCastTime() const uint32_t Action::Action::getCastTime() const
{ {
return m_castTimeMs; return m_castTimeMs;
@ -205,7 +265,6 @@ bool Action::Action::update()
if( isInterrupted() ) if( isInterrupted() )
{ {
interrupt();
return true; return true;
} }
@ -240,7 +299,6 @@ bool Action::Action::update()
if( !m_pTarget->isAlive() ) if( !m_pTarget->isAlive() )
{ {
// interrupt the cast if target died // interrupt the cast if target died
setInterrupted( Common::ActionInterruptType::RegularInterrupt );
interrupt(); interrupt();
return true; return true;
} }
@ -283,37 +341,31 @@ void Action::Action::start()
} }
} }
if( player )
{
// todo: m_recastTimeMs needs to be adjusted for player sks/sps // todo: m_recastTimeMs needs to be adjusted for player sks/sps
auto actionStartPkt = makeActorControlSelf( m_pSource->getId(), ActorControlType::ActionStart, 1, getId(), auto actionStartPkt = makeActorControlSelf( m_pSource->getId(), ActorControlType::ActionStart, 1, getId(),
m_recastTimeMs / 10 ); m_recastTimeMs / 10 );
player->queuePacket( actionStartPkt ); player->queuePacket( actionStartPkt );
}
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref(); auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
scriptMgr.onStart( *this );
// check the lut too and see if we have something usable, otherwise cancel the cast
if( !scriptMgr.onStart( *this ) && !ActionLut::validEntryExists( static_cast< uint16_t >( getId() ) ) )
{
// script not implemented and insufficient lut data (no potencies)
interrupt();
if( player )
{
player->sendUrgent( "Action not implemented, missing script/lut entry for action#{0}", getId() );
player->setCurrentAction( nullptr );
}
return;
}
// instantly finish cast if there's no cast time // instantly finish cast if there's no cast time
if( !hasCastTime() ) if( !hasCastTime() )
execute(); execute();
} }
void Action::Action::interrupt() void Action::Action::interrupt( ActionInterruptType type )
{ {
if( isInterrupted() )
return;
assert( m_pSource ); assert( m_pSource );
m_interruptType = type;
// things that aren't players don't care about cooldowns and state flags // things that aren't players don't care about cooldowns and state flags
if( m_pSource->isPlayer() ) if( m_pSource->isPlayer() )
{ {
@ -326,7 +378,7 @@ void Action::Action::interrupt()
player->unsetStateFlag( PlayerStateFlag::Casting ); player->unsetStateFlag( PlayerStateFlag::Casting );
} }
if( hasCastTime() ) if( m_startTime > 0 && hasCastTime() )
{ {
uint8_t interruptEffect = 0; uint8_t interruptEffect = 0;
if( m_interruptType == ActionInterruptType::DamageInterrupt ) if( m_interruptType == ActionInterruptType::DamageInterrupt )
@ -404,49 +456,15 @@ 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? if( m_isAutoAttack )
auto wepDmg = 1.f; return Math::CalcStats::calcAutoAttackDamage( *m_pSource, potency );
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 else
{ return Math::CalcStats::calcActionDamage( this, *m_pSource, potency, Math::CalcStats::getWeaponDamage( m_pSource ) );
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( this, *m_pSource, 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()
@ -454,92 +472,258 @@ void Action::Action::buildEffects()
snapshotAffectedActors( m_hitActors ); snapshotAffectedActors( m_hitActors );
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref(); auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
auto hasLutEntry = hasValidLutEntry();
if( !scriptMgr.onExecute( *this ) && !hasLutEntry ) for( const auto& statusIt : m_pSource->getStatusEffectMap() )
{ {
if( auto player = m_pSource->getAsPlayer() ) statusIt.second->onActionExecute( this );
{
player->sendUrgent( "missing lut entry for action#{}", getId() );
} }
scriptMgr.onExecute( *this );
if( isInterrupted() )
return; return;
}
if( !hasLutEntry || m_hitActors.empty() ) if( m_disableGenericHandler || !hasValidLutEntry() )
{ {
// 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
scriptMgr.onBeforeBuildEffect( *this, 0, 0 );
m_effectBuilder->buildAndSendPackets(); m_effectBuilder->buildAndSendPackets();
scriptMgr.onAfterBuildEffect( *this );
return; return;
} }
// no script exists but we have a valid lut entry // we have a valid lut entry
if( auto player = getSourceChara()->getAsPlayer() ) auto player = getSourceChara()->getAsPlayer();
if( player )
{ {
player->sendDebug( "Hit target: pot: {} (c: {}, f: {}, r: {}), heal pot: {}, mpp: {}", player->sendDebug( "type: {}, dpot: {} (dcpot: {}, ddpot: {}), hpot: {}, ss: {}, ts: {}, bonus: {}, breq: {}, bdata: {}",
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.selfStatus, m_lutEntry.targetStatus,
m_lutEntry.bonusEffect, m_lutEntry.bonusRequirement, m_lutEntry.getRawBonusData() );
} }
// when aoe, these effects are in the target whatever is hit first uint8_t victimCounter = 0, validVictimCounter = 0;
bool shouldRestoreMP = true;
bool shouldApplyComboSucceedEffect = true;
for( auto& actor : m_hitActors ) for( auto& actor : m_hitActors )
{ {
if( m_lutEntry.potency > 0 ) victimCounter++;
bool shouldHitThisTarget = true;
for( const auto& statusIt : getSourceChara()->getStatusEffectMap() )
{ {
auto dmg = calcDamage( isCorrectCombo() ? m_lutEntry.comboPotency : m_lutEntry.potency ); bool result = statusIt.second->onActionHitTarget( this, actor, victimCounter );
m_effectBuilder->damage( actor, actor, dmg.first, dmg.second ); if( !result )
shouldHitThisTarget = false;
if( dmg.first > 0 ) }
if( !shouldHitThisTarget )
continue;
if( m_lutEntry.damagePotency > 0 )
{
Common::AttackType attackType = static_cast< Common::AttackType >( m_actionData->attackType );
actor->onActionHostile( m_pSource ); actor->onActionHostile( m_pSource );
if( isCorrectCombo() && shouldApplyComboSucceedEffect ) auto dmg = calcDamage( isCorrectCombo() ? m_lutEntry.damageComboPotency : m_lutEntry.damagePotency );
if( victimCounter > 1 )
{ {
m_effectBuilder->comboSucceed( actor ); if( m_lutEntry.bonusEffect & Common::ActionBonusEffect::DamageFallOff )
shouldApplyComboSucceedEffect = false; {
if( checkActionBonusRequirement() )
{
dmg.first = static_cast< uint32_t >( 1.0 * dmg.first * ( m_lutEntry.getDamageFallOffPercentage() / 100.0 ) );
}
}
}
dmg.first = Math::CalcStats::applyDamageReceiveMultiplier( *actor, dmg.first, getActionTypeFilterFromAttackType( attackType ) );
float originalDamage = dmg.first;
bool dodged = false;
float blocked = 0;
float parried = 0;
if( dmg.first > 0 )
{
dodged = Math::CalcStats::calcDodge( *actor );
if( !dodged && dmg.second == Common::ActionHitSeverityType::NormalDamage && actor->isPlayer() )
{
blocked = Math::CalcStats::calcBlock( *actor, dmg.first );
} }
if( !isComboAction() || isCorrectCombo() ) if( !dodged && blocked == 0 && dmg.second == Common::ActionHitSeverityType::NormalDamage && actor->isPlayer() )
{ {
if( m_lutEntry.curePotency > 0 ) // actions with self heal if( isPhysical() )
{ {
auto heal = calcHealing( m_lutEntry.curePotency ); parried = Math::CalcStats::calcParry( *actor, dmg.first );
m_effectBuilder->heal( actor, m_pSource, heal.first, heal.second, Common::ActionEffectResultFlag::EffectOnSource ); }
}
} }
if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP ) if( dodged )
dmg.first = 0;
else
{ {
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource ); dmg.first -= blocked;
shouldRestoreMP = false; dmg.first -= parried;
} }
if ( !m_actionData->preservesCombo ) // we need something like m_actionData->hasNextComboAction if( dmg.first > 0 )
{
dmg.first = actor->applyShieldProtection( dmg.first );
if( blocked > 0 )
m_effectBuilder->blockedDamage( actor, actor, dmg.first, static_cast< uint16_t >( blocked / originalDamage * 100 ) , dmg.first == 0 ? Common::ActionEffectResultFlag::Absorbed : Common::ActionEffectResultFlag::None, getExecutionDelay() + victimCounter * 100 );
else if (parried > 0 )
m_effectBuilder->parriedDamage( actor, actor, dmg.first, static_cast< uint16_t >( parried / originalDamage * 100 ), dmg.first == 0 ? Common::ActionEffectResultFlag::Absorbed : Common::ActionEffectResultFlag::None, getExecutionDelay() + victimCounter * 100 );
else
m_effectBuilder->damage( actor, actor, dmg.first, dmg.second, dmg.first == 0 ? Common::ActionEffectResultFlag::Absorbed : Common::ActionEffectResultFlag::None, getExecutionDelay() + victimCounter * 100 );
auto reflectDmg = Math::CalcStats::calcDamageReflect( m_pSource, actor, dmg.first,
attackType == Common::AttackType::Physical ? Common::ActionTypeFilter::Physical :
( attackType == Common::AttackType::Magical ? Common::ActionTypeFilter::Magical : Common::ActionTypeFilter::Unknown ) );
if( reflectDmg.first > 0 )
{
m_effectBuilder->damage( actor, m_pSource, reflectDmg.first, reflectDmg.second, Common::ActionEffectResultFlag::Reflected, getExecutionDelay() + victimCounter * 100 );
}
auto absorb = Math::CalcStats::calcAbsorbHP( m_pSource, dmg.first );
if( absorb > 0 )
{
if( absorb > actor->getHp() )
absorb = actor->getHp();
m_effectBuilder->heal( actor, m_pSource, absorb, Common::ActionHitSeverityType::NormalHeal, Common::ActionEffectResultFlag::EffectOnSource, getExecutionDelay() + victimCounter * 100 );
}
}
else
{
if( dodged )
{
m_effectBuilder->dodge( actor, actor );
}
else
{
// todo: no effect or invulnerable
}
}
if( !dodged )
{
if( ( !isComboAction() || isCorrectCombo() ) )
{
if ( !m_actionData->preservesCombo ) // this matches retail packet, on all standalone actions even casts.
{ {
m_effectBuilder->startCombo( actor, getId() ); // this is on all targets hit m_effectBuilder->startCombo( actor, getId() ); // this is on all targets hit
} }
} }
}
else if( m_lutEntry.curePotency > 0 )
{
auto heal = calcHealing( m_lutEntry.curePotency );
m_effectBuilder->heal( actor, actor, heal.first, heal.second );
if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP ) if( m_lutEntry.bonusEffect & Common::ActionBonusEffect::SelfHeal )
{ {
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource ); if( checkActionBonusRequirement() )
shouldRestoreMP = false;
}
}
else if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP )
{ {
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource ); auto heal = calcHealing( m_lutEntry.getSelfHealPotency() );
shouldRestoreMP = false; heal.first = Math::CalcStats::applyHealingReceiveMultiplier( *m_pSource, heal.first );
m_effectBuilder->heal( actor, m_pSource, heal.first, heal.second, Common::ActionEffectResultFlag::EffectOnSource );
} }
} }
if( validVictimCounter == 0 )
{
if( isCorrectCombo() )
m_effectBuilder->comboSucceed( actor );
if( m_isAutoAttack && m_pSource->isPlayer() )
{
if( auto player = m_pSource->getAsPlayer() )
{
if( player->getClass() == Common::ClassJob::Paladin )
{
player->gaugePldSetOath( std::min( 100, player->gaugePldGetOath() + 5 ) );
}
}
}
if( m_lutEntry.bonusEffect & Common::ActionBonusEffect::GainMPPercentage )
{
if( checkActionBonusRequirement() )
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.getMPGainPercentage() / 100, Common::ActionEffectResultFlag::EffectOnSource );
}
if( m_lutEntry.bonusEffect & Common::ActionBonusEffect::GainJobResource )
{
if( checkActionBonusRequirement() )
{
switch( m_lutEntry.getAffectedJob() )
{
case Common::ClassJob::Marauder:
case Common::ClassJob::Warrior:
{
player->gaugeWarSetIb( std::min( 100, player->gaugeWarGetIb() + m_lutEntry.getJobResourceGain() ) );
break;
}
case Common::ClassJob::Darkknight:
{
player->gaugeDrkSetBlood( std::min( 100, player->gaugeDrkGetBlood() + m_lutEntry.getJobResourceGain() ) );
break;
}
case Common::ClassJob::Gunbreaker:
{
player->gaugeGnbSetAmmo( std::min( 2, player->gaugeGnbGetAmmo() + m_lutEntry.getJobResourceGain() ) );
break;
}
case Common::ClassJob::Samurai:
{
player->gaugeSamSetKenki( std::min( 100, player->gaugeSamGetKenki() + m_lutEntry.getJobResourceGain() ) );
break;
}
}
}
}
if( m_lutEntry.bonusEffect & Common::ActionBonusEffect::GainJobTimer )
{
if( checkActionBonusRequirement() )
{
switch( m_lutEntry.getAffectedJob() )
{
case Common::ClassJob::Darkknight:
{
player->gaugeDrkSetDarkSideTimer( std::min( 60000, player->gaugeDrkGetDarkSideTimer() + m_lutEntry.getJobTimerGain() ), true );
break;
}
case Common::ClassJob::Dragoon:
{
player->gaugeDrgSetDragonTimer( std::min( 30000, player->gaugeDrgGetDragonTimer() + m_lutEntry.getJobTimerGain() ), true );
break;
}
}
}
}
}
validVictimCounter++;
}
}
if( m_lutEntry.healPotency > 0 )
{
auto heal = calcHealing( m_lutEntry.healPotency );
heal.first = Math::CalcStats::applyHealingReceiveMultiplier( *actor, heal.first );
m_effectBuilder->heal( actor, actor, heal.first, heal.second, Common::ActionEffectResultFlag::None, getExecutionDelay() + victimCounter * 100 );
}
if( m_lutEntry.targetStatus != 0 )
{
if( !isComboAction() || isCorrectCombo() )
m_effectBuilder->applyStatusEffect( actor, m_pSource, m_lutEntry.targetStatus, m_lutEntry.targetStatusDuration, m_lutEntry.targetStatusParam, getExecutionDelay() + victimCounter * 100 );
}
}
if( m_lutEntry.selfStatus != 0 )
{
if( !isComboAction() || isCorrectCombo() )
m_effectBuilder->applyStatusEffect( m_pSource, m_pSource, m_lutEntry.selfStatus, m_lutEntry.selfStatusDuration, m_lutEntry.selfStatusParam );
}
scriptMgr.onBeforeBuildEffect( *this, victimCounter, validVictimCounter );
m_effectBuilder->buildAndSendPackets(); m_effectBuilder->buildAndSendPackets();
scriptMgr.onAfterBuildEffect( *this );
// 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
m_effectBuilder.reset(); m_effectBuilder.reset();
@ -547,6 +731,12 @@ void Action::Action::buildEffects()
bool Action::Action::preCheck() bool Action::Action::preCheck()
{ {
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
scriptMgr.onBeforePreCheck( *this );
if( isInterrupted() )
return false;
if( auto player = m_pSource->getAsPlayer() ) if( auto player = m_pSource->getAsPlayer() )
{ {
if( !playerPreCheck( *player ) ) if( !playerPreCheck( *player ) )
@ -619,6 +809,9 @@ void Action::Action::setAdditionalData( uint32_t data )
bool Action::Action::isCorrectCombo() const bool Action::Action::isCorrectCombo() const
{ {
if( m_shouldAlwaysCombo )
return true;
auto lastActionId = m_pSource->getLastComboActionId(); auto lastActionId = m_pSource->getLastComboActionId();
if( lastActionId == 0 ) if( lastActionId == 0 )
@ -634,6 +827,11 @@ bool Action::Action::isComboAction() const
return m_actionData->actionCombo != 0; return m_actionData->actionCombo != 0;
} }
void Sapphire::World::Action::Action::setAlwaysCombo()
{
m_shouldAlwaysCombo = true;
}
bool Action::Action::primaryCostCheck( bool subtractCosts ) bool Action::Action::primaryCostCheck( bool subtractCosts )
{ {
switch( m_primaryCostType ) switch( m_primaryCostType )
@ -666,6 +864,168 @@ bool Action::Action::primaryCostCheck( bool subtractCosts )
return true; return true;
} }
case Common::ActionPrimaryCostType::StatusEffect:
{
auto statusEntry = m_pSource->getStatusEffectById( m_primaryCost );
if( !statusEntry.second )
return false;
if( subtractCosts )
m_pSource->removeStatusEffect( statusEntry.first );
return true;
}
case Common::ActionPrimaryCostType::WARGauge:
{
auto pPlayer = m_pSource->getAsPlayer();
if( pPlayer )
{
auto ib = pPlayer->gaugeWarGetIb();
if( ib >= m_primaryCost )
{
if( subtractCosts )
pPlayer->gaugeWarSetIb( ib - m_primaryCost );
return true;
}
}
return false;
}
case Common::ActionPrimaryCostType::PLDGauge:
{
auto pPlayer = m_pSource->getAsPlayer();
if( pPlayer )
{
auto oath = pPlayer->gaugePldGetOath();
if( oath >= m_primaryCost )
{
if( subtractCosts )
pPlayer->gaugePldSetOath( oath - m_primaryCost );
return true;
}
}
return false;
}
case Common::ActionPrimaryCostType::WHMBloodLily:
{
auto pPlayer = m_pSource->getAsPlayer();
if( pPlayer )
{
auto bloodLily = pPlayer->gaugeWhmGetBloodLily();
if( bloodLily >= m_primaryCost )
{
if( subtractCosts )
pPlayer->gaugeWhmSetLilies( pPlayer->gaugeWhmGetLily(), bloodLily - m_primaryCost );
return true;
}
}
return false;
}
case Common::ActionPrimaryCostType::WHMLily:
{
auto pPlayer = m_pSource->getAsPlayer();
if( pPlayer )
{
auto lily = pPlayer->gaugeWhmGetLily();
if( lily >= m_primaryCost )
{
if( subtractCosts )
{
lily -= m_primaryCost;
auto bloodLily = pPlayer->gaugeWhmGetBloodLily();
if( pPlayer->getLevel() >= 74 )
{
bloodLily = std::min( 3, bloodLily + m_primaryCost );
}
pPlayer->gaugeWhmSetLilies( lily, bloodLily );
}
return true;
}
}
return false;
}
case Common::ActionPrimaryCostType::DRKGauge:
{
auto pPlayer = m_pSource->getAsPlayer();
if( pPlayer )
{
auto blood = pPlayer->gaugeDrkGetBlood();
if( blood >= m_primaryCost )
{
if( subtractCosts )
pPlayer->gaugeDrkSetBlood( blood - m_primaryCost );
return true;
}
}
return false;
}
case Common::ActionPrimaryCostType::GNBAmmo:
{
auto pPlayer = m_pSource->getAsPlayer();
if( pPlayer )
{
auto ammo = pPlayer->gaugeGnbGetAmmo();
if( ammo >= m_primaryCost )
{
if( subtractCosts )
pPlayer->gaugeGnbSetAmmo( ammo - m_primaryCost );
return true;
}
}
return false;
}
case Common::ActionPrimaryCostType::SAMKenki:
{
auto pPlayer = m_pSource->getAsPlayer();
if( pPlayer )
{
auto kenki = pPlayer->gaugeSamGetKenki();
if( kenki >= m_primaryCost )
{
if( subtractCosts )
pPlayer->gaugeSamSetKenki( kenki - m_primaryCost );
return true;
}
}
return false;
}
case Common::ActionPrimaryCostType::SAMSen:
{
auto pPlayer = m_pSource->getAsPlayer();
if( pPlayer )
{
// trust the client and assume the action is correct, you can cheat this by performing one sen midare :)
if( pPlayer->gaugeSamHasAnySen() )
{
if( subtractCosts )
pPlayer->gaugeSamSetSen( Common::SamSen::None );
return true;
}
}
return false;
}
case Common::ActionPrimaryCostType::SAMMeditation:
{
return true;
}
// free casts, likely just pure ogcds // free casts, likely just pure ogcds
case Common::ActionPrimaryCostType::None: case Common::ActionPrimaryCostType::None:
{ {
@ -751,10 +1111,12 @@ void Action::Action::addDefaultActorFilters()
break; break;
} }
// case Common::CastType::RectangularAOE: case Common::CastType::RectangularAOE:
// { {
// break; auto filter = std::make_shared< World::Util::ActorFilterInRange >( m_pos, m_effectRange );
// } addActorFilter( filter );
break;
}
default: default:
{ {
@ -775,16 +1137,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;
@ -807,11 +1169,128 @@ 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.selfStatus != 0 ||
m_lutEntry.rearPotency != 0 || m_lutEntry.curePotency != 0 || m_lutEntry.restoreMPPercentage != 0; m_lutEntry.targetStatus != 0 || m_lutEntry.bonusEffect != 0;
}
float Action::Action::getAnimationLock()
{
switch( static_cast< Common::ActionCategory >( m_actionData->actionCategory ) )
{
case Common::ActionCategory::Item:
{
return 1.1f;
}
case Common::ActionCategory::Mount:
{
return 0.1f;
}
}
return hasCastTime() ? 0.1f : 0.6f;
} }
Action::EffectBuilderPtr Action::Action::getEffectbuilder() Action::EffectBuilderPtr Action::Action::getEffectbuilder()
{ {
return m_effectBuilder; return m_effectBuilder;
} }
Data::ActionPtr Action::Action::getActionData()
{
return m_actionData;
}
Action::ActionEntry& Action::Action::getActionEntry()
{
return m_lutEntry;
}
void Action::Action::setAutoAttack()
{
m_isAutoAttack = true;
}
void Action::Action::disableGenericHandler()
{
m_disableGenericHandler = true;
}
bool Action::Action::isPhysical() const
{
return isAttackTypePhysical( static_cast< Common::AttackType >( m_actionData->attackType ) );
}
bool Action::Action::isMagical() const
{
return isAttackTypeMagical( static_cast< Common::AttackType >( m_actionData->attackType ) );
}
bool Action::Action::isGCD() const
{
auto actionCategory = static_cast< Common::ActionCategory >( m_actionData->actionCategory );
return actionCategory == Common::ActionCategory::Weaponskill || actionCategory == Common::ActionCategory::Spell;
}
bool Action::Action::isWeaponSkill() const
{
auto actionCategory = static_cast< Common::ActionCategory >( m_actionData->actionCategory );
return actionCategory == Common::ActionCategory::Weaponskill;
}
bool Action::Action::isAttackTypePhysical( Common::AttackType attackType )
{
return attackType == Common::AttackType::Physical;
}
bool Action::Action::isAttackTypeMagical( Common::AttackType attackType )
{
return attackType == Common::AttackType::Magical;
}
ActionTypeFilter Sapphire::World::Action::Action::getActionTypeFilterFromAttackType( AttackType attackType )
{
switch( attackType )
{
case AttackType::Physical:
return ActionTypeFilter::Physical;
case AttackType::Magical:
return ActionTypeFilter::Magical;
case AttackType::Slashing:
return static_cast< ActionTypeFilter >( static_cast< uint32_t >( ActionTypeFilter::Slashing ) + static_cast< uint32_t >( ActionTypeFilter::Physical ) );
case AttackType::Piercing:
return static_cast< ActionTypeFilter >( static_cast< uint32_t >( ActionTypeFilter::Piercing ) + static_cast< uint32_t >( ActionTypeFilter::Physical ) );
case AttackType::Blunt:
return static_cast< ActionTypeFilter >( static_cast< uint32_t >( ActionTypeFilter::Blunt ) + static_cast< uint32_t >( ActionTypeFilter::Physical ) );
default:
return ActionTypeFilter::Unknown;
}
}
void Action::Action::setPrimaryCost( Common::ActionPrimaryCostType type, uint16_t cost )
{
m_primaryCostType = type;
m_primaryCost = cost;
}
bool Action::Action::checkActionBonusRequirement()
{
if( !m_pSource->isPlayer() )
return false;
if( m_lutEntry.bonusRequirement & Common::ActionBonusEffectRequirement::RequireCorrectCombo )
{
if( !isCorrectCombo() )
return false;
}
if( m_lutEntry.bonusRequirement & Common::ActionBonusEffectRequirement::RequireCorrectPositional )
{
// todo
}
return true;
}
uint64_t Action::Action::getExecutionDelay() const
{
// let's see how 3.x is going to do it
return 600;
}

View file

@ -6,6 +6,7 @@
#include "Util/ActorFilter.h" #include "Util/ActorFilter.h"
#include "ForwardsZone.h" #include "ForwardsZone.h"
#include "EffectBuilder.h" #include "EffectBuilder.h"
#include "StatusEffect/StatusEffect.h"
namespace Sapphire::Data namespace Sapphire::Data
{ {
@ -40,7 +41,6 @@ namespace Sapphire::World::Action
bool isInterrupted() const; bool isInterrupted() const;
Common::ActionInterruptType getInterruptType() const; Common::ActionInterruptType getInterruptType() const;
void setInterrupted( Common::ActionInterruptType type );
uint32_t getCastTime() const; uint32_t getCastTime() const;
void setCastTime( uint32_t castTime ); void setCastTime( uint32_t castTime );
@ -52,6 +52,14 @@ namespace Sapphire::World::Action
bool isComboAction() const; bool isComboAction() const;
void setAlwaysCombo();
void setAutoAttack();
void disableGenericHandler();
bool checkActionBonusRequirement();
/*! /*!
* @brief Checks if a chara has enough resources available to cast the action (tp/mp/etc) * @brief Checks if a chara has enough resources available to cast the action (tp/mp/etc)
* @return true if they have the required resources * @return true if they have the required resources
@ -118,6 +126,22 @@ namespace Sapphire::World::Action
*/ */
Entity::CharaPtr getHitChara(); Entity::CharaPtr getHitChara();
Data::ActionPtr getActionData();
ActionEntry& getActionEntry();
float getAnimationLock();
void setPrimaryCost( Common::ActionPrimaryCostType type, uint16_t cost );
bool isPhysical() const;
bool isMagical() const;
bool isGCD() const;
bool isWeaponSkill() const;
uint64_t getExecutionDelay() const;
static bool isAttackTypePhysical( Common::AttackType attackType );
static bool isAttackTypeMagical( Common::AttackType attackType );
static ActionTypeFilter getActionTypeFilterFromAttackType( AttackType 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).
*/ */
@ -133,7 +157,7 @@ namespace Sapphire::World::Action
* *
* m_interruptType will have the reason why the action was interrupted (eg. damage, movement, ...) * m_interruptType will have the reason why the action was interrupted (eg. damage, movement, ...)
*/ */
virtual void interrupt(); virtual void interrupt( Common::ActionInterruptType type = Common::ActionInterruptType::RegularInterrupt );
/*! /*!
* @brief Called on each player update tick * @brief Called on each player update tick
@ -180,6 +204,9 @@ namespace Sapphire::World::Action
bool m_canTargetFriendly; bool m_canTargetFriendly;
bool m_canTargetHostile; bool m_canTargetHostile;
bool m_canTargetDead; bool m_canTargetDead;
bool m_isAutoAttack;
bool m_disableGenericHandler;
bool m_shouldAlwaysCombo;
Common::ActionInterruptType m_interruptType; Common::ActionInterruptType m_interruptType;

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.selfStatus != 0 ||
entry.rearPotency != 0 || entry.curePotency != 0; entry.targetStatus != 0 || entry.bonusEffect != 0;
} }
const ActionEntry& ActionLut::getEntry( uint16_t actionId ) const ActionEntry& ActionLut::getEntry( uint16_t actionId )
@ -25,3 +25,407 @@ 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.getType() != StatusEffectType::Invalid;
}
const StatusEffectEntry& ActionLut::getStatusEffectEntry( uint16_t statusId )
{
auto it = m_statusEffectTable.find( statusId );
assert( it != m_statusEffectTable.end() );
return it->second;
}
Sapphire::World::Action::StatusEffectEntry::StatusEffectEntry( uint32_t type, int32_t v1, int32_t v2, int32_t v3, int32_t v4 )
{
init( static_cast< StatusEffectType >( type ), v1, v2, v3, v4 );
}
void Sapphire::World::Action::StatusEffectEntry::init( StatusEffectType type, int32_t v1, int32_t v2, int32_t v3, int32_t v4 )
{
effectType = static_cast< uint32_t >( type );
effectValue1 = v1;
effectValue2 = v2;
effectValue3 = v3;
effectValue4 = v4;
}
StatusEffectType Sapphire::World::Action::StatusEffectEntry::getType() const
{
return static_cast< StatusEffectType >( effectType );
}
ActionTypeFilter Sapphire::World::Action::StatusEffectEntry::getActionTypeFilter() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::Dot:
case StatusEffectType::Hot:
case StatusEffectType::DamageMultiplier:
case StatusEffectType::DamageReceiveMultiplier:
case StatusEffectType::CritDHRateBonus:
case StatusEffectType::DamageDealtTrigger:
case StatusEffectType::DamageReceiveTrigger:
return static_cast< ActionTypeFilter >( effectValue1 );
default:
return ActionTypeFilter::Unknown;
}
}
int32_t StatusEffectEntry::getMPRestoreTick() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::MPRestore:
return effectValue1 * 10;
default:
return 0;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getDotHotPotency() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::Dot:
case StatusEffectType::Hot:
return effectValue2;
default:
return 0;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getOutgoingDamageMultiplier() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::DamageMultiplier:
return effectValue2;
default:
return 0;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getIncomingDamageMultiplier() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::DamageReceiveMultiplier:
return effectValue2;
default:
return 0;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getOutgoingHealMultiplier() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::HealCastMultiplier:
return effectValue2;
default:
return 0;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getIncomingHealMultiplier() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::HealReceiveMultiplier:
return effectValue2;
default:
return 0;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getPotencyMultiplier() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::PotencyMultiplier:
return effectValue4;
default:
return 0;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getGCDBasedMPRestorePercentage() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::MPRestorePerGCD:
return effectValue1;
default:
return 0;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getRemainingCharges() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::AlwaysCombo:
case StatusEffectType::InstantCast:
case StatusEffectType::PotencyMultiplier:
return effectValue1;
default:
return -1;
}
}
void Sapphire::World::Action::StatusEffectEntry::setRemainingCharges( int32_t charges )
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::AlwaysCombo:
case StatusEffectType::InstantCast:
case StatusEffectType::PotencyMultiplier:
effectValue1 = charges;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getRemainingShield() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::Shield:
return effectValue1;
default:
return 0;
}
}
void Sapphire::World::Action::StatusEffectEntry::setRemainingShield( int32_t shield )
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::Shield:
effectValue1 = shield;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getHasteBonus() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::Haste:
return effectValue1;
default:
return 0;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getBlockRateBonus() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::BlockParryRateBonus:
return effectValue2;
case StatusEffectType::DamageReceiveMultiplier:
return effectValue3;
default:
return 0;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getParryRateBonus() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::BlockParryRateBonus:
return effectValue3;
case StatusEffectType::DamageReceiveMultiplier:
return effectValue4;
default:
return 0;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getCritRateBonus() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::CritDHRateBonus:
return effectValue2;
default:
return 0;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getDirectHitRateBonus() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::CritDHRateBonus:
return effectValue3;
default:
return 0;
}
}
StatusEffectTriggerResult Sapphire::World::Action::StatusEffectEntry::getTriggerResult() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::DamageDealtTrigger:
case StatusEffectType::DamageReceiveTrigger:
return static_cast< StatusEffectTriggerResult >( effectValue3 );
default:
return StatusEffectTriggerResult::None;
}
}
int32_t Sapphire::World::Action::StatusEffectEntry::getTriggerValue() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::DamageDealtTrigger:
case StatusEffectType::DamageReceiveTrigger:
return effectValue2;
default:
return 0;
}
}
ActionTypeFilter Sapphire::World::Action::StatusEffectEntry::getTriggerDamageType() const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::DamageDealtTrigger:
case StatusEffectType::DamageReceiveTrigger:
if( effectValue4 == 0 ) // data missing in lut, default to physical
return ActionTypeFilter::Physical;
else
return static_cast< ActionTypeFilter >( effectValue4 );
default:
return ActionTypeFilter::Unknown;
}
}
bool Sapphire::World::Action::StatusEffectEntry::canApplyToAction( uint32_t actionId, uint8_t actionCategory ) const
{
switch( static_cast< StatusEffectType >( effectType ) )
{
case StatusEffectType::AlwaysCombo:
case StatusEffectType::InstantCast:
case StatusEffectType::PotencyMultiplier:
case StatusEffectType::MPRestorePerGCD:
if ( effectValue2 != 0 )
{
if( actionId != effectValue2 && actionId != effectValue3 && actionId != effectValue4 )
return false;
}
else if ( effectValue3 != 0 )
{
uint8_t cat1 = static_cast< uint8_t >( effectValue3 );
uint8_t cat2 = static_cast< uint8_t >( effectValue3 >> 8 );
uint8_t cat3 = static_cast< uint8_t >( effectValue3 >> 16 );
uint8_t cat4 = static_cast< uint8_t >( effectValue3 >> 24 );
bool passed = false;
if( cat1 != 0 && actionCategory == cat1 )
passed = true;
else if ( cat2 != 0 && actionCategory == cat2 )
passed = true;
else if ( cat3 != 0 && actionCategory == cat3 )
passed = true;
else if ( cat4 != 0 && actionCategory == cat4 )
passed = true;
if( !passed )
return false;
}
return true;
default:
return true;
}
}
Sapphire::World::Action::ActionEntry::ActionEntry( uint16_t dp, uint16_t dcp, uint16_t ddp, uint16_t hp, uint16_t ss, uint32_t ssd, uint16_t ssp, uint16_t ts, uint32_t tsd, uint16_t tsp, uint8_t be, uint8_t br, uint32_t bdu32 )
{
damagePotency = dp;
damageComboPotency = dcp;
damageDirectionalPotency = ddp;
healPotency = hp;
selfStatus = ss;
selfStatusDuration = ssd;
selfStatusParam = ssp;
targetStatus = ts;
targetStatusDuration = tsd;
targetStatusParam = tsp;
bonusEffect = be;
bonusRequirement = br;
bonusDataUInt32 = bdu32;
}
uint32_t Sapphire::World::Action::ActionEntry::getRawBonusData() const
{
return bonusDataUInt32;
}
uint8_t Sapphire::World::Action::ActionEntry::getDamageFallOffPercentage() const
{
if( bonusEffect & ActionBonusEffect::DamageFallOff )
return bonusDataByte1;
return 100;
}
uint16_t Sapphire::World::Action::ActionEntry::getSelfHealPotency() const
{
if( bonusEffect & ActionBonusEffect::SelfHeal )
return bonusDataUInt16L;
return 0;
}
uint16_t Sapphire::World::Action::ActionEntry::getMPGainPercentage() const
{
if( bonusEffect & ActionBonusEffect::GainMPPercentage )
return bonusDataUInt16L;
return 0;
}
ClassJob Sapphire::World::Action::ActionEntry::getAffectedJob() const
{
if( bonusEffect & ActionBonusEffect::GainJobResource ||
bonusEffect & ActionBonusEffect::GainJobTimer )
return static_cast< Common::ClassJob >( bonusDataByte3 );
return ClassJob::Adventurer;
}
uint8_t Sapphire::World::Action::ActionEntry::getJobResourceGain() const
{
if( bonusEffect & ActionBonusEffect::GainJobResource )
return bonusDataByte4;
return 0;
}
uint16_t Sapphire::World::Action::ActionEntry::getJobTimerGain() const
{
if( bonusEffect & ActionBonusEffect::GainJobTimer )
return bonusDataUInt16L;
return 0;
}
uint16_t Sapphire::World::Action::ActionEntry::getCritRateBonus() const
{
if( bonusEffect & ActionBonusEffect::CritBonus )
return bonusDataUInt16L;
return 0;
}
uint16_t Sapphire::World::Action::ActionEntry::getDirectHitRateBonus() const
{
if( bonusEffect & ActionBonusEffect::DHBonus )
return bonusDataUInt16L;
return 0;
}

View file

@ -2,29 +2,110 @@
#define SAPPHIRE_ACTIONLUT_H #define SAPPHIRE_ACTIONLUT_H
#include <unordered_map> #include <unordered_map>
#include <Common.h>
using namespace Sapphire::Common;
namespace Sapphire::World::Action namespace Sapphire::World::Action
{ {
struct ActionEntry struct ActionEntry
{ {
uint16_t potency; public:
uint16_t comboPotency; uint16_t damagePotency;
uint16_t flankPotency; uint16_t damageComboPotency;
uint16_t frontPotency; uint16_t damageDirectionalPotency;
uint16_t rearPotency; uint16_t healPotency;
uint16_t curePotency; uint16_t selfStatus;
uint16_t restoreMPPercentage; uint32_t selfStatusDuration;
uint16_t selfStatusParam;
uint16_t targetStatus;
uint32_t targetStatusDuration;
uint16_t targetStatusParam;
uint8_t bonusEffect;
uint8_t bonusRequirement;
private:
union
{
uint32_t bonusDataUInt32;
struct
{
uint16_t bonusDataUInt16L;
uint16_t bonusDataUInt16H;
};
struct
{
uint8_t bonusDataByte1;
uint8_t bonusDataByte2;
uint8_t bonusDataByte3;
uint8_t bonusDataByte4;
};
};
public:
ActionEntry() = default;
ActionEntry( uint16_t dp, uint16_t dcp, uint16_t ddp, uint16_t hp, uint16_t ss, uint32_t ssd, uint16_t ssp, uint16_t ts, uint32_t tsd, uint16_t tsp, uint8_t be, uint8_t br, uint32_t bdu32 );
uint32_t getRawBonusData() const;
uint8_t getDamageFallOffPercentage() const; // as the result percentage for 2nd (or more) victims, not the percentage to subtract from 100%
uint16_t getSelfHealPotency() const;
uint16_t getMPGainPercentage() const;
ClassJob getAffectedJob() const;
uint8_t getJobResourceGain() const;
uint16_t getJobTimerGain() const;
uint16_t getCritRateBonus() const;
uint16_t getDirectHitRateBonus() const;
};
struct StatusEffectEntry
{
private:
uint32_t effectType;
int32_t effectValue1;
int32_t effectValue2;
int32_t effectValue3;
int32_t effectValue4;
public:
StatusEffectEntry() = default;
StatusEffectEntry( uint32_t type, int32_t v1, int32_t v2, int32_t v3, int32_t v4 );
void init( StatusEffectType type, int32_t v1, int32_t v2, int32_t v3, int32_t v4 );
StatusEffectType getType() const;
ActionTypeFilter getActionTypeFilter() const;
int32_t getMPRestoreTick() const;
int32_t getDotHotPotency() const;
int32_t getOutgoingDamageMultiplier() const;
int32_t getIncomingDamageMultiplier() const;
int32_t getOutgoingHealMultiplier() const;
int32_t getIncomingHealMultiplier() const;
int32_t getPotencyMultiplier() const;
int32_t getGCDBasedMPRestorePercentage() const;
int32_t getRemainingCharges() const; // -1 is infinite charges
void setRemainingCharges( int32_t charges );
int32_t getRemainingShield() const;
void setRemainingShield( int32_t shield );
int32_t getHasteBonus() const;
int32_t getBlockRateBonus() const;
int32_t getParryRateBonus() const;
int32_t getCritRateBonus() const;
int32_t getDirectHitRateBonus() const;
StatusEffectTriggerResult getTriggerResult() const;
int32_t getTriggerValue() const;
ActionTypeFilter getTriggerDamageType() const;
bool canApplyToAction( uint32_t actionId, uint8_t actionCategory ) const;
}; };
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

@ -19,18 +19,12 @@ using namespace Sapphire::Network::Packets;
EffectBuilder::EffectBuilder( Entity::CharaPtr source, uint32_t actionId, uint16_t sequence ) : EffectBuilder::EffectBuilder( Entity::CharaPtr source, uint32_t actionId, uint16_t sequence ) :
m_sourceChara( std::move( source ) ), m_sourceChara( std::move( source ) ),
m_actionId( actionId ), m_actionId( actionId ),
m_animationLock( 0.6f ),
m_sequence( sequence ) m_sequence( sequence )
{ {
} }
uint64_t EffectBuilder::getResultDelayMs()
{
// todo: actually figure this retarded shit out
return Common::Util::getTimeMs() + 850;
}
void EffectBuilder::moveToResultList( Entity::CharaPtr& chara, EffectResultPtr result ) void EffectBuilder::moveToResultList( Entity::CharaPtr& chara, EffectResultPtr result )
{ {
auto it = m_resolvedEffects.find( chara->getId() ); auto it = m_resolvedEffects.find( chara->getId() );
@ -49,27 +43,48 @@ void EffectBuilder::moveToResultList( Entity::CharaPtr& chara, EffectResultPtr r
it->second->push_back( std::move( result ) ); it->second->push_back( std::move( result ) );
} }
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, uint64_t resultDelayMs )
{ {
EffectResultPtr nextResult = make_EffectResult( healingTarget, getResultDelayMs() ); EffectResultPtr nextResult = make_EffectResult( healingTarget, Common::Util::getTimeMs() + resultDelayMs );
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, uint64_t resultDelayMs )
{ {
EffectResultPtr nextResult = make_EffectResult( restoringTarget, getResultDelayMs() ); // restore mp source actor EffectResultPtr nextResult = make_EffectResult( restoringTarget, Common::Util::getTimeMs() + resultDelayMs );
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::dodge( Entity::CharaPtr& effectTarget, Entity::CharaPtr& dodgingTarget, Common::ActionEffectResultFlag flag )
{ {
EffectResultPtr nextResult = make_EffectResult( damagingTarget, getResultDelayMs() ); EffectResultPtr nextResult = make_EffectResult( dodgingTarget, 0 );
nextResult->dodge( flag );
moveToResultList( effectTarget, nextResult );
}
void EffectBuilder::damage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag, uint64_t resultDelayMs )
{
EffectResultPtr nextResult = make_EffectResult( damagingTarget, Common::Util::getTimeMs() + resultDelayMs );
nextResult->damage( amount, severity, flag ); nextResult->damage( amount, severity, flag );
moveToResultList( effectTarget, nextResult ); moveToResultList( effectTarget, nextResult );
} }
void EffectBuilder::blockedDamage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, uint16_t rate, Common::ActionEffectResultFlag flag, uint64_t resultDelayMs )
{
EffectResultPtr nextResult = make_EffectResult( damagingTarget, Common::Util::getTimeMs() + resultDelayMs );
nextResult->blockedDamage( amount, rate, flag );
moveToResultList( effectTarget, nextResult );
}
void EffectBuilder::parriedDamage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, uint16_t rate, Common::ActionEffectResultFlag flag, uint64_t resultDelayMs )
{
EffectResultPtr nextResult = make_EffectResult( damagingTarget, Common::Util::getTimeMs() + resultDelayMs );
nextResult->parriedDamage( amount, rate, flag );
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, 0 );
@ -84,25 +99,51 @@ void EffectBuilder::comboSucceed( Entity::CharaPtr& target )
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, uint16_t param, uint64_t resultDelayMs )
{ {
EffectResultPtr nextResult = make_EffectResult( target, 0 ); EffectResultPtr nextResult = make_EffectResult( target, source, Common::Util::getTimeMs() + resultDelayMs );
nextResult->applyStatusEffect( statusId, param ); nextResult->applyStatusEffect( statusId, duration, param );
moveToResultList( target, nextResult ); moveToResultList( target, nextResult );
} }
void EffectBuilder::mount( Entity::CharaPtr& target, uint16_t mountId ) void EffectBuilder::applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, StatusEffect::StatusEffectPtr pStatusEffect, uint64_t resultDelayMs )
{ {
EffectResultPtr nextResult = make_EffectResult( target, getResultDelayMs() ); EffectResultPtr nextResult = make_EffectResult( target, source, Common::Util::getTimeMs() + resultDelayMs );
nextResult->applyStatusEffect( pStatusEffect );
moveToResultList( target, nextResult );
}
void EffectBuilder::statusNoEffect( Entity::CharaPtr& target, uint16_t statusId )
{
EffectResultPtr nextResult = make_EffectResult( target, 0 );
nextResult->statusNoEffect( statusId );
moveToResultList( target, nextResult );
}
void EffectBuilder::mount( Entity::CharaPtr& target, uint16_t mountId, uint64_t resultDelayMs )
{
EffectResultPtr nextResult = make_EffectResult( target, Common::Util::getTimeMs() + resultDelayMs );
nextResult->mount( mountId ); nextResult->mount( mountId );
moveToResultList( target, nextResult ); moveToResultList( target, nextResult );
} }
void Sapphire::World::Action::EffectBuilder::provoke( Entity::CharaPtr& target )
{
EffectResultPtr nextResult = make_EffectResult( target, 0 );
nextResult->provoke();
moveToResultList( target, nextResult );
}
void EffectBuilder::setAnimationLock( float animationLock )
{
m_animationLock = animationLock;
}
void EffectBuilder::buildAndSendPackets() void EffectBuilder::buildAndSendPackets()
{ {
auto targetCount = m_resolvedEffects.size(); auto targetCount = m_resolvedEffects.size();
Logger::debug( "EffectBuilder result: " ); //Logger::debug( "EffectBuilder result: " );
Logger::debug( "Targets afflicted: {}", targetCount ); //Logger::debug( "Targets afflicted: {}", targetCount );
auto globalSequence = m_sourceChara->getCurrentTerritory()->getNextEffectSequence(); auto globalSequence = m_sourceChara->getCurrentTerritory()->getNextEffectSequence();
@ -183,6 +224,7 @@ std::shared_ptr< FFXIVPacketBase > EffectBuilder::buildNextEffectPacket( uint32_
pHeader->effectCount = static_cast< uint8_t >( remainingTargetCount > packetSize ? packetSize : remainingTargetCount ); pHeader->effectCount = static_cast< uint8_t >( remainingTargetCount > packetSize ? packetSize : remainingTargetCount );
pHeader->sourceSequence = m_sequence; pHeader->sourceSequence = m_sequence;
pHeader->globalSequence = globalSequence; pHeader->globalSequence = globalSequence;
pHeader->animationLockTime = m_animationLock;
uint8_t targetIndex = 0; uint8_t targetIndex = 0;
for( auto it = m_resolvedEffects.begin(); it != m_resolvedEffects.end(); ) for( auto it = m_resolvedEffects.begin(); it != m_resolvedEffects.end(); )
@ -191,7 +233,7 @@ std::shared_ptr< FFXIVPacketBase > EffectBuilder::buildNextEffectPacket( uint32_
assert( !resultList->empty() ); assert( !resultList->empty() );
auto firstResult = resultList->data()[ 0 ]; auto firstResult = resultList->data()[ 0 ];
pEffectTargetId[ targetIndex ] = firstResult->getTarget()->getId(); pEffectTargetId[ targetIndex ] = firstResult->getTarget()->getId();
Logger::debug( " - id: {}", pEffectTargetId[ targetIndex ] ); //Logger::debug( " - id: {}", pEffectTargetId[ targetIndex ] );
for( auto i = 0; i < resultList->size(); i++ ) for( auto i = 0; i < resultList->size(); i++ )
{ {
@ -220,13 +262,14 @@ std::shared_ptr< FFXIVPacketBase > EffectBuilder::buildNextEffectPacket( uint32_
auto resultList = m_resolvedEffects.begin()->second; auto resultList = m_resolvedEffects.begin()->second;
assert( !resultList->empty() ); assert( !resultList->empty() );
auto firstResult = resultList->data()[ 0 ]; auto firstResult = resultList->data()[ 0 ];
Logger::debug( " - id: {}", firstResult->getTarget()->getId() ); //Logger::debug( " - id: {}", firstResult->getTarget()->getId() );
auto seq = m_sourceChara->getCurrentTerritory()->getNextEffectSequence(); auto seq = m_sourceChara->getCurrentTerritory()->getNextEffectSequence();
auto effectPacket = std::make_shared< Server::EffectPacket >( m_sourceChara->getId(), firstResult->getTarget()->getId(), m_actionId ); auto effectPacket = std::make_shared< Server::EffectPacket >( m_sourceChara->getId(), firstResult->getTarget()->getId(), m_actionId );
effectPacket->setRotation( Common::Util::floatToUInt16Rot( m_sourceChara->getRot() ) ); effectPacket->setRotation( Common::Util::floatToUInt16Rot( m_sourceChara->getRot() ) );
effectPacket->setSequence( seq, m_sequence ); effectPacket->setSequence( seq, m_sequence );
effectPacket->data().animationLockTime = m_animationLock;
for( int i = 0; i < resultList->size(); i++ ) for( int i = 0; i < resultList->size(); i++ )
{ {
@ -254,6 +297,7 @@ std::shared_ptr< FFXIVPacketBase > EffectBuilder::buildNextEffectPacket( uint32_
effectPacket->data().effectCount = 0; effectPacket->data().effectCount = 0;
effectPacket->data().sourceSequence = m_sequence; effectPacket->data().sourceSequence = m_sequence;
effectPacket->data().globalSequence = globalSequence; effectPacket->data().globalSequence = globalSequence;
effectPacket->data().animationLockTime = m_animationLock;
return effectPacket; return effectPacket;
} }

View file

@ -11,37 +11,50 @@ namespace Sapphire::World::Action
public: public:
EffectBuilder( Entity::CharaPtr source, uint32_t actionId, uint16_t sequence ); EffectBuilder( Entity::CharaPtr source, uint32_t actionId, uint16_t sequence );
void setAnimationLock( float animationLock );
void heal( Entity::CharaPtr& effectTarget, Entity::CharaPtr& healingTarget, uint32_t amount, void heal( Entity::CharaPtr& effectTarget, Entity::CharaPtr& healingTarget, uint32_t amount,
Common::ActionHitSeverityType severity = Common::ActionHitSeverityType::NormalHeal, Common::ActionHitSeverityType severity = Common::ActionHitSeverityType::NormalHeal,
Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None); Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None, uint64_t resultDelayMs = 600 );
void restoreMP( Entity::CharaPtr& effectTarget, Entity::CharaPtr& restoringTarget, uint32_t amount, void restoreMP( Entity::CharaPtr& effectTarget, Entity::CharaPtr& restoringTarget, uint32_t amount,
Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None); Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None, uint64_t resultDelayMs = 600 );
void dodge( Entity::CharaPtr& effectTarget, Entity::CharaPtr& dodgingTarget, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None );
void damage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, void damage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount,
Common::ActionHitSeverityType severity = Common::ActionHitSeverityType::NormalDamage, Common::ActionHitSeverityType severity = Common::ActionHitSeverityType::NormalDamage,
Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None); Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None, uint64_t resultDelayMs = 600 );
void blockedDamage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, uint16_t rate,
Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None, uint64_t resultDelayMs = 600 );
void parriedDamage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, uint16_t rate,
Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None, uint64_t resultDelayMs = 600 );
void startCombo( Entity::CharaPtr& target, uint16_t actionId ); void startCombo( Entity::CharaPtr& target, uint16_t actionId );
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, uint16_t param, uint64_t resultDelayMs = 500 );
void applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, StatusEffect::StatusEffectPtr pStatusEffect, uint64_t resultDelayMs = 500 );
void mount( Entity::CharaPtr& target, uint16_t mountId ); void statusNoEffect( Entity::CharaPtr& target, uint16_t statusId );
void mount( Entity::CharaPtr& target, uint16_t mountId, uint64_t resultDelayMs = 600 );
void provoke( Entity::CharaPtr& target );
void buildAndSendPackets(); void buildAndSendPackets();
private: private:
void moveToResultList( Entity::CharaPtr& chara, EffectResultPtr result ); void moveToResultList( Entity::CharaPtr& chara, EffectResultPtr result );
uint64_t getResultDelayMs();
std::shared_ptr< Sapphire::Network::Packets::FFXIVPacketBase > buildNextEffectPacket( uint32_t globalSequence ); std::shared_ptr< Sapphire::Network::Packets::FFXIVPacketBase > buildNextEffectPacket( uint32_t globalSequence );
private: private:
uint32_t m_actionId; uint32_t m_actionId;
float m_animationLock;
uint16_t m_sequence; uint16_t m_sequence;
Entity::CharaPtr m_sourceChara; Entity::CharaPtr m_sourceChara;

View file

@ -9,19 +9,32 @@ 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_statusDuration( 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 ),
m_pPreBuiltStatusEffect( nullptr )
{ {
} }
EffectResult::EffectResult( Entity::CharaPtr target, uint64_t delayMs ) :
EffectResult::EffectResult( std::move( target ), nullptr, delayMs )
{
}
Entity::CharaPtr EffectResult::getSource() const
{
return m_source;
}
Entity::CharaPtr EffectResult::getTarget() const Entity::CharaPtr EffectResult::getTarget() const
{ {
return m_target; return m_target;
@ -37,6 +50,13 @@ uint64_t EffectResult::getDelay()
return m_delayMs; return m_delayMs;
} }
void EffectResult::dodge( Common::ActionEffectResultFlag flag )
{
m_flag = flag;
m_type = Common::ActionEffectType::Miss;
}
void EffectResult::damage( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag ) void EffectResult::damage( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag )
{ {
m_param0 = static_cast< uint8_t >( severity ); m_param0 = static_cast< uint8_t >( severity );
@ -46,6 +66,24 @@ void EffectResult::damage( uint32_t amount, Common::ActionHitSeverityType severi
m_type = Common::ActionEffectType::Damage; m_type = Common::ActionEffectType::Damage;
} }
void EffectResult::blockedDamage( uint32_t amount, uint16_t rate, Common::ActionEffectResultFlag flag )
{
m_value = amount;
m_flag = flag;
m_param2 = rate;
m_type = Common::ActionEffectType::BlockedDamage;
}
void EffectResult::parriedDamage( uint32_t amount, uint16_t rate, Common::ActionEffectResultFlag flag )
{
m_value = amount;
m_flag = flag;
m_param2 = rate;
m_type = Common::ActionEffectType::ParriedDamage;
}
void EffectResult::heal( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag ) void EffectResult::heal( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag )
{ {
m_param1 = static_cast< uint8_t >( severity ); m_param1 = static_cast< uint8_t >( severity );
@ -77,14 +115,31 @@ 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, uint16_t param )
{ {
m_value = statusId; m_value = statusId;
m_statusDuration = duration;
m_param2 = param; m_param2 = param;
m_type = Common::ActionEffectType::ApplyStatusEffectTarget; m_type = Common::ActionEffectType::ApplyStatusEffectTarget;
} }
void EffectResult::applyStatusEffect( StatusEffect::StatusEffectPtr pStatusEffect )
{
m_value = pStatusEffect->getId();
m_param2 = pStatusEffect->getParam();
m_pPreBuiltStatusEffect = std::move( pStatusEffect );
m_type = Common::ActionEffectType::ApplyStatusEffectTarget;
}
void EffectResult::statusNoEffect( uint16_t statusId )
{
m_value = statusId;
m_type = Common::ActionEffectType::StatusNoEffect;
}
void EffectResult::mount( uint16_t mountId ) void EffectResult::mount( uint16_t mountId )
{ {
m_value = mountId; m_value = mountId;
@ -93,6 +148,11 @@ void EffectResult::mount( uint16_t mountId )
m_type = Common::ActionEffectType::Mount; m_type = Common::ActionEffectType::Mount;
} }
void Sapphire::World::Action::EffectResult::provoke()
{
m_type = Common::ActionEffectType::Provoke;
}
Common::EffectEntry EffectResult::buildEffectEntry() const Common::EffectEntry EffectResult::buildEffectEntry() const
{ {
Common::EffectEntry entry{}; Common::EffectEntry entry{};
@ -110,7 +170,7 @@ Common::EffectEntry EffectResult::buildEffectEntry() const
} }
entry.param0 = m_param0; entry.param0 = m_param0;
entry.param1 = m_param1; entry.param1 = m_param1;
entry.param2 = m_param2; entry.param2 = static_cast< uint8_t >( m_param2 );
return entry; return entry;
} }
@ -120,6 +180,8 @@ void EffectResult::execute()
switch( m_type ) switch( m_type )
{ {
case Common::ActionEffectType::Damage: case Common::ActionEffectType::Damage:
case Common::ActionEffectType::BlockedDamage:
case Common::ActionEffectType::ParriedDamage:
{ {
m_target->takeDamage( m_value ); m_target->takeDamage( m_value );
break; break;
@ -137,6 +199,38 @@ void EffectResult::execute()
break; break;
} }
case Common::ActionEffectType::ApplyStatusEffectTarget:
case Common::ActionEffectType::ApplyStatusEffectSource:
{
//refreshing old buff
for( auto const& entry : m_target->getStatusEffectMap() )
{
auto statusEffect = entry.second;
if( statusEffect->getId() == m_value && statusEffect->getSrcActorId() == m_source->getId() )
{
if( m_pPreBuiltStatusEffect )
{
statusEffect->refresh( m_pPreBuiltStatusEffect->getEffectEntry() );
}
else
{
statusEffect->refresh();
}
m_target->sendStatusEffectUpdate();
return;
}
}
if( m_pPreBuiltStatusEffect )
{
m_target->addStatusEffect( m_pPreBuiltStatusEffect );
}
else
m_target->addStatusEffectById( m_value, m_statusDuration, *m_source, m_param2 );
break;
}
case Common::ActionEffectType::Mount: case Common::ActionEffectType::Mount:
{ {
auto pPlayer = m_target->getAsPlayer(); auto pPlayer = m_target->getAsPlayer();

View file

@ -4,6 +4,8 @@
#include <ForwardsZone.h> #include <ForwardsZone.h>
#include <Common.h> #include <Common.h>
#include "StatusEffect/StatusEffect.h"
namespace Sapphire::World::Action namespace Sapphire::World::Action
{ {
/*! /*!
@ -13,16 +15,24 @@ namespace Sapphire::World::Action
class EffectResult class EffectResult
{ {
public: public:
explicit EffectResult( Entity::CharaPtr target, Entity::CharaPtr source, uint64_t delayMs );
explicit EffectResult( Entity::CharaPtr target, uint64_t delayMs ); explicit EffectResult( Entity::CharaPtr target, uint64_t delayMs );
void dodge( Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None );
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 blockedDamage( uint32_t amount, uint16_t rate, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None );
void parriedDamage( uint32_t amount, uint16_t rate, 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, uint16_t param );
void applyStatusEffect( StatusEffect::StatusEffectPtr pStatusEffect );
void statusNoEffect( uint16_t statusId );
void mount( uint16_t mountId ); void mount( uint16_t mountId );
void provoke();
Entity::CharaPtr getSource() const;
Entity::CharaPtr getTarget() const; Entity::CharaPtr getTarget() const;
uint32_t getValue() const; uint32_t getValue() const;
@ -36,16 +46,20 @@ 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;
uint8_t m_param0; uint8_t m_param0;
uint8_t m_param1; uint8_t m_param1;
uint8_t m_param2; uint16_t m_param2;
uint32_t m_value; uint32_t m_value;
uint32_t m_statusDuration;
Common::ActionEffectResultFlag m_flag; Common::ActionEffectResultFlag m_flag;
StatusEffect::StatusEffectPtr m_pPreBuiltStatusEffect;
}; };
} }

View file

@ -90,11 +90,13 @@ void Action::EventAction::execute()
} }
void Action::EventAction::interrupt() void Action::EventAction::interrupt( Common::ActionInterruptType type )
{ {
if( !m_pSource ) if( !m_pSource )
return; return;
m_interruptType = type;
try try
{ {

View file

@ -22,7 +22,7 @@ public:
void execute() override; void execute() override;
void interrupt() override; void interrupt( Common::ActionInterruptType type = Common::ActionInterruptType::RegularInterrupt ) override;
private: private:
uint32_t m_eventId; uint32_t m_eventId;

View file

@ -64,7 +64,7 @@ void ItemAction::execute()
} }
} }
void ItemAction::interrupt() void ItemAction::interrupt( Common::ActionInterruptType type )
{ {
} }

View file

@ -22,7 +22,7 @@ namespace Sapphire::World::Action
void execute() override; void execute() override;
void interrupt() override; void interrupt( Common::ActionInterruptType type = Common::ActionInterruptType::RegularInterrupt ) override;
private: private:
void handleVFXItem(); void handleVFXItem();

View file

@ -679,9 +679,14 @@ void Sapphire::Entity::BNpc::setFlag( uint32_t flag )
m_flags |= flag; m_flags |= flag;
} }
/*!
TODO: this only solves attacks from melee classes.
Will have to be extended for ranged attacks.
\param ActorPtr the autoAttack is performed on
*/
void Sapphire::Entity::BNpc::autoAttack( CharaPtr pTarget ) void Sapphire::Entity::BNpc::autoAttack( CharaPtr pTarget )
{ {
uint64_t tick = Util::getTimeMs(); uint64_t tick = Util::getTimeMs();
// todo: this needs to use the auto attack delay for the equipped weapon // todo: this needs to use the auto attack delay for the equipped weapon
@ -689,23 +694,20 @@ void Sapphire::Entity::BNpc::autoAttack( CharaPtr pTarget )
{ {
pTarget->onActionHostile( getAsChara() ); pTarget->onActionHostile( getAsChara() );
m_lastAttack = tick; m_lastAttack = tick;
srand( static_cast< uint32_t >( tick ) );
auto damage = Math::CalcStats::calcAutoAttackDamage( *this ); auto& exdData = Common::Service< Data::ExdDataGenerated >::ref();
auto actionData = exdData.get< Data::Action >( 7 );
assert( actionData );
auto action = World::Action::make_Action( getAsChara(), 7, 0, actionData );
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 ); action->setTargetId( pTarget->getId() );
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) ); action->setPos( getPos() );
Common::EffectEntry effectEntry{}; action->setAutoAttack();
effectEntry.value = static_cast< int16_t >( damage.first );
effectEntry.effectType = ActionEffectType::Damage;
effectEntry.param0 = static_cast< uint8_t >( damage.second );
effectEntry.param2 = 0x71;
effectPacket->addEffect( effectEntry );
sendToInRangeSet( effectPacket );
pTarget->takeDamage( static_cast< uint16_t >( damage.first ) );
if( action->init() )
{
action->start();
}
} }
} }

View file

@ -391,8 +391,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
*/ */
@ -483,43 +481,8 @@ uint32_t Sapphire::Entity::Chara::getBonusStat( Common::BaseParam bonus ) const
return m_bonusStats[ static_cast< uint8_t >( bonus ) ]; return m_bonusStats[ static_cast< uint8_t >( bonus ) ];
} }
/*!
Autoattack prototype implementation
TODO: move the check if the autoAttack can be performed to the callee
also rename autoAttack to autoAttack as that is more elaborate
On top of that, this only solves attacks from melee classes.
Will have to be extended for ranged attacks.
\param ActorPtr the autoAttack is performed on
*/
void Sapphire::Entity::Chara::autoAttack( CharaPtr pTarget ) void Sapphire::Entity::Chara::autoAttack( CharaPtr pTarget )
{ {
uint64_t tick = Util::getTimeMs();
// todo: this needs to use the auto attack delay for the equipped weapon
if( ( tick - m_lastAttack ) > 2500 )
{
pTarget->onActionHostile( getAsChara() );
m_lastAttack = tick;
srand( static_cast< uint32_t >( tick ) );
auto damage = static_cast< uint16_t >( 10 + rand() % 12 );
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 );
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
Common::EffectEntry effectEntry{};
effectEntry.value = static_cast< int16_t >( damage );
effectEntry.effectType = ActionEffectType::Damage;
effectEntry.param0 = static_cast< uint8_t >( ActionHitSeverityType::NormalDamage );
effectEntry.param2 = 0x71;
effectPacket->addEffect( effectEntry );
sendToInRangeSet( effectPacket );
pTarget->takeDamage( damage );
}
} }
/*! \param StatusEffectPtr to be applied to the actor */ /*! \param StatusEffectPtr to be applied to the actor */
@ -551,28 +514,49 @@ void Sapphire::Entity::Chara::addStatusEffect( StatusEffect::StatusEffectPtr pEf
status.index = static_cast< uint8_t >( nextSlot ); status.index = static_cast< uint8_t >( nextSlot );
status.param = pEffect->getParam(); status.param = pEffect->getParam();
sendToInRangeSet( statusEffectAdd, isPlayer() ); float totalShieldValue = 0;
for( auto effectIt : m_statusEffectMap )
{
auto statusEffect = effectIt.second;
totalShieldValue += statusEffect->getEffectEntry().getRemainingShield();
}
if( totalShieldValue > 0 )
{
totalShieldValue /= getMaxHp();
totalShieldValue *= 100;
statusEffectAdd->data().shieldPercentage = static_cast< uint8_t >( std::min( 255.0f, totalShieldValue ) );
}
sendToInRangeSet( statusEffectAdd, true );
sendStatusEffectUpdate(); // although client buff displays correctly without this but retail sends it so we do it as well
} }
/*! \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, bool sendActorControl )
void Sapphire::Entity::Chara::addStatusEffectById( uint32_t id, int32_t duration, Entity::Chara& source, uint16_t param )
{ {
auto effect = StatusEffect::make_StatusEffect( id, source.getAsChara(), getAsChara(), duration, 3000 ); auto effect = StatusEffect::make_StatusEffect( id, source.getAsChara(), getAsChara(), duration, 3000 );
effect->setParam( param ); effect->setParam( param );
if( sendActorControl )
{
auto p = makeActorControl( getId(), Network::ActorControl::StatusEffectGain, id, 0, 0, 0 );
sendToInRangeSet( p, true );
}
addStatusEffect( effect ); addStatusEffect( effect );
} }
/*! \param StatusEffectPtr to be applied to the actor */ void Sapphire::Entity::Chara::addStatusEffectByIdIfNotExist( uint32_t id, int32_t duration, Entity::Chara& source, uint16_t param, bool sendActorControl )
void Sapphire::Entity::Chara::addStatusEffectByIdIfNotExist( uint32_t id, int32_t duration, Entity::Chara& source,
uint16_t param )
{ {
if( hasStatusEffect( id ) ) if( getStatusEffectById( id ).second )
return; return;
auto effect = StatusEffect::make_StatusEffect( id, source.getAsChara(), getAsChara(), duration, 3000 ); auto effect = StatusEffect::make_StatusEffect( id, source.getAsChara(), getAsChara(), duration, 3000 );
effect->setParam( param ); effect->setParam( param );
if( sendActorControl )
{
auto p = makeActorControl( getId(), Network::ActorControl::StatusEffectGain, id, 0, 0, 0 );
sendToInRangeSet( p, true );
}
addStatusEffect( effect ); addStatusEffect( effect );
} }
int8_t Sapphire::Entity::Chara::getStatusEffectFreeSlot() int8_t Sapphire::Entity::Chara::getStatusEffectFreeSlot()
@ -593,33 +577,34 @@ 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 sendStatusList )
{ {
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, sendStatusList );
break; break;
} }
} }
} }
void Sapphire::Entity::Chara::removeStatusEffect( uint8_t effectSlotId ) void Sapphire::Entity::Chara::removeStatusEffect( uint8_t effectSlotId, bool sendStatusList )
{ {
auto pEffectIt = m_statusEffectMap.find( effectSlotId ); auto pEffectIt = m_statusEffectMap.find( effectSlotId );
if( pEffectIt == m_statusEffectMap.end() ) if( pEffectIt == m_statusEffectMap.end() )
return; return;
statusEffectFreeSlot( effectSlotId );
auto pEffect = pEffectIt->second; auto pEffect = pEffectIt->second;
pEffect->removeStatus();
sendToInRangeSet( makeActorControl( getId(), StatusEffectLose, pEffect->getId() ), isPlayer() ); statusEffectFreeSlot( effectSlotId );
m_statusEffectMap.erase( effectSlotId ); m_statusEffectMap.erase( effectSlotId );
pEffect->removeStatus();
sendToInRangeSet( makeActorControl( getId(), StatusEffectLose, pEffect->getId() ), true );
if( sendStatusList )
sendStatusEffectUpdate(); sendStatusEffectUpdate();
} }
@ -652,7 +637,6 @@ void Sapphire::Entity::Chara::sendStatusEffectUpdate()
{ {
uint64_t currentTimeMs = Util::getTimeMs(); uint64_t currentTimeMs = Util::getTimeMs();
auto statusEffectList = makeZonePacket< FFXIVIpcStatusEffectList >( getId() ); auto statusEffectList = makeZonePacket< FFXIVIpcStatusEffectList >( getId() );
statusEffectList->data().classId = static_cast< uint8_t >( getClass() ); statusEffectList->data().classId = static_cast< uint8_t >( getClass() );
statusEffectList->data().level = getLevel(); statusEffectList->data().level = getLevel();
@ -662,18 +646,56 @@ void Sapphire::Entity::Chara::sendStatusEffectUpdate()
statusEffectList->data().max_hp = getMaxHp(); statusEffectList->data().max_hp = getMaxHp();
statusEffectList->data().max_mp = getMaxMp(); statusEffectList->data().max_mp = getMaxMp();
uint8_t slot = 0; uint8_t slot = 0;
float totalShieldValue = 0;
for( auto effectIt : m_statusEffectMap ) for( auto effectIt : m_statusEffectMap )
{ {
float timeLeft = static_cast< float >( effectIt.second->getDuration() - auto statusEffect = effectIt.second;
( currentTimeMs - effectIt.second->getStartTimeMs() ) ) / 1000; totalShieldValue += statusEffect->getEffectEntry().getRemainingShield();
float timeLeft = static_cast< float >( statusEffect->getDuration() -
( currentTimeMs - statusEffect->getStartTimeMs() ) ) / 1000;
statusEffectList->data().effect[ slot ].duration = timeLeft; statusEffectList->data().effect[ slot ].duration = timeLeft;
statusEffectList->data().effect[ slot ].effect_id = effectIt.second->getId(); statusEffectList->data().effect[ slot ].effect_id = statusEffect->getId();
statusEffectList->data().effect[ slot ].sourceActorId = effectIt.second->getSrcActorId(); statusEffectList->data().effect[ slot ].param = statusEffect->getParam();
statusEffectList->data().effect[ slot ].sourceActorId = statusEffect->getSrcActorId();
slot++; slot++;
} }
sendToInRangeSet( statusEffectList, isPlayer() ); if( totalShieldValue > 0 )
{
totalShieldValue /= getMaxHp();
totalShieldValue *= 100;
statusEffectList->data().shieldPercentage = static_cast< uint8_t >( std::min( 255.0f, totalShieldValue ) );
}
sendToInRangeSet( statusEffectList, true );
}
void Sapphire::Entity::Chara::sendShieldUpdate()
{
auto pPacket = makeZonePacket< FFXIVIpcEffectResult >( getId() );
pPacket->data().actor_id = getId();
pPacket->data().current_hp = getHp();
pPacket->data().current_mp = static_cast< uint16_t >( getMp() );
pPacket->data().max_hp = getMaxHp();
pPacket->data().classId = static_cast< uint8_t >( getClass() );
float totalShieldValue = 0;
for( auto effectIt : m_statusEffectMap )
{
auto statusEffect = effectIt.second;
totalShieldValue += statusEffect->getEffectEntry().getRemainingShield();
}
if( totalShieldValue > 0 )
{
totalShieldValue /= getMaxHp();
totalShieldValue *= 100;
pPacket->data().shieldPercentage = static_cast< uint8_t >( std::min( 255.0f, totalShieldValue ) );
}
sendToInRangeSet( pPacket, true );
} }
void Sapphire::Entity::Chara::updateStatusEffects() void Sapphire::Entity::Chara::updateStatusEffects()
@ -690,7 +712,7 @@ 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( duration > 0 && ( currentTimeMs - startTime ) > duration ) if( effect->isMarkedToRemove() || ( duration > 0 && ( currentTimeMs - startTime ) > duration ) )
{ {
// remove status effect // remove status effect
removeStatusEffect( effectIndex ); removeStatusEffect( effectIndex );
@ -706,9 +728,16 @@ void Sapphire::Entity::Chara::updateStatusEffects()
} }
} }
bool Sapphire::Entity::Chara::hasStatusEffect( uint32_t id ) std::pair< uint8_t, Sapphire::StatusEffect::StatusEffectPtr > Sapphire::Entity::Chara::getStatusEffectById( uint32_t id )
{ {
return m_statusEffectMap.find( id ) != m_statusEffectMap.end(); for( auto effectIt : m_statusEffectMap )
{
if( effectIt.second->getId() == id && !effectIt.second->isMarkedToRemove() )
{
return std::make_pair( effectIt.first, effectIt.second );
}
}
return std::make_pair( 0, nullptr );
} }
int64_t Sapphire::Entity::Chara::getLastUpdateTime() const int64_t Sapphire::Entity::Chara::getLastUpdateTime() const
@ -871,6 +900,10 @@ uint32_t Sapphire::Entity::Chara::getStatValue( Sapphire::Common::BaseParam base
case Common::BaseParam::Haste: case Common::BaseParam::Haste:
{ {
value = m_baseStats.haste; value = m_baseStats.haste;
for( auto const& statusIt : m_statusEffectMap )
{
value -= statusIt.second->getEffectEntry().getHasteBonus();
}
break; break;
} }
@ -912,6 +945,40 @@ uint32_t Sapphire::Entity::Chara::getStatValue( Sapphire::Common::BaseParam base
return value + getBonusStat( baseParam ); return value + getBonusStat( baseParam );
} }
float Sapphire::Entity::Chara::applyShieldProtection( float damage )
{
float remainingDamage = damage;
bool shieldChanged = false;
for( auto const& entry : getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.getType() == Sapphire::Common::StatusEffectType::Shield )
{
shieldChanged = true;
if( remainingDamage < effectEntry.getRemainingShield() )
{
effectEntry.setRemainingShield( effectEntry.getRemainingShield() - static_cast< int32_t >( remainingDamage ) );
status->replaceEffectEntry( effectEntry );
remainingDamage = 0;
break;
}
else
{
remainingDamage -= effectEntry.getRemainingShield();
status->markToRemove();
}
}
}
if( shieldChanged )
sendShieldUpdate();
return remainingDamage;
}
uint32_t Sapphire::Entity::Chara::getVisualEffect() uint32_t Sapphire::Entity::Chara::getVisualEffect()
{ {
return m_effect; return m_effect;
@ -957,6 +1024,8 @@ void Sapphire::Entity::Chara::onTick()
if( thisTickDmg != 0 ) if( thisTickDmg != 0 )
{ {
thisTickDmg = applyShieldProtection( thisTickDmg );
if( thisTickDmg > 0 )
takeDamage( thisTickDmg ); takeDamage( thisTickDmg );
sendToInRangeSet( makeActorControl( getId(), HPFloatingText, 0, sendToInRangeSet( makeActorControl( getId(), HPFloatingText, 0,
static_cast< uint8_t >( ActionEffectType::Damage ), thisTickDmg ), true ); static_cast< uint8_t >( ActionEffectType::Damage ), thisTickDmg ), true );

View file

@ -149,13 +149,13 @@ 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 sendStatusList = true );
void removeSingleStatusEffectById( uint32_t id ); void removeSingleStatusEffectById( uint32_t id, bool sendStatusList = true );
void updateStatusEffects(); void updateStatusEffects();
bool hasStatusEffect( uint32_t id ); std::pair< uint8_t, StatusEffect::StatusEffectPtr > getStatusEffectById( uint32_t id );
int8_t getStatusEffectFreeSlot(); int8_t getStatusEffectFreeSlot();
@ -169,19 +169,19 @@ namespace Sapphire::Entity
void sendStatusEffectUpdate(); void sendStatusEffectUpdate();
void sendShieldUpdate();
/*! return a const pointer to the look array */ /*! return a const pointer to the look array */
const uint8_t* getLookArray() const; const uint8_t* getLookArray() const;
const uint32_t* getModelArray() const; const uint32_t* getModelArray() const;
// add a status effect by id // add a status effect by id
void addStatusEffectById( uint32_t id, int32_t duration, Entity::Chara& source, uint16_t param = 0 ); void addStatusEffectById( uint32_t id, int32_t duration, Entity::Chara& source, uint16_t param = 0, bool sendActorControl = false );
// add a status effect by id if it doesn't exist // add a status effect by id if it doesn't exist
void addStatusEffectByIdIfNotExist( uint32_t id, int32_t duration, Entity::Chara& source, uint16_t param = 0 ); void addStatusEffectByIdIfNotExist( uint32_t id, int32_t duration, Entity::Chara& source, uint16_t param = 0, bool sendActorControl = false );
// remove a status effect by id
void removeSingleStatusEffectFromId( uint32_t id );
/// End Status Effect Functions /// End Status Effect Functions
std::string getName() const; std::string getName() const;
@ -291,6 +291,8 @@ namespace Sapphire::Entity
Common::BaseParam getPrimaryStat() const; Common::BaseParam getPrimaryStat() const;
float applyShieldProtection( float damage );
uint32_t getVisualEffect(); uint32_t getVisualEffect();
void setVisualEffect( uint32_t effect, bool sendPacket = true ); void setVisualEffect( uint32_t effect, bool sendPacket = true );
void sendVisualEffect(); void sendVisualEffect();

View file

@ -20,6 +20,8 @@
#include "Manager/TerritoryMgr.h" #include "Manager/TerritoryMgr.h"
#include "Manager/RNGMgr.h" #include "Manager/RNGMgr.h"
#include "Manager/MapMgr.h" #include "Manager/MapMgr.h"
#include <Manager/PlayerMgr.h>
#include "Manager/EventMgr.h"
#include "Territory/Territory.h" #include "Territory/Territory.h"
#include "Territory/ZonePosition.h" #include "Territory/ZonePosition.h"
@ -236,6 +238,10 @@ Sapphire::Common::OnlineStatus Sapphire::Entity::Player::getOnlineStatus() const
void Sapphire::Entity::Player::setOnlineStatusMask( uint64_t status ) void Sapphire::Entity::Player::setOnlineStatusMask( uint64_t status )
{ {
m_onlineStatus = status; m_onlineStatus = status;
sendToInRangeSet( makeActorControl( getId(), SetStatusIcon, static_cast< uint8_t >( getOnlineStatus() ) ), true );
auto statusPacket = makeZonePacket< FFXIVIpcSetOnlineStatus >( getId() );
statusPacket->data().onlineStatusFlags = status;
queuePacket( statusPacket );
} }
uint64_t Sapphire::Entity::Player::getOnlineStatusMask() const uint64_t Sapphire::Entity::Player::getOnlineStatusMask() const
@ -414,10 +420,30 @@ void Sapphire::Entity::Player::teleport( uint16_t aetheryteId, uint8_t type )
} }
void Sapphire::Entity::Player::forceZoneing( uint32_t zoneId ) void Sapphire::Entity::Player::forceZoneing( uint32_t zoneId, float x, float y, float z, float r, bool showZoneName )
{ {
m_queuedZoneing = std::make_shared< QueuedZoning >( zoneId, getPos(), Util::getTimeMs(), 0.f ); if( zoneId == 0 )
//performZoning( zoneId, Common::ZoneingType::None, getPos() ); {
zoneId = getCurrentTerritory()->getTerritoryTypeId();
x = m_pos.x;
y = m_pos.y;
z = m_pos.z;
r = m_rot;
}
else
{
if( x == FLT_MAX )
x = m_pos.x;
if( y == FLT_MAX )
y = m_pos.y;
if( z == FLT_MAX )
z = m_pos.z;
if( r == FLT_MAX )
r = m_rot;
}
Common::FFXIVARR_POSITION3 pos { x, y, z };
m_queuedZoneing = std::make_shared< QueuedZoning >( zoneId, pos, Util::getTimeMs(), r );
prepareZoning( showZoneName ? zoneId : 0, true, 1, 0 );
} }
void Sapphire::Entity::Player::returnToHomepoint() void Sapphire::Entity::Player::returnToHomepoint()
@ -465,13 +491,17 @@ bool Sapphire::Entity::Player::setInstance( TerritoryPtr instance )
auto currentZone = getCurrentTerritory(); auto currentZone = getCurrentTerritory();
// zoning within the same zone won't cause the prev data to be overwritten // zoning within the same zone won't cause the prev data to be overwritten
if( instance->getTerritoryTypeId() != m_territoryTypeId ) if( instance->getTerritoryTypeId() != m_territoryTypeId && ( teriMgr.isDefaultTerritory( currentZone->getTerritoryTypeId() ) || teriMgr.isHousingTerritory( currentZone->getTerritoryTypeId() ) ) )
{
// never returning to a BeforeTrialDung zone.
if( currentZone->getTerritoryTypeInfo()->territoryIntendedUse != Sapphire::World::Manager::TerritoryMgr::TerritoryIntendedUse::BeforeTrialDung )
{ {
m_prevPos = m_pos; m_prevPos = m_pos;
m_prevRot = m_rot; m_prevRot = m_rot;
m_prevTerritoryTypeId = currentZone->getTerritoryTypeId(); m_prevTerritoryTypeId = currentZone->getTerritoryTypeId();
m_prevTerritoryId = getTerritoryId(); m_prevTerritoryId = getTerritoryId();
} }
}
return teriMgr.movePlayer( instance, getAsPlayer() ); return teriMgr.movePlayer( instance, getAsPlayer() );
} }
@ -486,13 +516,17 @@ bool Sapphire::Entity::Player::setInstance( TerritoryPtr instance, Common::FFXIV
auto currentZone = getCurrentTerritory(); auto currentZone = getCurrentTerritory();
// zoning within the same zone won't cause the prev data to be overwritten // zoning within the same zone won't cause the prev data to be overwritten
if( instance->getTerritoryTypeId() != m_territoryTypeId ) if( instance->getTerritoryTypeId() != m_territoryTypeId && ( teriMgr.isDefaultTerritory( currentZone->getTerritoryTypeId() ) || teriMgr.isHousingTerritory( currentZone->getTerritoryTypeId() ) ) )
{
// never returning to a BeforeTrialDung zone.
if( currentZone->getTerritoryTypeInfo()->territoryIntendedUse != Sapphire::World::Manager::TerritoryMgr::TerritoryIntendedUse::BeforeTrialDung )
{ {
m_prevPos = m_pos; m_prevPos = m_pos;
m_prevRot = m_rot; m_prevRot = m_rot;
m_prevTerritoryTypeId = currentZone->getTerritoryTypeId(); m_prevTerritoryTypeId = currentZone->getTerritoryTypeId();
m_prevTerritoryId = getTerritoryId(); m_prevTerritoryId = getTerritoryId();
} }
}
m_pos = pos; m_pos = pos;
m_rot = rot; m_rot = rot;
@ -797,15 +831,7 @@ void Sapphire::Entity::Player::gainLevel()
m_hp = getMaxHp(); m_hp = getMaxHp();
m_mp = getMaxMp(); m_mp = getMaxMp();
auto effectListPacket = makeZonePacket< FFXIVIpcStatusEffectList >( getId() ); sendStatusEffectUpdate();
effectListPacket->data().classId = static_cast< uint8_t > ( getClass() );
effectListPacket->data().level1 = getLevel();
effectListPacket->data().level = getLevel();
effectListPacket->data().current_hp = getMaxHp();
effectListPacket->data().current_mp = getMaxMp();
effectListPacket->data().max_hp = getMaxHp();
effectListPacket->data().max_mp = getMaxMp();
sendToInRangeSet( effectListPacket, true );
sendToInRangeSet( makeActorControl( getId(), LevelUpEffect, static_cast< uint8_t >( getClass() ), sendToInRangeSet( makeActorControl( getId(), LevelUpEffect, static_cast< uint8_t >( getClass() ),
getLevel(), getLevel() - 1 ), true ); getLevel(), getLevel() - 1 ), true );
@ -987,6 +1013,13 @@ void Sapphire::Entity::Player::spawn( Entity::PlayerPtr pTarget )
Logger::debug( "[{0}] Spawning {1} for {2}", pTarget->getId(), getName(), pTarget->getName() ); Logger::debug( "[{0}] Spawning {1} for {2}", pTarget->getId(), getName(), pTarget->getName() );
pTarget->queuePacket( std::make_shared< PlayerSpawnPacket >( *getAsPlayer(), *pTarget ) ); pTarget->queuePacket( std::make_shared< PlayerSpawnPacket >( *getAsPlayer(), *pTarget ) );
if( m_effect > 0 )
{
auto effect = makeZonePacket< FFXIVIpcCharaVisualEffect >( pTarget->getId() );
effect->setSourceActor( getId() );
effect->data().id = m_effect;
pTarget->queuePacket( effect );
}
} }
// despawn // despawn
@ -1080,7 +1113,6 @@ bool Sapphire::Entity::Player::hasStateFlag( Common::PlayerStateFlag flag ) cons
void Sapphire::Entity::Player::setStateFlag( Common::PlayerStateFlag flag ) void Sapphire::Entity::Player::setStateFlag( Common::PlayerStateFlag flag )
{ {
auto prevOnlineStatus = getOnlineStatus();
int32_t iFlag = static_cast< uint32_t >( flag ); int32_t iFlag = static_cast< uint32_t >( flag );
uint16_t index; uint16_t index;
@ -1089,13 +1121,6 @@ void Sapphire::Entity::Player::setStateFlag( Common::PlayerStateFlag flag )
m_stateFlags[ index ] |= value; m_stateFlags[ index ] |= value;
sendStateFlags(); sendStateFlags();
auto newOnlineStatus = getOnlineStatus();
if( prevOnlineStatus != newOnlineStatus )
sendToInRangeSet( makeActorControl( getId(), SetStatusIcon,
static_cast< uint8_t >( getOnlineStatus() ) ), true );
} }
void Sapphire::Entity::Player::setStateFlags( std::vector< Common::PlayerStateFlag > flags ) void Sapphire::Entity::Player::setStateFlags( std::vector< Common::PlayerStateFlag > flags )
@ -1116,8 +1141,6 @@ void Sapphire::Entity::Player::unsetStateFlag( Common::PlayerStateFlag flag )
if( !hasStateFlag( flag ) ) if( !hasStateFlag( flag ) )
return; return;
auto prevOnlineStatus = getOnlineStatus();
int32_t iFlag = static_cast< uint32_t >( flag ); int32_t iFlag = static_cast< uint32_t >( flag );
uint16_t index; uint16_t index;
@ -1126,11 +1149,6 @@ void Sapphire::Entity::Player::unsetStateFlag( Common::PlayerStateFlag flag )
m_stateFlags[ index ] ^= value; m_stateFlags[ index ] ^= value;
sendStateFlags(); sendStateFlags();
auto newOnlineStatus = getOnlineStatus();
if( prevOnlineStatus != newOnlineStatus )
sendToInRangeSet( makeActorControl( getId(), SetStatusIcon, static_cast< uint8_t >( getOnlineStatus() ) ), true );
} }
void Sapphire::Entity::Player::update( uint64_t tickCount ) void Sapphire::Entity::Player::update( uint64_t tickCount )
@ -1164,6 +1182,7 @@ void Sapphire::Entity::Player::update( uint64_t tickCount )
if( !isAlive() ) if( !isAlive() )
return; return;
int64_t interval = tickCount - m_lastUpdate;
m_lastUpdate = tickCount; m_lastUpdate = tickCount;
if( !checkAction() ) if( !checkAction() )
@ -1205,6 +1224,63 @@ void Sapphire::Entity::Player::update( uint64_t tickCount )
} }
} }
switch( getClass() )
{
case Common::ClassJob::Conjurer:
case Common::ClassJob::Whitemage:
{
if( hasStateFlag( Common::PlayerStateFlag::InCombat ) )
{
if( gaugeWhmGetLily() < 3 )
{
auto lilyTimer = gaugeWhmGetLilyTimer();
lilyTimer += interval;
if( lilyTimer >= 30000 )
{
gaugeWhmSetLilyTimer( 0 ); // packet is sent together with following lily update
gaugeWhmSetLilies( gaugeWhmGetLily() + 1, gaugeWhmGetBloodLily() );
}
else
{
gaugeWhmSetLilyTimer( lilyTimer ); // don't send any packet, the client increases the timer by itself
}
}
else
{
gaugeWhmSetLilyTimer( 0 );
}
}
break;
}
case Common::ClassJob::Darkknight:
{
auto dsTimer = gaugeDrkGetDarkSideTimer();
if( dsTimer > interval )
dsTimer -= interval;
else
dsTimer = 0;
gaugeDrkSetDarkSideTimer( dsTimer );
auto shadowTimer = gaugeDrkGetShadowTimer();
if( shadowTimer > interval )
shadowTimer -= interval;
else
shadowTimer = 0;
gaugeDrkSetShadowTimer( shadowTimer );
break;
}
case Common::ClassJob::Dragoon:
{
auto drTimer = gaugeDrgGetDragonTimer();
if( drTimer > interval )
drTimer -= interval;
else
drTimer = 0;
gaugeDrgSetDragonTimer( drTimer );
break;
}
}
Chara::update( tickCount ); Chara::update( tickCount );
} }
@ -1529,13 +1605,17 @@ void Sapphire::Entity::Player::onMobAggro( BNpcPtr pBNpc )
{ {
hateListAdd( pBNpc ); hateListAdd( pBNpc );
queuePacket( makeActorControl( getId(), ToggleAggro, 1 ) ); queuePacket( makeActorControl( getId(), ToggleAggro, 1 ) );
setStateFlag( Common::PlayerStateFlag::InCombat );
} }
void Sapphire::Entity::Player::onMobDeaggro( BNpcPtr pBNpc ) void Sapphire::Entity::Player::onMobDeaggro( BNpcPtr pBNpc )
{ {
hateListRemove( pBNpc ); hateListRemove( pBNpc );
if( m_actorIdTohateSlotMap.empty() ) if( m_actorIdTohateSlotMap.empty() )
{
queuePacket( makeActorControl( getId(), ToggleAggro ) ); queuePacket( makeActorControl( getId(), ToggleAggro ) );
unsetStateFlag( Common::PlayerStateFlag::InCombat );
}
} }
bool Sapphire::Entity::Player::isLogin() const bool Sapphire::Entity::Player::isLogin() const
@ -1677,52 +1757,30 @@ uint32_t Sapphire::Entity::Player::getPersistentEmote() const
void Sapphire::Entity::Player::autoAttack( CharaPtr pTarget ) void Sapphire::Entity::Player::autoAttack( CharaPtr pTarget )
{ {
auto mainWeap = getItemAt( Common::GearSet0, Common::GearSetSlot::MainHand );
pTarget->onActionHostile( getAsChara() ); pTarget->onActionHostile( getAsChara() );
//uint64_t tick = Util::getTimeMs(); auto& exdData = Common::Service< Data::ExdDataGenerated >::ref();
//srand(static_cast< uint32_t >(tick)); World::Action::ActionPtr action;
auto& RNGMgr = Common::Service< World::Manager::RNGMgr >::ref();
auto variation = static_cast< uint32_t >( RNGMgr.getRandGenerator< float >( 0, 3 ).next() );
auto damage = Math::CalcStats::calcAutoAttackDamage( *this );
if( getClass() == ClassJob::Machinist || getClass() == ClassJob::Bard || getClass() == ClassJob::Archer ) if( getClass() == ClassJob::Machinist || getClass() == ClassJob::Bard || getClass() == ClassJob::Archer )
{ {
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 8 ); auto actionData = exdData.get< Data::Action >( 8 );
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) ); assert( actionData );
action = World::Action::make_Action( getAsChara(), 8, 0, actionData );
Common::EffectEntry entry{};
entry.value = damage.first;
entry.effectType = Common::ActionEffectType::Damage;
entry.param0 = static_cast< uint8_t >( damage.second );
entry.param2 = 0x72;
effectPacket->addEffect( entry );
sendToInRangeSet( effectPacket, true );
} }
else else
{ {
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 ); auto actionData = exdData.get< Data::Action >( 7 );
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) ); assert( actionData );
action = World::Action::make_Action( getAsChara(), 7, 0, actionData );
Common::EffectEntry entry{};
entry.value = damage.first;
entry.effectType = Common::ActionEffectType::Damage;
entry.param0 = static_cast< uint8_t >( damage.second );
entry.param2 = 0x73;
effectPacket->addEffect( entry );
sendToInRangeSet( effectPacket, true );
} }
pTarget->takeDamage( damage.first ); action->setTargetId( pTarget->getId() );
action->setPos( getPos() );
action->setAutoAttack();
if( action->init() )
{
action->start();
}
} }
@ -2216,7 +2274,7 @@ void Sapphire::Entity::Player::updateHuntingLog( uint16_t id )
const auto maxRank = 4; const auto maxRank = 4;
auto& pExdData = Common::Service< Data::ExdDataGenerated >::ref(); auto& pExdData = Common::Service< Data::ExdDataGenerated >::ref();
auto& logEntry = m_huntingLogEntries[ static_cast< uint8_t >( getClass() ) - 1 ]; auto logEntryIndex = static_cast< uint8_t >( getClass() ) - 1;
bool logChanged = false; bool logChanged = false;
@ -2225,6 +2283,10 @@ void Sapphire::Entity::Player::updateHuntingLog( uint16_t id )
auto classJobInfo = pExdData.get< Sapphire::Data::ClassJob >( currentClass ); auto classJobInfo = pExdData.get< Sapphire::Data::ClassJob >( currentClass );
if( !classJobInfo ) if( !classJobInfo )
return; return;
if( classJobInfo->classJobParent > 0 )
logEntryIndex = classJobInfo->classJobParent;
auto& logEntry = m_huntingLogEntries[ logEntryIndex ];
bool allSectionsComplete = true; bool allSectionsComplete = true;
for( int i = 1; i <= 10; ++i ) for( int i = 1; i <= 10; ++i )
@ -2301,7 +2363,7 @@ bool Sapphire::Entity::Player::hasQueuedAction() const
void Sapphire::Entity::Player::setQueuedAction( Sapphire::World::Action::ActionPtr pAction ) void Sapphire::Entity::Player::setQueuedAction( Sapphire::World::Action::ActionPtr pAction )
{ {
m_pQueuedAction = std::move( pAction ); // overwrite previous queued action if any m_pQueuedAction = std::move( pAction );
} }
bool Sapphire::Entity::Player::checkAction() bool Sapphire::Entity::Player::checkAction()
@ -2375,3 +2437,280 @@ void Sapphire::Entity::Player::gaugeSetRaw( uint8_t* pData )
std::memcpy( &m_gauge, pData, 15 ); std::memcpy( &m_gauge, pData, 15 );
sendActorGauge(); sendActorGauge();
} }
void Sapphire::Entity::Player::gaugeWarSetIb( uint8_t value )
{
assert( value >= 0 && value <= 100 );
auto oldValue = gaugeWarGetIb();
if( ( oldValue == 0 && value != 0 ) ||
( oldValue != 0 && value == 0 ) )
{
if( m_effect == 0 && value != 0 )
setVisualEffect( 7, true );
else if ( m_effect == 7 && value == 0 )
setVisualEffect( 0, true );
}
m_gauge.war.beastGauge = value;
if( oldValue != value )
sendActorGauge();
}
uint8_t Sapphire::Entity::Player::gaugeWarGetIb()
{
return m_gauge.war.beastGauge;
}
void Sapphire::Entity::Player::gaugePldSetOath( uint8_t value )
{
assert( value >= 0 && value <= 100 );
auto oldValue = gaugePldGetOath();
m_gauge.pld.oathGauge = value;
if( oldValue != value )
sendActorGauge();
}
uint8_t Sapphire::Entity::Player::gaugePldGetOath()
{
return m_gauge.pld.oathGauge;
}
uint8_t Sapphire::Entity::Player::gaugeWhmGetLily()
{
return m_gauge.whm.lilies;
}
uint8_t Sapphire::Entity::Player::gaugeWhmGetBloodLily()
{
return m_gauge.whm.bloodLilies;
}
void Sapphire::Entity::Player::gaugeWhmSetLilies( uint8_t liles, uint8_t bloodLilies )
{
assert( liles >= 0 && liles <= 3 );
assert( bloodLilies >= 0 && bloodLilies <= 3 );
m_gauge.whm.lilies = liles;
m_gauge.whm.bloodLilies = bloodLilies;
sendActorGauge();
}
void Sapphire::Entity::Player::gaugeWhmSetLilyTimer( uint16_t value, bool sendPacket )
{
assert( value >= 0 && value <= 30000 );
m_gauge.whm.lilyTimer = value;
if( sendPacket )
sendActorGauge();
}
uint16_t Sapphire::Entity::Player::gaugeWhmGetLilyTimer()
{
return m_gauge.whm.lilyTimer;
}
void Sapphire::Entity::Player::gaugeDrkSetBlood( uint8_t value )
{
assert( value >= 0 && value <= 100 );
auto oldValue = gaugeDrkGetBlood();
m_gauge.drk.blood = value;
if( oldValue != value )
sendActorGauge();
}
uint8_t Sapphire::Entity::Player::gaugeDrkGetBlood()
{
return m_gauge.drk.blood;
}
void Sapphire::Entity::Player::gaugeDrkSetDarkArts( bool value )
{
auto oldValue = gaugeDrkGetDarkArts();
m_gauge.drk.darkArts = value ? 1 : 0;
if( oldValue != value )
sendActorGauge();
}
bool Sapphire::Entity::Player::gaugeDrkGetDarkArts()
{
return m_gauge.drk.darkArts > 0;
}
void Sapphire::Entity::Player::gaugeDrkSetDarkSideTimer( uint16_t value, bool sendPacket )
{
assert( value >= 0 && value <= 60000 );
auto oldValue = gaugeDrkGetDarkSideTimer();
m_gauge.drk.darksideTimer = value;
if( ( oldValue == 0 && value != 0 ) ||
( oldValue != 0 && value == 0 ) )
{
if( m_effect == 0 && value != 0 )
{
setVisualEffect( 22, true );
sendDebug( "{0}", gaugeDrkGetDarkSideTimer() );
}
else if ( m_effect == 22 && value == 0 )
{
setVisualEffect( 0, true );
sendDebug( "{0}", gaugeDrkGetDarkSideTimer() );
}
}
if( sendPacket )
sendActorGauge();
}
uint16_t Sapphire::Entity::Player::gaugeDrkGetDarkSideTimer()
{
return m_gauge.drk.darksideTimer;
}
void Sapphire::Entity::Player::gaugeDrkSetShadowTimer( uint16_t value, bool sendPacket )
{
assert( value >= 0 && value <= 60000 );
m_gauge.drk.shadowTimer = value;
if( sendPacket )
sendActorGauge();
}
uint16_t Sapphire::Entity::Player::gaugeDrkGetShadowTimer()
{
return m_gauge.drk.shadowTimer;
}
void Sapphire::Entity::Player::gaugeGnbSetAmmo( uint8_t value )
{
assert( value >= 0 && value <= 2 );
auto oldValue = gaugeGnbGetAmmo();
m_gauge.gnb.ammo = value;
if( oldValue != value )
sendActorGauge();
}
uint8_t Sapphire::Entity::Player::gaugeGnbGetAmmo()
{
return m_gauge.gnb.ammo;
}
void Sapphire::Entity::Player::gaugeGnbSetComboStep( uint8_t value )
{
assert( value >= 0 && value <= 2 );
auto oldValue = gaugeGnbGetComboStep();
m_gauge.gnb.ammoComboStep = value;
if( oldValue != value )
sendActorGauge();
}
uint8_t Sapphire::Entity::Player::gaugeGnbGetComboStep()
{
return m_gauge.gnb.ammoComboStep;
}
void Sapphire::Entity::Player::gaugeDrgSetDragonTimer( uint16_t value, bool sendPacket )
{
assert( value >= 0 && value <= 30000 );
auto oldValue = gaugeDrgGetDragonTimer();
m_gauge.drg.dragonTimer = value;
if( ( oldValue == 0 && value != 0 ) ||
( oldValue != 0 && value == 0 ) )
{
if( m_effect == 0 && value != 0 )
{
setVisualEffect( 21, true );
}
else if ( m_effect == 34 && value == 0 )
{
gaugeDrgSetDragonState( Sapphire::Common::DrgState::BloodOfTheDragon );
setVisualEffect( 21, true );
gaugeDrgSetDragonTimer( 30000, true );
}
else if ( m_effect == 21 && value == 0 )
{
gaugeDrgSetDragonState( Sapphire::Common::DrgState::None );
setVisualEffect( 0, true );
}
}
if( sendPacket )
sendActorGauge();
}
uint16_t Sapphire::Entity::Player::gaugeDrgGetDragonTimer()
{
return m_gauge.drg.dragonTimer;
}
void Sapphire::Entity::Player::gaugeDrgSetDragonState( Sapphire::Common::DrgState value )
{
auto oldValue = gaugeDrgGetDragonStateRaw();
m_gauge.drg.dragonState = value;
if( oldValue != value )
sendActorGauge();
}
bool Sapphire::Entity::Player::gaugeDrgGetDragonState( Sapphire::Common::DrgState state )
{
return ( static_cast< uint8_t >( m_gauge.drg.dragonState ) & static_cast< uint8_t >( state ) ) > 0;
}
Sapphire::Common::DrgState Sapphire::Entity::Player::gaugeDrgGetDragonStateRaw()
{
return m_gauge.drg.dragonState;
}
void Sapphire::Entity::Player::gaugeDrgSetEyes( uint8_t value )
{
assert( value >= 0 && value <= 2 );
auto oldValue = gaugeDrgGetEyes();
m_gauge.drg.eyes = value;
if( oldValue != value )
sendActorGauge();
}
uint8_t Sapphire::Entity::Player::gaugeDrgGetEyes()
{
return m_gauge.drg.eyes;
}
void Sapphire::Entity::Player::gaugeSamSetKenki( uint8_t value )
{
assert( value >= 0 && value <= 100 );
auto oldValue = gaugeSamGetKenki();
m_gauge.sam.kenki = value;
if( oldValue != value )
sendActorGauge();
}
uint8_t Sapphire::Entity::Player::gaugeSamGetKenki()
{
return m_gauge.sam.kenki;
}
void Sapphire::Entity::Player::gaugeSamSetSen( Sapphire::Common::SamSen type, bool value )
{
auto sen = static_cast< uint8_t >( type );
auto current = static_cast< uint8_t >( gaugeSamGetSenRaw() );
if( value )
current |= sen;
else
current &= ~sen;
gaugeSamSetSen( static_cast< Sapphire::Common::SamSen >( current ) );
}
void Sapphire::Entity::Player::gaugeSamSetSen( Sapphire::Common::SamSen value )
{
auto oldValue = gaugeSamGetSenRaw();
m_gauge.sam.sen = value;
if( oldValue != value )
sendActorGauge();
}
bool Sapphire::Entity::Player::gaugeSamGetSen( Sapphire::Common::SamSen type )
{
return ( static_cast< uint8_t >( m_gauge.sam.sen ) & static_cast< uint8_t >( type ) ) > 0;
}
Sapphire::Common::SamSen Sapphire::Entity::Player::gaugeSamGetSenRaw()
{
return m_gauge.sam.sen;
}
bool Sapphire::Entity::Player::gaugeSamHasAnySen()
{
return static_cast< uint8_t >( m_gauge.sam.sen ) > 0;
}

View file

@ -511,7 +511,7 @@ namespace Sapphire::Entity
/*! gets the players territoryTypeId */ /*! gets the players territoryTypeId */
uint32_t getTerritoryTypeId() const; uint32_t getTerritoryTypeId() const;
void forceZoneing( uint32_t zoneId ); void forceZoneing( uint32_t zoneId = 0, float x = FLT_MAX, float y = FLT_MAX, float z = FLT_MAX, float r = FLT_MAX, bool showZoneName = false );
/*! return player to preset homepoint */ /*! return player to preset homepoint */
void returnToHomepoint(); void returnToHomepoint();
@ -1016,6 +1016,48 @@ namespace Sapphire::Entity
void sendActorGauge(); void sendActorGauge();
void gaugeSetRaw( uint8_t* pData ); void gaugeSetRaw( uint8_t* pData );
void gaugeWarSetIb( uint8_t value );
uint8_t gaugeWarGetIb();
void gaugePldSetOath( uint8_t value );
uint8_t gaugePldGetOath();
uint8_t gaugeWhmGetLily();
uint8_t gaugeWhmGetBloodLily();
void gaugeWhmSetLilies( uint8_t liles, uint8_t bloodLilies );
void gaugeWhmSetLilyTimer( uint16_t value, bool sendPacket = false );
uint16_t gaugeWhmGetLilyTimer();
void gaugeDrkSetBlood( uint8_t value );
uint8_t gaugeDrkGetBlood();
void gaugeDrkSetDarkArts( bool value );
bool gaugeDrkGetDarkArts();
void gaugeDrkSetDarkSideTimer( uint16_t value, bool sendPacket = false );
uint16_t gaugeDrkGetDarkSideTimer();
void gaugeDrkSetShadowTimer( uint16_t value, bool sendPacket = false );
uint16_t gaugeDrkGetShadowTimer();
void gaugeGnbSetAmmo( uint8_t value );
uint8_t gaugeGnbGetAmmo();
void gaugeGnbSetComboStep( uint8_t value );
uint8_t gaugeGnbGetComboStep();
void gaugeDrgSetDragonTimer ( uint16_t value, bool sendPacket = false );
uint16_t gaugeDrgGetDragonTimer();
void gaugeDrgSetDragonState ( Sapphire::Common::DrgState value );
bool gaugeDrgGetDragonState( Common::DrgState state );
Common::DrgState gaugeDrgGetDragonStateRaw();
void Sapphire::Entity::Player::gaugeDrgSetEyes( uint8_t value );
uint8_t Sapphire::Entity::Player::gaugeDrgGetEyes();
void gaugeSamSetKenki( uint8_t value );
uint8_t gaugeSamGetKenki();
void gaugeSamSetSen( Common::SamSen type, bool value );
void gaugeSamSetSen( Common::SamSen value );
bool gaugeSamGetSen( Common::SamSen type );
Common::SamSen gaugeSamGetSenRaw();
bool gaugeSamHasAnySen();
////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////
void setPosAndNotifyClient( float x, float y, float z, float rot ); void setPosAndNotifyClient( float x, float y, float z, float rot );

View file

@ -7,6 +7,8 @@
#include "Script/ScriptMgr.h" #include "Script/ScriptMgr.h"
#include "Actor/Player.h" #include "Actor/Player.h"
#include "StatusEffect/StatusEffect.h"
#include <Exd/ExdDataGenerated.h> #include <Exd/ExdDataGenerated.h>
#include <Network/PacketWrappers/EffectPacket.h> #include <Network/PacketWrappers/EffectPacket.h>
@ -107,6 +109,14 @@ void World::Manager::ActionMgr::bootstrapAction( Entity::Player& player,
Action::ActionPtr currentAction, Action::ActionPtr currentAction,
Data::Action& actionData ) Data::Action& actionData )
{ {
for( const auto& statusIt : player.getStatusEffectMap() )
{
statusIt.second->onBeforeActionStart( currentAction.get() );
}
if( currentAction->isInterrupted() )
return;
if( !currentAction->preCheck() ) if( !currentAction->preCheck() )
{ {
player.sendDebug( "preCheck failed" ); player.sendDebug( "preCheck failed" );

View file

@ -636,6 +636,8 @@ bool Sapphire::World::Manager::HousingMgr::initHouseModels( Entity::Player& play
auto pItem = invMgr.createItem( player, static_cast< uint32_t >( item.second ) ); auto pItem = invMgr.createItem( player, static_cast< uint32_t >( item.second ) );
player.sendDebug( "container: {}, slot: {}, uid: {}", destContainer.first, item.first, pItem->getUId() );
container->setItem( static_cast< uint8_t >( item.first ), pItem ); container->setItem( static_cast< uint8_t >( item.first ), pItem );
} }

View file

@ -10,6 +10,10 @@
#include "Inventory/Item.h" #include "Inventory/Item.h"
#include "StatusEffect/StatusEffect.h"
#include "Action/Action.h"
#include "CalcStats.h" #include "CalcStats.h"
using namespace Sapphire::Math; using namespace Sapphire::Math;
@ -104,9 +108,7 @@ const int levelTable[81][6] =
{ 340, 380, 3300, 3600, 569, 569 }, { 340, 380, 3300, 3600, 569, 569 },
}; };
std::random_device CalcStats::dev; std::unique_ptr< RandGenerator< float > > CalcStats::rnd = nullptr;
std::mt19937 CalcStats::rng( dev() );
std::uniform_int_distribution< std::mt19937::result_type > CalcStats::range100( 0, 99 );
/* /*
Class used for battle-related formulas and calculations. Class used for battle-related formulas and calculations.
@ -181,16 +183,42 @@ uint32_t CalcStats::calculateMaxHp( PlayerPtr pPlayer )
return result; return result;
} }
float CalcStats::dodgeProbability( const Sapphire::Entity::Chara& chara )
{
// dummy value: 5% for players.
return chara.isPlayer() ? 5 : 0;
}
float CalcStats::blockProbability( const Chara& chara ) float CalcStats::blockProbability( const Chara& chara )
{ {
auto level = chara.getLevel(); auto level = chara.getLevel();
auto blockRate = static_cast< float >( chara.getStatValue( Common::BaseParam::BlockRate ) ); auto blockRate = static_cast< float >( chara.getStatValue( Common::BaseParam::BlockRate ) );
auto levelVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] ); auto levelVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
return std::floor( ( 30 * blockRate ) / levelVal + 10 ); float result = std::floor( ( 30 * blockRate ) / levelVal + 10 );
for( auto const& entry : chara.getStatusEffectMap() )
{
result += entry.second->getEffectEntry().getBlockRateBonus();
}
return result;
} }
float CalcStats::directHitProbability( const Chara& chara ) float CalcStats::parryProbability( const Sapphire::Entity::Chara& chara )
{
// dummy value: 10% for players.
float result = chara.isPlayer() ? 10 : 0;
for( auto const& entry : chara.getStatusEffectMap() )
{
result += entry.second->getEffectEntry().getParryRateBonus();
}
return result;
}
float CalcStats::directHitProbability( const Chara& chara, Sapphire::Common::CritDHBonusFilter filter )
{ {
const auto& baseStats = chara.getStats(); const auto& baseStats = chara.getStats();
auto level = chara.getLevel(); auto level = chara.getLevel();
@ -200,10 +228,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.getType() != Common::StatusEffectType::CritDHRateBonus )
continue;
if( static_cast< int32_t >( effectEntry.getActionTypeFilter() ) & static_cast< int32_t >( filter ) )
{
result += effectEntry.getDirectHitRateBonus();
}
}
return result;
} }
float CalcStats::criticalHitProbability( const Chara& chara ) float CalcStats::criticalHitProbability( const Chara& chara, Sapphire::Common::CritDHBonusFilter filter )
{ {
const auto& baseStats = chara.getStats(); const auto& baseStats = chara.getStats();
auto level = chara.getLevel(); auto level = chara.getLevel();
@ -213,7 +254,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.getType() != Common::StatusEffectType::CritDHRateBonus )
continue;
if( static_cast< int32_t >( effectEntry.getActionTypeFilter() ) & static_cast< int32_t >( filter ) )
{
result += effectEntry.getCritRateBonus();
}
}
return result;
} }
@ -222,15 +276,8 @@ float CalcStats::potency( uint16_t potency )
return potency / 100.f; return potency / 100.f;
} }
float CalcStats::autoAttackPotency( const Sapphire::Entity::Chara& chara ) float CalcStats::autoAttackPotency( const Sapphire::Entity::Chara& chara, uint32_t aaPotency )
{ {
uint32_t aaPotency = AUTO_ATTACK_POTENCY;
if( chara.getRole() == Common::Role::RangedPhysical )
{
aaPotency = RANGED_AUTO_ATTACK_POTENCY;
}
float autoAttackDelay = 2.5f; float autoAttackDelay = 2.5f;
// fetch actual auto attack delay if its a player // fetch actual auto attack delay if its a player
if( chara.isPlayer() ) if( chara.isPlayer() )
@ -328,6 +375,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( CharaPtr 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();
@ -409,6 +479,11 @@ float CalcStats::blockStrength( const Sapphire::Entity::Chara& chara )
return std::floor( ( 30 * blockStrength ) / levelVal + 10 ) / 100.f; return std::floor( ( 30 * blockStrength ) / levelVal + 10 ) / 100.f;
} }
float CalcStats::parryStrength( const Sapphire::Entity::Chara& chara )
{
return 0.15f;
}
float CalcStats::autoAttack( const Sapphire::Entity::Chara& chara ) float CalcStats::autoAttack( const Sapphire::Entity::Chara& chara )
{ {
// todo: default values for NPCs, not sure what we should have here // todo: default values for NPCs, not sure what we should have here
@ -428,6 +503,11 @@ float CalcStats::autoAttack( const Sapphire::Entity::Chara& chara )
autoAttackDelay = pItem->getDelay() / 1000.f; autoAttackDelay = pItem->getDelay() / 1000.f;
weaponDamage = pItem->getWeaponDmg(); weaponDamage = pItem->getWeaponDmg();
} }
else
{
// dummy value for BNpc
weaponDamage = chara.getLevel() * 3;
}
auto level = chara.getLevel(); auto level = chara.getLevel();
auto mainVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] ); auto mainVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::MAIN ] );
@ -442,12 +522,12 @@ float CalcStats::healingMagicPotency( const Sapphire::Entity::Chara& chara )
return std::floor( 100.f * ( chara.getStatValue( Common::BaseParam::HealingMagicPotency ) - 292.f ) / 264.f + 100.f ) / 100.f; return std::floor( 100.f * ( chara.getStatValue( Common::BaseParam::HealingMagicPotency ) - 292.f ) / 264.f + 100.f ) / 100.f;
} }
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcAutoAttackDamage( const Sapphire::Entity::Chara& chara ) std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcAutoAttackDamage( const Sapphire::Entity::Chara& chara, uint32_t ptc )
{ {
// D = ⌊ f(ptc) × f(aa) × f(ap) × f(det) × f(tnc) × traits ⌋ × f(ss) ⌋ × // D = ⌊ f(ptc) × f(aa) × f(ap) × f(det) × f(tnc) × traits ⌋ × f(ss) ⌋ ×
// f(chr) ⌋ × f(dhr) ⌋ × rand[ 0.95, 1.05 ] ⌋ × buff_1 ⌋ × buff... ⌋ // f(chr) ⌋ × f(dhr) ⌋ × rand[ 0.95, 1.05 ] ⌋ × buff_1 ⌋ × buff... ⌋
auto pot = autoAttackPotency( chara ); auto pot = autoAttackPotency( chara, ptc );
auto aa = autoAttack( chara ); auto aa = autoAttack( chara );
auto ap = getPrimaryAttackPower( chara ); auto ap = getPrimaryAttackPower( chara );
auto det = determination( chara ); auto det = determination( chara );
@ -464,13 +544,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, Common::CritDHBonusFilter::Damage ) > getRandomNumber0To100() )
{ {
factor *= criticalHitBonus( chara ); factor *= criticalHitBonus( chara );
hitType = Sapphire::Common::ActionHitSeverityType::CritDamage; hitType = Sapphire::Common::ActionHitSeverityType::CritDamage;
} }
if( directHitProbability( chara ) > range100( rng ) ) if( directHitProbability( chara, Common::CritDHBonusFilter::Damage ) > getRandomNumber0To100() )
{ {
factor *= 1.25f; factor *= 1.25f;
hitType = hitType == Sapphire::Common::ActionHitSeverityType::CritDamage ? hitType = hitType == Sapphire::Common::ActionHitSeverityType::CritDamage ?
@ -478,25 +558,42 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcAutoA
Sapphire::Common::ActionHitSeverityType::DirectHitDamage; Sapphire::Common::ActionHitSeverityType::DirectHitDamage;
} }
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f ); factor *= 1.0f + ( ( getRandomNumber0To100() - 50.0f ) / 1000.0f );
// todo: buffs for( auto const& entry : chara.getStatusEffectMap() )
constexpr auto format = "auto attack: pot: {} aa: {} ap: {} det: {} ten: {} = {}";
if( auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer() )
{ {
player->sendDebug( format, pot, aa, ap, det, ten, factor ); auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.getType() != Common::StatusEffectType::DamageMultiplier )
continue;
if( static_cast< int32_t >( effectEntry.getActionTypeFilter() ) & static_cast< int32_t >( Common::ActionTypeFilter::Physical ) )
{
factor *= 1.0f + ( effectEntry.getOutgoingDamageMultiplier() / 100.0f );
} }
else }
if( chara.isPlayer() )
{ {
Logger::debug( format, pot, aa, ap, det, ten, factor ); auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer();
switch( player->getClass() )
{
case Common::ClassJob::Darkknight:
{
if( player->gaugeDrkGetDarkSideTimer() > 0 )
{
factor *= 1.1f;
}
break;
}
}
constexpr auto format = "auto attack: pot: {} aa: {} ap: {} det: {} ten: {} = {}";
player->sendDebug( format, pot, aa, ap, det, ten, factor );
} }
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,25 +608,6 @@ 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 );
Sapphire::Common::ActionHitSeverityType hitType = Sapphire::Common::ActionHitSeverityType::NormalDamage;
if( criticalHitProbability( chara ) > range100( rng ) )
{
factor *= criticalHitBonus( chara );
hitType = Sapphire::Common::ActionHitSeverityType::CritDamage;
}
if( directHitProbability( chara ) > range100( rng ) )
{
factor *= 1.25f;
hitType = hitType == Sapphire::Common::ActionHitSeverityType::CritDamage ?
Sapphire::Common::ActionHitSeverityType::CritDirectHitDamage :
Sapphire::Common::ActionHitSeverityType::DirectHitDamage;
}
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f );
// todo: buffs
constexpr auto format = "dmg: pot: {} ({}) wd: {} ({}) ap: {} det: {} ten: {} = {}"; constexpr auto format = "dmg: pot: {} ({}) wd: {} ({}) ap: {} det: {} ten: {} = {}";
@ -537,27 +615,185 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActio
{ {
player->sendDebug( format, pot, ptc, wd, wepDmg, ap, det, ten, factor ); player->sendDebug( format, pot, ptc, wd, wepDmg, ap, det, ten, factor );
} }
else
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() )
{ {
Logger::debug( format, pot, ptc, wd, wepDmg, ap, det, ten, factor ); player->sendDebug( format, pot, ptc, wd, wepDmg, ap, det, factor );
}
return factor;
}
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActionDamage( Sapphire::World::Action::Action* pAction, const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg )
{
auto factor = calcDamageBaseOnPotency( chara, ptc, wepDmg );
Sapphire::Common::ActionHitSeverityType hitType = Sapphire::Common::ActionHitSeverityType::NormalDamage;
auto critProb = criticalHitProbability( chara, Common::CritDHBonusFilter::Damage );
if( pAction )
{
auto lutEntry = pAction->getActionEntry();
if( lutEntry.bonusEffect & Common::ActionBonusEffect::CritBonus )
{
if( pAction->checkActionBonusRequirement() )
{
critProb += lutEntry.getCritRateBonus();
}
}
}
if( critProb > getRandomNumber0To100() )
{
factor *= criticalHitBonus( chara );
hitType = Sapphire::Common::ActionHitSeverityType::CritDamage;
}
auto dhProb = directHitProbability( chara, Common::CritDHBonusFilter::Damage );
if( pAction )
{
auto lutEntry = pAction->getActionEntry();
if( lutEntry.bonusEffect & Common::ActionBonusEffect::DHBonus )
{
if( pAction->checkActionBonusRequirement() )
{
dhProb += lutEntry.getDirectHitRateBonus();
}
}
}
if( dhProb > getRandomNumber0To100() )
{
factor *= 1.25f;
hitType = hitType == Sapphire::Common::ActionHitSeverityType::CritDamage ?
Sapphire::Common::ActionHitSeverityType::CritDirectHitDamage :
Sapphire::Common::ActionHitSeverityType::DirectHitDamage;
}
factor *= 1.0f + ( ( getRandomNumber0To100() - 50.0f ) / 1000.0f );
Common::ActionTypeFilter actionTypeFilter = Common::ActionTypeFilter::Physical;
if( pAction && pAction->isMagical() )
{
actionTypeFilter = Common::ActionTypeFilter::Magical;
}
for( auto const& entry : chara.getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.getType() != Common::StatusEffectType::DamageMultiplier )
continue;
if( static_cast< int32_t >( effectEntry.getActionTypeFilter() ) & static_cast< int32_t >( actionTypeFilter ) )
{
factor *= 1.0f + ( effectEntry.getOutgoingDamageMultiplier() / 100.0f );
}
}
if( chara.isPlayer() )
{
auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer();
switch( player->getClass() )
{
case Common::ClassJob::Darkknight:
{
if( player->gaugeDrkGetDarkSideTimer() > 0 )
{
factor *= 1.1f;
}
break;
}
}
} }
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, Common::ActionTypeFilter typeFilter )
{ {
// lol just for testing if( typeFilter == ActionTypeFilter::Unknown )
auto factor = std::floor( ptc * ( wepDmg / 10.0f ) + ptc ); return originalDamage;
float damage = originalDamage;
for( auto const& entry : chara.getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.getType() != Common::StatusEffectType::DamageReceiveMultiplier )
continue;
if( static_cast< int32_t >( effectEntry.getActionTypeFilter() ) & static_cast< int32_t >( typeFilter ) )
{
damage *= ( 1.0f + ( effectEntry.getIncomingDamageMultiplier() / 100.0f ) );
}
}
return damage;
}
float CalcStats::applyHealingReceiveMultiplier( const Sapphire::Entity::Chara& chara, float originalHeal )
{
float heal = originalHeal;
for( auto const& entry : chara.getStatusEffectMap() )
{
heal *= ( 1.0f + ( entry.second->getEffectEntry().getIncomingHealMultiplier() / 100.0f ) );
}
return heal;
}
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActionHealing( Sapphire::World::Action::Action* pAction, const Sapphire::Entity::Chara& chara, 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 ) ) auto critProb = criticalHitProbability( chara, Common::CritDHBonusFilter::Heal );
if( pAction )
{
auto lutEntry = pAction->getActionEntry();
if( lutEntry.bonusEffect & Common::ActionBonusEffect::CritBonus )
{
if( pAction->checkActionBonusRequirement() )
{
critProb += lutEntry.getCritRateBonus();
}
}
}
if( critProb > getRandomNumber0To100() )
{ {
factor *= criticalHitBonus( chara ); factor *= criticalHitBonus( chara );
hitType = Sapphire::Common::ActionHitSeverityType::CritHeal; hitType = Sapphire::Common::ActionHitSeverityType::CritHeal;
} }
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f ); factor *= 1.0f + ( ( getRandomNumber0To100() - 50.0f ) / 1000.0f );
for( auto const& entry : chara.getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.getType() != Common::StatusEffectType::HealCastMultiplier )
continue;
if( pAction->isGCD() ) // must be a "cast"
{
factor *= 1.0f + ( effectEntry.getOutgoingHealMultiplier() / 100.0f );
}
}
return std::pair( factor, hitType ); return std::pair( factor, hitType );
} }
@ -566,3 +802,80 @@ uint32_t CalcStats::primaryStatValue( const Sapphire::Entity::Chara& chara )
{ {
return chara.getStatValue( chara.getPrimaryStat() ); return chara.getStatValue( chara.getPrimaryStat() );
} }
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcDamageReflect( Sapphire::Entity::CharaPtr pCharaAttacker, Sapphire::Entity::CharaPtr pCharaVictim, float damage, Sapphire::Common::ActionTypeFilter filter )
{
for( auto const& entry : pCharaVictim->getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.getType() == Common::StatusEffectType::DamageReceiveTrigger && ( static_cast< int32_t >( effectEntry.getActionTypeFilter() ) & static_cast< int32_t >( filter ) ) )
{
if( effectEntry.getTriggerResult() == Common::StatusEffectTriggerResult::ReflectDamage )
{
auto wepDmg = Sapphire::Math::CalcStats::getWeaponDamage( pCharaVictim );
auto damage = Sapphire::Math::CalcStats::calcActionDamage( nullptr, *pCharaVictim, effectEntry.getTriggerValue(), wepDmg );
damage.first = Math::CalcStats::applyDamageReceiveMultiplier( *pCharaAttacker, damage.first, effectEntry.getTriggerDamageType() );
return damage;
}
}
}
return std::pair< float, Sapphire::Common::ActionHitSeverityType >( 0, Sapphire::Common::ActionHitSeverityType::NormalDamage );
}
float CalcStats::calcAbsorbHP( Sapphire::Entity::CharaPtr pChara, float damage )
{
float result = 0;
for( auto const& entry : pChara->getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.getType() == Common::StatusEffectType::DamageDealtTrigger )
{
if( effectEntry.getTriggerResult() == Common::StatusEffectTriggerResult::AbsorbHP )
{
result += damage * effectEntry.getTriggerValue() / 100.0f;
}
}
}
return result;
}
bool CalcStats::calcDodge( const Sapphire::Entity::Chara& chara )
{
if( dodgeProbability( chara ) > getRandomNumber0To100() )
{
return true;
}
return false;
}
float CalcStats::calcBlock( const Sapphire::Entity::Chara& chara, float damage )
{
if( blockProbability( chara ) > getRandomNumber0To100() )
{
return damage * blockStrength( chara );
}
return 0;
}
float CalcStats::calcParry( const Sapphire::Entity::Chara& chara, float damage )
{
if( parryProbability( chara ) > getRandomNumber0To100() )
{
return damage * parryStrength( chara );
}
return 0;
}
float CalcStats::getRandomNumber0To100()
{
if( !rnd )
{
rnd = std::make_unique< RandGenerator< float > >( Service< RNGMgr >::ref().getRandGenerator< float >( 0, 100 ) );
}
return rnd->next();
}

View file

@ -1,37 +1,39 @@
#ifndef _CALCSTATS_H #ifndef _CALCSTATS_H
#define _CALCSTATS_H #define _CALCSTATS_H
#include <random>
#include <Common.h> #include <Common.h>
#include "Forwards.h" #include "Forwards.h"
#include "Manager/RNGMgr.h"
namespace Sapphire::Math namespace Sapphire::Math
{ {
using namespace Sapphire::World::Manager;
class CalcStats class CalcStats
{ {
public: public:
static const uint32_t AUTO_ATTACK_POTENCY = 110;
static const uint32_t RANGED_AUTO_ATTACK_POTENCY = 100;
static float calculateBaseStat( const Entity::Chara& chara ); static float calculateBaseStat( const Entity::Chara& chara );
static uint32_t calculateMaxHp( Sapphire::Entity::PlayerPtr pPlayer ); static uint32_t calculateMaxHp( Sapphire::Entity::PlayerPtr pPlayer );
static float dodgeProbability( const Sapphire::Entity::Chara& chara );
/*! /*!
* @brief Calculates the probability of a block happening * @brief Calculates the probability of a block happening
*/ */
static float blockProbability( const Sapphire::Entity::Chara& chara ); static float blockProbability( const Sapphire::Entity::Chara& chara );
static float parryProbability( const Sapphire::Entity::Chara& chara );
/*! /*!
* @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, Sapphire::Common::CritDHBonusFilter filter );
/*! /*!
* @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, Sapphire::Common::CritDHBonusFilter filter );
/*! /*!
* @brief Calculates the contribution of potency to damage output. * @brief Calculates the contribution of potency to damage output.
@ -40,7 +42,7 @@ namespace Sapphire::Math
*/ */
static float potency( uint16_t potency ); static float potency( uint16_t potency );
static float autoAttackPotency( const Sapphire::Entity::Chara& chara ); static float autoAttackPotency( const Sapphire::Entity::Chara& chara, uint32_t aaPotency );
/*! /*!
* @brief Weapon damage is the contribution the weapon's damage rating * @brief Weapon damage is the contribution the weapon's damage rating
@ -64,6 +66,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::CharaPtr chara );
/*! /*!
* @brief Calculates determinations contribution to damage and healing output. * @brief Calculates determinations contribution to damage and healing output.
* *
@ -116,6 +120,8 @@ namespace Sapphire::Math
*/ */
static float blockStrength( const Sapphire::Entity::Chara& chara ); static float blockStrength( const Sapphire::Entity::Chara& chara );
static float parryStrength( const Sapphire::Entity::Chara& chara );
static float autoAttack( const Sapphire::Entity::Chara& chara ); static float autoAttack( const Sapphire::Entity::Chara& chara );
/*! /*!
@ -129,13 +135,33 @@ namespace Sapphire::Math
//////////////////////////////////////////// ////////////////////////////////////////////
static std::pair< float, Common::ActionHitSeverityType > calcAutoAttackDamage( const Sapphire::Entity::Chara& chara ); static float calcDamageBaseOnPotency( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg );
static std::pair< float, Common::ActionHitSeverityType > calcActionDamage( 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 > calcActionHealing( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg ); static std::pair< float, Common::ActionHitSeverityType > calcAutoAttackDamage( const Sapphire::Entity::Chara& chara, uint32_t ptc );
static std::pair< float, Common::ActionHitSeverityType > calcActionDamage( World::Action::Action* pAction, const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg );
static float applyDamageReceiveMultiplier( const Sapphire::Entity::Chara& chara, float originalDamage, Common::ActionTypeFilter typeFilter );
static float applyHealingReceiveMultiplier( const Sapphire::Entity::Chara& chara, float originalHeal );
static std::pair< float, Common::ActionHitSeverityType > calcActionHealing( World::Action::Action* pAction, const Sapphire::Entity::Chara& chara, 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::pair< float, Sapphire::Common::ActionHitSeverityType > calcDamageReflect( Sapphire::Entity::CharaPtr pCharaAttacker, Sapphire::Entity::CharaPtr pCharaVictim, float damage, Sapphire::Common::ActionTypeFilter filter );
static float calcAbsorbHP( Sapphire::Entity::CharaPtr pChara, float damage );
static bool calcDodge( const Sapphire::Entity::Chara& chara );
static float calcBlock( const Sapphire::Entity::Chara& chara, float damage );
static float calcParry( const Sapphire::Entity::Chara& chara, float damage );
static float getRandomNumber0To100();
private: private:
/*! /*!
@ -145,9 +171,7 @@ namespace Sapphire::Math
*/ */
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::unique_ptr< RandGenerator< float > > rnd;
static std::mt19937 rng;
static std::uniform_int_distribution< std::mt19937::result_type > range100;
}; };
} }

View file

@ -146,7 +146,7 @@ void Sapphire::Network::GameConnection::clientTriggerHandler( const Packets::FFX
case ClientTriggerType::CastCancel: // Cancel cast case ClientTriggerType::CastCancel: // Cancel cast
{ {
if( player.getCurrentAction() ) if( player.getCurrentAction() )
player.getCurrentAction()->setInterrupted( Common::ActionInterruptType::RegularInterrupt ); player.getCurrentAction()->interrupt();
break; break;
} }
case ClientTriggerType::Examine: case ClientTriggerType::Examine:

View file

@ -256,19 +256,12 @@ void Sapphire::Network::GameConnection::gm1Handler( const Packets::FFXIVARR_PACK
{ {
targetPlayer->setOnlineStatusMask( param1 ); targetPlayer->setOnlineStatusMask( param1 );
auto statusPacket = makeZonePacket< FFXIVIpcSetOnlineStatus >( player.getId() );
statusPacket->data().onlineStatusFlags = param1;
queueOutPacket( statusPacket );
auto searchInfoPacket = makeZonePacket< FFXIVIpcSetSearchInfo >( player.getId() ); auto searchInfoPacket = makeZonePacket< FFXIVIpcSetSearchInfo >( player.getId() );
searchInfoPacket->data().onlineStatusFlags = param1; searchInfoPacket->data().onlineStatusFlags = param1;
searchInfoPacket->data().selectRegion = targetPlayer->getSearchSelectRegion(); searchInfoPacket->data().selectRegion = targetPlayer->getSearchSelectRegion();
strcpy( searchInfoPacket->data().searchMessage, targetPlayer->getSearchMessage() ); strcpy( searchInfoPacket->data().searchMessage, targetPlayer->getSearchMessage() );
targetPlayer->queuePacket( searchInfoPacket ); targetPlayer->queuePacket( searchInfoPacket );
targetPlayer->sendToInRangeSet( makeActorControl( player.getId(), SetStatusIcon,
static_cast< uint8_t >( player.getOnlineStatus() ) ),
true );
player.sendNotice( "Icon for {0} was set to {1}", targetPlayer->getName(), param1 ); player.sendNotice( "Icon for {0} was set to {1}", targetPlayer->getName(), param1 );
break; break;
} }

View file

@ -91,18 +91,11 @@ void Sapphire::Network::GameConnection::setSearchInfoHandler( const Packets::FFX
// mark player as new adventurer // mark player as new adventurer
player.setNewAdventurer( true ); player.setNewAdventurer( true );
auto statusPacket = makeZonePacket< FFXIVIpcSetOnlineStatus >( player.getId() );
statusPacket->data().onlineStatusFlags = status;
queueOutPacket( statusPacket );
auto searchInfoPacket = makeZonePacket< FFXIVIpcSetSearchInfo >( player.getId() ); auto searchInfoPacket = makeZonePacket< FFXIVIpcSetSearchInfo >( player.getId() );
searchInfoPacket->data().onlineStatusFlags = status; searchInfoPacket->data().onlineStatusFlags = status;
searchInfoPacket->data().selectRegion = player.getSearchSelectRegion(); searchInfoPacket->data().selectRegion = player.getSearchSelectRegion();
strcpy( searchInfoPacket->data().searchMessage, player.getSearchMessage() ); strcpy( searchInfoPacket->data().searchMessage, player.getSearchMessage() );
queueOutPacket( searchInfoPacket ); queueOutPacket( searchInfoPacket );
player.sendToInRangeSet( makeActorControl( player.getId(), SetStatusIcon,
static_cast< uint8_t >( player.getOnlineStatus() ) ), true );
} }
void Sapphire::Network::GameConnection::reqSearchInfoHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket, void Sapphire::Network::GameConnection::reqSearchInfoHandler( const Packets::FFXIVARR_PACKET_RAW& inPacket,
@ -184,7 +177,7 @@ void Sapphire::Network::GameConnection::updatePositionHandler( const Packets::FF
Entity::Player& player ) Entity::Player& player )
{ {
// if the player is marked for zoning we no longer want to update his pos // if the player is marked for zoning we no longer want to update his pos
if( player.isMarkedForZoning() ) if( player.isMarkedForZoning() || !player.isLoadingComplete() )
return; return;
const auto updatePositionPacket = ZoneChannelPacket< Client::FFXIVIpcUpdatePosition >( inPacket ); const auto updatePositionPacket = ZoneChannelPacket< Client::FFXIVIpcUpdatePosition >( inPacket );
@ -200,7 +193,7 @@ void Sapphire::Network::GameConnection::updatePositionHandler( const Packets::FF
player.setPos( updatePositionPacket.data().position ); player.setPos( updatePositionPacket.data().position );
if( ( player.getCurrentAction() != nullptr ) && bPosChanged ) if( ( player.getCurrentAction() != nullptr ) && bPosChanged )
player.getCurrentAction()->setInterrupted( Common::ActionInterruptType::RegularInterrupt ); player.getCurrentAction()->interrupt();
// if no one is in range, don't bother trying to send a position update // if no one is in range, don't bother trying to send a position update
if( !player.hasInRangeActor() ) if( !player.hasInRangeActor() )
@ -416,6 +409,7 @@ void Sapphire::Network::GameConnection::finishLoadingHandler( const Packets::FFX
player.setIsLogin( false ); player.setIsLogin( false );
} }
player.setVisualEffect( 0, false );
// spawn the player for himself // spawn the player for himself
player.spawn( player.getAsPlayer() ); player.spawn( player.getAsPlayer() );

View file

@ -76,6 +76,10 @@ namespace Sapphire::ScriptAPI
{ {
} }
void ActionScript::onBeforePreCheck( Sapphire::World::Action::Action& action )
{
}
void ActionScript::onStart( Sapphire::World::Action::Action& action ) void ActionScript::onStart( Sapphire::World::Action::Action& action )
{ {
} }
@ -84,6 +88,14 @@ namespace Sapphire::ScriptAPI
{ {
} }
void ActionScript::onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter )
{
}
void ActionScript::onAfterBuildEffect( Sapphire::World::Action::Action& action )
{
}
void ActionScript::onInterrupt( Sapphire::World::Action::Action& action ) void ActionScript::onInterrupt( Sapphire::World::Action::Action& action )
{ {
} }

View file

@ -3,7 +3,11 @@
#include <string> #include <string>
#include <Event/EventHandler.h> #include <Event/EventHandler.h>
#include "Manager/EventMgr.h"
#include "Manager/PlayerMgr.h"
#include "Manager/TerritoryMgr.h"
#include "ForwardsZone.h" #include "ForwardsZone.h"
#include <Service.h>
#ifdef _MSC_VER #ifdef _MSC_VER
#define EXPORT __declspec( dllexport ) #define EXPORT __declspec( dllexport )
@ -125,10 +129,16 @@ namespace Sapphire::ScriptAPI
public: public:
explicit ActionScript( uint32_t actionId ); explicit ActionScript( uint32_t actionId );
virtual void onBeforePreCheck( Sapphire::World::Action::Action& action );
virtual void onStart( Sapphire::World::Action::Action& action ); virtual void onStart( Sapphire::World::Action::Action& action );
virtual void onExecute( Sapphire::World::Action::Action& action ); virtual void onExecute( Sapphire::World::Action::Action& action );
virtual void onBeforeBuildEffect( Sapphire::World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter );
virtual void onAfterBuildEffect( Sapphire::World::Action::Action& action );
virtual void onInterrupt( Sapphire::World::Action::Action& action ); virtual void onInterrupt( Sapphire::World::Action::Action& action );
}; };

View file

@ -329,6 +329,18 @@ bool Sapphire::Scripting::ScriptMgr::onEObjHit( Sapphire::Entity::Player& player
return didCallScript; return didCallScript;
} }
bool Sapphire::Scripting::ScriptMgr::onBeforePreCheck( World::Action::Action& action )
{
auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::ActionScript >( action.getId() );
if( script )
{
script->onBeforePreCheck( action );
return true;
}
return false;
}
bool Sapphire::Scripting::ScriptMgr::onExecute( World::Action::Action& action ) bool Sapphire::Scripting::ScriptMgr::onExecute( World::Action::Action& action )
{ {
auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::ActionScript >( action.getId() ); auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::ActionScript >( action.getId() );
@ -341,6 +353,30 @@ bool Sapphire::Scripting::ScriptMgr::onExecute( World::Action::Action& action )
return false; return false;
} }
bool Sapphire::Scripting::ScriptMgr::onBeforeBuildEffect( World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter )
{
auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::ActionScript >( action.getId() );
if( script )
{
script->onBeforeBuildEffect( action, victimCounter, validVictimCounter );
return true;
}
return false;
};
bool Sapphire::Scripting::ScriptMgr::onAfterBuildEffect( World::Action::Action& action )
{
auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::ActionScript >( action.getId() );
if( script )
{
script->onAfterBuildEffect( action );
return true;
}
return false;
}
bool Sapphire::Scripting::ScriptMgr::onInterrupt( World::Action::Action& action ) bool Sapphire::Scripting::ScriptMgr::onInterrupt( World::Action::Action& action )
{ {
auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::ActionScript >( action.getId() ); auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::ActionScript >( action.getId() );

View file

@ -71,12 +71,18 @@ namespace Sapphire::Scripting
bool onEObjHit( Entity::Player& player, uint64_t actorId, uint32_t actionId ); bool onEObjHit( Entity::Player& player, uint64_t actorId, uint32_t actionId );
bool onBeforePreCheck( World::Action::Action& action );
bool onStart( World::Action::Action& action ); bool onStart( World::Action::Action& action );
bool onInterrupt( World::Action::Action& action ); bool onInterrupt( World::Action::Action& action );
bool onExecute( World::Action::Action& action ); bool onExecute( World::Action::Action& action );
bool onBeforeBuildEffect( World::Action::Action& action, uint8_t victimCounter, uint8_t validVictimCounter );
bool onAfterBuildEffect( World::Action::Action& action );
bool onStatusReceive( Entity::CharaPtr pActor, uint32_t effectId ); bool onStatusReceive( Entity::CharaPtr pActor, uint32_t effectId );
bool onStatusTick( Entity::CharaPtr pActor, Sapphire::StatusEffect::StatusEffect& effect ); bool onStatusTick( Entity::CharaPtr pActor, Sapphire::StatusEffect::StatusEffect& effect );

View file

@ -6,11 +6,16 @@
#include <algorithm> #include <algorithm>
#include <Service.h> #include <Service.h>
#include "Actor/Player.h"
#include "Actor/Chara.h" #include "Actor/Chara.h"
#include "Actor/Actor.h" #include "Actor/Actor.h"
#include "Action/Action.h"
#include "Script/ScriptMgr.h" #include "Script/ScriptMgr.h"
#include "Math/CalcStats.h"
#include "StatusEffect.h" #include "StatusEffect.h"
using namespace Sapphire::Common; using namespace Sapphire::Common;
@ -25,7 +30,11 @@ Sapphire::StatusEffect::StatusEffect::StatusEffect( uint32_t id, Entity::CharaPt
m_duration( duration ), m_duration( duration ),
m_startTime( 0 ), m_startTime( 0 ),
m_tickRate( tickRate ), m_tickRate( tickRate ),
m_lastTick( 0 ) m_lastTick( 0 ),
m_value( 0 ),
m_cachedSourceCrit( 0 ),
m_cachedSourceCritBonus( 0 ),
m_markToRemove( false )
{ {
auto& exdData = Common::Service< Data::ExdDataGenerated >::ref(); auto& exdData = Common::Service< Data::ExdDataGenerated >::ref();
auto entry = exdData.get< Sapphire::Data::Status >( id ); auto entry = exdData.get< Sapphire::Data::Status >( id );
@ -40,8 +49,12 @@ 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.init( Common::StatusEffectType::Invalid, 0, 0, 0, 0 );
}
Sapphire::StatusEffect::StatusEffect::~StatusEffect() Sapphire::StatusEffect::StatusEffect::~StatusEffect()
{ {
@ -54,9 +67,40 @@ 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; switch( m_effectEntry.getType() )
{
case Common::StatusEffectType::Dot:
{
auto value = m_value;
if( m_cachedSourceCrit > Sapphire::Math::CalcStats::getRandomNumber0To100() )
{
value *= m_cachedSourceCritBonus;
}
value *= 1.0f + ( ( Sapphire::Math::CalcStats::getRandomNumber0To100() - 50.0f ) / 1000.0f );
m_currTickEffect = std::make_pair( 1, value );
break;
}
case Common::StatusEffectType::Hot:
{
auto value = m_value;
if( m_cachedSourceCrit > Sapphire::Math::CalcStats::getRandomNumber0To100() )
{
value *= m_cachedSourceCritBonus;
}
value *= 1.0f + ( ( Sapphire::Math::CalcStats::getRandomNumber0To100() - 50.0f ) / 1000.0f );
m_currTickEffect = std::make_pair( 2, value );
break;
}
default:
{
m_currTickEffect = std::make_pair( 0, 0 ); m_currTickEffect = std::make_pair( 0, 0 );
return thisTick; break;
}
}
return m_currTickEffect;
} }
void Sapphire::StatusEffect::StatusEffect::onTick() void Sapphire::StatusEffect::StatusEffect::onTick()
@ -65,6 +109,10 @@ void Sapphire::StatusEffect::StatusEffect::onTick()
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref(); auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
scriptMgr.onStatusTick( m_targetActor, *this ); scriptMgr.onStatusTick( m_targetActor, *this );
auto mp = m_effectEntry.getMPRestoreTick();
if( mp > 0 )
m_targetActor->restoreMP( mp );
} }
uint32_t Sapphire::StatusEffect::StatusEffect::getSrcActorId() const uint32_t Sapphire::StatusEffect::StatusEffect::getSrcActorId() const
@ -86,32 +134,90 @@ void Sapphire::StatusEffect::StatusEffect::applyStatus()
{ {
m_startTime = Util::getTimeMs(); m_startTime = Util::getTimeMs();
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref(); auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
// this is only right when an action is being used by the player
// else you probably need to use an actorcontrol
//GamePacketNew< FFXIVIpcEffect > effectPacket( m_sourceActor->getId() );
//effectPacket.data().targetId = m_sourceActor->getId();
//effectPacket.data().actionAnimationId = 3;
//effectPacket.data().unknown_3 = 1;
//effectPacket.data().actionTextId = 3;
//effectPacket.data().unknown_5 = 1;
//effectPacket.data().unknown_6 = 321;
//effectPacket.data().rotation = ( uint16_t ) ( 0x8000 * ( ( m_sourceActor->getPos().getR() + 3.1415926 ) ) / 3.1415926 );
//effectPacket.data().effectTargetId = m_sourceActor->getId();
//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_sourceActor->sendToInRangeSet( effectPacket, true );
scriptMgr.onStatusReceive( m_targetActor, m_id ); scriptMgr.onStatusReceive( m_targetActor, m_id );
switch( m_effectEntry.getType() )
{
case Common::StatusEffectType::Dot:
{
auto wepDmg = Sapphire::Math::CalcStats::getWeaponDamage( m_sourceActor );
auto damage = Sapphire::Math::CalcStats::calcDamageBaseOnPotency( *m_sourceActor, m_effectEntry.getDotHotPotency(), wepDmg );
for( auto const& entry : m_sourceActor->getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.getType() != Common::StatusEffectType::DamageMultiplier )
continue;
if( static_cast< int32_t >( effectEntry.getActionTypeFilter() ) & static_cast< int32_t >( m_effectEntry.getActionTypeFilter() ) )
{
damage *= 1.0f + ( effectEntry.getOutgoingDamageMultiplier() / 100.0f );
}
}
m_value = Sapphire::Math::CalcStats::applyDamageReceiveMultiplier( *m_targetActor, damage, m_effectEntry.getActionTypeFilter() );
m_cachedSourceCrit = Sapphire::Math::CalcStats::criticalHitProbability( *m_sourceActor, Common::CritDHBonusFilter::Damage );
m_cachedSourceCritBonus = Sapphire::Math::CalcStats::criticalHitBonus( *m_sourceActor );
break;
}
case Common::StatusEffectType::Hot:
{
auto wepDmg = Sapphire::Math::CalcStats::getWeaponDamage( m_sourceActor );
auto heal = Sapphire::Math::CalcStats::calcHealBaseOnPotency( *m_sourceActor, m_effectEntry.getDotHotPotency(), wepDmg );
for( auto const& entry : m_sourceActor->getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.getType() != Common::StatusEffectType::HealCastMultiplier )
continue;
heal *= 1.0f + ( effectEntry.getOutgoingHealMultiplier() / 100.0f );
}
m_value = Sapphire::Math::CalcStats::applyHealingReceiveMultiplier( *m_targetActor, heal );
m_cachedSourceCrit = Sapphire::Math::CalcStats::criticalHitProbability( *m_sourceActor, Common::CritDHBonusFilter::Heal );
m_cachedSourceCritBonus = Sapphire::Math::CalcStats::criticalHitBonus( *m_sourceActor );
break;
}
case Common::StatusEffectType::Haste:
{
auto pPlayer = m_targetActor->getAsPlayer();
if( pPlayer )
pPlayer->sendStats();
break;
}
}
} }
void Sapphire::StatusEffect::StatusEffect::removeStatus() void Sapphire::StatusEffect::StatusEffect::removeStatus()
{ {
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref(); auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
scriptMgr.onStatusTimeOut( m_targetActor, m_id ); scriptMgr.onStatusTimeOut( m_targetActor, m_id );
switch( m_effectEntry.getType() )
{
case Common::StatusEffectType::Haste:
{
auto pPlayer = m_targetActor->getAsPlayer();
if( pPlayer )
pPlayer->sendStats();
break;
}
}
// hardcoded for now, TODO: add m_statusForceRemoveReason so a proper check for "shield completely absorbed" is possible
if( m_markToRemove && m_id == 1178 )
{
if( auto drk = m_sourceActor->getAsPlayer() )
{
if( drk->getClass() == Common::ClassJob::Darkknight )
{
drk->gaugeDrkSetDarkArts( true );
}
}
}
} }
uint32_t Sapphire::StatusEffect::StatusEffect::getId() const uint32_t Sapphire::StatusEffect::StatusEffect::getId() const
@ -149,7 +255,140 @@ void Sapphire::StatusEffect::StatusEffect::setParam( uint16_t param )
m_param = param; m_param = param;
} }
void Sapphire::StatusEffect::StatusEffect::setStacks( uint8_t stacks )
{
if( ( m_param & 0x00FF ) != 0x00FF )
{
m_param = stacks;
}
}
uint8_t Sapphire::StatusEffect::StatusEffect::getStacks()
{
if( ( m_param & 0x00FF ) == 0x00FF )
return 0;
return static_cast< uint8_t >( m_param & 0x00FF );
}
const std::string& Sapphire::StatusEffect::StatusEffect::getName() const 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;
}
void Sapphire::StatusEffect::StatusEffect::replaceEffectEntry( Sapphire::World::Action::StatusEffectEntry entryOverride )
{
m_effectEntry = entryOverride;
}
void Sapphire::StatusEffect::StatusEffect::onBeforeActionStart( Sapphire::World::Action::Action* action )
{
// todo: add script function for this if needed
switch( m_effectEntry.getType() )
{
case Common::StatusEffectType::InstantCast:
{
if( action->hasCastTime() && applyToAction( action ) )
action->setCastTime( 0 );
break;
}
case Common::StatusEffectType::AlwaysCombo:
{
if( applyToAction( action ) )
action->setAlwaysCombo();
break;
}
}
}
void Sapphire::StatusEffect::StatusEffect::onActionExecute( Sapphire::World::Action::Action* action )
{
// todo: add script function for this if needed
switch( m_effectEntry.getType() )
{
case Common::StatusEffectType::PotencyMultiplier:
{
if( applyToAction( action ) )
{
action->getActionEntry().damagePotency *= 1.0 + ( m_effectEntry.getPotencyMultiplier() / 100.0 );
action->getActionEntry().damageComboPotency *= 1.0 + ( m_effectEntry.getPotencyMultiplier() / 100.0 );
action->getActionEntry().damageDirectionalPotency *= 1.0 + ( m_effectEntry.getPotencyMultiplier() / 100.0 );
}
break;
}
}
}
bool Sapphire::StatusEffect::StatusEffect::isMarkedToRemove()
{
return m_markToRemove;
}
void Sapphire::StatusEffect::StatusEffect::markToRemove()
{
m_markToRemove = true;
}
void Sapphire::StatusEffect::StatusEffect::refresh()
{
m_value = 0;
m_cachedSourceCritBonus = 0;
m_cachedSourceCrit = 0;
applyStatus();
}
void Sapphire::StatusEffect::StatusEffect::refresh( Sapphire::World::Action::StatusEffectEntry newEntry )
{
m_effectEntry = newEntry;
refresh();
}
bool Sapphire::StatusEffect::StatusEffect::onActionHitTarget( World::Action::Action* action, Entity::CharaPtr victim, int victimCounter )
{
switch( m_effectEntry.getType() )
{
case Common::StatusEffectType::MPRestorePerGCD:
{
if( victimCounter == 1 && action->isGCD() ) // only restore mp on first victim in case of aoe
{
if( applyToAction( action ) )
{
float restored = 0.01f * m_targetActor->getMaxMp() * m_effectEntry.getGCDBasedMPRestorePercentage();
action->getEffectbuilder()->restoreMP( victim, m_targetActor, static_cast< uint32_t >( restored ), Sapphire::Common::ActionEffectResultFlag::EffectOnSource );
}
}
break;
}
}
return true;
}
bool Sapphire::StatusEffect::StatusEffect::applyToAction( World::Action::Action* action )
{
if( m_effectEntry.getRemainingCharges() == 0 )
return false;
if( !m_effectEntry.canApplyToAction( action->getId(), action->getActionData()->actionCategory ) )
return false;
if( m_effectEntry.getRemainingCharges() > 0 )
{
if( m_effectEntry.getRemainingCharges() == getStacks() )
{
// if stacks equal to remaining charges, assume it is synced
setStacks( getStacks() - 1 );
m_targetActor->sendStatusEffectUpdate();
}
m_effectEntry.setRemainingCharges( m_effectEntry.getRemainingCharges() - 1 );
if( m_effectEntry.getRemainingCharges() == 0 )
{
markToRemove();
}
}
return true;
}

View file

@ -3,6 +3,8 @@
#include "Forwards.h" #include "Forwards.h"
#include "Action/ActionLut.h"
namespace Sapphire { namespace Sapphire {
namespace StatusEffect { namespace StatusEffect {
@ -17,6 +19,12 @@ public:
void onTick(); void onTick();
void onBeforeActionStart( World::Action::Action* action );
void onActionExecute( World::Action::Action* action );
bool onActionHitTarget( World::Action::Action* action, Entity::CharaPtr victim, int victimCounter );
void applyStatus(); void applyStatus();
void removeStatus(); void removeStatus();
@ -41,13 +49,30 @@ public:
void setParam( uint16_t param ); void setParam( uint16_t param );
void setStacks( uint8_t stacks );
uint8_t getStacks();
void registerTickEffect( uint8_t type, uint32_t param ); void registerTickEffect( uint8_t type, uint32_t param );
std::pair< uint8_t, uint32_t > getTickEffect(); std::pair< uint8_t, uint32_t > getTickEffect();
const std::string& getName() const; const std::string& getName() const;
const Sapphire::World::Action::StatusEffectEntry& getEffectEntry() const;
void replaceEffectEntry( Sapphire::World::Action::StatusEffectEntry entryOverride );
bool isMarkedToRemove();
void markToRemove();
void refresh();
void refresh( Sapphire::World::Action::StatusEffectEntry newEntry );
private: private:
bool applyToAction( Sapphire::World::Action::Action* action );
uint32_t m_id; uint32_t m_id;
Entity::CharaPtr m_sourceActor; Entity::CharaPtr m_sourceActor;
Entity::CharaPtr m_targetActor; Entity::CharaPtr m_targetActor;
@ -58,7 +83,11 @@ private:
uint16_t m_param; uint16_t m_param;
std::string m_name; std::string m_name;
std::pair< uint8_t, uint32_t > m_currTickEffect; std::pair< uint8_t, uint32_t > m_currTickEffect;
Sapphire::World::Action::StatusEffectEntry m_effectEntry;
uint32_t m_value;
float m_cachedSourceCrit;
float m_cachedSourceCritBonus;
bool m_markToRemove;
}; };
} }