1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-05-03 17:27:47 +00:00

Sapphire 5.58 now with actions.

This commit is contained in:
collett 2023-03-07 05:12:18 +09:00
parent f608e1e8cf
commit f00f930366
72 changed files with 7853 additions and 2017 deletions

View file

@ -1103,6 +1103,73 @@ namespace Sapphire::Common
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
{
None = 0,
@ -1224,7 +1291,7 @@ namespace Sapphire::Common
{
uint8_t ammo;
uint8_t unused;
uint16_t maxTimerDuration;
uint16_t maxTimerDuration; // what is this?
uint8_t ammoComboStep;
} gnb;
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
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/BNpc.h"
#include "Action/ActionLut.h"
#include "Territory/Territory.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_startTime( 0 ),
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_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_range = m_actionData->range;
m_effectRange = m_actionData->effectRange;
@ -107,6 +119,57 @@ bool Action::Action::init()
m_primaryCostType = static_cast< Common::ActionPrimaryCostType >( m_actionData->primaryCostType );
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 )
{
// override pos to target position
@ -134,6 +197,8 @@ bool Action::Action::init()
addDefaultActorFilters();
m_effectBuilder->setAnimationLock( getAnimationLock() );
return true;
}
@ -172,11 +237,6 @@ Common::ActionInterruptType Action::Action::getInterruptType() const
return m_interruptType;
}
void Action::Action::setInterrupted( Common::ActionInterruptType type )
{
m_interruptType = type;
}
uint32_t Action::Action::getCastTime() const
{
return m_castTimeMs;
@ -205,7 +265,6 @@ bool Action::Action::update()
if( isInterrupted() )
{
interrupt();
return true;
}
@ -240,7 +299,6 @@ bool Action::Action::update()
if( !m_pTarget->isAlive() )
{
// interrupt the cast if target died
setInterrupted( Common::ActionInterruptType::RegularInterrupt );
interrupt();
return true;
}
@ -283,37 +341,31 @@ void Action::Action::start()
}
}
// todo: m_recastTimeMs needs to be adjusted for player sks/sps
auto actionStartPkt = makeActorControlSelf( m_pSource->getId(), ActorControlType::ActionStart, 1, getId(),
m_recastTimeMs / 10 );
player->queuePacket( actionStartPkt );
if( player )
{
// todo: m_recastTimeMs needs to be adjusted for player sks/sps
auto actionStartPkt = makeActorControlSelf( m_pSource->getId(), ActorControlType::ActionStart, 1, getId(),
m_recastTimeMs / 10 );
player->queuePacket( actionStartPkt );
}
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
// 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;
}
scriptMgr.onStart( *this );
// instantly finish cast if there's no cast time
if( !hasCastTime() )
execute();
}
void Action::Action::interrupt()
void Action::Action::interrupt( ActionInterruptType type )
{
if( isInterrupted() )
return;
assert( m_pSource );
m_interruptType = type;
// things that aren't players don't care about cooldowns and state flags
if( m_pSource->isPlayer() )
{
@ -326,7 +378,7 @@ void Action::Action::interrupt()
player->unsetStateFlag( PlayerStateFlag::Casting );
}
if( hasCastTime() )
if( m_startTime > 0 && hasCastTime() )
{
uint8_t interruptEffect = 0;
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 )
{
// todo: what do for npcs?
auto wepDmg = 1.f;
if( auto player = m_pSource->getAsPlayer() )
{
auto item = player->getEquippedWeapon();
assert( item );
auto role = player->getRole();
if( role == Common::Role::RangedMagical || role == Common::Role::Healer )
{
wepDmg = item->getMagicalDmg();
}
else
{
wepDmg = item->getPhysicalDmg();
}
}
return Math::CalcStats::calcActionDamage( *m_pSource, potency, wepDmg );
if( m_isAutoAttack )
return Math::CalcStats::calcAutoAttackDamage( *m_pSource, potency );
else
return Math::CalcStats::calcActionDamage( this, *m_pSource, potency, Math::CalcStats::getWeaponDamage( m_pSource ) );
}
std::pair< uint32_t, Common::ActionHitSeverityType > Action::Action::calcHealing( uint32_t potency )
{
auto wepDmg = 1.f;
if( auto player = m_pSource->getAsPlayer() )
{
auto item = player->getEquippedWeapon();
assert( item );
auto role = player->getRole();
if( role == Common::Role::RangedMagical || role == Common::Role::Healer )
{
wepDmg = item->getMagicalDmg();
}
else
{
wepDmg = item->getPhysicalDmg();
}
}
return Math::CalcStats::calcActionHealing( *m_pSource, potency, wepDmg );
return Math::CalcStats::calcActionHealing( this, *m_pSource, potency, Math::CalcStats::getWeaponDamage( m_pSource ) );
}
void Action::Action::buildEffects()
@ -454,92 +472,258 @@ void Action::Action::buildEffects()
snapshotAffectedActors( m_hitActors );
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() )
{
player->sendUrgent( "missing lut entry for action#{}", getId() );
}
return;
statusIt.second->onActionExecute( this );
}
if( !hasLutEntry || m_hitActors.empty() )
scriptMgr.onExecute( *this );
if( isInterrupted() )
return;
if( m_disableGenericHandler || !hasValidLutEntry() )
{
// send any effect packet added by script or an empty one just to play animation for other players
m_effectBuilder->buildAndSendPackets();
scriptMgr.onBeforeBuildEffect( *this, 0, 0 );
m_effectBuilder->buildAndSendPackets();
scriptMgr.onAfterBuildEffect( *this );
return;
}
// no script exists but we have a valid lut entry
if( auto player = getSourceChara()->getAsPlayer() )
// we have a valid lut entry
auto player = getSourceChara()->getAsPlayer();
if( player )
{
player->sendDebug( "Hit target: pot: {} (c: {}, f: {}, r: {}), heal pot: {}, mpp: {}",
m_lutEntry.potency, m_lutEntry.comboPotency, m_lutEntry.flankPotency, m_lutEntry.rearPotency,
m_lutEntry.curePotency, m_lutEntry.restoreMPPercentage );
player->sendDebug( "type: {}, dpot: {} (dcpot: {}, ddpot: {}), hpot: {}, ss: {}, ts: {}, bonus: {}, breq: {}, bdata: {}",
m_actionData->attackType,
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
bool shouldRestoreMP = true;
bool shouldApplyComboSucceedEffect = true;
uint8_t victimCounter = 0, validVictimCounter = 0;
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 );
m_effectBuilder->damage( actor, actor, dmg.first, dmg.second );
bool result = statusIt.second->onActionHitTarget( this, actor, victimCounter );
if( !result )
shouldHitThisTarget = false;
}
if( !shouldHitThisTarget )
continue;
if( m_lutEntry.damagePotency > 0 )
{
Common::AttackType attackType = static_cast< Common::AttackType >( m_actionData->attackType );
actor->onActionHostile( m_pSource );
auto dmg = calcDamage( isCorrectCombo() ? m_lutEntry.damageComboPotency : m_lutEntry.damagePotency );
if( victimCounter > 1 )
{
if( m_lutEntry.bonusEffect & Common::ActionBonusEffect::DamageFallOff )
{
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 )
actor->onActionHostile( m_pSource );
if( isCorrectCombo() && shouldApplyComboSucceedEffect )
{
m_effectBuilder->comboSucceed( actor );
shouldApplyComboSucceedEffect = false;
dodged = Math::CalcStats::calcDodge( *actor );
if( !dodged && dmg.second == Common::ActionHitSeverityType::NormalDamage && actor->isPlayer() )
{
blocked = Math::CalcStats::calcBlock( *actor, dmg.first );
}
if( !dodged && blocked == 0 && dmg.second == Common::ActionHitSeverityType::NormalDamage && actor->isPlayer() )
{
if( isPhysical() )
{
parried = Math::CalcStats::calcParry( *actor, dmg.first );
}
}
}
if( dodged )
dmg.first = 0;
else
{
dmg.first -= blocked;
dmg.first -= parried;
}
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
}
}
if( m_lutEntry.bonusEffect & Common::ActionBonusEffect::SelfHeal )
{
if( checkActionBonusRequirement() )
{
auto heal = calcHealing( m_lutEntry.getSelfHealPotency() );
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() )
{
if( m_lutEntry.curePotency > 0 ) // actions with self heal
{
auto heal = calcHealing( m_lutEntry.curePotency );
m_effectBuilder->heal( actor, m_pSource, heal.first, heal.second, Common::ActionEffectResultFlag::EffectOnSource );
}
if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP )
{
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
shouldRestoreMP = false;
}
if ( !m_actionData->preservesCombo ) // we need something like m_actionData->hasNextComboAction
{
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 )
{
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
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 );
shouldRestoreMP = false;
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();
scriptMgr.onAfterBuildEffect( *this );
// at this point we're done with it and no longer need it
m_effectBuilder.reset();
@ -547,6 +731,12 @@ void Action::Action::buildEffects()
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( !playerPreCheck( *player ) )
@ -619,6 +809,9 @@ void Action::Action::setAdditionalData( uint32_t data )
bool Action::Action::isCorrectCombo() const
{
if( m_shouldAlwaysCombo )
return true;
auto lastActionId = m_pSource->getLastComboActionId();
if( lastActionId == 0 )
@ -634,6 +827,11 @@ bool Action::Action::isComboAction() const
return m_actionData->actionCombo != 0;
}
void Sapphire::World::Action::Action::setAlwaysCombo()
{
m_shouldAlwaysCombo = true;
}
bool Action::Action::primaryCostCheck( bool subtractCosts )
{
switch( m_primaryCostType )
@ -666,6 +864,168 @@ bool Action::Action::primaryCostCheck( bool subtractCosts )
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
case Common::ActionPrimaryCostType::None:
{
@ -751,10 +1111,12 @@ void Action::Action::addDefaultActorFilters()
break;
}
// case Common::CastType::RectangularAOE:
// {
// break;
// }
case Common::CastType::RectangularAOE:
{
auto filter = std::make_shared< World::Util::ActorFilterInRange >( m_pos, m_effectRange );
addActorFilter( filter );
break;
}
default:
{
@ -775,18 +1137,18 @@ bool Action::Action::preFilterActor( Sapphire::Entity::Actor& actor ) const
if( kind != ObjKind::BattleNpc && kind != ObjKind::Player )
return false;
if( !m_canTargetSelf && chara->getId() == m_pSource->getId() )
if( m_lutEntry.damagePotency > 0 && chara->getId() == m_pSource->getId() ) // !m_canTargetSelf
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;
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;
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 true;
}
@ -807,11 +1169,128 @@ Sapphire::Entity::CharaPtr Action::Action::getHitChara()
bool Action::Action::hasValidLutEntry() const
{
return m_lutEntry.potency != 0 || m_lutEntry.comboPotency != 0 || m_lutEntry.flankPotency != 0 || m_lutEntry.frontPotency != 0 ||
m_lutEntry.rearPotency != 0 || m_lutEntry.curePotency != 0 || m_lutEntry.restoreMPPercentage != 0;
return m_lutEntry.damagePotency != 0 || m_lutEntry.healPotency != 0 || m_lutEntry.selfStatus != 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()
{
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 "ForwardsZone.h"
#include "EffectBuilder.h"
#include "StatusEffect/StatusEffect.h"
namespace Sapphire::Data
{
@ -40,7 +41,6 @@ namespace Sapphire::World::Action
bool isInterrupted() const;
Common::ActionInterruptType getInterruptType() const;
void setInterrupted( Common::ActionInterruptType type );
uint32_t getCastTime() const;
void setCastTime( uint32_t castTime );
@ -52,6 +52,14 @@ namespace Sapphire::World::Action
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)
* @return true if they have the required resources
@ -118,6 +126,22 @@ namespace Sapphire::World::Action
*/
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).
*/
@ -133,7 +157,7 @@ namespace Sapphire::World::Action
*
* 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
@ -180,6 +204,9 @@ namespace Sapphire::World::Action
bool m_canTargetFriendly;
bool m_canTargetHostile;
bool m_canTargetDead;
bool m_isAutoAttack;
bool m_disableGenericHandler;
bool m_shouldAlwaysCombo;
Common::ActionInterruptType m_interruptType;

View file

@ -12,9 +12,9 @@ bool ActionLut::validEntryExists( uint16_t actionId )
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
return entry.potency != 0 || entry.comboPotency != 0 || entry.flankPotency != 0 || entry.frontPotency != 0 ||
entry.rearPotency != 0 || entry.curePotency != 0;
// if all of these fields are 0, it's not 'valid' due to parse error or no useful data
return entry.damagePotency != 0 || entry.healPotency != 0 || entry.selfStatus != 0 ||
entry.targetStatus != 0 || entry.bonusEffect != 0;
}
const ActionEntry& ActionLut::getEntry( uint16_t actionId )
@ -24,4 +24,408 @@ const ActionEntry& ActionLut::getEntry( uint16_t actionId )
assert( it != m_actionLut.end() );
return it->second;
}
}
bool ActionLut::validStatusEffectExists( uint16_t statusId )
{
auto it = m_statusEffectTable.find( statusId );
if( it == m_statusEffectTable.end() )
return false;
const auto& entry = it->second;
return entry.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
#include <unordered_map>
#include <Common.h>
using namespace Sapphire::Common;
namespace Sapphire::World::Action
{
struct ActionEntry
{
uint16_t potency;
uint16_t comboPotency;
uint16_t flankPotency;
uint16_t frontPotency;
uint16_t rearPotency;
uint16_t curePotency;
uint16_t restoreMPPercentage;
public:
uint16_t damagePotency;
uint16_t damageComboPotency;
uint16_t damageDirectionalPotency;
uint16_t healPotency;
uint16_t selfStatus;
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
{
public:
using Lut = std::unordered_map< uint16_t, ActionEntry >;
using StatusEffectTable = std::unordered_map< uint16_t, StatusEffectEntry >;
static bool validEntryExists( 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 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 ) :
m_sourceChara( std::move( source ) ),
m_actionId( actionId ),
m_animationLock( 0.6f ),
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 )
{
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 ) );
}
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 );
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 );
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 );
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 )
{
EffectResultPtr nextResult = make_EffectResult( target, 0 );
@ -84,25 +99,51 @@ void EffectBuilder::comboSucceed( Entity::CharaPtr& target )
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 );
nextResult->applyStatusEffect( statusId, param );
EffectResultPtr nextResult = make_EffectResult( target, source, Common::Util::getTimeMs() + resultDelayMs );
nextResult->applyStatusEffect( statusId, duration, param );
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 );
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()
{
auto targetCount = m_resolvedEffects.size();
Logger::debug( "EffectBuilder result: " );
Logger::debug( "Targets afflicted: {}", targetCount );
//Logger::debug( "EffectBuilder result: " );
//Logger::debug( "Targets afflicted: {}", targetCount );
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->sourceSequence = m_sequence;
pHeader->globalSequence = globalSequence;
pHeader->animationLockTime = m_animationLock;
uint8_t targetIndex = 0;
for( auto it = m_resolvedEffects.begin(); it != m_resolvedEffects.end(); )
@ -191,7 +233,7 @@ std::shared_ptr< FFXIVPacketBase > EffectBuilder::buildNextEffectPacket( uint32_
assert( !resultList->empty() );
auto firstResult = resultList->data()[ 0 ];
pEffectTargetId[ targetIndex ] = firstResult->getTarget()->getId();
Logger::debug( " - id: {}", pEffectTargetId[ targetIndex ] );
//Logger::debug( " - id: {}", pEffectTargetId[ targetIndex ] );
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;
assert( !resultList->empty() );
auto firstResult = resultList->data()[ 0 ];
Logger::debug( " - id: {}", firstResult->getTarget()->getId() );
//Logger::debug( " - id: {}", firstResult->getTarget()->getId() );
auto seq = m_sourceChara->getCurrentTerritory()->getNextEffectSequence();
auto effectPacket = std::make_shared< Server::EffectPacket >( m_sourceChara->getId(), firstResult->getTarget()->getId(), m_actionId );
effectPacket->setRotation( Common::Util::floatToUInt16Rot( m_sourceChara->getRot() ) );
effectPacket->setSequence( seq, m_sequence );
effectPacket->data().animationLockTime = m_animationLock;
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().sourceSequence = m_sequence;
effectPacket->data().globalSequence = globalSequence;
effectPacket->data().animationLockTime = m_animationLock;
return effectPacket;
}

View file

@ -11,37 +11,50 @@ namespace Sapphire::World::Action
public:
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,
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,
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,
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 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();
private:
void moveToResultList( Entity::CharaPtr& chara, EffectResultPtr result );
uint64_t getResultDelayMs();
std::shared_ptr< Sapphire::Network::Packets::FFXIVPacketBase > buildNextEffectPacket( uint32_t globalSequence );
private:
uint32_t m_actionId;
float m_animationLock;
uint16_t m_sequence;
Entity::CharaPtr m_sourceChara;

View file

@ -9,19 +9,32 @@ using namespace Sapphire;
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_source( std::move( source ) ),
m_delayMs( runAfter ),
m_type( Common::ActionEffectType::Nothing ),
m_value( 0 ),
m_statusDuration( 0 ),
m_param0( 0 ),
m_param1( 0 ),
m_type( Common::ActionEffectType::Nothing ),
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
{
return m_target;
@ -37,6 +50,13 @@ uint64_t EffectResult::getDelay()
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 )
{
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;
}
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 )
{
m_param1 = static_cast< uint8_t >( severity );
@ -77,14 +115,31 @@ void EffectResult::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_statusDuration = duration;
m_param2 = param;
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 )
{
m_value = mountId;
@ -93,6 +148,11 @@ void EffectResult::mount( uint16_t mountId )
m_type = Common::ActionEffectType::Mount;
}
void Sapphire::World::Action::EffectResult::provoke()
{
m_type = Common::ActionEffectType::Provoke;
}
Common::EffectEntry EffectResult::buildEffectEntry() const
{
Common::EffectEntry entry{};
@ -110,7 +170,7 @@ Common::EffectEntry EffectResult::buildEffectEntry() const
}
entry.param0 = m_param0;
entry.param1 = m_param1;
entry.param2 = m_param2;
entry.param2 = static_cast< uint8_t >( m_param2 );
return entry;
}
@ -120,6 +180,8 @@ void EffectResult::execute()
switch( m_type )
{
case Common::ActionEffectType::Damage:
case Common::ActionEffectType::BlockedDamage:
case Common::ActionEffectType::ParriedDamage:
{
m_target->takeDamage( m_value );
break;
@ -137,6 +199,38 @@ void EffectResult::execute()
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:
{
auto pPlayer = m_target->getAsPlayer();

View file

@ -4,6 +4,8 @@
#include <ForwardsZone.h>
#include <Common.h>
#include "StatusEffect/StatusEffect.h"
namespace Sapphire::World::Action
{
/*!
@ -13,16 +15,24 @@ namespace Sapphire::World::Action
class EffectResult
{
public:
explicit EffectResult( Entity::CharaPtr target, Entity::CharaPtr source, 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 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 restoreMP( uint32_t amount, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None );
void startCombo( uint16_t actionId );
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 provoke();
Entity::CharaPtr getSource() const;
Entity::CharaPtr getTarget() const;
uint32_t getValue() const;
@ -36,16 +46,20 @@ namespace Sapphire::World::Action
private:
uint64_t m_delayMs;
Entity::CharaPtr m_source;
Entity::CharaPtr m_target;
Common::ActionEffectType m_type;
uint8_t m_param0;
uint8_t m_param1;
uint8_t m_param2;
uint16_t m_param2;
uint32_t m_value;
uint32_t m_statusDuration;
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 )
return;
m_interruptType = type;
try
{

View file

@ -22,7 +22,7 @@ public:
void execute() override;
void interrupt() override;
void interrupt( Common::ActionInterruptType type = Common::ActionInterruptType::RegularInterrupt ) override;
private:
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 interrupt() override;
void interrupt( Common::ActionInterruptType type = Common::ActionInterruptType::RegularInterrupt ) override;
private:
void handleVFXItem();

View file

@ -679,9 +679,14 @@ void Sapphire::Entity::BNpc::setFlag( uint32_t 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 )
{
uint64_t tick = Util::getTimeMs();
// 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() );
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 );
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
Common::EffectEntry effectEntry{};
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 ) );
action->setTargetId( pTarget->getId() );
action->setPos( getPos() );
action->setAutoAttack();
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
according to resulting hp, propagates new hp value to players
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
*/
@ -483,43 +481,8 @@ uint32_t Sapphire::Entity::Chara::getBonusStat( Common::BaseParam bonus ) const
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 )
{
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 */
@ -551,28 +514,49 @@ void Sapphire::Entity::Chara::addStatusEffect( StatusEffect::StatusEffectPtr pEf
status.index = static_cast< uint8_t >( nextSlot );
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 )
void Sapphire::Entity::Chara::addStatusEffectById( uint32_t id, int32_t duration, Entity::Chara& source, uint16_t param, bool sendActorControl )
{
auto effect = StatusEffect::make_StatusEffect( id, source.getAsChara(), getAsChara(), duration, 3000 );
effect->setParam( param );
if( sendActorControl )
{
auto p = makeActorControl( getId(), Network::ActorControl::StatusEffectGain, id, 0, 0, 0 );
sendToInRangeSet( p, true );
}
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 )
void Sapphire::Entity::Chara::addStatusEffectByIdIfNotExist( uint32_t id, int32_t duration, Entity::Chara& source, uint16_t param, bool sendActorControl )
{
if( hasStatusEffect( id ) )
if( getStatusEffectById( id ).second )
return;
auto effect = StatusEffect::make_StatusEffect( id, source.getAsChara(), getAsChara(), duration, 3000 );
effect->setParam( param );
if( sendActorControl )
{
auto p = makeActorControl( getId(), Network::ActorControl::StatusEffectGain, id, 0, 0, 0 );
sendToInRangeSet( p, true );
}
addStatusEffect( effect );
}
int8_t Sapphire::Entity::Chara::getStatusEffectFreeSlot()
@ -593,34 +577,35 @@ void Sapphire::Entity::Chara::statusEffectFreeSlot( uint8_t 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 )
{
if( effectIt.second->getId() == id )
{
removeStatusEffect( effectIt.first );
removeStatusEffect( effectIt.first, sendStatusList );
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 );
if( pEffectIt == m_statusEffectMap.end() )
return;
statusEffectFreeSlot( effectSlotId );
auto pEffect = pEffectIt->second;
pEffect->removeStatus();
sendToInRangeSet( makeActorControl( getId(), StatusEffectLose, pEffect->getId() ), isPlayer() );
statusEffectFreeSlot( effectSlotId );
m_statusEffectMap.erase( effectSlotId );
sendStatusEffectUpdate();
pEffect->removeStatus();
sendToInRangeSet( makeActorControl( getId(), StatusEffectLose, pEffect->getId() ), true );
if( sendStatusList )
sendStatusEffectUpdate();
}
std::map< uint8_t, Sapphire::StatusEffect::StatusEffectPtr > Sapphire::Entity::Chara::getStatusEffectMap() const
@ -652,7 +637,6 @@ void Sapphire::Entity::Chara::sendStatusEffectUpdate()
{
uint64_t currentTimeMs = Util::getTimeMs();
auto statusEffectList = makeZonePacket< FFXIVIpcStatusEffectList >( getId() );
statusEffectList->data().classId = static_cast< uint8_t >( getClass() );
statusEffectList->data().level = getLevel();
@ -662,18 +646,56 @@ void Sapphire::Entity::Chara::sendStatusEffectUpdate()
statusEffectList->data().max_hp = getMaxHp();
statusEffectList->data().max_mp = getMaxMp();
uint8_t slot = 0;
float totalShieldValue = 0;
for( auto effectIt : m_statusEffectMap )
{
float timeLeft = static_cast< float >( effectIt.second->getDuration() -
( currentTimeMs - effectIt.second->getStartTimeMs() ) ) / 1000;
auto statusEffect = effectIt.second;
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 ].effect_id = effectIt.second->getId();
statusEffectList->data().effect[ slot ].sourceActorId = effectIt.second->getSrcActorId();
statusEffectList->data().effect[ slot ].effect_id = statusEffect->getId();
statusEffectList->data().effect[ slot ].param = statusEffect->getParam();
statusEffectList->data().effect[ slot ].sourceActorId = statusEffect->getSrcActorId();
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()
@ -690,7 +712,7 @@ void Sapphire::Entity::Chara::updateStatusEffects()
uint32_t duration = effect->getDuration();
uint32_t tickRate = effect->getTickRate();
if( duration > 0 && ( currentTimeMs - startTime ) > duration )
if( effect->isMarkedToRemove() || ( duration > 0 && ( currentTimeMs - startTime ) > duration ) )
{
// remove status effect
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
@ -871,6 +900,10 @@ uint32_t Sapphire::Entity::Chara::getStatValue( Sapphire::Common::BaseParam base
case Common::BaseParam::Haste:
{
value = m_baseStats.haste;
for( auto const& statusIt : m_statusEffectMap )
{
value -= statusIt.second->getEffectEntry().getHasteBonus();
}
break;
}
@ -912,6 +945,40 @@ uint32_t Sapphire::Entity::Chara::getStatValue( Sapphire::Common::BaseParam base
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()
{
return m_effect;
@ -957,7 +1024,9 @@ void Sapphire::Entity::Chara::onTick()
if( thisTickDmg != 0 )
{
takeDamage( thisTickDmg );
thisTickDmg = applyShieldProtection( thisTickDmg );
if( thisTickDmg > 0 )
takeDamage( thisTickDmg );
sendToInRangeSet( makeActorControl( getId(), HPFloatingText, 0,
static_cast< uint8_t >( ActionEffectType::Damage ), thisTickDmg ), true );
}

View file

@ -149,13 +149,13 @@ namespace Sapphire::Entity
/// Status effect functions
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();
bool hasStatusEffect( uint32_t id );
std::pair< uint8_t, StatusEffect::StatusEffectPtr > getStatusEffectById( uint32_t id );
int8_t getStatusEffectFreeSlot();
@ -169,19 +169,19 @@ namespace Sapphire::Entity
void sendStatusEffectUpdate();
void sendShieldUpdate();
/*! return a const pointer to the look array */
const uint8_t* getLookArray() const;
const uint32_t* getModelArray() const;
// 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
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
std::string getName() const;
@ -291,6 +291,8 @@ namespace Sapphire::Entity
Common::BaseParam getPrimaryStat() const;
float applyShieldProtection( float damage );
uint32_t getVisualEffect();
void setVisualEffect( uint32_t effect, bool sendPacket = true );
void sendVisualEffect();

View file

@ -20,6 +20,8 @@
#include "Manager/TerritoryMgr.h"
#include "Manager/RNGMgr.h"
#include "Manager/MapMgr.h"
#include <Manager/PlayerMgr.h>
#include "Manager/EventMgr.h"
#include "Territory/Territory.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 )
{
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
@ -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 );
//performZoning( zoneId, Common::ZoneingType::None, getPos() );
if( zoneId == 0 )
{
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()
@ -465,12 +491,16 @@ bool Sapphire::Entity::Player::setInstance( TerritoryPtr instance )
auto currentZone = getCurrentTerritory();
// 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() ) ) )
{
m_prevPos = m_pos;
m_prevRot = m_rot;
m_prevTerritoryTypeId = currentZone->getTerritoryTypeId();
m_prevTerritoryId = getTerritoryId();
// never returning to a BeforeTrialDung zone.
if( currentZone->getTerritoryTypeInfo()->territoryIntendedUse != Sapphire::World::Manager::TerritoryMgr::TerritoryIntendedUse::BeforeTrialDung )
{
m_prevPos = m_pos;
m_prevRot = m_rot;
m_prevTerritoryTypeId = currentZone->getTerritoryTypeId();
m_prevTerritoryId = getTerritoryId();
}
}
return teriMgr.movePlayer( instance, getAsPlayer() );
@ -486,12 +516,16 @@ bool Sapphire::Entity::Player::setInstance( TerritoryPtr instance, Common::FFXIV
auto currentZone = getCurrentTerritory();
// 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() ) ) )
{
m_prevPos = m_pos;
m_prevRot = m_rot;
m_prevTerritoryTypeId = currentZone->getTerritoryTypeId();
m_prevTerritoryId = getTerritoryId();
// never returning to a BeforeTrialDung zone.
if( currentZone->getTerritoryTypeInfo()->territoryIntendedUse != Sapphire::World::Manager::TerritoryMgr::TerritoryIntendedUse::BeforeTrialDung )
{
m_prevPos = m_pos;
m_prevRot = m_rot;
m_prevTerritoryTypeId = currentZone->getTerritoryTypeId();
m_prevTerritoryId = getTerritoryId();
}
}
m_pos = pos;
@ -797,15 +831,7 @@ void Sapphire::Entity::Player::gainLevel()
m_hp = getMaxHp();
m_mp = getMaxMp();
auto effectListPacket = makeZonePacket< FFXIVIpcStatusEffectList >( getId() );
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 );
sendStatusEffectUpdate();
sendToInRangeSet( makeActorControl( getId(), LevelUpEffect, static_cast< uint8_t >( getClass() ),
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() );
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
@ -1080,7 +1113,6 @@ bool Sapphire::Entity::Player::hasStateFlag( Common::PlayerStateFlag flag ) cons
void Sapphire::Entity::Player::setStateFlag( Common::PlayerStateFlag flag )
{
auto prevOnlineStatus = getOnlineStatus();
int32_t iFlag = static_cast< uint32_t >( flag );
uint16_t index;
@ -1089,13 +1121,6 @@ void Sapphire::Entity::Player::setStateFlag( Common::PlayerStateFlag flag )
m_stateFlags[ index ] |= value;
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 )
@ -1116,8 +1141,6 @@ void Sapphire::Entity::Player::unsetStateFlag( Common::PlayerStateFlag flag )
if( !hasStateFlag( flag ) )
return;
auto prevOnlineStatus = getOnlineStatus();
int32_t iFlag = static_cast< uint32_t >( flag );
uint16_t index;
@ -1126,11 +1149,6 @@ void Sapphire::Entity::Player::unsetStateFlag( Common::PlayerStateFlag flag )
m_stateFlags[ index ] ^= value;
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 )
@ -1164,6 +1182,7 @@ void Sapphire::Entity::Player::update( uint64_t tickCount )
if( !isAlive() )
return;
int64_t interval = tickCount - m_lastUpdate;
m_lastUpdate = tickCount;
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 );
}
@ -1529,13 +1605,17 @@ void Sapphire::Entity::Player::onMobAggro( BNpcPtr pBNpc )
{
hateListAdd( pBNpc );
queuePacket( makeActorControl( getId(), ToggleAggro, 1 ) );
setStateFlag( Common::PlayerStateFlag::InCombat );
}
void Sapphire::Entity::Player::onMobDeaggro( BNpcPtr pBNpc )
{
hateListRemove( pBNpc );
if( m_actorIdTohateSlotMap.empty() )
{
queuePacket( makeActorControl( getId(), ToggleAggro ) );
unsetStateFlag( Common::PlayerStateFlag::InCombat );
}
}
bool Sapphire::Entity::Player::isLogin() const
@ -1677,52 +1757,30 @@ uint32_t Sapphire::Entity::Player::getPersistentEmote() const
void Sapphire::Entity::Player::autoAttack( CharaPtr pTarget )
{
auto mainWeap = getItemAt( Common::GearSet0, Common::GearSetSlot::MainHand );
pTarget->onActionHostile( getAsChara() );
//uint64_t tick = Util::getTimeMs();
//srand(static_cast< uint32_t >(tick));
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 );
auto& exdData = Common::Service< Data::ExdDataGenerated >::ref();
World::Action::ActionPtr action;
if( getClass() == ClassJob::Machinist || getClass() == ClassJob::Bard || getClass() == ClassJob::Archer )
{
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 8 );
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
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 );
auto actionData = exdData.get< Data::Action >( 8 );
assert( actionData );
action = World::Action::make_Action( getAsChara(), 8, 0, actionData );
}
else
{
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 );
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
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 );
auto actionData = exdData.get< Data::Action >( 7 );
assert( actionData );
action = World::Action::make_Action( getAsChara(), 7, 0, actionData );
}
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;
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;
@ -2225,6 +2283,10 @@ void Sapphire::Entity::Player::updateHuntingLog( uint16_t id )
auto classJobInfo = pExdData.get< Sapphire::Data::ClassJob >( currentClass );
if( !classJobInfo )
return;
if( classJobInfo->classJobParent > 0 )
logEntryIndex = classJobInfo->classJobParent;
auto& logEntry = m_huntingLogEntries[ logEntryIndex ];
bool allSectionsComplete = true;
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 )
{
m_pQueuedAction = std::move( pAction ); // overwrite previous queued action if any
m_pQueuedAction = std::move( pAction );
}
bool Sapphire::Entity::Player::checkAction()
@ -2374,4 +2436,281 @@ void Sapphire::Entity::Player::gaugeSetRaw( uint8_t* pData )
{
std::memcpy( &m_gauge, pData, 15 );
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 */
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 */
void returnToHomepoint();
@ -1016,6 +1016,48 @@ namespace Sapphire::Entity
void sendActorGauge();
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 );

View file

@ -7,6 +7,8 @@
#include "Script/ScriptMgr.h"
#include "Actor/Player.h"
#include "StatusEffect/StatusEffect.h"
#include <Exd/ExdDataGenerated.h>
#include <Network/PacketWrappers/EffectPacket.h>
@ -107,6 +109,14 @@ void World::Manager::ActionMgr::bootstrapAction( Entity::Player& player,
Action::ActionPtr currentAction,
Data::Action& actionData )
{
for( const auto& statusIt : player.getStatusEffectMap() )
{
statusIt.second->onBeforeActionStart( currentAction.get() );
}
if( currentAction->isInterrupted() )
return;
if( !currentAction->preCheck() )
{
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 ) );
player.sendDebug( "container: {}, slot: {}, uid: {}", destContainer.first, item.first, pItem->getUId() );
container->setItem( static_cast< uint8_t >( item.first ), pItem );
}

View file

@ -10,6 +10,10 @@
#include "Inventory/Item.h"
#include "StatusEffect/StatusEffect.h"
#include "Action/Action.h"
#include "CalcStats.h"
using namespace Sapphire::Math;
@ -104,9 +108,7 @@ const int levelTable[81][6] =
{ 340, 380, 3300, 3600, 569, 569 },
};
std::random_device CalcStats::dev;
std::mt19937 CalcStats::rng( dev() );
std::uniform_int_distribution< std::mt19937::result_type > CalcStats::range100( 0, 99 );
std::unique_ptr< RandGenerator< float > > CalcStats::rnd = nullptr;
/*
Class used for battle-related formulas and calculations.
@ -181,16 +183,42 @@ uint32_t CalcStats::calculateMaxHp( PlayerPtr pPlayer )
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 )
{
auto level = chara.getLevel();
auto blockRate = static_cast< float >( chara.getStatValue( Common::BaseParam::BlockRate ) );
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();
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 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();
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 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;
}
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;
// fetch actual auto attack delay if its a player
if( chara.isPlayer() )
@ -328,6 +375,29 @@ float CalcStats::healingMagicPower( const Sapphire::Entity::Chara& chara )
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 )
{
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;
}
float CalcStats::parryStrength( const Sapphire::Entity::Chara& chara )
{
return 0.15f;
}
float CalcStats::autoAttack( const Sapphire::Entity::Chara& chara )
{
// 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;
weaponDamage = pItem->getWeaponDmg();
}
else
{
// dummy value for BNpc
weaponDamage = chara.getLevel() * 3;
}
auto level = chara.getLevel();
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;
}
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) ⌋ ×
// 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 ap = getPrimaryAttackPower( chara );
auto det = determination( chara );
@ -464,13 +544,13 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcAutoA
factor = std::floor( factor * speed( chara ) );
if( criticalHitProbability( chara ) > range100( rng ) )
if( criticalHitProbability( chara, Common::CritDHBonusFilter::Damage ) > getRandomNumber0To100() )
{
factor *= criticalHitBonus( chara );
hitType = Sapphire::Common::ActionHitSeverityType::CritDamage;
}
if( directHitProbability( chara ) > range100( rng ) )
if( directHitProbability( chara, Common::CritDHBonusFilter::Damage ) > getRandomNumber0To100() )
{
factor *= 1.25f;
hitType = hitType == Sapphire::Common::ActionHitSeverityType::CritDamage ?
@ -478,25 +558,42 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcAutoA
Sapphire::Common::ActionHitSeverityType::DirectHitDamage;
}
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f );
factor *= 1.0f + ( ( getRandomNumber0To100() - 50.0f ) / 1000.0f );
// todo: buffs
constexpr auto format = "auto attack: pot: {} aa: {} ap: {} det: {} ten: {} = {}";
if( auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer() )
for( auto const& entry : chara.getStatusEffectMap() )
{
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 );
}
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 ⌋
// × 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 );
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: {} = {}";
@ -537,27 +615,185 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActio
{
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 );
}
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
auto factor = std::floor( ptc * ( wepDmg / 10.0f ) + ptc );
if( typeFilter == ActionTypeFilter::Unknown )
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;
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 );
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 );
}
@ -565,4 +801,81 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActio
uint32_t CalcStats::primaryStatValue( const Sapphire::Entity::Chara& chara )
{
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
#define _CALCSTATS_H
#include <random>
#include <Common.h>
#include "Forwards.h"
#include "Manager/RNGMgr.h"
namespace Sapphire::Math
{
using namespace Sapphire::World::Manager;
class CalcStats
{
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 uint32_t calculateMaxHp( Sapphire::Entity::PlayerPtr pPlayer );
static float dodgeProbability( const Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates the probability of a block happening
*/
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
*/
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
*/
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.
@ -40,7 +42,7 @@ namespace Sapphire::Math
*/
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
@ -64,6 +66,8 @@ namespace Sapphire::Math
static float healingMagicPower( const Sapphire::Entity::Chara& chara );
static float getWeaponDamage( Sapphire::Entity::CharaPtr chara );
/*!
* @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 parryStrength( 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 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:
/*!
@ -145,9 +171,7 @@ namespace Sapphire::Math
*/
static float calcAttackPower( const Sapphire::Entity::Chara& chara, uint32_t attackPower );
static std::random_device dev;
static std::mt19937 rng;
static std::uniform_int_distribution< std::mt19937::result_type > range100;
static std::unique_ptr< RandGenerator< float > > rnd;
};
}

View file

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

View file

@ -256,19 +256,12 @@ void Sapphire::Network::GameConnection::gm1Handler( const Packets::FFXIVARR_PACK
{
targetPlayer->setOnlineStatusMask( param1 );
auto statusPacket = makeZonePacket< FFXIVIpcSetOnlineStatus >( player.getId() );
statusPacket->data().onlineStatusFlags = param1;
queueOutPacket( statusPacket );
auto searchInfoPacket = makeZonePacket< FFXIVIpcSetSearchInfo >( player.getId() );
searchInfoPacket->data().onlineStatusFlags = param1;
searchInfoPacket->data().selectRegion = targetPlayer->getSearchSelectRegion();
strcpy( searchInfoPacket->data().searchMessage, targetPlayer->getSearchMessage() );
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 );
break;
}

View file

@ -91,18 +91,11 @@ void Sapphire::Network::GameConnection::setSearchInfoHandler( const Packets::FFX
// mark player as new adventurer
player.setNewAdventurer( true );
auto statusPacket = makeZonePacket< FFXIVIpcSetOnlineStatus >( player.getId() );
statusPacket->data().onlineStatusFlags = status;
queueOutPacket( statusPacket );
auto searchInfoPacket = makeZonePacket< FFXIVIpcSetSearchInfo >( player.getId() );
searchInfoPacket->data().onlineStatusFlags = status;
searchInfoPacket->data().selectRegion = player.getSearchSelectRegion();
strcpy( searchInfoPacket->data().searchMessage, player.getSearchMessage() );
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,
@ -184,7 +177,7 @@ void Sapphire::Network::GameConnection::updatePositionHandler( const Packets::FF
Entity::Player& player )
{
// if the player is marked for zoning we no longer want to update his pos
if( player.isMarkedForZoning() )
if( player.isMarkedForZoning() || !player.isLoadingComplete() )
return;
const auto updatePositionPacket = ZoneChannelPacket< Client::FFXIVIpcUpdatePosition >( inPacket );
@ -200,7 +193,7 @@ void Sapphire::Network::GameConnection::updatePositionHandler( const Packets::FF
player.setPos( updatePositionPacket.data().position );
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( !player.hasInRangeActor() )
@ -416,6 +409,7 @@ void Sapphire::Network::GameConnection::finishLoadingHandler( const Packets::FFX
player.setIsLogin( false );
}
player.setVisualEffect( 0, false );
// spawn the player for himself
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 )
{
}
@ -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 )
{
}

View file

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

View file

@ -329,6 +329,18 @@ bool Sapphire::Scripting::ScriptMgr::onEObjHit( Sapphire::Entity::Player& player
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 )
{
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;
}
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 )
{
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 onBeforePreCheck( World::Action::Action& action );
bool onStart( World::Action::Action& action );
bool onInterrupt( 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 onStatusTick( Entity::CharaPtr pActor, Sapphire::StatusEffect::StatusEffect& effect );

View file

@ -6,11 +6,16 @@
#include <algorithm>
#include <Service.h>
#include "Actor/Player.h"
#include "Actor/Chara.h"
#include "Actor/Actor.h"
#include "Action/Action.h"
#include "Script/ScriptMgr.h"
#include "Math/CalcStats.h"
#include "StatusEffect.h"
using namespace Sapphire::Common;
@ -25,7 +30,11 @@ Sapphire::StatusEffect::StatusEffect::StatusEffect( uint32_t id, Entity::CharaPt
m_duration( duration ),
m_startTime( 0 ),
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 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, ')' );
}
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()
{
@ -54,9 +67,40 @@ void Sapphire::StatusEffect::StatusEffect::registerTickEffect( uint8_t type, uin
std::pair< uint8_t, uint32_t > Sapphire::StatusEffect::StatusEffect::getTickEffect()
{
auto thisTick = m_currTickEffect;
m_currTickEffect = std::make_pair( 0, 0 );
return thisTick;
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 );
break;
}
}
return m_currTickEffect;
}
void Sapphire::StatusEffect::StatusEffect::onTick()
@ -65,6 +109,10 @@ void Sapphire::StatusEffect::StatusEffect::onTick()
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
scriptMgr.onStatusTick( m_targetActor, *this );
auto mp = m_effectEntry.getMPRestoreTick();
if( mp > 0 )
m_targetActor->restoreMP( mp );
}
uint32_t Sapphire::StatusEffect::StatusEffect::getSrcActorId() const
@ -86,32 +134,90 @@ void Sapphire::StatusEffect::StatusEffect::applyStatus()
{
m_startTime = Util::getTimeMs();
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 );
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()
{
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
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
@ -149,7 +255,140 @@ void Sapphire::StatusEffect::StatusEffect::setParam( uint16_t 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
{
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 "Action/ActionLut.h"
namespace Sapphire {
namespace StatusEffect {
@ -17,6 +19,12 @@ public:
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 removeStatus();
@ -41,13 +49,30 @@ public:
void setParam( uint16_t param );
void setStacks( uint8_t stacks );
uint8_t getStacks();
void registerTickEffect( uint8_t type, uint32_t param );
std::pair< uint8_t, uint32_t > getTickEffect();
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:
bool applyToAction( Sapphire::World::Action::Action* action );
uint32_t m_id;
Entity::CharaPtr m_sourceActor;
Entity::CharaPtr m_targetActor;
@ -58,7 +83,11 @@ private:
uint16_t m_param;
std::string m_name;
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;
};
}