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

View file

@ -391,14 +391,14 @@ namespace Excel
uint8_t EffectWidth; uint8_t EffectWidth;
uint8_t CostType; uint8_t CostType;
uint8_t Cond; uint8_t Cond;
uint8_t RecastGroup;
uint8_t Element; uint8_t Element;
uint8_t ProcStatus; uint8_t ProcStatus;
uint8_t UseClassJob; uint8_t ClassJobCategory;
uint8_t RecastGroup;
uint8_t Init; uint8_t Init;
uint8_t Omen; uint8_t Omen;
uint8_t Unknown; uint8_t Learn;
int8_t Learn; int8_t UseClassJob;
int8_t SelectRange; int8_t SelectRange;
int8_t SelectCorpse; int8_t SelectCorpse;
int8_t AttackType; int8_t AttackType;
@ -430,7 +430,7 @@ namespace Excel
uint8_t HideCastBar : 1; uint8_t HideCastBar : 1;
uint8_t IsTargetLine : 1; uint8_t IsTargetLine : 1;
int8_t padding0; int8_t unknown : 8;
}; };
/* 75653 */ /* 75653 */
@ -2054,7 +2054,8 @@ namespace Excel
uint8_t NotControl : 1; uint8_t NotControl : 1;
uint8_t NotAction : 1; uint8_t NotAction : 1;
uint8_t NotMove : 1; uint8_t NotMove : 1;
uint8_t padding0 : 6; uint8_t padding0 : 5;
uint8_t CanOff : 1;
uint8_t SemiTransparent : 1; uint8_t SemiTransparent : 1;
uint8_t FcAction : 1; uint8_t FcAction : 1;
int8_t padding1[2]; 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; break;
} }
// CharacterId, ClassIdx, Exp, Lvl // CharacterId, ClassIdx, Exp, Lvl, BorrowAction
auto stmtClass = g_charaDb.getPreparedStatement( Db::ZoneDbStatements::CHARA_CLASS_INS ); auto stmtClass = g_charaDb.getPreparedStatement( Db::ZoneDbStatements::CHARA_CLASS_INS );
stmtClass->setUInt64( 1, m_characterId ); stmtClass->setUInt64( 1, m_characterId );
stmtClass->setInt( 2, g_exdData.getRow< Excel::ClassJob >( m_class )->data().WorkIndex ); stmtClass->setInt( 2, g_exdData.getRow< Excel::ClassJob >( m_class )->data().WorkIndex );
stmtClass->setInt( 3, 0 ); stmtClass->setInt( 3, 0 );
stmtClass->setInt( 4, 1 ); stmtClass->setInt( 4, 1 );
std::vector< uint8_t > borrowActionVec( Common::ARRSIZE_BORROWACTION * 4 );
stmtClass->setBinary( 5, borrowActionVec );
g_charaDb.directExecute( stmtClass ); g_charaDb.directExecute( stmtClass );
auto stmtSearchInfo = g_charaDb.getPreparedStatement( Db::ZoneDbStatements::CHARA_SEARCHINFO_INS ); 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_UNLOCKS = 64u;
const uint16_t ARRSIZE_ORCHESTRION = 40u; const uint16_t ARRSIZE_ORCHESTRION = 40u;
const uint16_t ARRSIZE_MONSTERNOTE = 12u; const uint16_t ARRSIZE_MONSTERNOTE = 12u;
const uint16_t ARRSIZE_BORROWACTION = 10u;
const uint8_t TOWN_COUNT = 6; const uint8_t TOWN_COUNT = 6;
@ -887,22 +888,55 @@ namespace Sapphire::Common
Perception = 73, Perception = 73,
// Unique modifiers // Unique modifiers
HPPercent = 1000, TickHeal = 1000,
MPPercent = 1001, TickDamage = 1001,
TPPercent = 1002, StrengthPercent = 1002,
GPPercent = 1003, DexterityPercent = 1003,
CPPercent = 1004, VitalityPercent = 1004,
PhysicalDamagePercent = 1005, IntelligencePercent = 1005,
MagicDamagePercent = 1006, MindPercent = 1006,
AttackPowerPercent = 1007, PietyPercent = 1007,
DefensePercent = 1008, HPPercent = 1008,
AccuracyPercent = 1009, MPPercent = 1009,
EvasionPercent = 1010, TPPercent = 1010,
MagicDefensePercent = 1011, GPPercent = 1011,
CriticalHitPowerPercent = 1012, CPPercent = 1012,
CriticalHitResiliencePercent = 1013, PhysicalDamagePercent = 1013,
CriticalHitPercent = 1014, MagicDamagePercent = 1014,
EnmityPercent = 1015 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 enum struct ActionAspect : uint8_t
@ -923,6 +957,7 @@ namespace Sapphire::Common
MagicPoints = 3, MagicPoints = 3,
TacticsPoints = 5, TacticsPoints = 5,
TacticsPoints1 = 6, TacticsPoints1 = 6,
StatusEffect = 10,
Sprint = 18, Sprint = 18,
// WARGauge = 22, // WARGauge = 22,
// DRKGauge = 25, // DRKGauge = 25,
@ -1830,8 +1865,8 @@ namespace Sapphire::Common
{ {
SingleTarget = 1, SingleTarget = 1,
CircularAOE = 2, CircularAOE = 2,
Type3 = 3, // another single target? no idea how to call it RectangularAOE = 3,
RectangularAOE = 4, ConeAOE = 4,
CircularAoEPlaced = 7 CircularAoEPlaced = 7
}; };

View file

@ -161,11 +161,11 @@ void Sapphire::Db::ZoneDbConnection::doPrepareStatements()
prepareStatement( CHARA_SEL_QUEST, "SELECT * FROM charaquest WHERE CharacterId = ?;", CONNECTION_SYNC ); prepareStatement( CHARA_SEL_QUEST, "SELECT * FROM charaquest WHERE CharacterId = ?;", CONNECTION_SYNC );
/// CLASS INFO /// 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 ); 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 ); 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 ); CONNECTION_ASYNC );
prepareStatement( CHARA_CLASS_DEL, "DELETE FROM characlass WHERE CharacterId = ?;", 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 struct StatusEntry
{ {
uint16_t id; uint16_t id;
int32_t duration;
uint32_t flag;
std::vector< StatusModifier > modifiers; std::vector< StatusModifier > modifiers;
}; };
@ -76,6 +78,8 @@ void to_json( nlohmann::ordered_json& j, const StatusEntry& statusEntry )
{ {
j = nlohmann::ordered_json{ j = nlohmann::ordered_json{
{ "id", statusEntry.id }, { "id", statusEntry.id },
{ "duration", statusEntry.duration },
{ "flag", statusEntry.flag },
{ "modifiers", statusEntry.modifiers } { "modifiers", statusEntry.modifiers }
}; };
} }

View file

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

View file

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

View file

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

View file

@ -85,6 +85,14 @@ std::unordered_map< std::string, Common::ParamModifier > ActionLutData::m_modifi
{ "Control", Common::ParamModifier::Control }, { "Control", Common::ParamModifier::Control },
{ "Gathering", Common::ParamModifier::Gathering }, { "Gathering", Common::ParamModifier::Gathering },
{ "Perception", Common::ParamModifier::Perception }, { "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 }, { "HPPercent", Common::ParamModifier::HPPercent },
{ "MPPercent", Common::ParamModifier::MPPercent }, { "MPPercent", Common::ParamModifier::MPPercent },
{ "TPPercent", Common::ParamModifier::TPPercent }, { "TPPercent", Common::ParamModifier::TPPercent },
@ -100,7 +108,15 @@ std::unordered_map< std::string, Common::ParamModifier > ActionLutData::m_modifi
{ "CriticalHitPowerPercent", Common::ParamModifier::CriticalHitPowerPercent }, { "CriticalHitPowerPercent", Common::ParamModifier::CriticalHitPowerPercent },
{ "CriticalHitResiliencePercent", Common::ParamModifier::CriticalHitResiliencePercent }, { "CriticalHitResiliencePercent", Common::ParamModifier::CriticalHitResiliencePercent },
{ "CriticalHitPercent", Common::ParamModifier::CriticalHitPercent }, { "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() bool ActionLutData::cacheActions()

View file

@ -32,6 +32,9 @@ namespace Sapphire::World::Action
inline void from_json( const nlohmann::json& j, StatusEntry& statusEntry ) inline void from_json( const nlohmann::json& j, StatusEntry& statusEntry )
{ {
j.at( "id" ).get_to( statusEntry.id ); 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" ) ) if( j.contains( "modifiers" ) )
j.at( "modifiers" ).get_to( statusEntry.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_result.Type = CalcResultType::TypeSetStatus;
m_bOverrideStatus = shouldOverride; 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 ); m_pStatus->setParam( param );
} }
@ -89,6 +101,19 @@ void ActionResult::applyStatusEffectSelf( uint32_t id, int32_t duration, uint8_t
m_pStatus->setParam( param ); 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 ) void ActionResult::mount( uint16_t mountId )
{ {
m_result.Value = static_cast< int16_t >( mountId ); m_result.Value = static_cast< int16_t >( mountId );
@ -101,7 +126,7 @@ const Common::CalcResultParam& ActionResult::getCalcResultParam() const
return m_result; return m_result;
} }
const StatusEffect::StatusEffectPtr ActionResult::getStatusEffect() const const Sapphire::StatusEffect::StatusEffectPtr ActionResult::getStatusEffect() const
{ {
return m_pStatus; return m_pStatus;
} }

View file

@ -2,6 +2,7 @@
#include <ForwardsZone.h> #include <ForwardsZone.h>
#include <Common.h> #include <Common.h>
#include "ActionLut.h"
namespace Sapphire::World::Action namespace Sapphire::World::Action
{ {
@ -20,7 +21,11 @@ namespace Sapphire::World::Action
void startCombo( uint16_t actionId ); void startCombo( uint16_t actionId );
void comboSucceed(); 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, 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, 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 ); void mount( uint16_t mountId );
Entity::CharaPtr getTarget() const; Entity::CharaPtr getTarget() const;

View file

@ -1,4 +1,4 @@
#include "ActionResultBuilder.h" #include "ActionResultBuilder.h"
#include "ActionResult.h" #include "ActionResult.h"
#include <Actor/Player.h> #include <Actor/Player.h>
@ -93,6 +93,14 @@ void ActionResultBuilder::applyStatusEffect( Entity::CharaPtr& target, uint16_t
addResultToActor( target, nextResult ); 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 ) void ActionResultBuilder::applyStatusEffectSelf( uint16_t statusId, uint32_t duration, uint8_t param, bool shouldOverride )
{ {
ActionResultPtr nextResult = make_ActionResult( m_sourceChara ); ActionResultPtr nextResult = make_ActionResult( m_sourceChara );
@ -100,6 +108,14 @@ void ActionResultBuilder::applyStatusEffectSelf( uint16_t statusId, uint32_t dur
addResultToActor( m_sourceChara, nextResult ); 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 ) void ActionResultBuilder::mount( Entity::CharaPtr& target, uint16_t mountId )
{ {
ActionResultPtr nextResult = make_ActionResult( target ); ActionResultPtr nextResult = make_ActionResult( target );

View file

@ -2,6 +2,7 @@
#include <ForwardsZone.h> #include <ForwardsZone.h>
#include <Common.h> #include <Common.h>
#include "ActionLut.h"
namespace Sapphire::World::Action namespace Sapphire::World::Action
{ {
@ -25,8 +26,12 @@ namespace Sapphire::World::Action
void comboSucceed( Entity::CharaPtr& target ); void comboSucceed( Entity::CharaPtr& target );
void applyStatusEffect( Entity::CharaPtr& target, 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 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,
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 ); 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 <Network/CommonActorControl.h>
#include <Service.h> #include <Service.h>
#include "Forwards.h" #include "Forwards.h"
#include "Territory/Territory.h" #include "Territory/Territory.h"
@ -25,6 +24,7 @@
#include "Player.h" #include "Player.h"
#include "Manager/TerritoryMgr.h" #include "Manager/TerritoryMgr.h"
#include "Manager/MgrUtil.h" #include "Manager/MgrUtil.h"
#include "Manager/PlayerMgr.h"
#include "Common.h" #include "Common.h"
using namespace Sapphire; using namespace Sapphire;
@ -497,9 +497,9 @@ void Chara::addStatusEffect( StatusEffect::StatusEffectPtr pEffect )
if( nextSlot == -1 ) if( nextSlot == -1 )
return; return;
pEffect->applyStatus();
pEffect->setSlot( nextSlot ); pEffect->setSlot( nextSlot );
m_statusEffectMap[ nextSlot ] = pEffect; m_statusEffectMap[ nextSlot ] = pEffect;
pEffect->applyStatus();
} }
/*! \param StatusEffectPtr to be applied to the actor */ /*! \param StatusEffectPtr to be applied to the actor */
@ -515,7 +515,6 @@ void Chara::addStatusEffectByIdIfNotExist( StatusEffect::StatusEffectPtr pStatus
return; return;
addStatusEffect( pStatus ); addStatusEffect( pStatus );
} }
int8_t Chara::getStatusEffectFreeSlot() int8_t Chara::getStatusEffectFreeSlot()
@ -536,6 +535,18 @@ void Chara::statusEffectFreeSlot( uint8_t slotId )
m_statusEffectFreeSlotQueue.push( 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 ) void Chara::removeSingleStatusEffectById( uint32_t id )
{ {
for( const auto& effectIt : m_statusEffectMap ) 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 ); auto pEffectIt = m_statusEffectMap.find( effectSlotId );
if( pEffectIt == m_statusEffectMap.end() ) if( pEffectIt == m_statusEffectMap.end() )
@ -559,7 +581,8 @@ std::map< uint8_t, StatusEffect::StatusEffectPtr >::iterator Chara::removeStatus
auto pEffect = pEffectIt->second; auto pEffect = pEffectIt->second;
pEffect->removeStatus(); 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 ); auto it = m_statusEffectMap.erase( pEffectIt );
Network::Util::Packet::sendHudParam( *this ); Network::Util::Packet::sendHudParam( *this );
@ -571,6 +594,17 @@ std::map< uint8_t, StatusEffect::StatusEffectPtr > Chara::getStatusEffectMap() c
return m_statusEffectMap; 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 const uint8_t* Chara::getLookArray() const
{ {
return m_customize; return m_customize;
@ -595,7 +629,6 @@ void Chara::sendStatusEffectUpdate()
{ {
uint64_t currentTimeMs = Common::Util::getTimeMs(); uint64_t currentTimeMs = Common::Util::getTimeMs();
auto statusEffectList = makeZonePacket< FFXIVIpcStatus >( getId() ); auto statusEffectList = makeZonePacket< FFXIVIpcStatus >( getId() );
uint8_t slot = 0; uint8_t slot = 0;
for( const auto& effectIt : m_statusEffectMap ) for( const auto& effectIt : m_statusEffectMap )
@ -640,7 +673,13 @@ void Chara::updateStatusEffects()
bool Chara::hasStatusEffect( uint32_t id ) 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 int64_t Chara::getLastUpdateTime() const
@ -735,6 +774,27 @@ void Chara::setStatValue( Common::BaseParam baseParam, uint32_t value )
m_baseStats[ index ] = 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() void Chara::onTick()
{ {
uint32_t thisTickDmg = 0; uint32_t thisTickDmg = 0;
@ -745,13 +805,13 @@ void Chara::onTick()
auto thisEffect = effectIt.second->getTickEffect(); auto thisEffect = effectIt.second->getTickEffect();
switch( thisEffect.first ) switch( thisEffect.first )
{ {
case 1: case Common::ParamModifier::TickDamage:
{ {
thisTickDmg += thisEffect.second; thisTickDmg += thisEffect.second;
break; break;
} }
case 2: case Common::ParamModifier::TickHeal:
{ {
thisTickHeal += thisEffect.second; thisTickHeal += thisEffect.second;
break; 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 ) if( thisTickDmg != 0 )
{ {
takeDamage( thisTickDmg ); takeDamage( thisTickDmg );
Network::Util::Packet::sendActorControl( getInRangePlayerIds( isPlayer() ), getId(), HPFloatingText, 0, Network::Util::Packet::sendActorControl( getInRangePlayerIds( isPlayer() ), getId(), HPFloatingText, 0,
CalcResultType::TypeDamageHp, thisTickDmg ); CalcResultType::TypeDamageHp, thisTickDmg );
Network::Util::Packet::sendHudParam( *this );
} }
if( thisTickHeal != 0 ) if( thisTickHeal != 0 )
@ -771,5 +835,7 @@ void Chara::onTick()
heal( thisTickHeal ); heal( thisTickHeal );
Network::Util::Packet::sendActorControl( getInRangePlayerIds( isPlayer() ), getId(), HPFloatingText, 0, Network::Util::Packet::sendActorControl( getInRangePlayerIds( isPlayer() ), getId(), HPFloatingText, 0,
CalcResultType::TypeRecoverMp, thisTickHeal ); CalcResultType::TypeRecoverMp, thisTickHeal );
Network::Util::Packet::sendHudParam( *this );
} }
} }

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <Common.h> #include <Common.h>
#include "Action/ActionLut.h"
#include "Forwards.h" #include "Forwards.h"
#include "GameObject.h" #include "GameObject.h"
@ -8,6 +9,7 @@
#include <map> #include <map>
#include <queue> #include <queue>
#include <array> #include <array>
#include <numeric>
namespace Sapphire::Entity namespace Sapphire::Entity
{ {
@ -106,10 +108,14 @@ namespace Sapphire::Entity
/// Status effect functions /// Status effect functions
void addStatusEffect( StatusEffect::StatusEffectPtr pEffect ); 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 removeSingleStatusEffectById( uint32_t id );
void removeStatusEffectByFlag( Common::StatusEffectFlag flag );
void updateStatusEffects(); void updateStatusEffects();
bool hasStatusEffect( uint32_t id ); bool hasStatusEffect( uint32_t id );
@ -124,6 +130,8 @@ namespace Sapphire::Entity
std::map< uint8_t, Sapphire::StatusEffect::StatusEffectPtr > getStatusEffectMap() const; std::map< uint8_t, Sapphire::StatusEffect::StatusEffectPtr > getStatusEffectMap() const;
Sapphire::StatusEffect::StatusEffectPtr getStatusEffectById( uint32_t id ) const;
void sendStatusEffectUpdate(); void sendStatusEffectUpdate();
/*! return a const pointer to the look array */ /*! return a const pointer to the look array */
@ -159,6 +167,8 @@ namespace Sapphire::Entity
void setStatValue( Common::BaseParam baseParam, uint32_t value ); void setStatValue( Common::BaseParam baseParam, uint32_t value );
float getModifier( Common::ParamModifier paramModifier ) const;
uint32_t getHp() const; uint32_t getHp() const;
uint32_t getHpPercent() 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 ); 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 ) void Player::learnSong( uint8_t songId, uint32_t itemId )
{ {
uint16_t index; uint16_t index;
@ -712,6 +728,7 @@ void Player::setClassJob( Common::ClassJob classJob )
m_tp = 0; m_tp = 0;
Network::Util::Packet::sendChangeClass( *this );
Network::Util::Packet::sendStatusUpdate( *this ); Network::Util::Packet::sendStatusUpdate( *this );
Network::Util::Packet::sendActorControl( getInRangePlayerIds( true ), getId(), ClassJobChange, 4 ); Network::Util::Packet::sendActorControl( getInRangePlayerIds( true ), getId(), ClassJobChange, 4 );
Network::Util::Packet::sendHudParam( *this ); Network::Util::Packet::sendHudParam( *this );

View file

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

View file

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

View file

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

View file

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

View file

@ -92,7 +92,7 @@ std::unique_ptr< RandGenerator< float > > CalcStats::rnd = nullptr;
Big thanks to the Theoryjerks group! Big thanks to the Theoryjerks group!
NOTE: 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. 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. 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 hpMod = paramGrowthInfo->data().ParamBase;
uint16_t jobModHp = classInfo->data().Hp; uint16_t jobModHp = classInfo->data().Hp;
float approxBaseHp = 0.0f; // Read above float approxBaseHp = 0.0f; // Read above
float hpModPercent = player.getModifier( Common::ParamModifier::HPPercent );
approxBaseHp = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::HP ] ); approxBaseHp = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::HP ] );
auto result = static_cast< uint32_t >( floor( jobModHp * ( approxBaseHp / 100.0f ) ) + auto result = static_cast< uint32_t >( floor( jobModHp * ( approxBaseHp / 100.0f ) ) +
floor( hpMod / 100.0f * ( vitStat - baseStat ) ) ); floor( hpMod / 100.0f * ( vitStat - baseStat ) ) );
result *= hpModPercent;
return result; return result;
} }
@ -601,6 +604,7 @@ std::pair< float, Sapphire::Common::CalcResultType > CalcStats::calcActionDamage
auto wd = weaponDamage( chara, wepDmg ); auto wd = weaponDamage( chara, wepDmg );
auto ap = getPrimaryAttackPower( chara ); auto ap = getPrimaryAttackPower( chara );
auto det = determination( chara ); auto det = determination( chara );
auto damageDealtMod = chara.getModifier( Common::ParamModifier::DamageDealtPercent );
auto factor = Common::Util::trunc( pot * wd * ap * det, 0 ); auto factor = Common::Util::trunc( pot * wd * ap * det, 0 );
Sapphire::Common::CalcResultType hitType = Sapphire::Common::CalcResultType::TypeDamageHp; Sapphire::Common::CalcResultType hitType = Sapphire::Common::CalcResultType::TypeDamageHp;
@ -615,11 +619,14 @@ std::pair< float, Sapphire::Common::CalcResultType > CalcStats::calcActionDamage
// todo: buffs // todo: buffs
factor *= damageDealtMod;
constexpr auto format = "dmg: pot: {} ({}) wd: {} ({}) ap: {} det: {} = {}"; constexpr auto format = "dmg: pot: {} ({}) wd: {} ({}) ap: {} det: {} = {}";
if( auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer() ) if( auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer() )
{ {
PlayerMgr::sendDebug( *player, format, pot, ptc, wd, wepDmg, ap, det, factor ); PlayerMgr::sendDebug( *player, format, pot, ptc, wd, wepDmg, ap, det, factor );
PlayerMgr::sendDebug( *player, "DamageDealtPercent: {}", damageDealtMod );
} }
else else
{ {
@ -662,4 +669,4 @@ float CalcStats::getRandomNumber0To100()
rnd = std::make_unique< RandGenerator< float > >( Common::Service< RNGMgr >::ref().getRandGenerator< float >( 0, 100 ) ); rnd = std::make_unique< RandGenerator< float > >( Common::Service< RNGMgr >::ref().getRandGenerator< float >( 0, 100 ) );
} }
return rnd->next(); return rnd->next();
} }

View file

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

View file

@ -307,10 +307,12 @@ void Util::Packet::sendPlayerSetup( Entity::Player& player )
void Util::Packet::sendChangeClass( Entity::Player& player ) void Util::Packet::sendChangeClass( Entity::Player& player )
{ {
auto classInfo = makeZonePacket< FFXIVIpcChangeClass >( player.getId() ); auto classInfo = makeZonePacket< FFXIVIpcChangeClass >( player.getId() );
auto& borrowAction = player.getBorrowAction();
classInfo->data().ClassJob = static_cast< uint8_t >( player.getClass() ); classInfo->data().ClassJob = static_cast< uint8_t >( player.getClass() );
classInfo->data().Lv = player.getLevel(); classInfo->data().Lv = player.getLevel();
classInfo->data().Lv1 = player.getLevel(); classInfo->data().Lv1 = player.getLevel();
classInfo->data().Login = player.isLogin() ? 1 : 0; classInfo->data().Login = player.isLogin() ? 1 : 0;
memcpy( &classInfo->data().BorrowAction[ 0 ], borrowAction.data(), borrowAction.size() * 4 );
server().queueForPlayer( player.getCharacterId(), classInfo ); 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() ); auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::StatusEffectScript >( effect.getId() );
if( script ) if( script )
{ {
if( pChara->isPlayer() )
PlayerMgr::sendDebug( *pChara->getAsPlayer(), "Calling status tick for statusid#{0}", effect.getId() );
script->onTick( *pChara ); script->onTick( *pChara );
return true; return true;
} }

View file

@ -6,26 +6,41 @@
#include <algorithm> #include <algorithm>
#include <Service.h> #include <Service.h>
#include "Manager/PlayerMgr.h"
#include "Actor/Chara.h" #include "Actor/Chara.h"
#include "Actor/Player.h"
#include "Actor/GameObject.h" #include "Actor/GameObject.h"
#include "Script/ScriptMgr.h" #include "Script/ScriptMgr.h"
#include "StatusEffect.h" #include "StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::Common; using namespace Sapphire::Common;
using namespace Sapphire::Network::Packets; using namespace Sapphire::Network::Packets;
//using namespace Sapphire::Network::Packets::WorldPackets::Server; //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, Sapphire::StatusEffect::StatusEffect::StatusEffect( uint32_t id, Entity::CharaPtr sourceActor, Entity::CharaPtr targetActor,
uint32_t duration, uint32_t tickRate ) : uint32_t duration, uint32_t tickRate ) :
m_id( id ), m_id( id ),
m_sourceActor( sourceActor ), m_sourceActor( sourceActor ),
m_targetActor( targetActor ), m_targetActor( targetActor ),
m_duration( duration ), m_duration( duration ),
m_modifiers( 0 ),
m_startTime( 0 ), m_startTime( 0 ),
m_tickRate( tickRate ), m_tickRate( tickRate ),
m_lastTick( 0 ) m_lastTick( 0 ),
m_flag( 0 )
{ {
auto& exdData = Common::Service< Data::ExdData >::ref(); auto& exdData = Common::Service< Data::ExdData >::ref();
auto entry = exdData.getRow< Excel::Status >( id ); 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, '(' ); 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 ); 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; return m_currTickEffect;
m_currTickEffect = std::make_pair( 0, 0 );
return thisTick;
} }
void Sapphire::StatusEffect::StatusEffect::onTick() void Sapphire::StatusEffect::StatusEffect::onTick()
@ -87,16 +109,60 @@ uint16_t Sapphire::StatusEffect::StatusEffect::getParam() const
return m_param; 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() void Sapphire::StatusEffect::StatusEffect::applyStatus()
{ {
m_startTime = Util::getTimeMs(); m_startTime = Util::getTimeMs();
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref(); auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
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 ); scriptMgr.onStatusReceive( m_targetActor, m_id );
} }
void Sapphire::StatusEffect::StatusEffect::removeStatus() void Sapphire::StatusEffect::StatusEffect::removeStatus()
{ {
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref(); auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
m_modifiers.clear();
m_targetActor->calculateStats();
scriptMgr.onStatusTimeOut( m_targetActor, m_id ); scriptMgr.onStatusTimeOut( m_targetActor, m_id );
} }
@ -125,6 +191,21 @@ uint64_t Sapphire::StatusEffect::StatusEffect::getStartTimeMs() const
return m_startTime; 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 ) void Sapphire::StatusEffect::StatusEffect::setLastTick( uint64_t lastTick )
{ {
m_lastTick = lastTick; m_lastTick = lastTick;

View file

@ -2,6 +2,7 @@
#define _STATUSEFFECT_H_ #define _STATUSEFFECT_H_
#include "Forwards.h" #include "Forwards.h"
#include "Action/ActionLut.h"
namespace Sapphire { namespace Sapphire {
namespace StatusEffect { namespace StatusEffect {
@ -10,6 +11,9 @@ namespace StatusEffect {
class StatusEffect class StatusEffect
{ {
public: 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, StatusEffect( uint32_t id, Entity::CharaPtr sourceActor, Entity::CharaPtr targetActor,
uint32_t duration, uint32_t tickRate ); uint32_t duration, uint32_t tickRate );
@ -17,6 +21,12 @@ public:
void onTick(); 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 applyStatus();
void removeStatus(); void removeStatus();
@ -38,13 +48,19 @@ public:
uint16_t getParam() const; uint16_t getParam() const;
uint32_t getFlag() const;
std::vector< World::Action::StatusModifier > getStatusModifiers() const;
void setLastTick( uint64_t lastTick ); void setLastTick( uint64_t lastTick );
void setParam( uint16_t param ); 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; const std::string& getName() const;
@ -60,9 +76,12 @@ private:
uint32_t m_tickRate; uint32_t m_tickRate;
uint64_t m_lastTick; uint64_t m_lastTick;
uint16_t m_param; uint16_t m_param;
uint32_t m_flag;
std::string m_name; 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; uint8_t m_slot;
std::pair< uint8_t, uint32_t > m_currTickEffect;
}; };