1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-22 20:57:46 +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

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

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