1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-20 03:37:48 +00:00

[3.x] WIP: Initial Action and StatusEffect implementation (#958)

* Check statuses to determine valid lut entry

* Add duration field to statuses

* Rename buildEffects to make more sense

* Add basic generic handler for applying statuseffects

* Add more modifiers

* Add basic modifier impl for Chara

* Apply/remove modifiers for statuseffects

* Add some example statuses to lut

* Fix windows build error

* Don't clear tick effect

* Add status entry for Maim

* Apply status effects properly for self when having a target

* Fix hasStatusEffect to prevent duplicates

* Basic dot/hot ticks implemented

* Update HP on tick effects

* Apply effect to correct target

* Add method to simplify applying statuses to self

* Add job actions for warrior

* Add some actions and statuses for war

* Add even more modifiers

* Add statuseffect cost type

* Add option to not send statusremove order

* Change delModifier assert to return early instead

* Add option for scripts to enable the generic/lut handler

* Add enums for common action values

* fix indentation

* Fix modifier name for Defiance

* Remove status tick logging

* Move modifiers to statuseffect

* Add ParryPercent modifier

* Remove wrath when Defiance ends

* Apply modifiers in applyStatus

* Remove unused method

* Persistence for cross-class skills

* Add flags to StatusEffects

* Some exd struct fixes

* Some aoe work

* Add flags to lut

* Add missing changeclass

* Add SET_STATUS_ME to ActionIntegrity

* Improve offensive action check

* Add flag to overloaded applyStatusEffectSelf

* indentation fix

* Some calculation work

* Null-check ActionResultBuilder

---------

Co-authored-by: Lucy <44952533+Skyliegirl33@users.noreply.github.com>
Co-authored-by: Mordred <30826167+SapphireMordred@users.noreply.github.com>
This commit is contained in:
Mimi 2024-06-21 04:27:01 +02:00 committed by GitHub
parent 5189042247
commit 7bfd9538c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 902 additions and 87 deletions

View file

@ -398,7 +398,18 @@
"restorePercentage": 0,
"nextCombo": [],
"statuses": {
"caster": [],
"caster": [
{
"id": 83,
"duration": 20000,
"modifiers": [
{
"modifier": "DefensePercent",
"value": 20
}
]
}
],
"target": []
}
},
@ -414,7 +425,18 @@
"nextCombo": [],
"statuses": {
"caster": [],
"target": []
"target": [
{
"id": 244,
"duration": 30000,
"modifiers": [
{
"modifier": "TickDamage",
"value": 20
}
]
}
]
}
},
"34": {
@ -478,7 +500,18 @@
45
],
"statuses": {
"caster": [],
"caster": [
{
"id": 85,
"duration": 24000,
"modifiers": [
{
"modifier": "DamageDealtPercent",
"value": 20
}
]
}
],
"target": []
}
},
@ -493,7 +526,18 @@
"restorePercentage": 0,
"nextCombo": [],
"statuses": {
"caster": [],
"caster": [
{
"id": 86,
"duration": 20000,
"modifiers": [
{
"modifier": "AttackPowerPercent",
"value": 50
}
]
}
],
"target": []
}
},
@ -523,7 +567,18 @@
"restorePercentage": 0,
"nextCombo": [],
"statuses": {
"caster": [],
"caster": [
{
"id": 87,
"duration": 20000,
"modifiers": [
{
"modifier": "HPPercent",
"value": 20
}
]
}
],
"target": []
}
},
@ -574,7 +629,7 @@
},
"44": {
"name": "Vengeance",
"potency": 50,
"potency": 0,
"comboPotency": 0,
"flankPotency": 0,
"frontPotency": 0,
@ -583,7 +638,18 @@
"restorePercentage": 0,
"nextCombo": [],
"statuses": {
"caster": [],
"caster": [
{
"id": 89,
"duration": 20000,
"modifiers": [
{
"modifier": "ReflectPhysical",
"value": 50
}
]
}
],
"target": []
}
},
@ -643,7 +709,34 @@
"restorePercentage": 0,
"nextCombo": [],
"statuses": {
"caster": [],
"caster": [
{
"id": 91,
"duration": 0,
"modifiers": [
{
"modifier": "HPPercent",
"value": 25
},
{
"modifier": "DamageDealtPercent",
"value": -25
},
{
"modifier": "HealingMagicRecoveryPercent",
"value": 20
},
{
"modifier": "AccuracyPercent",
"value": 5
},
{
"modifier": "EnmityPercent",
"value": 20
}
]
}
],
"target": []
}
},
@ -703,7 +796,12 @@
"restorePercentage": 0,
"nextCombo": [],
"statuses": {
"caster": [],
"caster": [
{
"id": 97,
"duration": 30000
}
],
"target": []
}
},
@ -1176,7 +1274,19 @@
"restorePercentage": 0,
"nextCombo": [],
"statuses": {
"caster": [],
"caster": [
{
"id": 116,
"duration": 10000,
"flag": 4096,
"modifiers": [
{
"modifier": "CriticalHitPercent",
"value": 100
}
]
}
],
"target": []
}
},
@ -2454,7 +2564,38 @@
"nextCombo": [],
"statuses": {
"caster": [],
"target": []
"target": [
{
"id": 180,
"duration": 24000,
"modifiers": [
{
"modifier": "TickDamage",
"value": 35
}
]
},
{
"id": 191,
"duration": 24000,
"modifiers": [
{
"modifier": "HealingRecoveryPercent",
"value": -20
}
]
},
{
"id": 240,
"duration": 24000,
"modifiers": [
{
"modifier": "HeavyPercent",
"value": 40
}
]
}
]
}
},
"169": {

View file

@ -391,14 +391,14 @@ namespace Excel
uint8_t EffectWidth;
uint8_t CostType;
uint8_t Cond;
uint8_t RecastGroup;
uint8_t Element;
uint8_t ProcStatus;
uint8_t UseClassJob;
uint8_t ClassJobCategory;
uint8_t RecastGroup;
uint8_t Init;
uint8_t Omen;
uint8_t Unknown;
int8_t Learn;
uint8_t Learn;
int8_t UseClassJob;
int8_t SelectRange;
int8_t SelectCorpse;
int8_t AttackType;
@ -430,7 +430,7 @@ namespace Excel
uint8_t HideCastBar : 1;
uint8_t IsTargetLine : 1;
int8_t padding0;
int8_t unknown : 8;
};
/* 75653 */
@ -2054,7 +2054,8 @@ namespace Excel
uint8_t NotControl : 1;
uint8_t NotAction : 1;
uint8_t NotMove : 1;
uint8_t padding0 : 6;
uint8_t padding0 : 5;
uint8_t CanOff : 1;
uint8_t SemiTransparent : 1;
uint8_t FcAction : 1;
int8_t padding1[2];

View file

@ -0,0 +1 @@
ALTER TABLE `characlass` ADD `BorrowAction` binary(40) DEFAULT NULL NULL AFTER `Lvl`;

View file

@ -256,12 +256,14 @@ void PlayerMinimal::saveAsNew()
break;
}
// CharacterId, ClassIdx, Exp, Lvl
// CharacterId, ClassIdx, Exp, Lvl, BorrowAction
auto stmtClass = g_charaDb.getPreparedStatement( Db::ZoneDbStatements::CHARA_CLASS_INS );
stmtClass->setUInt64( 1, m_characterId );
stmtClass->setInt( 2, g_exdData.getRow< Excel::ClassJob >( m_class )->data().WorkIndex );
stmtClass->setInt( 3, 0 );
stmtClass->setInt( 4, 1 );
std::vector< uint8_t > borrowActionVec( Common::ARRSIZE_BORROWACTION * 4 );
stmtClass->setBinary( 5, borrowActionVec );
g_charaDb.directExecute( stmtClass );
auto stmtSearchInfo = g_charaDb.getPreparedStatement( Db::ZoneDbStatements::CHARA_SEARCHINFO_INS );

View file

@ -37,6 +37,7 @@ namespace Sapphire::Common
const uint16_t ARRSIZE_UNLOCKS = 64u;
const uint16_t ARRSIZE_ORCHESTRION = 40u;
const uint16_t ARRSIZE_MONSTERNOTE = 12u;
const uint16_t ARRSIZE_BORROWACTION = 10u;
const uint8_t TOWN_COUNT = 6;
@ -887,22 +888,55 @@ namespace Sapphire::Common
Perception = 73,
// Unique modifiers
HPPercent = 1000,
MPPercent = 1001,
TPPercent = 1002,
GPPercent = 1003,
CPPercent = 1004,
PhysicalDamagePercent = 1005,
MagicDamagePercent = 1006,
AttackPowerPercent = 1007,
DefensePercent = 1008,
AccuracyPercent = 1009,
EvasionPercent = 1010,
MagicDefensePercent = 1011,
CriticalHitPowerPercent = 1012,
CriticalHitResiliencePercent = 1013,
CriticalHitPercent = 1014,
EnmityPercent = 1015
TickHeal = 1000,
TickDamage = 1001,
StrengthPercent = 1002,
DexterityPercent = 1003,
VitalityPercent = 1004,
IntelligencePercent = 1005,
MindPercent = 1006,
PietyPercent = 1007,
HPPercent = 1008,
MPPercent = 1009,
TPPercent = 1010,
GPPercent = 1011,
CPPercent = 1012,
PhysicalDamagePercent = 1013,
MagicDamagePercent = 1014,
AttackPowerPercent = 1015,
DefensePercent = 1016,
AccuracyPercent = 1017,
EvasionPercent = 1018,
MagicDefensePercent = 1019,
CriticalHitPowerPercent = 1020,
CriticalHitResiliencePercent = 1021,
CriticalHitPercent = 1022,
EnmityPercent = 1023,
DamageDealtPercent = 1024,
DamageTakenPercent = 1025,
HealingMagicRecoveryPercent = 1026,
SlashingResistancePercent = 1027,
PiercingResistancePercent = 1028,
BluntResistancePercent = 1029,
ProjectileResistancePercent = 1030,
ParryPercent = 1031
};
enum class StatusEffectFlag : uint32_t
{
BuffCategory = 1,
DebuffCategory = 2,
Permanent = 4,
IsGaze = 8,
Transfiguration = 16,
CanDispel = 32,
LockActions = 64,
LockControl = 128,
LockMovement = 256,
Invisibilty = 512,
CanStatusOff = 1024,
FcBuff = 2048,
RemoveOnSuccessfulHit = 4096
};
enum struct ActionAspect : uint8_t
@ -923,6 +957,7 @@ namespace Sapphire::Common
MagicPoints = 3,
TacticsPoints = 5,
TacticsPoints1 = 6,
StatusEffect = 10,
Sprint = 18,
// WARGauge = 22,
// DRKGauge = 25,
@ -1830,8 +1865,8 @@ namespace Sapphire::Common
{
SingleTarget = 1,
CircularAOE = 2,
Type3 = 3, // another single target? no idea how to call it
RectangularAOE = 4,
RectangularAOE = 3,
ConeAOE = 4,
CircularAoEPlaced = 7
};

View file

@ -161,11 +161,11 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements()
prepareStatement( CHARA_SEL_QUEST, "SELECT * FROM charaquest WHERE CharacterId = ?;", CONNECTION_SYNC );
/// CLASS INFO
prepareStatement( CHARA_CLASS_SEL, "SELECT ClassIdx, Exp, Lvl FROM characlass WHERE CharacterId = ?;",
prepareStatement( CHARA_CLASS_SEL, "SELECT ClassIdx, Exp, Lvl, BorrowAction FROM characlass WHERE CharacterId = ?;",
CONNECTION_SYNC );
prepareStatement( CHARA_CLASS_INS, "INSERT INTO characlass ( CharacterId, ClassIdx, Exp, Lvl ) VALUES( ?,?,?,? );",
prepareStatement( CHARA_CLASS_INS, "INSERT INTO characlass ( CharacterId, ClassIdx, Exp, Lvl, BorrowAction ) VALUES( ?,?,?,?,? );",
CONNECTION_BOTH );
prepareStatement( CHARA_CLASS_UP, "UPDATE characlass SET Exp = ?, Lvl = ? WHERE CharacterId = ? AND ClassIdx = ?;",
prepareStatement( CHARA_CLASS_UP, "UPDATE characlass SET Exp = ?, Lvl = ?, BorrowAction = ? WHERE CharacterId = ? AND ClassIdx = ?;",
CONNECTION_ASYNC );
prepareStatement( CHARA_CLASS_DEL, "DELETE FROM characlass WHERE CharacterId = ?;", CONNECTION_ASYNC );

View file

@ -0,0 +1,47 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/CommonAction.h>
#include <Action/Action.h>
#include <StatusEffect/StatusEffect.h>
using namespace Sapphire;
using namespace Sapphire::World::Action;
class ActionInnerBeast : public Sapphire::ScriptAPI::ActionScript
{
public:
ActionInnerBeast() : Sapphire::ScriptAPI::ActionScript( 49 )
{
}
static constexpr auto Potency = 300;
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
auto pSource = action.getSourceChara();
auto pTarget = action.getHitChara();
auto pActionBuilder = action.getActionResultBuilder();
if( !pPlayer || !pActionBuilder )
return;
if( auto status = pPlayer->getStatusEffectById( Defiance ); status )
status->setModifier( Common::ParamModifier::DamageDealtPercent, 0 );
auto dmg = action.calcDamage( Potency );
pActionBuilder->damage( pSource, pTarget, dmg.first, dmg.second );
pActionBuilder->heal( pTarget, pSource, dmg.first, Common::CalcResultType::TypeRecoverHp, Common::ActionResultFlag::EffectOnSource );
pActionBuilder->applyStatusEffectSelf( InnerBeast, 15000, 0, { StatusModifier{ Common::ParamModifier::DamageTakenPercent, -20 } } );
if( !pPlayer->hasStatusEffect( Unchained ) )
{
if( auto status = pPlayer->getStatusEffectById( Defiance ); status )
status->setModifier( Common::ParamModifier::DamageDealtPercent, -25 );
}
}
};
EXPOSE_SCRIPT( ActionInnerBeast );

View file

@ -0,0 +1,33 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/CommonAction.h>
#include <Action/Action.h>
#include <StatusEffect/StatusEffect.h>
using namespace Sapphire;
using namespace Sapphire::World::Action;
class ActionUnchained : public Sapphire::ScriptAPI::ActionScript
{
public:
ActionUnchained() : Sapphire::ScriptAPI::ActionScript( 50 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pPlayer = action.getSourceChara()->getAsPlayer();
auto pActionBuilder = action.getActionResultBuilder();
if( !pPlayer || !pActionBuilder )
return;
if( auto status = pPlayer->getStatusEffectById( Defiance ); status )
status->setModifier( Common::ParamModifier::DamageDealtPercent, 0 );
pActionBuilder->applyStatusEffectSelf( Unchained, 20000, 0 );
}
};
EXPOSE_SCRIPT( ActionUnchained );

View file

@ -0,0 +1,27 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/CommonAction.h>
using namespace Sapphire;
using namespace Sapphire::World::Action;
class StatusEffectDefiance : public Sapphire::ScriptAPI::StatusEffectScript
{
public:
StatusEffectDefiance() : Sapphire::ScriptAPI::StatusEffectScript( 91 )
{
}
void onExpire( Entity::Chara& actor ) override
{
actor.removeSingleStatusEffectById( Unchained );
actor.removeSingleStatusEffectById( Wrath );
actor.removeSingleStatusEffectById( WrathII );
actor.removeSingleStatusEffectById( WrathIII );
actor.removeSingleStatusEffectById( WrathIV );
actor.removeSingleStatusEffectById( Infuriated );
}
};
EXPOSE_SCRIPT( StatusEffectDefiance );

View file

@ -0,0 +1,24 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/CommonAction.h>
#include <StatusEffect/StatusEffect.h>
using namespace Sapphire;
using namespace Sapphire::World::Action;
class StatusEffectUnchained : public Sapphire::ScriptAPI::StatusEffectScript
{
public:
StatusEffectUnchained() : Sapphire::ScriptAPI::StatusEffectScript( 92 )
{
}
void onExpire( Entity::Chara& actor ) override
{
if( auto status = actor.getStatusEffectById( Defiance ); status )
status->setModifier( Common::ParamModifier::DamageDealtPercent, -25 );
}
};
EXPOSE_SCRIPT( StatusEffectUnchained );

View file

@ -40,6 +40,8 @@ struct StatusModifier
struct StatusEntry
{
uint16_t id;
int32_t duration;
uint32_t flag;
std::vector< StatusModifier > modifiers;
};
@ -76,6 +78,8 @@ void to_json( nlohmann::ordered_json& j, const StatusEntry& statusEntry )
{
j = nlohmann::ordered_json{
{ "id", statusEntry.id },
{ "duration", statusEntry.duration },
{ "flag", statusEntry.flag },
{ "modifiers", statusEntry.modifiers }
};
}

View file

@ -32,6 +32,8 @@
#include <Service.h>
#include "WorldServer.h"
#include "Job/Warrior.h"
using namespace Sapphire;
using namespace Sapphire::Common;
using namespace Sapphire::Network;
@ -100,6 +102,7 @@ bool Action::Action::init()
m_cooldownGroup = m_actionData->data().RecastGroup;
m_range = m_actionData->data().SelectRange;
m_effectRange = m_actionData->data().EffectRange;
m_effectWidth = m_actionData->data().EffectWidth;
m_category = static_cast< Common::ActionCategory >( m_actionData->data().Category );
m_castType = static_cast< Common::CastType >( m_actionData->data().EffectType );
m_aspect = static_cast< Common::ActionAspect >( m_actionData->data().AttackType );
@ -131,7 +134,7 @@ bool Action::Action::init()
m_primaryCostType = static_cast< Common::ActionPrimaryCostType >( m_actionData->data().CostType );
m_primaryCost = m_actionData->data().CostValue;
/*if( !m_actionData->targetArea )
if( !m_actionData->data().SelectGround )
{
// override pos to target position
// todo: this is kinda dirty
@ -143,7 +146,7 @@ bool Action::Action::init()
break;
}
}
}*/
}
// todo: add missing rows for secondaryCostType/secondaryCostType and rename the current rows to primaryCostX
@ -503,17 +506,21 @@ void Action::Action::buildActionResults()
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
auto hasLutEntry = hasValidLutEntry();
auto hasScript = scriptMgr.onExecute( *this );
if( !scriptMgr.onExecute( *this ) && !hasLutEntry )
if( !hasScript && !hasLutEntry )
{
if( auto player = m_pSource->getAsPlayer() )
Manager::PlayerMgr::sendUrgent( *player, "missing lut entry for action#{}", getId() );
return;
}
if( !hasScript )
m_enableGenericHandler = true;
Network::Util::Packet::sendHudParam( *m_pSource );
if( !hasLutEntry || m_hitActors.empty() )
if( !m_enableGenericHandler || !hasLutEntry || m_hitActors.empty() )
{
// send any effect packet added by script or an empty one just to play animation for other players
m_actionResultBuilder->sendActionResults( {} );
@ -583,6 +590,18 @@ void Action::Action::buildActionResults()
shouldRestoreMP = false;
}
}
// If we hit an enemy
if( m_hitActors.size() > 0 && getHitChara()->getObjKind() != m_pSource->getObjKind() )
{
m_pSource->removeStatusEffectByFlag( Common::StatusEffectFlag::RemoveOnSuccessfulHit );
}
handleJobAction();
if( m_lutEntry.statuses.caster.size() > 0 || m_lutEntry.statuses.target.size() > 0 )
handleStatusEffects();
m_actionResultBuilder->sendActionResults( m_hitActors );
// TODO: disabled, reset kills our queued actions
@ -590,6 +609,53 @@ void Action::Action::buildActionResults()
// m_effectBuilder.reset();
}
void Action::Action::handleStatusEffects()
{
auto pActionBuilder = getActionResultBuilder();
if( !pActionBuilder )
return;
if( isComboAction() && !isCorrectCombo() )
return;
// handle caster statuses
if( m_lutEntry.statuses.caster.size() > 0 )
{
for( auto& status : m_lutEntry.statuses.caster )
{
pActionBuilder->applyStatusEffectSelf( status.id, status.duration, 0, status.modifiers, status.flag, true );
}
}
// handle hit actor statuses
if( m_lutEntry.statuses.target.size() > 0 && m_hitActors.size() > 0 )
{
for( auto& actor : m_hitActors )
{
for( auto& status : m_lutEntry.statuses.target )
{
pActionBuilder->applyStatusEffect( actor, status.id, status.duration, 0, status.modifiers, status.flag, true );
}
if( actor->getStatusEffectMap().size() > 0 )
actor->onActionHostile( m_pSource );
}
}
}
void Action::Action::handleJobAction()
{
switch( m_pSource->getClass() )
{
case ClassJob::Warrior:
{
Warrior::onAction( *m_pSource->getAsPlayer(), *this );
break;
}
}
}
bool Action::Action::preCheck()
{
if( auto player = m_pSource->getAsPlayer() )
@ -712,6 +778,17 @@ bool Action::Action::primaryCostCheck( bool subtractCosts )
return true;
}
case Common::ActionPrimaryCostType::StatusEffect:
{
if( !m_pSource->hasStatusEffect( m_primaryCost ) )
return false;
if( subtractCosts )
m_pSource->removeSingleStatusEffectById( m_primaryCost );
return true;
}
// free casts, likely just pure ogcds
case Common::ActionPrimaryCostType::None:
{
@ -780,7 +857,6 @@ void Action::Action::addDefaultActorFilters()
switch( m_castType )
{
case Common::CastType::SingleTarget:
case Common::CastType::Type3:
{
auto filter = std::make_shared< World::Util::ActorFilterSingleTarget >( static_cast< uint32_t >( m_targetId ) );
addActorFilter( filter );
@ -849,7 +925,8 @@ 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;
m_lutEntry.rearPotency != 0 || m_lutEntry.curePotency != 0 || m_lutEntry.restoreMPPercentage != 0 ||
m_lutEntry.statuses.caster.size() > 0 || m_lutEntry.statuses.target.size() > 0;
}
Action::ActionResultBuilderPtr Action::Action::getActionResultBuilder()
@ -876,3 +953,8 @@ uint64_t Action::Action::getCastTimeRest() const
{
return m_castTimeRestMs;
}
void Action::Action::enableGenericHandler()
{
m_enableGenericHandler = true;
}

View file

@ -55,6 +55,8 @@ namespace Sapphire::World::Action
uint64_t getCastTimeRest() const;
void enableGenericHandler();
std::shared_ptr< Excel::ExcelStruct< Excel::Action > > getActionData() const;
/*!
@ -111,6 +113,10 @@ namespace Sapphire::World::Action
void buildActionResults();
void handleStatusEffects();
void handleJobAction();
/*!
* @brief Adds an actor filter to this action.
* @param filter The ptr to the ActorFilter to add
@ -190,6 +196,7 @@ namespace Sapphire::World::Action
uint8_t m_cooldownGroup{};
int8_t m_range{};
uint8_t m_effectRange{};
uint8_t m_effectWidth{};
uint8_t m_xAxisModifier{};
Common::ActionAspect m_aspect;
Common::CastType m_castType;
@ -206,6 +213,7 @@ namespace Sapphire::World::Action
bool m_canTargetFriendly{};
bool m_canTargetHostile{};
bool m_canTargetDead{};
bool m_enableGenericHandler{};
Common::ActionInterruptType m_interruptType;

View file

@ -15,7 +15,8 @@ bool ActionLut::validEntryExists( uint16_t actionId )
// 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;
entry.rearPotency != 0 || entry.curePotency != 0 ||
entry.statuses.caster.size() > 0 || entry.statuses.target.size() > 0;
}
const ActionEntry& ActionLut::getEntry( uint16_t actionId )

View file

@ -17,6 +17,8 @@ namespace Sapphire::World::Action
struct StatusEntry
{
uint16_t id;
int32_t duration;
uint32_t flag;
std::vector< StatusModifier > modifiers;
};

View file

@ -85,6 +85,14 @@ std::unordered_map< std::string, Common::ParamModifier > ActionLutData::m_modifi
{ "Control", Common::ParamModifier::Control },
{ "Gathering", Common::ParamModifier::Gathering },
{ "Perception", Common::ParamModifier::Perception },
{ "TickHeal", Common::ParamModifier::TickHeal },
{ "TickDamage", Common::ParamModifier::TickDamage },
{ "StrengthPercent", Common::ParamModifier::StrengthPercent },
{ "DexterityPercent", Common::ParamModifier::DexterityPercent },
{ "VitalityPercent", Common::ParamModifier::VitalityPercent },
{ "IntelligencePercent", Common::ParamModifier::IntelligencePercent },
{ "MindPercent", Common::ParamModifier::MindPercent },
{ "PietyPercent", Common::ParamModifier::PietyPercent },
{ "HPPercent", Common::ParamModifier::HPPercent },
{ "MPPercent", Common::ParamModifier::MPPercent },
{ "TPPercent", Common::ParamModifier::TPPercent },
@ -100,7 +108,15 @@ std::unordered_map< std::string, Common::ParamModifier > ActionLutData::m_modifi
{ "CriticalHitPowerPercent", Common::ParamModifier::CriticalHitPowerPercent },
{ "CriticalHitResiliencePercent", Common::ParamModifier::CriticalHitResiliencePercent },
{ "CriticalHitPercent", Common::ParamModifier::CriticalHitPercent },
{ "EnmityPercent", Common::ParamModifier::EnmityPercent }
{ "EnmityPercent", Common::ParamModifier::EnmityPercent },
{ "DamageDealtPercent", Common::ParamModifier::DamageDealtPercent },
{ "DamageTakenPercent", Common::ParamModifier::DamageTakenPercent },
{ "HealingMagicRecoveryPercent", Common::ParamModifier::HealingMagicRecoveryPercent },
{ "SlashingResistancePercent", Common::ParamModifier::SlashingResistancePercent },
{ "PiercingResistancePercent", Common::ParamModifier::PiercingResistancePercent },
{ "BluntResistancePercent", Common::ParamModifier::BluntResistancePercent },
{ "ProjectileResistancePercent", Common::ParamModifier::ProjectileResistancePercent },
{ "ParryPercent", Common::ParamModifier::ParryPercent }
};
bool ActionLutData::cacheActions()

View file

@ -32,6 +32,9 @@ namespace Sapphire::World::Action
inline void from_json( const nlohmann::json& j, StatusEntry& statusEntry )
{
j.at( "id" ).get_to( statusEntry.id );
j.at( "duration" ).get_to( statusEntry.duration );
if( j.contains( "flag" ) )
j.at( "flag" ).get_to( statusEntry.flag );
if( j.contains( "modifiers" ) )
j.at( "modifiers" ).get_to( statusEntry.modifiers );
}

View file

@ -73,7 +73,19 @@ void ActionResult::applyStatusEffect( uint32_t id, int32_t duration, Entity::Cha
m_result.Type = CalcResultType::TypeSetStatus;
m_bOverrideStatus = shouldOverride;
m_pStatus = StatusEffect::make_StatusEffect( id, source.getAsChara(), m_target, duration, 3000 );
m_pStatus = Sapphire::StatusEffect::make_StatusEffect( id, source.getAsChara(), m_target, duration, 3000 );
m_pStatus->setParam( param );
}
void ActionResult::applyStatusEffect( uint32_t id, int32_t duration, Entity::Chara& source, uint8_t param,
std::vector< StatusModifier > modifiers, uint32_t flag, bool shouldOverride )
{
m_result.Value = static_cast< int16_t >( id );
m_result.Arg2 = param;
m_result.Type = CalcResultType::TypeSetStatus;
m_bOverrideStatus = shouldOverride;
m_pStatus = Sapphire::StatusEffect::make_StatusEffect( id, source.getAsChara(), m_target, duration, modifiers, flag, 3000 );
m_pStatus->setParam( param );
}
@ -89,6 +101,19 @@ void ActionResult::applyStatusEffectSelf( uint32_t id, int32_t duration, uint8_t
m_pStatus->setParam( param );
}
void ActionResult::applyStatusEffectSelf( uint32_t id, int32_t duration, uint8_t param, std::vector< World::Action::StatusModifier > modifiers,
uint32_t flag, bool shouldOverride )
{
m_result.Value = static_cast< int16_t >( id );
m_result.Arg2 = param;
m_result.Type = CalcResultType::TypeSetStatusMe;
m_result.Flag = static_cast< uint8_t >( Common::ActionResultFlag::EffectOnSource );
m_bOverrideStatus = shouldOverride;
m_pStatus = Sapphire::StatusEffect::make_StatusEffect( id, m_target, m_target, duration, modifiers, flag, 3000 );
m_pStatus->setParam( param );
}
void ActionResult::mount( uint16_t mountId )
{
m_result.Value = static_cast< int16_t >( mountId );
@ -101,7 +126,7 @@ const Common::CalcResultParam& ActionResult::getCalcResultParam() const
return m_result;
}
const StatusEffect::StatusEffectPtr ActionResult::getStatusEffect() const
const Sapphire::StatusEffect::StatusEffectPtr ActionResult::getStatusEffect() const
{
return m_pStatus;
}

View file

@ -2,6 +2,7 @@
#include <ForwardsZone.h>
#include <Common.h>
#include "ActionLut.h"
namespace Sapphire::World::Action
{
@ -20,7 +21,11 @@ namespace Sapphire::World::Action
void startCombo( uint16_t actionId );
void comboSucceed();
void applyStatusEffect( uint32_t id, int32_t duration, Entity::Chara& source, uint8_t param, bool shouldOverride );
void applyStatusEffect( uint32_t id, int32_t duration, Entity::Chara& source, uint8_t param,
std::vector< World::Action::StatusModifier > modifiers, uint32_t flag, bool shouldOverride );
void applyStatusEffectSelf( uint32_t id, int32_t duration, uint8_t param, bool shouldOverride );
void applyStatusEffectSelf( uint32_t id, int32_t duration, uint8_t param, std::vector< World::Action::StatusModifier > modifiers,
uint32_t flag, bool shouldOverride );
void mount( uint16_t mountId );
Entity::CharaPtr getTarget() const;

View file

@ -1,4 +1,4 @@
#include "ActionResultBuilder.h"
#include "ActionResultBuilder.h"
#include "ActionResult.h"
#include <Actor/Player.h>
@ -93,6 +93,14 @@ void ActionResultBuilder::applyStatusEffect( Entity::CharaPtr& target, uint16_t
addResultToActor( target, nextResult );
}
void ActionResultBuilder::applyStatusEffect( Entity::CharaPtr& target, uint16_t statusId, uint32_t duration, uint8_t param,
std::vector< World::Action::StatusModifier > modifiers, uint32_t flag, bool shouldOverride )
{
ActionResultPtr nextResult = make_ActionResult( target );
nextResult->applyStatusEffect( statusId, duration, *m_sourceChara, param, modifiers, flag, shouldOverride );
addResultToActor( target, nextResult );
}
void ActionResultBuilder::applyStatusEffectSelf( uint16_t statusId, uint32_t duration, uint8_t param, bool shouldOverride )
{
ActionResultPtr nextResult = make_ActionResult( m_sourceChara );
@ -100,6 +108,14 @@ void ActionResultBuilder::applyStatusEffectSelf( uint16_t statusId, uint32_t dur
addResultToActor( m_sourceChara, nextResult );
}
void ActionResultBuilder::applyStatusEffectSelf( uint16_t statusId, uint32_t duration, uint8_t param, std::vector< World::Action::StatusModifier > modifiers,
uint32_t flag, bool shouldOverride )
{
ActionResultPtr nextResult = make_ActionResult( m_sourceChara );
nextResult->applyStatusEffectSelf( statusId, duration, param, modifiers, flag, shouldOverride );
addResultToActor( m_sourceChara, nextResult );
}
void ActionResultBuilder::mount( Entity::CharaPtr& target, uint16_t mountId )
{
ActionResultPtr nextResult = make_ActionResult( target );

View file

@ -2,6 +2,7 @@
#include <ForwardsZone.h>
#include <Common.h>
#include "ActionLut.h"
namespace Sapphire::World::Action
{
@ -25,8 +26,12 @@ namespace Sapphire::World::Action
void comboSucceed( Entity::CharaPtr& target );
void applyStatusEffect( Entity::CharaPtr& target, uint16_t statusId, uint32_t duration, uint8_t param, bool shouldOverride );
void applyStatusEffectSelf( uint16_t statusId, uint32_t duration, uint8_t param, bool shouldOverride );
void applyStatusEffect( Entity::CharaPtr& target, uint16_t statusId, uint32_t duration, uint8_t param, bool shouldOverride = false );
void applyStatusEffect( Entity::CharaPtr& target, uint16_t statusId, uint32_t duration, uint8_t param,
std::vector< World::Action::StatusModifier > modifiers, uint32_t flag = 0, bool shouldOverride = false );
void applyStatusEffectSelf( uint16_t statusId, uint32_t duration, uint8_t param, bool shouldOverride = false );
void applyStatusEffectSelf( uint16_t statusId, uint32_t duration, uint8_t param, std::vector< World::Action::StatusModifier > modifiers,
uint32_t flag = 0, bool shouldOverride = false );
void mount( Entity::CharaPtr& target, uint16_t mountId );

View file

@ -0,0 +1,30 @@
#pragma once
#include <cstdint>
#include <vector>
#include <string>
namespace Sapphire::World::Action
{
enum ActionSkill
{
SkullSunder = 35,
Maim = 37,
StormsPath = 42,
StormsEye = 45,
ButchersBlock = 47
};
enum ActionStatus
{
Defiance = 91,
Unchained = 92,
Wrath = 93,
WrathII = 94,
WrathIII = 95,
WrathIV = 96,
Infuriated = 97,
InnerBeast = 411,
Deliverance = 729
};
}

View file

@ -0,0 +1,70 @@
#include "Warrior.h"
#include <Action/CommonAction.h>
#include <Action/Action.h>
#include <Actor/Player.h>
using namespace Sapphire;
using namespace Sapphire::World::Action;
void Warrior::onAction( Entity::Player& player, Action& action )
{
switch( action.getId() )
{
case Maim:
case StormsEye:
case StormsPath:
case SkullSunder:
case ButchersBlock:
{
if( action.isComboAction() && !action.isCorrectCombo() )
break;
if( player.hasStatusEffect( Defiance ) )
handleWrath( player, action );
break;
}
}
}
void Warrior::handleWrath( Entity::Player& player, Action& action )
{
auto effectToApply = Wrath;
auto parry = 2;
auto asChara = player.getAsChara();
auto pActionBuilder = action.getActionResultBuilder();
if( !pActionBuilder )
return;
if( player.hasStatusEffect( Wrath ) )
{
player.replaceSingleStatusEffectById( Wrath );
effectToApply = WrathII;
parry += 2;
}
else if( player.hasStatusEffect( WrathII ) )
{
player.replaceSingleStatusEffectById( WrathII );
effectToApply = WrathIII;
parry += 2;
}
else if( player.hasStatusEffect( WrathIII ) )
{
player.replaceSingleStatusEffectById( WrathIII );
effectToApply = WrathIV;
parry += 2;
}
else if( player.hasStatusEffect( WrathIV ) )
{
player.replaceSingleStatusEffectById( WrathIV );
effectToApply = Infuriated;
parry += 2;
}
if( !player.hasStatusEffect( Infuriated ) )
{
pActionBuilder->applyStatusEffectSelf( effectToApply, 30000, 0, { StatusModifier{ Common::ParamModifier::ParryPercent, parry } }, 0, false );
}
}

View file

@ -0,0 +1,20 @@
#pragma once
#include <Common.h>
#include "ForwardsZone.h"
#include <cstdint>
#include <unordered_map>
#include <string>
#include <vector>
namespace Sapphire::World::Action
{
class Warrior
{
public:
static void onAction( Entity::Player& player, Action& action );
private:
static void handleWrath( Entity::Player& player, Action& action );
};
}

View file

@ -5,7 +5,6 @@
#include <Network/CommonActorControl.h>
#include <Service.h>
#include "Forwards.h"
#include "Territory/Territory.h"
@ -25,6 +24,7 @@
#include "Player.h"
#include "Manager/TerritoryMgr.h"
#include "Manager/MgrUtil.h"
#include "Manager/PlayerMgr.h"
#include "Common.h"
using namespace Sapphire;
@ -497,9 +497,9 @@ void Chara::addStatusEffect( StatusEffect::StatusEffectPtr pEffect )
if( nextSlot == -1 )
return;
pEffect->applyStatus();
pEffect->setSlot( nextSlot );
m_statusEffectMap[ nextSlot ] = pEffect;
pEffect->applyStatus();
}
/*! \param StatusEffectPtr to be applied to the actor */
@ -515,7 +515,6 @@ void Chara::addStatusEffectByIdIfNotExist( StatusEffect::StatusEffectPtr pStatus
return;
addStatusEffect( pStatus );
}
int8_t Chara::getStatusEffectFreeSlot()
@ -536,6 +535,18 @@ void Chara::statusEffectFreeSlot( uint8_t slotId )
m_statusEffectFreeSlotQueue.push( slotId );
}
void Chara::replaceSingleStatusEffectById( uint32_t id )
{
for( const auto& effectIt : m_statusEffectMap )
{
if( effectIt.second->getId() == id )
{
removeStatusEffect( effectIt.first, false );
break;
}
}
}
void Chara::removeSingleStatusEffectById( uint32_t id )
{
for( const auto& effectIt : m_statusEffectMap )
@ -548,7 +559,18 @@ void Chara::removeSingleStatusEffectById( uint32_t id )
}
}
std::map< uint8_t, StatusEffect::StatusEffectPtr >::iterator Chara::removeStatusEffect( uint8_t effectSlotId )
void Chara::removeStatusEffectByFlag( Common::StatusEffectFlag flag )
{
for( auto effectIt = m_statusEffectMap.begin(); effectIt != m_statusEffectMap.end(); )
{
if( effectIt->second->getFlag() & static_cast< uint32_t >( flag ) )
effectIt = removeStatusEffect( effectIt->first );
else
++effectIt;
}
}
std::map< uint8_t, Sapphire::StatusEffect::StatusEffectPtr >::iterator Chara::removeStatusEffect( uint8_t effectSlotId, bool sendOrder )
{
auto pEffectIt = m_statusEffectMap.find( effectSlotId );
if( pEffectIt == m_statusEffectMap.end() )
@ -559,7 +581,8 @@ std::map< uint8_t, StatusEffect::StatusEffectPtr >::iterator Chara::removeStatus
auto pEffect = pEffectIt->second;
pEffect->removeStatus();
Network::Util::Packet::sendActorControl( getInRangePlayerIds( isPlayer() ), getId(), StatusEffectLose, pEffect->getId() );
if( sendOrder )
Network::Util::Packet::sendActorControl( getInRangePlayerIds( isPlayer() ), getId(), StatusEffectLose, pEffect->getId() );
auto it = m_statusEffectMap.erase( pEffectIt );
Network::Util::Packet::sendHudParam( *this );
@ -571,6 +594,17 @@ std::map< uint8_t, StatusEffect::StatusEffectPtr > Chara::getStatusEffectMap() c
return m_statusEffectMap;
}
Sapphire::StatusEffect::StatusEffectPtr Chara::getStatusEffectById( uint32_t id ) const
{
for( const auto& effectIt : m_statusEffectMap )
{
if( effectIt.second->getId() == id )
return effectIt.second;
}
return nullptr;
}
const uint8_t* Chara::getLookArray() const
{
return m_customize;
@ -595,7 +629,6 @@ void Chara::sendStatusEffectUpdate()
{
uint64_t currentTimeMs = Common::Util::getTimeMs();
auto statusEffectList = makeZonePacket< FFXIVIpcStatus >( getId() );
uint8_t slot = 0;
for( const auto& effectIt : m_statusEffectMap )
@ -640,7 +673,13 @@ void Chara::updateStatusEffects()
bool Chara::hasStatusEffect( uint32_t id )
{
return m_statusEffectMap.find( id ) != m_statusEffectMap.end();
for( const auto& [ key, val ] : m_statusEffectMap )
{
if( val->getId() == id )
return true;
}
return false;
}
int64_t Chara::getLastUpdateTime() const
@ -735,6 +774,27 @@ void Chara::setStatValue( Common::BaseParam baseParam, uint32_t value )
m_baseStats[ index ] = value;
}
float Chara::getModifier( Common::ParamModifier paramModifier ) const
{
auto result = paramModifier >= Common::ParamModifier::StrengthPercent ? 1.0f : 0;
for( const auto& [ key, status ] : m_statusEffectMap )
{
for( const auto& [ mod, val ] : status->getModifiers() )
{
if( mod != paramModifier )
continue;
if( paramModifier >= Common::ParamModifier::StrengthPercent )
result *= 1.0f + ( val / 100.0f );
else
result += val;
}
}
return result;
}
void Chara::onTick()
{
uint32_t thisTickDmg = 0;
@ -745,13 +805,13 @@ void Chara::onTick()
auto thisEffect = effectIt.second->getTickEffect();
switch( thisEffect.first )
{
case 1:
case Common::ParamModifier::TickDamage:
{
thisTickDmg += thisEffect.second;
break;
}
case 2:
case Common::ParamModifier::TickHeal:
{
thisTickHeal += thisEffect.second;
break;
@ -759,11 +819,15 @@ void Chara::onTick()
}
}
// TODO: don't really like how this is handled
// TODO: calculate actual damage from potency
if( thisTickDmg != 0 )
{
takeDamage( thisTickDmg );
Network::Util::Packet::sendActorControl( getInRangePlayerIds( isPlayer() ), getId(), HPFloatingText, 0,
CalcResultType::TypeDamageHp, thisTickDmg );
Network::Util::Packet::sendHudParam( *this );
}
if( thisTickHeal != 0 )
@ -771,5 +835,7 @@ void Chara::onTick()
heal( thisTickHeal );
Network::Util::Packet::sendActorControl( getInRangePlayerIds( isPlayer() ), getId(), HPFloatingText, 0,
CalcResultType::TypeRecoverMp, thisTickHeal );
Network::Util::Packet::sendHudParam( *this );
}
}

View file

@ -1,6 +1,7 @@
#pragma once
#include <Common.h>
#include "Action/ActionLut.h"
#include "Forwards.h"
#include "GameObject.h"
@ -8,6 +9,7 @@
#include <map>
#include <queue>
#include <array>
#include <numeric>
namespace Sapphire::Entity
{
@ -106,10 +108,14 @@ namespace Sapphire::Entity
/// Status effect functions
void addStatusEffect( StatusEffect::StatusEffectPtr pEffect );
std::map< uint8_t, StatusEffect::StatusEffectPtr >::iterator removeStatusEffect( uint8_t effectSlotId );
std::map< uint8_t, StatusEffect::StatusEffectPtr >::iterator removeStatusEffect( uint8_t effectSlotId, bool sendOrder = true );
void replaceSingleStatusEffectById( uint32_t id );
void removeSingleStatusEffectById( uint32_t id );
void removeStatusEffectByFlag( Common::StatusEffectFlag flag );
void updateStatusEffects();
bool hasStatusEffect( uint32_t id );
@ -124,6 +130,8 @@ namespace Sapphire::Entity
std::map< uint8_t, Sapphire::StatusEffect::StatusEffectPtr > getStatusEffectMap() const;
Sapphire::StatusEffect::StatusEffectPtr getStatusEffectById( uint32_t id ) const;
void sendStatusEffectUpdate();
/*! return a const pointer to the look array */
@ -159,6 +167,8 @@ namespace Sapphire::Entity
void setStatValue( Common::BaseParam baseParam, uint32_t value );
float getModifier( Common::ParamModifier paramModifier ) const;
uint32_t getHp() const;
uint32_t getHpPercent() const;

View file

@ -563,6 +563,22 @@ void Player::setRewardFlag( Common::UnlockEntry unlockId )
Network::Util::Packet::sendActorControlSelf( *this, getId(), SetRewardFlag, unlock, 1 );
}
void Player::setBorrowAction( uint8_t slot, uint32_t action )
{
if( slot > Common::ARRSIZE_BORROWACTION )
return;
auto& borrowAction = getBorrowAction();
borrowAction[ slot ] = action;
}
Player::BorrowAction& Player::getBorrowAction()
{
auto& exdData = Common::Service< Data::ExdData >::ref();
uint8_t classJobIndex = exdData.getRow< Excel::ClassJob >( static_cast<uint8_t>( getClass() ) )->data().WorkIndex;
return m_borrowActions[ classJobIndex ];
}
void Player::learnSong( uint8_t songId, uint32_t itemId )
{
uint16_t index;
@ -712,6 +728,7 @@ void Player::setClassJob( Common::ClassJob classJob )
m_tp = 0;
Network::Util::Packet::sendChangeClass( *this );
Network::Util::Packet::sendStatusUpdate( *this );
Network::Util::Packet::sendActorControl( getInRangePlayerIds( true ), getId(), ClassJobChange, 4 );
Network::Util::Packet::sendHudParam( *this );

View file

@ -38,6 +38,7 @@ namespace Sapphire::Entity
using ClassList = std::array< uint16_t, Common::ARRSIZE_CLASSJOB >;
using ExpList = std::array< uint32_t, Common::ARRSIZE_CLASSJOB >;
using BorrowAction = std::array< uint32_t, Common::ARRSIZE_BORROWACTION >;
struct AchievementData {
std::array< uint8_t, 2048 / 8 > unlockList;
@ -440,6 +441,10 @@ namespace Sapphire::Entity
/*! learn an action / update the unlock bitmask. */
void setRewardFlag( Common::UnlockEntry unlockId );
void setBorrowAction( uint8_t slot, uint32_t action );
BorrowAction& getBorrowAction();
/*! learn a song / update the unlock bitmask. */
void learnSong( uint8_t songId, uint32_t itemId );
@ -960,6 +965,8 @@ namespace Sapphire::Entity
std::array< Common::HuntingLogEntry, Common::ARRSIZE_MONSTERNOTE > m_huntingLogEntries{};
std::array< BorrowAction, Common::ARRSIZE_CLASSJOB > m_borrowActions{};
FriendListIDVec m_friendList{};
FriendListDataVec m_friendInviteList{};

View file

@ -258,7 +258,7 @@ bool Player::loadAchievements()
bool Player::loadClassData()
{
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
// ClassIdx, Exp, Lvl
// ClassIdx, Exp, Lvl, BorrowAction
auto stmt = db.getPreparedStatement( Db::ZoneDbStatements::CHARA_CLASS_SEL );
stmt->setUInt64( 1, m_characterId );
auto res = db.query( stmt );
@ -268,9 +268,11 @@ bool Player::loadClassData()
auto index = res->getUInt16( 1 );
auto exp = res->getUInt( 2 );
auto lvl = res->getUInt8( 3 );
auto borrowAction = res->getBlobVector( "BorrowAction" );
m_classArray[ index ] = lvl;
m_expArray[ index ] = exp;
memcpy( m_borrowActions[ index ].data(), borrowAction.data(), borrowAction.size() );
}
return true;
@ -487,13 +489,19 @@ void Player::updateDbClass() const
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
auto& exdData = Common::Service< Data::ExdData >::ref();
uint8_t classJobIndex = exdData.getRow< Excel::ClassJob >( static_cast<uint8_t>( getClass() ) )->data().WorkIndex;
auto& borrowAction = m_borrowActions[ classJobIndex ];
//Exp = ?, Lvl = ? WHERE CharacterId = ? AND ClassIdx = ?
//Exp = ?, Lvl = ?, BorrowAction = ? WHERE CharacterId = ? AND ClassIdx = ?
auto stmtS = db.getPreparedStatement( Db::CHARA_CLASS_UP );
stmtS->setInt( 1, getExp() );
stmtS->setInt( 2, getLevel() );
stmtS->setUInt64( 3, m_characterId );
stmtS->setInt( 4, classJobIndex );
std::vector< uint8_t > borrowActionVec( borrowAction.size() * 4 );
memcpy( borrowActionVec.data(), borrowAction.data(), borrowAction.size() * 4 );
stmtS->setBinary( 3, borrowActionVec );
stmtS->setUInt64( 4, m_characterId );
stmtS->setInt( 5, classJobIndex );
db.execute( stmtS );
}
@ -581,10 +589,14 @@ void Player::insertDbClass( const uint8_t classJobIndex, uint8_t level ) const
{
auto& db = Common::Service< Db::DbWorkerPool< Db::ZoneDbConnection > >::ref();
auto stmtClass = db.getPreparedStatement( Db::CHARA_CLASS_INS );
auto& borrowAction = m_borrowActions[ classJobIndex ];
stmtClass->setUInt64( 1, m_characterId );
stmtClass->setInt( 2, classJobIndex );
stmtClass->setInt( 3, 0 );
stmtClass->setInt( 4, level );
std::vector< uint8_t > borrowActionVec( borrowAction.size() );
stmtClass->setBinary( 5, borrowActionVec );
db.directExecute( stmtClass );
}

View file

@ -7,6 +7,7 @@ file( GLOB SERVER_SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
*.cpp
Actor/*.cpp
Action/*.cpp
Action/Job/*.cpp
AI/*.cpp
AI/Fsm/*.cpp
ContentFinder/*.cpp

View file

@ -71,13 +71,6 @@ void ActionMgr::handleTargetedAction( Entity::Chara& src, uint32_t actionId, uin
auto actionData = action->getActionData();
// cancel any aoe actions casted with this packet
if( actionData->data().EffectRange )
{
action->interrupt();
return;
}
bootstrapAction( src, action, actionData );
}

View file

@ -92,7 +92,7 @@ std::unique_ptr< RandGenerator< float > > CalcStats::rnd = nullptr;
Big thanks to the Theoryjerks group!
NOTE:
Formulas here shouldn't be considered final. It's possible that the formula it was based on is correct but
Formulas here shouldn't be considered final. It's possible that the formula it was based on is correct but
wasn't implemented correctly here, or approximated things due to limited knowledge of how things work in retail.
It's also possible that we're using formulas that were correct for previous patches, but not the current version.
@ -142,12 +142,15 @@ uint32_t CalcStats::calculateMaxHp( Player& player )
uint16_t hpMod = paramGrowthInfo->data().ParamBase;
uint16_t jobModHp = classInfo->data().Hp;
float approxBaseHp = 0.0f; // Read above
float hpModPercent = player.getModifier( Common::ParamModifier::HPPercent );
approxBaseHp = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::HP ] );
auto result = static_cast< uint32_t >( floor( jobModHp * ( approxBaseHp / 100.0f ) ) +
floor( hpMod / 100.0f * ( vitStat - baseStat ) ) );
result *= hpModPercent;
return result;
}
@ -601,6 +604,7 @@ std::pair< float, Sapphire::Common::CalcResultType > CalcStats::calcActionDamage
auto wd = weaponDamage( chara, wepDmg );
auto ap = getPrimaryAttackPower( chara );
auto det = determination( chara );
auto damageDealtMod = chara.getModifier( Common::ParamModifier::DamageDealtPercent );
auto factor = Common::Util::trunc( pot * wd * ap * det, 0 );
Sapphire::Common::CalcResultType hitType = Sapphire::Common::CalcResultType::TypeDamageHp;
@ -615,11 +619,14 @@ std::pair< float, Sapphire::Common::CalcResultType > CalcStats::calcActionDamage
// todo: buffs
factor *= damageDealtMod;
constexpr auto format = "dmg: pot: {} ({}) wd: {} ({}) ap: {} det: {} = {}";
if( auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer() )
{
PlayerMgr::sendDebug( *player, format, pot, ptc, wd, wepDmg, ap, det, factor );
PlayerMgr::sendDebug( *player, "DamageDealtPercent: {}", damageDealtMod );
}
else
{
@ -662,4 +669,4 @@ float CalcStats::getRandomNumber0To100()
rnd = std::make_unique< RandGenerator< float > >( Common::Service< RNGMgr >::ref().getRandGenerator< float >( 0, 100 ) );
}
return rnd->next();
}
}

View file

@ -491,6 +491,11 @@ void Sapphire::Network::GameConnection::commandHandler( const Packets::FFXIVARR_
Network::Util::Packet::sendTitleList( player );
break;
}
case PacketCommand::BORROW_ACTION:
{
player.setBorrowAction( static_cast< uint8_t >( data.Arg1 ), data.Arg2 );
break;
}
case PacketCommand::SET_HOWTO: // Update howtos seen
{
player.updateHowtosSeen( data.Arg0 );

View file

@ -307,10 +307,12 @@ void Util::Packet::sendPlayerSetup( Entity::Player& player )
void Util::Packet::sendChangeClass( Entity::Player& player )
{
auto classInfo = makeZonePacket< FFXIVIpcChangeClass >( player.getId() );
auto& borrowAction = player.getBorrowAction();
classInfo->data().ClassJob = static_cast< uint8_t >( player.getClass() );
classInfo->data().Lv = player.getLevel();
classInfo->data().Lv1 = player.getLevel();
classInfo->data().Login = player.isLogin() ? 1 : 0;
memcpy( &classInfo->data().BorrowAction[ 0 ], borrowAction.data(), borrowAction.size() * 4 );
server().queueForPlayer( player.getCharacterId(), classInfo );
}

View file

@ -644,9 +644,6 @@ bool Sapphire::Scripting::ScriptMgr::onStatusTick( Entity::CharaPtr pChara, Sapp
auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::StatusEffectScript >( effect.getId() );
if( script )
{
if( pChara->isPlayer() )
PlayerMgr::sendDebug( *pChara->getAsPlayer(), "Calling status tick for statusid#{0}", effect.getId() );
script->onTick( *pChara );
return true;
}

View file

@ -6,26 +6,41 @@
#include <algorithm>
#include <Service.h>
#include "Manager/PlayerMgr.h"
#include "Actor/Chara.h"
#include "Actor/Player.h"
#include "Actor/GameObject.h"
#include "Script/ScriptMgr.h"
#include "StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::Common;
using namespace Sapphire::Network::Packets;
//using namespace Sapphire::Network::Packets::WorldPackets::Server;
Sapphire::StatusEffect::StatusEffect::StatusEffect( uint32_t id, Entity::CharaPtr sourceActor, Entity::CharaPtr targetActor,
uint32_t duration,std::vector< World::Action::StatusModifier >& modifiers,
uint32_t flag, uint32_t tickRate ) :
StatusEffect( id, sourceActor, targetActor, duration, tickRate )
{
m_statusModifiers = std::move( modifiers );
m_flag = flag;
}
Sapphire::StatusEffect::StatusEffect::StatusEffect( uint32_t id, Entity::CharaPtr sourceActor, Entity::CharaPtr targetActor,
uint32_t duration, uint32_t tickRate ) :
m_id( id ),
m_sourceActor( sourceActor ),
m_targetActor( targetActor ),
m_duration( duration ),
m_modifiers( 0 ),
m_startTime( 0 ),
m_tickRate( tickRate ),
m_lastTick( 0 )
m_lastTick( 0 ),
m_flag( 0 )
{
auto& exdData = Common::Service< Data::ExdData >::ref();
auto entry = exdData.getRow< Excel::Status >( id );
@ -40,6 +55,15 @@ Sapphire::StatusEffect::StatusEffect::StatusEffect( uint32_t id, Entity::CharaPt
Util::eraseAll( m_name, '-' );
Util::eraseAll( m_name, '(' );
Util::eraseAll( m_name, ')' );
m_flag |= entry->data().Category;
m_flag |= static_cast< uint32_t >( entry->data().Forever ) << static_cast< uint32_t >( Common::StatusEffectFlag::Permanent );
m_flag |= static_cast< uint32_t >( entry->data().CanOff ) << static_cast< uint32_t >( Common::StatusEffectFlag::CanStatusOff );
m_flag |= static_cast< uint32_t >( entry->data().NotAction ) << static_cast< uint32_t >( Common::StatusEffectFlag::LockActions );
m_flag |= static_cast< uint32_t >( entry->data().NotControl ) << static_cast< uint32_t >( Common::StatusEffectFlag::LockControl );
m_flag |= static_cast< uint32_t >( entry->data().NotMove ) << static_cast< uint32_t >( Common::StatusEffectFlag::LockMovement );
m_flag |= static_cast< uint32_t >( entry->data().NotLookAt ) << static_cast< uint32_t >( Common::StatusEffectFlag::IsGaze );
m_flag |= static_cast< uint32_t >( entry->data().FcAction ) << static_cast< uint32_t >( Common::StatusEffectFlag::FcBuff );
}
@ -47,16 +71,14 @@ Sapphire::StatusEffect::StatusEffect::~StatusEffect()
{
}
void Sapphire::StatusEffect::StatusEffect::registerTickEffect( uint8_t type, uint32_t param )
void Sapphire::StatusEffect::StatusEffect::registerTickEffect( ParamModifier type, uint32_t param )
{
m_currTickEffect = std::make_pair( type, param );
}
std::pair< uint8_t, uint32_t > Sapphire::StatusEffect::StatusEffect::getTickEffect()
std::pair< ParamModifier, uint32_t > Sapphire::StatusEffect::StatusEffect::getTickEffect()
{
auto thisTick = m_currTickEffect;
m_currTickEffect = std::make_pair( 0, 0 );
return thisTick;
return m_currTickEffect;
}
void Sapphire::StatusEffect::StatusEffect::onTick()
@ -87,16 +109,60 @@ uint16_t Sapphire::StatusEffect::StatusEffect::getParam() const
return m_param;
}
std::unordered_map< Common::ParamModifier, int32_t >& Sapphire::StatusEffect::StatusEffect::getModifiers()
{
return m_modifiers;
}
void Sapphire::StatusEffect::StatusEffect::setModifier( Common::ParamModifier paramModifier, int32_t value )
{
m_modifiers[ paramModifier ] = value;
if( auto pPlayer = m_targetActor->getAsPlayer(); pPlayer )
Common::Service< World::Manager::PlayerMgr >::ref().sendDebug( *pPlayer, "Modifier: {}, value: {}", static_cast< int32_t >( paramModifier ),
pPlayer->getModifier( paramModifier ) );
}
void Sapphire::StatusEffect::StatusEffect::delModifier( Common::ParamModifier paramModifier )
{
if( m_modifiers.find( paramModifier ) == m_modifiers.end() )
return;
m_modifiers.erase( paramModifier );
if( auto pPlayer = m_targetActor->getAsPlayer(); pPlayer )
Common::Service< World::Manager::PlayerMgr >::ref().sendDebug( *pPlayer, "Modifier: {}, value: {}", static_cast< int32_t >( paramModifier ),
pPlayer->getModifier( paramModifier ) );
}
void Sapphire::StatusEffect::StatusEffect::applyStatus()
{
m_startTime = Util::getTimeMs();
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
for( const auto& mod : m_statusModifiers )
{
if( mod.modifier != Common::ParamModifier::TickDamage && mod.modifier != Common::ParamModifier::TickHeal )
setModifier( mod.modifier, mod.value );
else if( mod.modifier == Common::ParamModifier::TickDamage )
registerTickEffect( mod.modifier, mod.value );
else if( mod.modifier == Common::ParamModifier::TickHeal )
registerTickEffect( mod.modifier, mod.value );
}
m_targetActor->calculateStats();
scriptMgr.onStatusReceive( m_targetActor, m_id );
}
void Sapphire::StatusEffect::StatusEffect::removeStatus()
{
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
m_modifiers.clear();
m_targetActor->calculateStats();
scriptMgr.onStatusTimeOut( m_targetActor, m_id );
}
@ -125,6 +191,21 @@ uint64_t Sapphire::StatusEffect::StatusEffect::getStartTimeMs() const
return m_startTime;
}
uint32_t Sapphire::StatusEffect::StatusEffect::getFlag() const
{
return m_flag;
}
std::vector< World::Action::StatusModifier > Sapphire::StatusEffect::StatusEffect::getStatusModifiers() const
{
return m_statusModifiers;
}
void Sapphire::StatusEffect::StatusEffect::setFlag( uint32_t flag )
{
m_flag = flag;
}
void Sapphire::StatusEffect::StatusEffect::setLastTick( uint64_t lastTick )
{
m_lastTick = lastTick;

View file

@ -2,6 +2,7 @@
#define _STATUSEFFECT_H_
#include "Forwards.h"
#include "Action/ActionLut.h"
namespace Sapphire {
namespace StatusEffect {
@ -10,6 +11,9 @@ namespace StatusEffect {
class StatusEffect
{
public:
StatusEffect( uint32_t id, Entity::CharaPtr sourceActor, Entity::CharaPtr targetActor,
uint32_t duration, std::vector< World::Action::StatusModifier >& modifiers, uint32_t flag, uint32_t tickRate );
StatusEffect( uint32_t id, Entity::CharaPtr sourceActor, Entity::CharaPtr targetActor,
uint32_t duration, uint32_t tickRate );
@ -17,6 +21,12 @@ public:
void onTick();
std::unordered_map< Common::ParamModifier, int32_t >& getModifiers();
void setModifier( Common::ParamModifier paramModifier, int32_t value );
void delModifier( Common::ParamModifier paramModifier );
void applyStatus();
void removeStatus();
@ -38,13 +48,19 @@ public:
uint16_t getParam() const;
uint32_t getFlag() const;
std::vector< World::Action::StatusModifier > getStatusModifiers() const;
void setLastTick( uint64_t lastTick );
void setParam( uint16_t param );
void registerTickEffect( uint8_t type, uint32_t param );
void setFlag( uint32_t flag );
std::pair< uint8_t, uint32_t > getTickEffect();
void registerTickEffect( Common::ParamModifier type, uint32_t param );
std::pair< Common::ParamModifier, uint32_t > getTickEffect();
const std::string& getName() const;
@ -60,9 +76,12 @@ private:
uint32_t m_tickRate;
uint64_t m_lastTick;
uint16_t m_param;
uint32_t m_flag;
std::string m_name;
std::pair< Common::ParamModifier, uint32_t > m_currTickEffect;
std::vector< World::Action::StatusModifier > m_statusModifiers;
std::unordered_map< Common::ParamModifier, int32_t > m_modifiers;
uint8_t m_slot;
std::pair< uint8_t, uint32_t > m_currTickEffect;
};