1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-28 15:17:46 +00:00

split encounter timeline stuff into separate files

- todo: use selector in CastAction timepoint
This commit is contained in:
Tahir 2024-06-21 00:27:41 +01:00
parent 8fb9846c0b
commit 7cc3f65571
14 changed files with 2091 additions and 1625 deletions

View file

@ -4,6 +4,8 @@
#include <Actor/BNpc.h>
#include <Actor/Chara.h>
#include <Actor/Player.h>
#include <Manager/PartyMgr.h>
#include <Manager/RNGMgr.h>
#include <Util/UtilMath.h>
#include <Service.h>
@ -11,27 +13,28 @@
namespace Sapphire::World::AI
{
bool InsideRadiusFilter::isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
bool InsideRadiusFilter::isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
{
bool ret = Common::Util::distance( pSrc->getPos(), pTarget->getPos() ) <= m_distance;
return m_negate ? !ret : ret;
}
bool OutsideRadiusFilter::isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
bool OutsideRadiusFilter::isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
{
bool ret = Common::Util::distance( pSrc->getPos(), pTarget->getPos() ) >= m_distance;
return m_negate ? !ret : ret;
}
bool PlayerFilter::isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
bool PlayerFilter::isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
{
bool ret = pTarget->isPlayer();
return m_negate ? !ret : ret;
}
bool AllyFilter::isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
bool AllyFilter::isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
{
bool ret = false;
// todo: pets, companions, enpc
if( pSrc->isPlayer() )
{
auto pBNpcTarget = pTarget->getAsBNpc();
@ -52,24 +55,24 @@ namespace Sapphire::World::AI
return m_negate ? !ret : ret;
}
bool OwnBattalionFilter::isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
bool OwnBattalionFilter::isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
{
return false;
}
bool TankFilter::isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
bool TankFilter::isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
{
bool ret = pTarget->getRole() == Common::Role::Tank;
return m_negate ? !ret : ret;
}
bool HealerFilter::isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
bool HealerFilter::isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
{
bool ret = pTarget->getRole() == Common::Role::Healer;
return m_negate ? !ret : ret;
}
bool DpsFilter::isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
bool DpsFilter::isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
{
bool ret = true;
switch( pTarget->getRole() )
@ -86,13 +89,13 @@ namespace Sapphire::World::AI
return m_negate ? !ret : ret;
}
bool HasStatusEffectFilter::isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
bool HasStatusEffectFilter::isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
{
auto ret = pTarget->hasStatusEffect( m_statusId );
return m_negate ? !ret : ret;
}
bool TopAggroFilter ::isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
bool TopAggroFilter::isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
{
auto pBNpc = pSrc->getAsBNpc();
bool ret = false;
@ -103,7 +106,7 @@ namespace Sapphire::World::AI
return m_negate ? !ret : ret;
}
bool SecondAggroFilter ::isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
bool SecondAggroFilter::isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
{
auto pBNpc = pSrc->getAsBNpc();
bool ret = false;
@ -126,10 +129,40 @@ namespace Sapphire::World::AI
return m_negate ? !ret : ret;
}
void Snapshot::createSnapshot( Entity::CharaPtr pSrc, const std::set< Entity::GameObjectPtr >& inRange,
int count, bool fillWithRandom, const std::set< uint32_t >& exclude )
bool PartyMemberFilter::isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
{
m_targets.clear();
bool ret = false;
// todo: pets, companions, enpc
if( auto pPlayer = pSrc->getAsPlayer() )
{
if( auto pTargetPlayer = pTarget->getAsPlayer() )
{
ret = pPlayer->getPartyId() == pTargetPlayer->getPartyId();
}
else if( auto pBNpc = pTarget->getAsBNpc() )
{
ret = pBNpc->getBNpcType() == 0;
}
}
else if( auto pBNpc = pSrc->getAsBNpc() )
{
if( auto pTargetPlayer = pTarget->getAsPlayer() )
{
ret = pPlayer->getPartyId() == pTargetPlayer->getPartyId();
}
else if( auto pTargetBNpc = pTarget->getAsBNpc() )
{
ret = pBNpc->getBNpcType() == pTargetBNpc->getEnemyType();
}
}
return m_negate ? !ret : ret;
}
void Snapshot::createSnapshot( Entity::CharaPtr pSrc, const std::set< Entity::GameObjectPtr >& inRange,
int count, bool fillWithRandom, const std::vector< uint32_t >& exclude )
{
m_results.clear();
m_targetIds.clear();
auto& RNGMgr = Common::Service< World::Manager::RNGMgr >::ref();
for( const auto& pActor : inRange )
@ -138,12 +171,17 @@ namespace Sapphire::World::AI
if( pChara == nullptr )
continue;
if( exclude.find( pChara->getId() ) != exclude.end() ) continue;
// exclude this character from the result set
auto excludeIt = std::find_if( exclude.begin(), exclude.end(),
[ pChara ]( uint32_t id ) { return pChara->getId() == id; }
);
if( excludeIt != exclude.end() )
continue;
bool matches = true;
for( const auto& filter : m_filters )
{
if( !filter->isConditionMet( pSrc, pChara ) )
if( !filter->isApplicable( pSrc, pChara ) )
{
matches = false;
break;
@ -157,12 +195,13 @@ namespace Sapphire::World::AI
entry.m_pos = pChara->getPos();
entry.m_rot = pChara->getRot();
m_targets.push_back( entry );
if( m_targets.size() == count ) break;
m_results.push_back( entry );
if( m_results.size() == count ) break;
}
}
if( fillWithRandom && m_targets.size() < count )
// fallback to fill with random entries if we dont have enough valid results
if( fillWithRandom && m_results.size() < count )
{
std::vector< Entity::CharaPtr > remaining;
for( const auto& pActor : inRange )
@ -171,48 +210,54 @@ namespace Sapphire::World::AI
if( pChara == nullptr )
continue;
if( exclude.find( pChara->getId() ) == exclude.end() && std::find_if( m_targets.begin(), m_targets.end(),
[ &pChara ]( CharaEntry entry ) { return entry.m_entityId == pChara->getId(); } ) == m_targets.end() )
auto excludeIt = std::find_if( exclude.begin(), exclude.end(),
[ pChara ]( uint32_t id ) { return pChara->getId() == id; }
);
if( excludeIt == exclude.end() && std::find_if( m_results.begin(), m_results.end(),
[ &pChara ]( CharaEntry entry ) { return entry.m_entityId == pChara->getId(); } ) == m_results.end() )
{
remaining.push_back( pChara );
}
}
while( m_targets.size() < count && !remaining.empty() )
while( m_results.size() < count && !remaining.empty() )
{
// idk
std::shuffle( remaining.begin(), remaining.end(), *RNGMgr.getRNGEngine().get() );
std::shuffle( remaining.begin(), remaining.end(), *RNGMgr.getRNGEngine() );
auto pChara = remaining.back();
CharaEntry entry;
entry.m_entityId = pChara->getId();
entry.m_pos = pChara->getPos();
entry.m_rot = pChara->getRot();
m_targets.emplace_back( entry );
m_results.emplace_back( entry );
remaining.pop_back();
}
}
// sort by distance at the end always
auto srcPos = pSrc->getPos();
std::sort( m_targets.begin(), m_targets.end(),
std::sort( m_results.begin(), m_results.end(),
[ srcPos ]( CharaEntry l, CharaEntry r )
{
return Common::Util::distance( srcPos, l.m_pos ) < Common::Util::distance( srcPos, r.m_pos );
}
);
// we might want the target ids separately
m_targetIds.resize( m_results.size() );
for( auto i = 0; i < m_results.size(); ++i )
m_targetIds[ i ] = m_results[ i ].m_entityId;
}
const std::vector< Snapshot::CharaEntry >& Snapshot::getResults() const
{
return m_targets;
return m_results;
}
const std::vector< uint32_t > Snapshot::getTargetIds() const
{
std::vector< uint32_t > ret( m_targets.size() );
for( auto i = 0; i < m_targets.size(); ++i )
ret[ i ] = m_targets[ i ].m_entityId;
return ret;
return m_targetIds;
}
};// namespace Sapphire::World::AI

View file

@ -35,6 +35,8 @@ namespace Sapphire::World::AI
TopAggro,
SecondAggro,
PartyMember,
AllianceA,
AllianceB,
AllianceC
@ -54,7 +56,7 @@ namespace Sapphire::World::AI
{
}
bool isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
virtual bool isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const
{
return false;
};
@ -78,7 +80,7 @@ namespace Sapphire::World::AI
{
}
bool isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const;
bool isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const override;
};
class OutsideRadiusFilter : public TargetSelectFilter
@ -92,7 +94,7 @@ namespace Sapphire::World::AI
{
}
bool isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const;
bool isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const override;
};
class PlayerFilter : public TargetSelectFilter
@ -103,7 +105,7 @@ namespace Sapphire::World::AI
{
}
bool isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const;
bool isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const override;
};
class AllyFilter : public TargetSelectFilter
@ -114,7 +116,7 @@ namespace Sapphire::World::AI
{
}
bool isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const;
bool isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const override;
};
class OwnBattalionFilter : public TargetSelectFilter
@ -125,7 +127,7 @@ namespace Sapphire::World::AI
{
}
bool isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const;
bool isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const override;
};
class TankFilter : public TargetSelectFilter
@ -136,7 +138,7 @@ namespace Sapphire::World::AI
{
}
bool isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const;
bool isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const override;
};
class HealerFilter : public TargetSelectFilter
@ -147,7 +149,7 @@ namespace Sapphire::World::AI
{
}
bool isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const;
bool isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const override;
};
class DpsFilter : public TargetSelectFilter
@ -158,7 +160,7 @@ namespace Sapphire::World::AI
{
}
bool isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const;
bool isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const override;
};
class HasStatusEffectFilter : public TargetSelectFilter
@ -172,7 +174,7 @@ namespace Sapphire::World::AI
{
}
bool isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const;
bool isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const override;
};
class TopAggroFilter : public TargetSelectFilter
@ -183,7 +185,7 @@ namespace Sapphire::World::AI
{
}
bool isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const;
bool isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const override;
};
class SecondAggroFilter : public TargetSelectFilter
@ -194,7 +196,18 @@ namespace Sapphire::World::AI
{
}
bool isConditionMet( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const;
bool isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const override;
};
class PartyMemberFilter : public TargetSelectFilter
{
public:
PartyMemberFilter( bool negate ) :
TargetSelectFilter( Type::PartyMember, negate )
{
}
bool isApplicable( Entity::CharaPtr& pSrc, Entity::CharaPtr& pTarget ) const override;
};
//
@ -203,6 +216,7 @@ namespace Sapphire::World::AI
class Snapshot :
public std::enable_shared_from_this< Snapshot >
{
public:
struct CharaEntry
{
uint32_t m_entityId;
@ -210,17 +224,20 @@ namespace Sapphire::World::AI
float m_rot;
// todo: status effects?
};
using Results = std::vector< CharaEntry >;
using TargetIds = std::vector< uint32_t >;
private:
std::vector< TargetSelectFilterPtr > m_filters;
std::vector< CharaEntry > m_targets;
std::vector< CharaEntry > m_results;
std::vector< uint32_t > m_targetIds;
public:
Snapshot( const std::vector< TargetSelectFilterPtr > filters ) :
public:
Snapshot( const std::vector< TargetSelectFilterPtr >& filters ) :
m_filters( filters )
{
}
void createSnapshot( Entity::CharaPtr pSrc, const std::set< Entity::GameObjectPtr >& inRange,
int count, bool fillWithRandom, const std::set< uint32_t >& exclude = {} );
int count, bool fillWithRandom, const std::vector< uint32_t >& exclude = {} );
// returns actors sorted by distance
const std::vector< CharaEntry >& getResults() const;

View file

@ -1,6 +1,11 @@
#include "EncounterFight.h"
#include "EncounterTimeline.h"
#include "Timepoint.h"
#include "TimelineActorState.h"
#include "TimelineActor.h"
#include "PhaseCondition.h"
#include "Selector.h"
#include <Action/Action.h>
#include <Actor/BNpc.h>
@ -15,349 +20,11 @@
#include <Service.h>
#include <Territory/QuestBattle.h>
#include <Territory/InstanceContent.h>
#include <Util/UtilMath.h>
namespace Sapphire
namespace Sapphire::Encounter
{
bool EncounterTimeline::ConditionHp::isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
auto pBNpc = pTeri->getActiveBNpcByLayoutId( layoutId );
if( !pBNpc )
return false;
// todo: check time elapsed
switch( m_conditionType )
{
case ConditionType::HpPctLessThan:
return pBNpc->getHpPercent() < hp.val;
case ConditionType::HpPctBetween:
{
auto hpPct = pBNpc->getHpPercent();
return hpPct >= hp.min && hpPct <= hp.max;
}
}
return false;
};
bool EncounterTimeline::ConditionDirectorVar::isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
Event::DirectorPtr pDirector = pTeri->getAsInstanceContent();
if( pDirector == nullptr )
pDirector = pTeri->getAsQuestBattle();
switch( m_conditionType )
{
case ConditionType::DirectorVarEquals:
return pDirector->getDirectorVar( param.index ) == param.value;
case ConditionType::DirectorVarGreaterThan:
return pDirector->getDirectorVar( param.index ) > param.value;
case ConditionType::DirectorFlagsEquals:
return pDirector->getFlags() == param.flags;
case ConditionType::DirectorFlagsGreaterThan:
return pDirector->getFlags() > param.flags;
case ConditionType::DirectorSeqEquals:
return pDirector->getSequence() == param.seq;
case ConditionType::DirectorSeqGreaterThan:
return pDirector->getSequence() > param.seq;
}
return false;
}
bool EncounterTimeline::ConditionCombatState::isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
auto pBattleNpc = pTeri->getActiveBNpcByLayoutId( this->layoutId );
// todo: these should really use callbacks when the state transitions or we could miss this tick
switch( combatState )
{
case CombatStateType::Idle:
return pBattleNpc->getState() == Entity::BNpcState::Idle;
case CombatStateType::Combat:
return pBattleNpc->getState() == Entity::BNpcState::Combat;
case CombatStateType::Retreat:
return pBattleNpc->getState() == Entity::BNpcState::Retreat;
case CombatStateType::Roaming:
return pBattleNpc->getState() == Entity::BNpcState::Roaming;
case CombatStateType::JustDied:
return pBattleNpc->getState() == Entity::BNpcState::JustDied;
case CombatStateType::Dead:
return pBattleNpc->getState() == Entity::BNpcState::Dead;
default:
break;
}
return false;
}
bool EncounterTimeline::ConditionEncounterTimeElapsed::isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
auto elapsed = time - pack.getStartTime();
// todo: check encounter time
return elapsed >= this->duration;
}
bool EncounterTimeline::ConditionBNpcFlags::isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
auto pBNpc = pTeri->getActiveBNpcByLayoutId( this->layoutId );
return pBNpc && pBNpc->hasFlag( this->flags );
}
void EncounterTimeline::Timepoint::update( TimepointState& state, TimelineActor& self, TerritoryPtr pTeri, uint64_t time ) const
{
state.m_lastTick = time;
// todo: separate execute and update?
if( state.m_finished )
return;
switch( m_type )
{
case TimepointDataType::Idle:
{
// just wait up the duration of this timepoint
}
break;
case TimepointDataType::CastAction:
{
auto pActionData = std::dynamic_pointer_cast< TimepointDataAction, TimepointData >( getData() );
auto pBNpc = pTeri->getActiveBNpcByLayoutId( pActionData->m_layoutId );
// todo: filter the correct target
// todo: tie to mechanic script?
// todo: mechanic should probably just be an Action::onTick, with instance/director passed to it
if( pBNpc)
{
auto actionMgr = Common::Service< Sapphire::World::Manager::ActionMgr >::ref();
// todo: this is probably wrong
if( pBNpc->getCurrentAction() && pBNpc->getCurrentAction()->getId() != pActionData->m_actionId )
actionMgr.handleTargetedAction( *pBNpc.get(), pActionData->m_actionId, pBNpc->getTargetId(), 0 );
}
}
break;
case TimepointDataType::MoveTo:
{
auto pMoveToData = std::dynamic_pointer_cast< TimepointDataMoveTo, TimepointData >( getData() );
auto pBNpc = pTeri->getActiveBNpcByLayoutId( pMoveToData->m_layoutId );
if( pBNpc )
{
auto currPos = pBNpc->getPos();
Common::FFXIVARR_POSITION3 targetPos = { pMoveToData->m_x, pMoveToData->m_y, pMoveToData->m_z };
auto distance = Common::Util::distance( currPos, targetPos );
if( distance > 0.5f )
{
if( pMoveToData->m_moveType == MoveType::WalkPath )
pBNpc->moveTo( targetPos );
else
pBNpc->setPos( pMoveToData->m_x, pMoveToData->m_y, pMoveToData->m_z );
}
else
{
// if we are at the pos, stop waiting
//state.m_finished = true;
}
pBNpc->setRot( pMoveToData->m_rot );
}
}
break;
case TimepointDataType::LogMessage:
{
auto pLogMessage = std::dynamic_pointer_cast< TimepointDataLogMessage, TimepointData >( getData() );
auto params = pLogMessage->m_params;
// todo: probably should use ContentDirector
{
auto& playerMgr = Common::Service< Sapphire::World::Manager::PlayerMgr >::ref();
for( auto player : pTeri->getPlayers() )
{
auto pPlayer = player.second;
if( pPlayer )
playerMgr.sendLogMessage( *pPlayer.get(), pLogMessage->m_messageId,
params[ 0 ], params[ 1 ], params[ 2 ], params[ 3 ], params[ 4 ] );
}
}
}
break;
case TimepointDataType::BattleTalk:
{
// todo: BattleTalk
auto pBtData = std::dynamic_pointer_cast< TimepointDataBattleTalk, TimepointData >( getData() );
auto params = pBtData->m_params;
auto& playerMgr = Common::Service< Sapphire::World::Manager::PlayerMgr >::ref();
for( auto player : pTeri->getPlayers() )
{
auto pPlayer = player.second;
if( pPlayer )
playerMgr.sendBattleTalk( *pPlayer.get(), pBtData->m_battleTalkId, pBtData->m_handlerId,
pBtData->m_kind, pBtData->m_nameId, pBtData->m_talkerId,
params[ 0 ], params[ 1 ], params[ 2 ], params[ 3 ],
params[ 4 ], params[ 5 ], params[ 6 ], params[ 7 ] );
}
}
break;
case TimepointDataType::DirectorSeq:
case TimepointDataType::DirectorVar:
case TimepointDataType::DirectorVarLR:
case TimepointDataType::DirectorFlags:
{
auto pDirectorData = std::dynamic_pointer_cast< TimepointDataDirector, TimepointData >( getData() );
uint32_t val = 0;
uint32_t param = 0;
// todo: expand for fates
Event::DirectorPtr pDirector = pTeri->getAsInstanceContent();
if( pDirector == nullptr )
pDirector = pTeri->getAsQuestBattle();
// todo: this should never not be set?
// todo: probably should use ContentDirector
// todo: this needs to resend packets too
if( pDirector )
{
switch( m_type )
{
case TimepointDataType::DirectorVar:
val = pDirector->getDirectorVar( pDirectorData->m_data.index );
param = pDirectorData->m_data.value.val;
break;
case TimepointDataType::DirectorFlags:
val = pDirector->getFlags();
param = pDirectorData->m_data.flags;
break;
case TimepointDataType::DirectorSeq:
val = pDirector->getSequence();
param = pDirectorData->m_data.seq;
break;
default:
break;
}
switch( pDirectorData->m_directorOp )
{
case DirectorOpId::Set: val = param; break;
case DirectorOpId::Add: val += param; break;
case DirectorOpId::Sub: val -= param; break;
case DirectorOpId::Mul: val *= param; break;
case DirectorOpId::Div: val /= param; break;
case DirectorOpId::Mod: val %= param; break;
case DirectorOpId::Sll: val = val << param; break;
case DirectorOpId::Srl: val = val >> param; break;
case DirectorOpId::Or: val |= param; break;
case DirectorOpId::Xor: val ^= param; break;
case DirectorOpId::Nor: val = ~( val | param ); break;
case DirectorOpId::And: val &= param; break;
default: break;
}
// todo: resend packets
switch( m_type )
{
case TimepointDataType::DirectorVar:
pDirector->setDirectorVar( pDirectorData->m_data.index, val );
break;
case TimepointDataType::DirectorFlags:
pDirector->setDirectorFlags( val );
break;
case TimepointDataType::DirectorSeq:
pDirector->setDirectorSequence( val );
break;
default:
break;
}
}
}
break;
case TimepointDataType::AddStatusEffect:
{
// todo:
}
break;
case TimepointDataType::RemoveStatusEffect:
{
}
break;
case TimepointDataType::SpawnBNpc:
{
auto pSpawnData = std::dynamic_pointer_cast< TimepointDataSpawnBNpc, TimepointData >( getData() );
auto pBNpc = pTeri->getActiveBNpcByLayoutId( pSpawnData->m_layoutId );
if( pBNpc )
{
pBNpc->clearFlags();
pBNpc->setFlag( pSpawnData->m_flags );
pBNpc->init();
}
}
break;
case TimepointDataType::SetBNpcFlags:
{
auto pBNpcFlagData = std::dynamic_pointer_cast< TimepointDataBNpcFlags, TimepointData >( getData() );
auto pBNpc = pTeri->getActiveBNpcByLayoutId( pBNpcFlagData->m_layoutId );
if( pBNpc )
{
pBNpc->clearFlags();
pBNpc->setFlag( pBNpcFlagData->m_flags );
// todo: resend some bnpc packet/actrl?
}
}
break;
case TimepointDataType::SetEObjState:
{
auto pEObjData = std::dynamic_pointer_cast< TimepointDataEObjState, TimepointData >( getData() );
auto pInstance = pTeri->getAsInstanceContent();
auto pQBattle = pTeri->getAsQuestBattle();
// todo: event objects on quest battles
// todo: SetEObjAnimationFlag?
if( pInstance )
{
auto pEObj = pInstance->getEObjById( pEObjData->m_eobjId );
if( pEObj )
{
pEObj->setState( pEObjData->m_state );
// todo: resend the eobj spawn packet?
}
}
}
break;
case TimepointDataType::SetBgm:
{
auto pBgmData = std::dynamic_pointer_cast< TimepointDataBGM, TimepointData >( getData() );
auto pInstance = pTeri->getAsInstanceContent();
auto pQBattle = pTeri->getAsQuestBattle();
// todo: quest battles refactor to inherit InstanceContent
if( pInstance )
{
pInstance->setCurrentBGM( pBgmData->m_bgmId );
}
}
break;
case TimepointDataType::SetCondition:
{
auto pConditionData = std::dynamic_pointer_cast< TimepointDataCondition, TimepointData >( getData() );
// todo: dont reset so things can resume? idk
self.resetConditionState( pConditionData->m_conditionId );
self.setConditionStateEnabled( pConditionData->m_conditionId, pConditionData->m_enabled );
}
}
if( m_type != TimepointDataType::MoveTo && m_type != TimepointDataType::CastAction )
state.m_finished = true;
state.m_finished = state.m_finished || state.m_startTime + m_duration <= time;
}
/*
class RngCondition : TimepointCondition
@ -421,384 +88,11 @@ namespace Sapphire
}
*/
void EncounterTimeline::Timepoint::execute( TimepointState& state, TimelineActor& self, TerritoryPtr pTeri, uint64_t time ) const
{
state.m_startTime = time;
update( state, self, pTeri, time );
}
//
// parsing stuff below
//
void EncounterTimeline::ConditionHp::from_json( nlohmann::json& json, Phase& phase, ConditionType condition,
const std::unordered_map< std::string, TimelineActor >& actors )
{
PhaseCondition::from_json( json, phase, condition );
auto paramData = json.at( "paramData" );
auto actorRef = paramData.at( "sourceActor" ).get< std::string >();
// resolve the actor whose hp we are checking
if( auto it = actors.find( actorRef ); it != actors.end() )
this->layoutId = it->second.m_layoutId;
else
throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::ConditionHp::from_json unable to find actor by name: %s" ), actorRef ) );
switch( condition )
{
case ConditionType::HpPctLessThan:
this->hp.val = paramData.at( "hp" ).get< uint32_t >();
break;
case ConditionType::HpPctBetween:
this->hp.min = paramData.at( "hpMin" ).get< uint32_t >(),
this->hp.max = paramData.at( "hpMax" ).get< uint32_t >();
break;
default:
break;
}
}
void EncounterTimeline::ConditionDirectorVar::from_json( nlohmann::json& json, Phase& phase, ConditionType condition )
{
PhaseCondition::from_json( json, phase, condition );
auto paramData = json.at( "paramData" );
switch( condition )
{
case ConditionType::DirectorVarEquals:
case ConditionType::DirectorVarGreaterThan:
{
param.index = paramData.at( "idx" ).get< uint32_t >();
param.value = paramData.at( "val" ).get< uint32_t >();
}
break;
case ConditionType::DirectorFlagsEquals:
case ConditionType::DirectorFlagsGreaterThan:
{
param.flags = paramData.at( "flags" ).get< uint32_t >();
}
break;
case ConditionType::DirectorSeqEquals:
case ConditionType::DirectorSeqGreaterThan:
{
param.seq = paramData.at( "seq" ).get< uint32_t >();
}
break;
default:
break;
}
}
void EncounterTimeline::ConditionCombatState::from_json( nlohmann::json& json, Phase& phase, ConditionType condition,
const std::unordered_map< std::string, TimelineActor >& actors )
{
PhaseCondition::from_json( json, phase, condition );
auto paramData = json.at( "paramData" );
auto actorRef = paramData.at( "sourceActor" ).get< std::string >();
// resolve the actor whose name we are checking
if( auto it = actors.find( actorRef ); it != actors.end() )
this->layoutId = it->second.m_layoutId;
else
throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::ConditionCombatState::from_json unable to find actor by name: %s" ), actorRef ) );
this->combatState = paramData.at( "combatState" ).get< CombatStateType >();
}
void EncounterTimeline::ConditionEncounterTimeElapsed::from_json( nlohmann::json& json, Phase& phase, ConditionType condition )
{
PhaseCondition::from_json( json, phase, condition );
auto paramData = json.at( "paramData" );
auto duration = paramData.at( "duration" ).get< uint64_t >();
this->duration = duration;
}
void EncounterTimeline::ConditionBNpcFlags::from_json( nlohmann::json& json, Phase& phase, ConditionType condition,
const std::unordered_map< std::string, TimelineActor >& actors )
{
PhaseCondition::from_json( json, phase, condition );
auto actorRef = json.at( "actor" ).get< std::string >();
// resolve the actor whose name we are checking
if( auto it = actors.find( actorRef ); it != actors.end() )
this->layoutId = it->second.m_layoutId;
else
throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::ConditionBNpcFlags::from_json unable to find actor by name: %s" ), actorRef ) );
this->flags = json.at( "flags" ).get< uint32_t >();
// todo: BNpcHasFlags
}
void EncounterTimeline::Timepoint::from_json( const nlohmann::json& json, const std::unordered_map< std::string, TimelineActor>& actors, uint32_t selfLayoutId )
{
const static std::unordered_map< std::string, TimepointDataType > timepointTypeMap =
{
{ "idle", TimepointDataType::Idle },
{ "castAction", TimepointDataType::CastAction },
{ "moveTo", TimepointDataType::MoveTo },
{ "logMessage", TimepointDataType::LogMessage },
{ "battleTalk", TimepointDataType::BattleTalk },
{ "directorVar", TimepointDataType::DirectorVar },
{ "directorSeq", TimepointDataType::DirectorSeq },
{ "directorFlags", TimepointDataType::DirectorFlags },
{ "addStatusEffect", TimepointDataType::AddStatusEffect },
{ "removeStatusEffect", TimepointDataType::RemoveStatusEffect },
{ "spawnBNpc", TimepointDataType::SpawnBNpc },
{ "bNpcFlags", TimepointDataType::SetBNpcFlags },
{ "setEObjState", TimepointDataType::SetEObjState },
{ "setCondition", TimepointDataType::SetCondition },
{ "snapshot", TimepointDataType::Snapshot }
};
const static std::unordered_map< std::string, TimepointOverrideFlags > overrideFlagMap =
{
{}
};
const static std::unordered_map< std::string, TimepointCallbackType > callbackTypeMap =
{
{ "onActionInit", TimepointCallbackType::OnActionInit },
{ "onActionStart", TimepointCallbackType::OnActionStart },
{ "onActionInterrupt", TimepointCallbackType::OnActionInterrupt },
{ "onActionExecute", TimepointCallbackType::OnActionExecute },
};
const static std::unordered_map< std::string, DirectorOpId > directorOpMap =
{
{ "set", DirectorOpId::Set },
{ "add", DirectorOpId::Add },
{ "sub", DirectorOpId::Sub },
{ "mul", DirectorOpId::Mul },
{ "div", DirectorOpId::Div },
{ "mod", DirectorOpId::Mod },
{ "sll", DirectorOpId::Sll },
{ "srl", DirectorOpId::Srl },
{ "or", DirectorOpId::Or },
{ "xor", DirectorOpId::Xor },
{ "nor", DirectorOpId::Nor },
{ "and", DirectorOpId::And }
};
const static std::unordered_map< std::string, Common::BNpcType > bnpcTypeMap =
{
{ "bnpc", Common::BNpcType::Enemy },
{ "ally", Common::BNpcType::Friendly } // todo: rename this
};
TimepointDataType tpType{ 0 };
auto typeStr = json.at( "type" ).get< std::string >();
if( auto it = timepointTypeMap.find( typeStr ); it != timepointTypeMap.end() )
tpType = it->second;
else
throw std::runtime_error( fmt::format( "Timepoint::from_json unable to find timepoint by type: %s", typeStr ) );
m_duration = json.at( "duration" ).get< uint64_t >();
//m_overrideFlags = json.at( "overrideFlags" ).get< TimepointOverrideFlags >();
m_description = json.at( "description" ).get< std::string >();
switch( tpType )
{
case TimepointDataType::Idle:
{
m_pData = std::make_shared< TimepointDataIdle >( selfLayoutId, m_duration );
}
break;
case TimepointDataType::CastAction:
{
// todo: CastAction
// todo: parse and build callback funcs
auto& dataJ = json.at( "data" );
auto actorRef = json.at( "sourceActor" ).get< std::string >();
auto actionId = json.at( "actionId" ).get< uint32_t >();
uint32_t layoutId = 0xE0000000;
if( auto it = actors.find( actorRef ); it != actors.end() )
layoutId = it->second.m_layoutId;
else
throw std::runtime_error( fmt::format( "EncounterTimeline::Timepoint::from_json: CastAction invalid actor ref: %s", actorRef ) );
m_pData = std::make_shared< TimepointDataAction >( layoutId, actionId );
}
break;
case TimepointDataType::MoveTo:
{
auto& dataJ = json.at( "data" );
auto pos = dataJ.at( "pos" ).get< std::vector< float > >();
auto rot = dataJ.at( "rot" ).get< float >();
auto pathReq = dataJ.at( "pathRequested" ).get< bool >() ? MoveType::WalkPath : MoveType::Teleport;
m_pData = std::make_shared< TimepointDataMoveTo >( selfLayoutId, pathReq, pos[ 0 ], pos[ 1 ], pos[ 2 ], rot );
}
break;
case TimepointDataType::LogMessage:
{
auto& dataJ = json.at( "data" );
auto messageId = dataJ.at( "messageId" ).get< uint32_t >();
auto params = dataJ.at( "params" ).get< std::vector< uint32_t > >();
m_pData = std::make_shared< TimepointDataLogMessage >( messageId, params );
}
break;
case TimepointDataType::BattleTalk:
{
auto& dataJ = json.at( "data" );
auto params = dataJ.at( "params" ).get< std::vector< uint32_t > >();
auto pBattleTalkData = std::make_shared< TimepointDataBattleTalk >( params );
pBattleTalkData->m_battleTalkId = dataJ.at( "battleTalkId" ).get< uint32_t >();
pBattleTalkData->m_handlerId = dataJ.at( "handlerId" ).get< uint32_t >();
pBattleTalkData->m_kind = dataJ.at( "kind" ).get< uint32_t >();
pBattleTalkData->m_nameId = dataJ.at( "nameId" ).get< uint32_t >();
pBattleTalkData->m_talkerId = dataJ.at( "talkerId" ).get< uint32_t >();
m_pData = pBattleTalkData;
}
break;
//
// Directors
//
case TimepointDataType::DirectorVar:
{
auto& dataJ = json.at( "data" );
auto index = dataJ.at( "idx" ).get< uint32_t >();
auto val = dataJ.at( "val" ).get< uint32_t >();
auto opStr = dataJ.at( "opc" ).get< std::string >();
DirectorOpId op = directorOpMap.find( opStr )->second;
auto pDirectorData = std::make_shared< TimepointDataDirector >( tpType, op );
pDirectorData->m_data.index = index;
pDirectorData->m_data.value.val = val;
m_pData = pDirectorData;
}
break;
case TimepointDataType::DirectorVarLR:
{
auto& dataJ = json.at( "data" );
auto index = dataJ.at( "idx" ).get< uint32_t >();
auto left = dataJ.at( "left" ).get< uint32_t >();
auto right = dataJ.at( "right" ).get< uint32_t >();
auto opStr = dataJ.at( "opc" ).get< std::string >();
DirectorOpId op = directorOpMap.find( opStr )->second;
auto pDirectorData = std::make_shared< TimepointDataDirector >( tpType, op );
pDirectorData->m_data.index = index;
pDirectorData->m_data.value.left = left;
pDirectorData->m_data.value.right = right;
m_pData = pDirectorData;
}
break;
case TimepointDataType::DirectorSeq:
{
auto& dataJ = json.at( "data" );
auto seq = dataJ.at( "val" ).get< uint32_t >();
auto opStr = dataJ.at( "opc" ).get< std::string >();
DirectorOpId op = directorOpMap.find( opStr )->second;
auto pDirectorData = std::make_shared< TimepointDataDirector >( tpType, op );
pDirectorData->m_data.seq = seq;
m_pData = pDirectorData;
}
break;
case TimepointDataType::DirectorFlags:
{
auto& dataJ = json.at( "data" );
auto flags = dataJ.at( "val" ).get< uint32_t >();
auto opStr = dataJ.at( "opc" ).get< std::string >();
DirectorOpId op = directorOpMap.at( opStr );
auto pDirectorData = std::make_shared< TimepointDataDirector >( tpType, op );
pDirectorData->m_data.flags = flags;
m_pData = pDirectorData;
}
break;
//
// Status Effects
//
case TimepointDataType::AddStatusEffect:
case TimepointDataType::RemoveStatusEffect:
{
// todo: add/remove status effects
}
break;
case TimepointDataType::SpawnBNpc:
{
auto& dataJ = json.at( "data" );
auto hateSrcJ = dataJ.at( "hateSrc" );
auto actorRef = dataJ.at( "spawnActor" ).get< std::string >();
auto flags = dataJ.at( "flags" ).get< uint32_t >();
// todo: batallion
// auto battalion = dataJ.at( "batallion" ).get< uint32_t >();
auto bnpcType = bnpcTypeMap.at( dataJ.at( "type" ).get< std::string >() );
// todo: hateSrc
uint32_t layoutId = 0xE0000000;
if( auto it = actors.find( actorRef ); it != actors.end() )
layoutId = it->second.m_layoutId;
else
throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::Timepoint::from_json: SpawnBNpc invalid actor ref: %s" ), actorRef ) );
m_pData = std::make_shared< TimepointDataSpawnBNpc >( layoutId, flags, bnpcType );
}
break;
case TimepointDataType::SetBNpcFlags:
{
auto& dataJ = json.at( "data" );
auto actorRef = dataJ.at( "spawnActor" ).get< std::string >();
auto flags = dataJ.at( "flags" ).get< uint32_t >();
// todo: hateSrc
uint32_t layoutId = 0xE0000000;
if( auto it = actors.find( actorRef ); it != actors.end() )
layoutId = it->second.m_layoutId;
else
throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::Timepoint::from_json: SetBNpcFlags invalid actor ref: %s" ), actorRef ) );
m_pData = std::make_shared< TimepointDataBNpcFlags >( layoutId, flags );
// todo: SetBNpcFlags
}
break;
case TimepointDataType::SetEObjState:
{
auto& dataJ = json.at( "data" );
// todo: SetEObjState
}
break;
case TimepointDataType::SetCondition:
{
auto& dataJ = json.at( "data" );
auto conditionId = dataJ.at( "conditionId" ).get< uint32_t >();
auto enabled = dataJ.at( "enabled" ).get< bool >();
m_pData = std::make_shared< TimepointDataCondition >( conditionId, enabled );
}
break;
default:
break;
}
}
EncounterTimeline::TimelinePack EncounterTimeline::getEncounterPack( const std::string& name, bool reload )
TimelinePack EncounterTimeline::getEncounterPack( const std::string& name, bool reload )
{
static std::map< std::string, TimelinePack > cache = {};
const static std::unordered_map< std::string, ConditionType > conditionMap =
@ -837,6 +131,16 @@ namespace Sapphire
std::unordered_map< std::string, TimelineActor > actorNameMap;
std::unordered_map< std::string, std::map< std::string, Phase > > actorNamePhaseMap;
for( const auto& selectorJ : json.at( "selectors" ).items() )
{
auto selectorV = selectorJ.value();
auto name = selectorV.at( "name" );
Selector selector;
selector.from_json( selectorV );
pack.addSelector( name, selector );
}
// first run through cache actor info
for( const auto& actorJ : json.at( "actors" ).items() )
{
@ -976,47 +280,66 @@ namespace Sapphire
return pack;
}
Entity::BNpcPtr EncounterTimeline::TimelineActor::spawnSubActor( TerritoryPtr pTeri, const std::string& name )
void TimelinePack::setName( const std::string& name )
{
// todo: retail straight up respawns sub actors, even bnpc parts (qarn adjudicator body parts respawn each time with new ids)
auto flags = Entity::BNpcFlag::Invincible | Entity::BNpcFlag::Untargetable |
Entity::BNpcFlag::Immobile | Entity::BNpcFlag::AutoAttackDisabled |
Entity::BNpcFlag::TurningDisabled;
auto pActor = getSubActor( name );
if( pActor == nullptr )
{
auto pParent = pTeri->getActiveBNpcByLayoutId( m_layoutId );
pActor = pTeri->createBNpcFromLayoutId( m_layoutId, 1000, pParent->getBNpcType() );
m_subActors[ name ] = pActor;
}
pActor->setInvincibilityType( Common::InvincibilityIgnoreDamage );
pActor->setFlag( flags );
pActor->init();
pTeri->pushActor( pActor );
return pActor;
m_name = name;
}
Entity::BNpcPtr EncounterTimeline::TimelineActor::getSubActor( const std::string& name )
void TimelinePack::addSelector( const std::string& name, const Selector& selector )
{
if( auto it = m_subActors.find( name ); it != m_subActors.end() )
return it->second;
m_selectors.emplace( std::make_pair( name, selector ) );
}
void TimelinePack::createSnapshot( const std::string& selectorName, Entity::CharaPtr pSrc, const std::vector< uint32_t >& exclude )
{
if( auto it = m_selectors.find( selectorName ); it != m_selectors.end() )
it->second.createSnapshot( pSrc, exclude );
}
const World::AI::Snapshot::Results& TimelinePack::getSnapshotResults( const std::string& selectorName )
{
if( auto it = m_selectors.find( selectorName ); it != m_selectors.end() )
return it->second.getResults();
return {};
}
const World::AI::Snapshot::TargetIds& TimelinePack::getSnapshotTargetIds( const std::string& selectorName )
{
if( auto it = m_selectors.find( selectorName ); it != m_selectors.end() )
return it->second.getTargetIds();
return {};
}
void TimelinePack::addTimelineActor( const TimelineActor& actor )
{
m_actors.emplace_back( actor );
}
Entity::BNpcPtr TimelinePack::getBNpcByActorRef( const std::string& name, TerritoryPtr pTeri, const std::string& subActorName )
{
for( const auto& actor : m_actors )
{
if( actor.m_name == name )
{
return actor.getBNpcByRef( name, pTeri );
}
}
return nullptr;
}
void EncounterTimeline::TimelineActor::resetSubActors( TerritoryPtr pTeri )
void TimelinePack::setStartTime( uint64_t time )
{
for( auto& subActor : m_subActors )
{
if( subActor.second )
{
auto pBNpc = subActor.second;
pTeri->removeActor( pBNpc );
// todo: despawn?
subActor.second = nullptr;
}
}
m_startTime = time;
}
}// namespace Sapphire
uint64_t TimelinePack::getStartTime() const
{
return m_startTime;
}
void TimelinePack::update( TerritoryPtr pTeri, uint64_t time )
{
for( auto& actor : m_actors )
actor.update( pTeri, *this, time );
}
}// namespace Sapphire::Encounter

View file

@ -1,3 +1,5 @@
#pragma once
#include <fstream>
#include <memory>
@ -8,841 +10,71 @@
#include <unordered_map>
#include <vector>
#include <Common.h>
#include <Forwards.h>
#include <nlohmann/json.hpp>
namespace Sapphire
#include <AI/TargetHelper.h>
#include <Territory/Territory.h>
#include <Common.h>
#include <Forwards.h>
#include "Selector.h"
#include "Forwards.h"
namespace Sapphire::Encounter
{
enum class TimelinePackType : uint32_t
{
Solo,
EncounterFight
};
// todo: actually handle solo stuff properly (or tie to zone director/content director at least)
class TimelinePack
{
TimelinePackType m_type{ TimelinePackType::EncounterFight };
std::vector< TimelineActor > m_actors;
std::string m_name;
std::unordered_map< std::string, Selector > m_selectors;
uint64_t m_startTime{ 0 };
public:
TimelinePack() {}
TimelinePack( const TimelinePack& rhs ) :
m_type( rhs.m_type ),
m_name( rhs.m_name ),
m_actors( rhs.m_actors ),
m_startTime( 0 )
{
}
TimelinePack( TimelinePackType type ) : m_type( type ) {}
void setName( const std::string& name );
void addSelector( const std::string& name, const Selector& selector );
void createSnapshot( const std::string& selectorName, Entity::CharaPtr pSrc,
const std::vector< uint32_t >& exclude );
const World::AI::Snapshot::Results& getSnapshotResults( const std::string& selectorName );
const World::AI::Snapshot::TargetIds& getSnapshotTargetIds( const std::string& selectorName );
void addTimelineActor( const TimelineActor& actor );
Entity::BNpcPtr getBNpcByActorRef( const std::string& name, TerritoryPtr pTeri, const std::string& subActorName = {} );
void setStartTime( uint64_t time );
uint64_t getStartTime() const;
void update( TerritoryPtr pTeri, uint64_t time );
};
class EncounterTimeline
{
public:
// forwards
class TimelineActor;
class TimelinePack;
//
// State tracking objects (per actor)
//
// todo: move ConditionState/TimepointState to Chara::GambitState?
struct TimepointState
{
uint64_t m_startTime{ 0 };
uint64_t m_lastTick{ 0 };
bool m_finished{ false };
};
struct ConditionState
{
uint64_t m_startTime{ 0 };
bool m_loop{ false };
bool m_completed{ false };
bool m_enabled{ false };
struct
{
uint64_t m_startTime{ 0 };
uint64_t m_lastTimepointTime{ 0 };
uint32_t m_lastTimepointIndex{ 0 };
std::vector< TimepointState > m_timepointStates;
} m_phaseInfo;
};
//
// Enums
//
// EncounterFight::OnTick() { switch EncounterTimepointcondition }
enum class ConditionType : uint32_t
{
HpPctLessThan,
HpPctBetween,
DirectorVarEquals,
DirectorVarGreaterThan,
DirectorSeqEquals,
DirectorSeqGreaterThan,
DirectorFlagsEquals,
DirectorFlagsGreaterThan,
EncounterTimeElapsed,
CombatState,
BNpcHasFlags
};
enum class DirectorOpId
{
Set, // idx = val
Add, // idx += val
Sub, // idx -= val
Mul, // idx *= val
Div, // idx /= val
Mod, // idx %= val
Sll, // idx << val
Srl, // idx >> val
Or, // idx |= val
Xor, // idx ^= val
Nor, // idx ~= val
And // idx &= val
};
// TODO: what should this do?
enum class TimepointOverrideFlags : uint32_t
{
None,
Invulnerable
};
enum class TimepointDataType : uint32_t
{
Idle,
CastAction,
MoveTo,
LogMessage,
BattleTalk,
DirectorVar,
DirectorVarLR,
DirectorSeq,
DirectorFlags,
AddStatusEffect,
RemoveStatusEffect,
SpawnBNpc,
SetBNpcFlags,
SetEObjState,
SetBgm,
SetCondition,
Snapshot
};
enum class TimepointCallbackType : uint32_t
{
OnActionInit,
OnActionStart,
OnActionInterrupt,
OnActionExecute
};
enum class TargetSelectFilterFlags : uint32_t
{
Self = 0x00000000,
Player = 0x00000001,
EnemyBattalion = 0x00000002,
OwnBattalion = 0x00000004,
Tank = 0x00000010,
Healer = 0x00000020,
Dps = 0x00000040,
Melee = 0x00000080,
Ranged = 0x00000100,
Closest = 0x00000200,
Furthest = 0x00000400,
Aggro1 = 0x10000000,
Aggro2 = 0x20000000,
};
enum class MoveType : uint32_t
{
WalkPath,
Teleport
};
enum class TimelineActorType : uint32_t
{
LayerSetObject,
BattleNpc
};
enum class CombatStateType
{
Idle,
Combat,
Retreat,
Roaming,
JustDied,
Dead
};
enum class TimelinePackType : uint32_t
{
Solo,
EncounterFight
};
struct TargetSelectFilter
{
TargetSelectFilterFlags m_flags;
};
//
// Timepoint.m_pData objects
//
using TimepointCallbackFunc = std::function< void( TerritoryPtr, uint64_t ) >;
// Timepoint Data Objects
struct TimepointCallbackData :
public std::enable_shared_from_this< TimepointCallbackData >
{
TimepointCallbackType m_type;
std::vector < TimepointCallbackFunc > m_callbacks;
};
using TimebackCallbackDataPtr = std::shared_ptr< TimepointCallbackData >;
using TimepointCallbacks = std::unordered_map< TimepointCallbackType, TimebackCallbackDataPtr >;
struct TimepointData :
public std::enable_shared_from_this< TimepointData >
{
TimepointData( TimepointDataType type ) : m_type( type ) {}
virtual ~TimepointData(){};
TimepointDataType m_type{ 0 };
};
using TimepointDataPtr = std::shared_ptr< TimepointData >;
struct TimepointDataIdle : public TimepointData
{
uint32_t m_layoutId{ 0xE0000000 };
uint64_t m_durationMs;
TimepointDataIdle( uint32_t layoutId, uint64_t durationMs ) :
TimepointData( TimepointDataType::Idle ),
m_layoutId( layoutId ),
m_durationMs( durationMs )
{
}
};
struct TimepointDataAddStatusEffect : public TimepointData
{
uint32_t m_statusEffectId;
TargetSelectFilter m_targetFilter;
uint32_t m_durationMs;
TimepointDataAddStatusEffect( uint32_t statusId, TargetSelectFilter targFilter, uint32_t durationMs ) :
TimepointData( TimepointDataType::AddStatusEffect ),
m_statusEffectId( statusId ),
m_targetFilter( targFilter ),
m_durationMs( durationMs )
{
}
};
struct TimepointDataRemoveStatusEffect : public TimepointData
{
uint32_t m_statusEffectId;
TargetSelectFilter m_targetFilter;
TimepointDataRemoveStatusEffect( uint32_t statusId, TargetSelectFilter targFilter ) :
TimepointData( TimepointDataType::RemoveStatusEffect ),
m_statusEffectId( statusId ),
m_targetFilter( targFilter )
{
}
};
struct TimepointDataAction : public TimepointData
{
uint32_t m_layoutId{ 0xE0000000 };
uint32_t m_actionId;
//TimepointCallbacks m_callbacks;
TimepointDataAction( uint32_t layoutId, uint32_t actionId ) :
TimepointData( TimepointDataType::CastAction ),
m_layoutId( layoutId ),
m_actionId( actionId )
//m_callbacks( callbacks )
{
}
};
struct TimepointDataMoveTo : public TimepointData
{
uint32_t m_layoutId{ 0xE0000000 };
MoveType m_moveType;
float m_x, m_y, m_z, m_rot;
TimepointDataMoveTo( uint32_t layoutId, MoveType moveType, float x, float y, float z, float rot ) :
TimepointData( TimepointDataType::MoveTo ),
m_layoutId( layoutId ),
m_moveType( moveType ),
m_x( x ), m_y( y ), m_z( z ), m_rot( rot )
{
}
};
struct TimepointDataLogMessage : public TimepointData
{
uint32_t m_messageId;
uint32_t m_params[ 5 ]{ 0 };
TimepointDataLogMessage( uint32_t messageId, const std::vector< uint32_t >& params ) :
TimepointData( TimepointDataType::LogMessage ),
m_messageId( messageId )
{
for( auto i = 0; i < params.size() && i < 5; ++i )
m_params[i] = params[i];
}
};
struct TimepointDataBattleTalk : public TimepointData
{
uint32_t m_battleTalkId;
uint32_t m_handlerId;
uint32_t m_kind;
uint32_t m_nameId;
uint32_t m_talkerId;
uint32_t m_params[ 8 ]{ 0 };
TimepointDataBattleTalk( const std::vector< uint32_t >& params ) :
TimepointData( TimepointDataType::BattleTalk )
{
for( auto i = 0; i < params.size() && i < 8; ++i )
m_params[i] = params[i];
}
};
struct TimepointDataDirector : public TimepointData
{
DirectorOpId m_directorOp{ 0 };
union
{
struct
{
uint8_t index;
union
{
uint8_t val;
struct
{
uint8_t left, right;
};
} value;
};
uint8_t seq;
uint8_t flags;
} m_data{ 0 };
TimepointDataDirector( TimepointDataType type, DirectorOpId op ) :
TimepointData( type ),
m_directorOp( op )
{
}
};
struct TimepointDataSpawnBNpc : public TimepointData
{
uint32_t m_layoutId{ 0xE0000000 };
uint32_t m_flags{ 0 };
uint32_t m_type{ 0 };
// todo: hate type, source
TimepointDataSpawnBNpc( uint32_t layoutId, uint32_t flags, uint32_t type ) :
TimepointData( TimepointDataType::SpawnBNpc ),
m_layoutId( layoutId ),
m_flags( flags ),
m_type( type )
{
}
};
struct TimepointDataBNpcFlags : public TimepointData
{
uint32_t m_layoutId{ 0xE0000000 };
uint32_t m_flags{ 0 };
TimepointDataBNpcFlags( uint32_t layoutId, uint32_t flags ) :
TimepointData( TimepointDataType::SetBNpcFlags ),
m_layoutId( layoutId ),
m_flags( flags )
{
}
};
struct TimepointDataEObjState : public TimepointData
{
uint32_t m_eobjId{ 0xE0000000 };
uint32_t m_state{ 0 };
TimepointDataEObjState( uint32_t eobjId, uint32_t state ) :
TimepointData( TimepointDataType::SetEObjState ),
m_eobjId( eobjId ),
m_state( state )
{
}
};
struct TimepointDataBGM : public TimepointData
{
uint32_t m_bgmId{ 0 };
TimepointDataBGM( uint32_t bgmId ):
TimepointData( TimepointDataType::SetBgm ),
m_bgmId( bgmId )
{
}
};
struct TimepointDataCondition : public TimepointData
{
// todo: rng?
uint32_t m_conditionId;
bool m_enabled;
TimepointDataCondition( uint32_t conditionId, bool enabled ) :
TimepointData( TimepointDataType::SetCondition ),
m_conditionId( conditionId ),
m_enabled( enabled )
{
}
};
// todo: refactor all this to allow solo actor to use
class Timepoint :
public std::enable_shared_from_this< Timepoint >
{
public:
TimepointDataType m_type;
uint64_t m_duration{ 0 }; // milliseconds
TimepointOverrideFlags m_overrideFlags;
TimepointDataPtr m_pData;
std::string m_description;
// todo: repeatable?
const TimepointDataPtr getData() const
{
return m_pData;
}
bool canExecute( const TimepointState& state, uint64_t elapsed ) const
{
return state.m_startTime == 0; // & &m_duration <= elapsed;
}
bool durationElapsed( uint64_t elapsed ) const
{
return m_duration < elapsed;
}
bool finished( const TimepointState& state, uint64_t elapsed ) const
{
return durationElapsed( elapsed ) || state.m_finished;
}
void reset( TimepointState& state ) const
{
state.m_startTime = 0;
state.m_lastTick = 0;
state.m_finished = false;
}
void from_json( const nlohmann::json& json, const std::unordered_map< std::string, TimelineActor >& actors, uint32_t selfLayoutId );
// todo: separate execute/update into onStart and onTick?
void update( TimepointState& state, TimelineActor& self, TerritoryPtr pTeri, uint64_t time ) const;
void execute( TimepointState& state, TimelineActor& self, TerritoryPtr pTeri, uint64_t time ) const;
};
class Phase :
public std::enable_shared_from_this< Phase >
{
public:
// todo: allow callbacks to push timepoints
std::string m_name;
std::vector< Timepoint > m_timepoints;
// todo: i wrote this very sleep deprived, ensure it is actually sane
void execute( ConditionState& state, TimelineActor& self, TerritoryPtr pTeri, uint64_t time ) const
{
if( state.m_startTime == 0 )
state.m_startTime = time;
if( state.m_phaseInfo.m_startTime == 0 )
state.m_phaseInfo.m_startTime = time;
if( state.m_phaseInfo.m_lastTimepointTime == 0 )
{
state.m_phaseInfo.m_lastTimepointTime = time;
state.m_phaseInfo.m_timepointStates.clear();
state.m_phaseInfo.m_timepointStates.resize( m_timepoints.size() );
}
for( auto i = state.m_phaseInfo.m_lastTimepointIndex; i < m_timepoints.size(); )
{
uint64_t phaseElapsed = time - state.m_phaseInfo.m_startTime;
uint64_t timepointElapsed = time - state.m_phaseInfo.m_lastTimepointTime;
auto& tpState = state.m_phaseInfo.m_timepointStates[ i ];
auto& timepoint = m_timepoints[ i ];
if( timepoint.canExecute( tpState, timepointElapsed ) )
{
timepoint.execute( tpState, self, pTeri, time );
state.m_phaseInfo.m_lastTimepointTime = time;
}
else if( !timepoint.finished( tpState, timepointElapsed ) )
{
timepoint.update( tpState, self, pTeri, time );
}
if( timepoint.durationElapsed( timepointElapsed ) && timepoint.finished( tpState, timepointElapsed ) )
{
// timepoint.reset( tpState );
// make sure this timepoint isnt run again unless phase loops
++i;
state.m_phaseInfo.m_lastTimepointIndex = i;
if( i == m_timepoints.size() )
{
state.m_phaseInfo.m_lastTimepointIndex++;
}
continue;
}
break;
}
}
void reset( ConditionState& state ) const
{
state.m_phaseInfo.m_startTime = 0;
state.m_phaseInfo.m_lastTimepointIndex = 0;
state.m_phaseInfo.m_lastTimepointTime = 0;
}
bool completed( const ConditionState& state ) const
{
return state.m_phaseInfo.m_lastTimepointIndex > m_timepoints.size();
}
};
using PhasePtr = std::shared_ptr< Phase >;
class PhaseCondition :
public std::enable_shared_from_this< PhaseCondition >
{
protected:
ConditionType m_conditionType{ 0 };
Phase m_phase;
std::string m_description;
uint32_t m_cooldown{ 0 };
bool m_loop{ false };
bool m_enabled{ true };
uint32_t m_id{ 0 };
public:
PhaseCondition() {}
~PhaseCondition() {}
virtual void from_json( nlohmann::json& json, Phase& phase, ConditionType condition )
{
this->m_conditionType = condition;
this->m_loop = json.at( "loop" ).get< bool >();
//this->m_cooldown = json.at( "cooldown" ).get< uint32_t >();
this->m_phase = phase;
this->m_description = json.at( "description" ).get< std::string >();
this->m_enabled = json.at( "enabled" ).get< bool >();
this->m_id = json.at( "id" ).get< uint32_t >();
}
void execute( ConditionState& state, TimelineActor& self, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
state.m_startTime = time;
m_phase.execute( state, self, pTeri, time );
};
void update( ConditionState& state, TimelineActor& self, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
m_phase.execute( state, self, pTeri, time );
}
void setEnabled( ConditionState& state, bool enabled )
{
state.m_enabled = enabled;
}
void reset( ConditionState& state ) const
{
state.m_startTime = 0;
state.m_enabled = isDefaultEnabled();
m_phase.reset( state );
}
bool inProgress( const ConditionState& state ) const
{
return state.m_startTime != 0;
}
// todo: better naming
bool isStateEnabled( const ConditionState& state ) const
{
return state.m_enabled;
}
bool isDefaultEnabled() const
{
return m_enabled;
}
bool completed( const ConditionState& state ) const
{
return m_phase.completed( state );
}
bool isLoopable() const
{
return m_loop;
}
bool loopReady( ConditionState& state, uint64_t time ) const
{
return m_phase.completed( state ) && m_loop && ( state.m_startTime + m_cooldown <= time );
}
virtual bool isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
return false;
};
uint32_t getId() const
{
return m_id;
}
};
using PhaseConditionPtr = std::shared_ptr< PhaseCondition >;
// todo: bnpc parts
class TimelineBNpcPart
{
uint32_t m_hp{ 0 };
std::string m_name;
};
class TimelineActor
{
protected:
std::unordered_map< uint32_t, PhaseConditionPtr > m_phaseConditions;
std::unordered_map< uint32_t, ConditionState > m_conditionStates;
// PARENTNAME_SUBACTOR_1, ..., PARENTNAME_SUBACTOR_69
std::unordered_map< std::string, Entity::BNpcPtr > m_subActors;
public:
uint32_t m_layoutId{ 0 };
uint32_t m_hp{ 0 };
std::string m_name;
TimelineActor() { }
TimelineActor( const TimelineActor& rhs ) :
m_layoutId( rhs.m_layoutId ),
m_hp( rhs.m_hp ),
m_name( rhs.m_name ),
m_phaseConditions( rhs.m_phaseConditions ),
m_conditionStates( rhs.m_conditionStates )
{
}
void addPhaseCondition( PhaseConditionPtr pCondition )
{
m_phaseConditions.emplace( std::make_pair( pCondition->getId(), pCondition ) );
m_conditionStates[ pCondition->getId() ] = {};
m_conditionStates[ pCondition->getId() ].m_enabled = pCondition->isDefaultEnabled();
}
// todo: make this sane
void update( TerritoryPtr pTeri, TimelinePack& pack, uint64_t time )
{
// todo: handle interrupts
for( const auto& condition : m_phaseConditions)
{
const auto& pCondition = condition.second;
auto& state = m_conditionStates[ pCondition->getId() ];
// ignore if not enabled, unless overriden to enable
if( !pCondition->isStateEnabled( state ) )
continue;
if( pCondition->completed( state ) )
{
if( pCondition->isLoopable() )
{
if( pCondition->loopReady( state, time ) )
pCondition->reset( state );
}
}
else if( pCondition->inProgress( state ) )
{
pCondition->update( state, *this, pTeri, pack, time );
}
else if( pCondition->isConditionMet( state, pTeri, pack, time ) )
{
pCondition->execute( state, *this, pTeri, pack, time );
if( pack.getStartTime() == 0 )
pack.setStartTime( state.m_startTime );
}
}
}
void resetConditionState( uint32_t conditionId )
{
if( auto it = m_phaseConditions.find( conditionId ); it != m_phaseConditions.end() )
{
auto& state = m_conditionStates.at( it->first );
it->second->reset( state );
}
}
void setConditionStateEnabled( uint32_t conditionId, bool enabled )
{
if( auto it = m_conditionStates.find( conditionId ); it != m_conditionStates.end() )
{
auto& state = m_conditionStates.at( it->first );
state.m_enabled = enabled;
}
}
void resetAllConditionStates()
{
for( const auto& condition : m_phaseConditions )
{
const auto& pCondition = condition.second;
auto& state = m_conditionStates.at( condition.first );
pCondition->reset( state );
}
}
Entity::BNpcPtr spawnSubActor( TerritoryPtr pTeri, const std::string& name );
Entity::BNpcPtr getSubActor( const std::string& name );
void resetSubActors( TerritoryPtr pTeri );
};
// todo: actually handle solo stuff properly (or tie to zone director/content director at least)
class TimelinePack
{
TimelinePackType m_type{TimelinePackType::EncounterFight};
std::vector< TimelineActor > m_actors;
std::string m_name;
uint64_t m_startTime{ 0 };
public:
TimelinePack() { }
TimelinePack( const TimelinePack& rhs ) :
m_type( rhs.m_type ),
m_name( rhs.m_name ),
m_actors( rhs.m_actors ),
m_startTime( 0 )
{
}
TimelinePack( TimelinePackType type ) : m_type( type ) {}
void setName( const std::string& name )
{
m_name = name;
}
void addTimelineActor( const TimelineActor& actor )
{
m_actors.emplace_back( actor );
}
void setStartTime( uint64_t time )
{
m_startTime = time;
}
uint64_t getStartTime() const
{
return m_startTime;
}
void update( TerritoryPtr pTeri, uint64_t time )
{
for( auto& actor : m_actors )
actor.update( pTeri, *this, time );
}
};
//
// Conditions
//
class ConditionHp : PhaseCondition
{
public:
uint32_t layoutId;
union
{
uint8_t val;
struct
{
uint8_t min, max;
};
} hp;
void from_json( nlohmann::json& json, Phase& phase, ConditionType condition,
const std::unordered_map< std::string, TimelineActor >& actors );
bool isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const override;
};
class ConditionDirectorVar : PhaseCondition
{
public:
union
{
struct
{
uint32_t index;
uint32_t value;
};
uint8_t seq;
uint8_t flags;
} param;
void from_json( nlohmann::json& json, Phase& phase, ConditionType condition );
bool isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const override;
};
class ConditionEncounterTimeElapsed : PhaseCondition
{
public:
uint64_t duration;
void from_json( nlohmann::json& json, Phase& phase, ConditionType condition );
bool isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const override;
};
class ConditionCombatState : PhaseCondition
{
public:
uint32_t layoutId;
CombatStateType combatState;
void from_json( nlohmann::json& json, Phase& phase, ConditionType condition, const std::unordered_map< std::string, TimelineActor >& actors );
bool isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const override;
};
class ConditionBNpcFlags : PhaseCondition
{
public:
uint32_t layoutId;
uint32_t flags;
void from_json( nlohmann::json& json, Phase& phase, ConditionType condition, const std::unordered_map< std::string, TimelineActor >& actors );
bool isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const override;
};
public:
TimelinePack getEncounterPack( const std::string& name, bool reload = false );

View file

@ -0,0 +1,16 @@
#pragma once
#include <memory>
namespace Sapphire::Encounter
{
class Selector;
class TimelineActor;
class Phase;
class PhaseCondition;
class Timepoint;
class TimelinePack;
using PhaseConditionPtr = std::shared_ptr< PhaseCondition >;
}

View file

@ -0,0 +1,271 @@
#include "PhaseCondition.h"
#include "EncounterTimeline.h"
#include "TimelineActor.h"
#include "TimelineActorState.h"
#include <Actor/Chara.h>
#include <Actor/BNpc.h>
#include <Actor/Player.h>
#include <Territory/Territory.h>
#include <Territory/InstanceContent.h>
#include <Territory/QuestBattle.h>
namespace Sapphire::Encounter
{
bool ConditionHp::isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
auto pBNpc = pTeri->getActiveBNpcByLayoutId( layoutId );
if( !pBNpc )
return false;
// todo: check time elapsed
switch( m_conditionType )
{
case ConditionType::HpPctLessThan:
return pBNpc->getHpPercent() < hp.val;
case ConditionType::HpPctBetween:
{
auto hpPct = pBNpc->getHpPercent();
return hpPct >= hp.min && hpPct <= hp.max;
}
}
return false;
};
bool ConditionDirectorVar::isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
Event::DirectorPtr pDirector = pTeri->getAsInstanceContent();
if( pDirector == nullptr )
pDirector = pTeri->getAsQuestBattle();
switch( m_conditionType )
{
case ConditionType::DirectorVarEquals:
return pDirector->getDirectorVar( param.index ) == param.value;
case ConditionType::DirectorVarGreaterThan:
return pDirector->getDirectorVar( param.index ) > param.value;
case ConditionType::DirectorFlagsEquals:
return pDirector->getFlags() == param.flags;
case ConditionType::DirectorFlagsGreaterThan:
return pDirector->getFlags() > param.flags;
case ConditionType::DirectorSeqEquals:
return pDirector->getSequence() == param.seq;
case ConditionType::DirectorSeqGreaterThan:
return pDirector->getSequence() > param.seq;
}
return false;
}
bool ConditionCombatState::isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
auto pBattleNpc = pTeri->getActiveBNpcByLayoutId( this->layoutId );
// todo: these should really use callbacks when the state transitions or we could miss this tick
switch( combatState )
{
case CombatStateType::Idle:
return pBattleNpc->getState() == Entity::BNpcState::Idle;
case CombatStateType::Combat:
return pBattleNpc->getState() == Entity::BNpcState::Combat;
case CombatStateType::Retreat:
return pBattleNpc->getState() == Entity::BNpcState::Retreat;
case CombatStateType::Roaming:
return pBattleNpc->getState() == Entity::BNpcState::Roaming;
case CombatStateType::JustDied:
return pBattleNpc->getState() == Entity::BNpcState::JustDied;
case CombatStateType::Dead:
return pBattleNpc->getState() == Entity::BNpcState::Dead;
default:
break;
}
return false;
}
bool ConditionEncounterTimeElapsed::isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
auto elapsed = time - pack.getStartTime();
// todo: check encounter time
return elapsed >= this->duration;
}
bool ConditionBNpcFlags::isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
auto pBNpc = pTeri->getActiveBNpcByLayoutId( this->layoutId );
return pBNpc && pBNpc->hasFlag( this->flags );
}
void ConditionHp::from_json( nlohmann::json& json, Phase& phase, ConditionType condition,
const std::unordered_map< std::string, TimelineActor >& actors )
{
PhaseCondition::from_json( json, phase, condition );
auto& paramData = json.at( "paramData" );
auto actorRef = paramData.at( "sourceActor" ).get< std::string >();
// resolve the actor whose hp we are checking
if( auto it = actors.find( actorRef ); it != actors.end() )
this->layoutId = it->second.m_layoutId;
else
throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::ConditionHp::from_json unable to find actor by name: %s" ), actorRef ) );
switch( condition )
{
case ConditionType::HpPctLessThan:
this->hp.val = paramData.at( "hp" ).get< uint32_t >();
break;
case ConditionType::HpPctBetween:
this->hp.min = paramData.at( "hpMin" ).get< uint32_t >(),
this->hp.max = paramData.at( "hpMax" ).get< uint32_t >();
break;
default:
break;
}
}
void ConditionDirectorVar::from_json( nlohmann::json& json, Phase& phase, ConditionType condition )
{
PhaseCondition::from_json( json, phase, condition );
auto& paramData = json.at( "paramData" );
switch( condition )
{
case ConditionType::DirectorVarEquals:
case ConditionType::DirectorVarGreaterThan:
{
param.index = paramData.at( "idx" ).get< uint32_t >();
param.value = paramData.at( "val" ).get< uint32_t >();
}
break;
case ConditionType::DirectorFlagsEquals:
case ConditionType::DirectorFlagsGreaterThan:
{
param.flags = paramData.at( "flags" ).get< uint32_t >();
}
break;
case ConditionType::DirectorSeqEquals:
case ConditionType::DirectorSeqGreaterThan:
{
param.seq = paramData.at( "seq" ).get< uint32_t >();
}
break;
default:
break;
}
}
void ConditionCombatState::from_json( nlohmann::json& json, Phase& phase, ConditionType condition,
const std::unordered_map< std::string, TimelineActor >& actors )
{
PhaseCondition::from_json( json, phase, condition );
auto& paramData = json.at( "paramData" );
auto actorRef = paramData.at( "sourceActor" ).get< std::string >();
// resolve the actor whose name we are checking
if( auto it = actors.find( actorRef ); it != actors.end() )
this->layoutId = it->second.m_layoutId;
else
throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::ConditionCombatState::from_json unable to find actor by name: %s" ), actorRef ) );
this->combatState = paramData.at( "combatState" ).get< CombatStateType >();
}
void ConditionEncounterTimeElapsed::from_json( nlohmann::json& json, Phase& phase, ConditionType condition )
{
PhaseCondition::from_json( json, phase, condition );
auto& paramData = json.at( "paramData" );
auto duration = paramData.at( "duration" ).get< uint64_t >();
this->duration = duration;
}
void ConditionBNpcFlags::from_json( nlohmann::json& json, Phase& phase, ConditionType condition,
const std::unordered_map< std::string, TimelineActor >& actors )
{
PhaseCondition::from_json( json, phase, condition );
auto& paramData = json.at( "paramData" );
auto actorRef = paramData.at( "sourceActor" ).get< std::string >();
// resolve the actor whose name we are checking
if( auto it = actors.find( actorRef ); it != actors.end() )
this->layoutId = it->second.m_layoutId;
else
throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::ConditionBNpcFlags::from_json unable to find actor by name: %s" ), actorRef ) );
this->flags = json.at( "flags" ).get< uint32_t >();
// todo: BNpcHasFlags
}
// todo: i wrote this very sleep deprived, ensure it is actually sane
void Phase::execute( ConditionState& state, TimelineActor& self, TerritoryPtr pTeri, uint64_t time ) const
{
if( state.m_startTime == 0 )
{
state.m_startTime = time;
self.spawnAllSubActors( pTeri );
}
if( state.m_phaseInfo.m_startTime == 0 )
state.m_phaseInfo.m_startTime = time;
if( state.m_phaseInfo.m_lastTimepointTime == 0 )
{
state.m_phaseInfo.m_lastTimepointTime = time;
state.m_phaseInfo.m_timepointStates.clear();
state.m_phaseInfo.m_timepointStates.resize( m_timepoints.size() );
}
for( auto i = state.m_phaseInfo.m_lastTimepointIndex; i < m_timepoints.size(); )
{
uint64_t phaseElapsed = time - state.m_phaseInfo.m_startTime;
uint64_t timepointElapsed = time - state.m_phaseInfo.m_lastTimepointTime;
auto& tpState = state.m_phaseInfo.m_timepointStates[ i ];
auto& timepoint = m_timepoints[ i ];
if( timepoint.canExecute( tpState, timepointElapsed ) )
{
timepoint.execute( tpState, self, pTeri, time );
state.m_phaseInfo.m_lastTimepointTime = time;
}
else if( !timepoint.finished( tpState, timepointElapsed ) )
{
timepoint.update( tpState, self, pTeri, time );
}
if( timepoint.durationElapsed( timepointElapsed ) && timepoint.finished( tpState, timepointElapsed ) )
{
// timepoint.reset( tpState );
// make sure this timepoint isnt run again unless phase loops
++i;
state.m_phaseInfo.m_lastTimepointIndex = i;
if( i == m_timepoints.size() )
{
state.m_phaseInfo.m_lastTimepointIndex++;
}
continue;
}
break;
}
}
void Phase::reset( ConditionState& state ) const
{
state.m_phaseInfo.m_startTime = 0;
state.m_phaseInfo.m_lastTimepointIndex = 0;
state.m_phaseInfo.m_lastTimepointTime = 0;
}
bool Phase::completed( const ConditionState& state ) const
{
return state.m_phaseInfo.m_lastTimepointIndex > m_timepoints.size();
}
}// namespace Sapphire::Encounter

View file

@ -0,0 +1,217 @@
#pragma once
#include <cstdint>
#include "Timepoint.h"
#include "TimelineActorState.h"
namespace Sapphire::Encounter
{
enum class CombatStateType
{
Idle,
Combat,
Retreat,
Roaming,
JustDied,
Dead
};
enum class ConditionType : uint32_t
{
HpPctLessThan,
HpPctBetween,
DirectorVarEquals,
DirectorVarGreaterThan,
DirectorSeqEquals,
DirectorSeqGreaterThan,
DirectorFlagsEquals,
DirectorFlagsGreaterThan,
EncounterTimeElapsed,
CombatState,
BNpcHasFlags
};
class Phase :
public std::enable_shared_from_this< Phase >
{
public:
// todo: allow callbacks to push timepoints
std::string m_name;
std::vector< Timepoint > m_timepoints;
// todo: i wrote this very sleep deprived, ensure it is actually sane
void execute( ConditionState& state, TimelineActor& self, TerritoryPtr pTeri, uint64_t time ) const;
void reset( ConditionState& state ) const;
bool completed( const ConditionState& state ) const;
};
using PhasePtr = std::shared_ptr< Phase >;
class PhaseCondition : public std::enable_shared_from_this< PhaseCondition >
{
protected:
ConditionType m_conditionType{ 0 };
Phase m_phase;
std::string m_description;
uint32_t m_cooldown{ 0 };
bool m_loop{ false };
bool m_enabled{ true };
uint32_t m_id{ 0 };
public:
PhaseCondition() {}
~PhaseCondition() {}
virtual void from_json( nlohmann::json& json, Phase& phase, ConditionType condition )
{
this->m_conditionType = condition;
this->m_loop = json.at( "loop" ).get< bool >();
//this->m_cooldown = json.at( "cooldown" ).get< uint32_t >();
this->m_phase = phase;
this->m_description = json.at( "description" ).get< std::string >();
this->m_enabled = json.at( "enabled" ).get< bool >();
this->m_id = json.at( "id" ).get< uint32_t >();
}
void execute( ConditionState& state, TimelineActor& self, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
state.m_startTime = time;
m_phase.execute( state, self, pTeri, time );
};
void update( ConditionState& state, TimelineActor& self, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
m_phase.execute( state, self, pTeri, time );
}
void setEnabled( ConditionState& state, bool enabled )
{
state.m_enabled = enabled;
}
void reset( ConditionState& state ) const
{
state.m_startTime = 0;
state.m_enabled = isDefaultEnabled();
m_phase.reset( state );
}
bool inProgress( const ConditionState& state ) const
{
return state.m_startTime != 0;
}
// todo: better naming
bool isStateEnabled( const ConditionState& state ) const
{
return state.m_enabled;
}
bool isDefaultEnabled() const
{
return m_enabled;
}
bool completed( const ConditionState& state ) const
{
return m_phase.completed( state );
}
bool isLoopable() const
{
return m_loop;
}
bool loopReady( ConditionState& state, uint64_t time ) const
{
return m_phase.completed( state ) && m_loop && ( state.m_startTime + m_cooldown <= time );
}
virtual bool isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const
{
return false;
};
uint32_t getId() const
{
return m_id;
}
};
using PhaseConditionPtr = std::shared_ptr< PhaseCondition >;
//
// Conditions
//
class ConditionHp : PhaseCondition
{
public:
uint32_t layoutId;
union
{
uint8_t val;
struct
{
uint8_t min, max;
};
} hp;
void from_json( nlohmann::json& json, Phase& phase, ConditionType condition,
const std::unordered_map< std::string, TimelineActor >& actors );
bool isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const override;
};
class ConditionDirectorVar : PhaseCondition
{
public:
union
{
struct
{
uint32_t index;
uint32_t value;
};
uint8_t seq;
uint8_t flags;
} param;
void from_json( nlohmann::json& json, Phase& phase, ConditionType condition );
bool isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const override;
};
class ConditionEncounterTimeElapsed : PhaseCondition
{
public:
uint64_t duration;
void from_json( nlohmann::json& json, Phase& phase, ConditionType condition );
bool isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const override;
};
class ConditionCombatState : PhaseCondition
{
public:
uint32_t layoutId;
CombatStateType combatState;
void from_json( nlohmann::json& json, Phase& phase, ConditionType condition, const std::unordered_map< std::string, TimelineActor >& actors );
bool isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const override;
};
class ConditionBNpcFlags : PhaseCondition
{
public:
uint32_t layoutId;
uint32_t flags;
void from_json( nlohmann::json& json, Phase& phase, ConditionType condition, const std::unordered_map< std::string, TimelineActor >& actors );
bool isConditionMet( ConditionState& state, TerritoryPtr pTeri, TimelinePack& pack, uint64_t time ) const override;
};
}// namespace Sapphire::Encounter

View file

@ -0,0 +1,137 @@
#include "Selector.h"
#include <Actor/Chara.h>
namespace Sapphire::Encounter
{
void Selector::from_json( const nlohmann::json& json )
{
const static std::unordered_map< std::string, World::AI::TargetSelectFilter::Type > filterMap =
{
{ "insideRadius", World::AI::TargetSelectFilter::Type::InsideRadius },
{ "outsideRadius", World::AI::TargetSelectFilter::Type::OutsideRadius },
{ "player", World::AI::TargetSelectFilter::Type::Player },
{ "ally", World::AI::TargetSelectFilter::Type::Ally },
{ "ownBattalion", World::AI::TargetSelectFilter::Type::OwnBattalion },
{ "tank", World::AI::TargetSelectFilter::Type::Tank },
{ "healer", World::AI::TargetSelectFilter::Type::Healer },
{ "dps", World::AI::TargetSelectFilter::Type::Dps },
{ "hasStatusEffect", World::AI::TargetSelectFilter::Type::HasStatusEffect },
{ "topAggro", World::AI::TargetSelectFilter::Type::TopAggro },
{ "secondAggro", World::AI::TargetSelectFilter::Type::SecondAggro },
{ "partyMember", World::AI::TargetSelectFilter::Type::PartyMember }
};
m_name = json.at( "name" ).get< std::string >();
m_fillWithRandom = json.at( "fillRandomEntries" ).get< bool >();
m_count = json.at( "count" ).get< uint32_t >();
std::vector< World::AI::TargetSelectFilterPtr > filters;
auto filtersJ = json.at( "filters" ).items();
for( const auto& filterJ : filtersJ )
{
auto filterV = filterJ.value();
auto name = filterV.at( "type" ).get< std::string >();
auto typeId = filterMap.find( name )->second;
auto negate = filterV.at( "negate" ).get< bool >();
World::AI::TargetSelectFilterPtr pFilter = nullptr;
switch( typeId )
{
case World::AI::TargetSelectFilter::Type::InsideRadius:
{
auto radius = filterV.at( "param" ).get< uint32_t >();
pFilter = std::make_shared< World::AI::InsideRadiusFilter >( static_cast< float >( radius ), negate );
}
break;
case World::AI::TargetSelectFilter::Type::OutsideRadius:
{
auto radius = filterV.at( "param" ).get< uint32_t >();
pFilter = std::make_shared< World::AI::OutsideRadiusFilter >( static_cast< float >( radius ), negate );
}
break;
case World::AI::TargetSelectFilter::Type::Player:
{
pFilter = std::make_shared< World::AI::PlayerFilter >( negate );
}
break;
case World::AI::TargetSelectFilter::Type::Ally:
{
pFilter = std::make_shared< World::AI::AllyFilter >( negate );
}
break;
case World::AI::TargetSelectFilter::Type::OwnBattalion:
{
pFilter = std::make_shared< World::AI::OwnBattalionFilter >( negate );
}
break;
case World::AI::TargetSelectFilter::Type::Tank:
{
pFilter = std::make_shared< World::AI::TankFilter >( negate );
}
break;
case World::AI::TargetSelectFilter::Type::Healer:
{
pFilter = std::make_shared< World::AI::HealerFilter >( negate );
}
break;
case World::AI::TargetSelectFilter::Type::Dps:
{
pFilter = std::make_shared< World::AI::DpsFilter >( negate );
}
break;
case World::AI::TargetSelectFilter::Type::HasStatusEffect:
{
auto statusId = filterV.at( "param" ).get< uint32_t >();
pFilter = std::make_shared< World::AI::HasStatusEffectFilter >( statusId, negate );
}
break;
case World::AI::TargetSelectFilter::Type::TopAggro:
{
pFilter = std::make_shared< World::AI::TopAggroFilter >( negate );
}
break;
case World::AI::TargetSelectFilter::Type::SecondAggro:
{
pFilter = std::make_shared< World::AI::SecondAggroFilter >( negate );
}
break;
case World::AI::TargetSelectFilter::Type::PartyMember:
{
pFilter = std::make_shared< World::AI::PartyMemberFilter >( negate );
}
break;
default:
break;
}
filters.push_back( pFilter );
}
m_snapshot = World::AI::Snapshot( filters );
}
void Selector::createSnapshot( Entity::CharaPtr pSrc, const std::vector< uint32_t >& exclude )
{
m_snapshot.createSnapshot( pSrc, pSrc->getInRangeActors(), m_count, m_fillWithRandom, exclude );
}
const World::AI::Snapshot::Results& Selector::getResults()
{
return m_snapshot.getResults();
}
const World::AI::Snapshot::TargetIds& Selector::getTargetIds()
{
return m_snapshot.getTargetIds();
}
}// namespace Sapphire::Encounter

View file

@ -0,0 +1,25 @@
#pragma once
#include <cstdint>
#include <AI/TargetHelper.h>
#include <nlohmann/json.hpp>
namespace Sapphire::Encounter
{
class Selector
{
private:
std::string m_name;
bool m_fillWithRandom{ true };
uint32_t m_count;
World::AI::Snapshot m_snapshot;
public:
Selector() : m_snapshot( {} ){};
void createSnapshot( Entity::CharaPtr pSrc, const std::vector< uint32_t >& exclude = {} );
const World::AI::Snapshot::Results& getResults();
const World::AI::Snapshot::TargetIds& getTargetIds();
void from_json( const nlohmann::json& json );
};
};// namespace Sapphire::Encounter

View file

@ -0,0 +1,171 @@
#include "TimelineActor.h"
#include "EncounterTimeline.h"
#include <Actor/BNpc.h>
namespace Sapphire::Encounter
{
void TimelineActor::addPhaseCondition( PhaseConditionPtr pCondition )
{
m_phaseConditions.emplace( std::make_pair( pCondition->getId(), pCondition ) );
m_conditionStates[ pCondition->getId() ] = {};
m_conditionStates[ pCondition->getId() ].m_enabled = pCondition->isDefaultEnabled();
}
// todo: make this sane
void TimelineActor::update( TerritoryPtr pTeri, TimelinePack& pack, uint64_t time )
{
// todo: handle interrupts
for( const auto& condition : m_phaseConditions )
{
const auto& pCondition = condition.second;
auto& state = m_conditionStates[ pCondition->getId() ];
// ignore if not enabled, unless overriden to enable
if( !pCondition->isStateEnabled( state ) )
continue;
if( pCondition->completed( state ) )
{
if( pCondition->isLoopable() )
{
if( pCondition->loopReady( state, time ) )
pCondition->reset( state );
}
}
else if( pCondition->inProgress( state ) )
{
pCondition->update( state, *this, pTeri, pack, time );
}
else if( pCondition->isConditionMet( state, pTeri, pack, time ) )
{
pCondition->execute( state, *this, pTeri, pack, time );
if( pack.getStartTime() == 0 )
pack.setStartTime( state.m_startTime );
}
}
}
void TimelineActor::resetConditionState( uint32_t conditionId )
{
if( auto it = m_phaseConditions.find( conditionId ); it != m_phaseConditions.end() )
{
auto& state = m_conditionStates.at( it->first );
it->second->reset( state );
}
}
void TimelineActor::setConditionStateEnabled( uint32_t conditionId, bool enabled )
{
if( auto it = m_conditionStates.find( conditionId ); it != m_conditionStates.end() )
{
auto& state = m_conditionStates.at( it->first );
state.m_enabled = enabled;
}
}
void TimelineActor::resetAllConditionStates()
{
for( const auto& condition : m_phaseConditions )
{
const auto& pCondition = condition.second;
auto& state = m_conditionStates.at( condition.first );
pCondition->reset( state );
}
}
void TimelineActor::spawnAllSubActors( TerritoryPtr pTeri )
{
std::vector< std::string > toSpawn;
for( const auto& subActor : m_subActors )
if( getSubActor( subActor.first ) == nullptr )
toSpawn.push_back( subActor.first );
for( const auto& name : toSpawn )
spawnSubActor( name, pTeri );
}
void TimelineActor::addPlaceholderSubactor( const std::string& name )
{
// populate m_subActors with nullptr BNpcs
// then spawn them all in first timepoint and ref them by name subsequently
if( getSubActor( name ) == nullptr )
m_subActors.emplace( std::make_pair( name, nullptr ) );
}
Entity::BNpcPtr TimelineActor::getBNpcByRef( const std::string& name, TerritoryPtr pTeri ) const
{
if( name == m_name )
return pTeri->getActiveBNpcByLayoutId( m_layoutId );
return getSubActor( name );
}
void TimelineActor::resetAllSubActors( TerritoryPtr pTeri )
{
for( auto& subActor : m_subActors )
{
if( subActor.second )
{
auto inRange = subActor.second->getInRangeActors();
for( const auto& pActor : inRange )
{
if( auto pPlayer = pActor->getAsPlayer() )
subActor.second->despawn( pPlayer );
}
pTeri->removeActor( subActor.second );
subActor.second = nullptr;
}
}
}
Entity::BNpcPtr TimelineActor::spawnSubActor( const std::string& name, TerritoryPtr pTeri )
{
// todo: retail straight up respawns sub actors, even bnpc parts (qarn adjudicator body parts respawn each time with new ids)
auto flags = Entity::BNpcFlag::Invincible | Entity::BNpcFlag::Untargetable |
Entity::BNpcFlag::Immobile | Entity::BNpcFlag::AutoAttackDisabled |
Entity::BNpcFlag::TurningDisabled;
auto pActor = getSubActor( name );
if( pActor == nullptr )
{
auto pParent = pTeri->getActiveBNpcByLayoutId( m_layoutId );
pActor = pTeri->createBNpcFromLayoutId( m_layoutId, 1000, pParent->getBNpcType() );
m_subActors[ name ] = pActor;
pActor->setInvincibilityType( Common::InvincibilityIgnoreDamage );
pActor->setFlag( flags );
pActor->init();
pTeri->pushActor( pActor );
}
return pActor;
}
Entity::BNpcPtr TimelineActor::getSubActor( const std::string& name ) const
{
if( auto it = m_subActors.find( name ); it != m_subActors.end() )
return it->second;
return nullptr;
}
void TimelineActor::resetSubActors( TerritoryPtr pTeri )
{
for( auto& subActor : m_subActors )
{
if( subActor.second )
{
auto pBNpc = subActor.second;
pTeri->removeActor( pBNpc );
// todo: despawn?
subActor.second = nullptr;
}
}
}
};

View file

@ -0,0 +1,69 @@
#include <cstdint>
#include "PhaseCondition.h"
#include "TimelineActorState.h"
namespace Sapphire::Encounter
{
enum class TimelineActorType : uint32_t
{
LayerSetObject,
BattleNpc
};
class TimelineActor
{
protected:
std::unordered_map< uint32_t, PhaseConditionPtr > m_phaseConditions;
std::unordered_map< uint32_t, ConditionState > m_conditionStates;
// PARENTNAME_SUBACTOR_1, ..., PARENTNAME_SUBACTOR_69
std::unordered_map< std::string, Entity::BNpcPtr > m_subActors;
public:
uint32_t m_layoutId{ 0 };
uint32_t m_hp{ 0 };
std::string m_name;
TimelineActor() {}
TimelineActor( const TimelineActor& rhs ) :
m_layoutId( rhs.m_layoutId ),
m_hp( rhs.m_hp ),
m_name( rhs.m_name ),
m_phaseConditions( rhs.m_phaseConditions )
{
// yeah
for( const auto& subActor : rhs.m_subActors )
m_subActors.emplace( std::make_pair( subActor.first, nullptr ) );
m_conditionStates = rhs.m_conditionStates;
for( const auto& state : rhs.m_phaseConditions )
state.second->reset( m_conditionStates[ state.first ] );
}
void addPhaseCondition( PhaseConditionPtr pCondition );
// todo: make this sane
void update( TerritoryPtr pTeri, TimelinePack& pack, uint64_t time );
void resetConditionState( uint32_t conditionId );
void setConditionStateEnabled( uint32_t conditionId, bool enabled );
void resetAllConditionStates();
void spawnAllSubActors( TerritoryPtr pTeri );
void resetAllSubActors( TerritoryPtr pTeri );
// get self or subactor
Entity::BNpcPtr getBNpcByRef( const std::string& name, TerritoryPtr pTeri ) const;
// todo: i hate this but it's the only way to ref subactors while staying self contained
void addPlaceholderSubactor( const std::string& name );
Entity::BNpcPtr spawnSubActor( const std::string& name, TerritoryPtr pTeri );
Entity::BNpcPtr getSubActor( const std::string& name ) const;
void resetSubActors( TerritoryPtr pTeri );
};
}

View file

@ -0,0 +1,31 @@
#pragma once
#include <cstdint>
#include <vector>
namespace Sapphire::Encounter
{
struct TimepointState
{
uint64_t m_startTime{ 0 };
uint64_t m_lastTick{ 0 };
bool m_finished{ false };
};
struct ConditionState
{
uint64_t m_startTime{ 0 };
bool m_loop{ false };
bool m_completed{ false };
bool m_enabled{ false };
struct
{
uint64_t m_startTime{ 0 };
uint64_t m_lastTimepointTime{ 0 };
uint32_t m_lastTimepointIndex{ 0 };
std::vector< TimepointState > m_timepointStates;
} m_phaseInfo;
};
}; // namespace Sapphire::Encounter

View file

@ -0,0 +1,617 @@
#include "Timepoint.h"
#include "TimelineActor.h"
#include <Action/Action.h>
#include <Actor/BNpc.h>
#include <Actor/Chara.h>
#include <Actor/EventObject.h>
#include <Actor/Player.h>
#include <Event/Director.h>
#include <Manager/ActionMgr.h>
#include <Manager/PlayerMgr.h>
#include <Service.h>
#include <Territory/QuestBattle.h>
#include <Territory/InstanceContent.h>
#include <Util/UtilMath.h>
namespace Sapphire::Encounter
{
const TimepointDataPtr Timepoint::getData() const
{
return m_pData;
}
bool Timepoint::canExecute( const TimepointState& state, uint64_t elapsed ) const
{
return state.m_startTime == 0;// & &m_duration <= elapsed;
}
bool Timepoint::durationElapsed( uint64_t elapsed ) const
{
return m_duration < elapsed;
}
bool Timepoint::finished( const TimepointState& state, uint64_t elapsed ) const
{
return durationElapsed( elapsed ) || state.m_finished;
}
void Timepoint::reset( TimepointState& state ) const
{
state.m_startTime = 0;
state.m_lastTick = 0;
state.m_finished = false;
}
void Timepoint::from_json( const nlohmann::json& json, const std::unordered_map< std::string, TimelineActor >& actors, uint32_t selfLayoutId )
{
const static std::unordered_map< std::string, TimepointDataType > timepointTypeMap =
{
{ "idle", TimepointDataType::Idle },
{ "castAction", TimepointDataType::CastAction },
{ "moveTo", TimepointDataType::MoveTo },
{ "logMessage", TimepointDataType::LogMessage },
{ "battleTalk", TimepointDataType::BattleTalk },
{ "directorVar", TimepointDataType::DirectorVar },
{ "directorSeq", TimepointDataType::DirectorSeq },
{ "directorFlags", TimepointDataType::DirectorFlags },
{ "addStatusEffect", TimepointDataType::AddStatusEffect },
{ "removeStatusEffect", TimepointDataType::RemoveStatusEffect },
{ "spawnBNpc", TimepointDataType::SpawnBNpc },
{ "bNpcFlags", TimepointDataType::SetBNpcFlags },
{ "setEObjState", TimepointDataType::SetEObjState },
{ "setCondition", TimepointDataType::SetCondition },
{ "snapshot", TimepointDataType::Snapshot }
};
const static std::unordered_map< std::string, DirectorOpId > directorOpMap =
{
{ "set", DirectorOpId::Set },
{ "add", DirectorOpId::Add },
{ "sub", DirectorOpId::Sub },
{ "mul", DirectorOpId::Mul },
{ "div", DirectorOpId::Div },
{ "mod", DirectorOpId::Mod },
{ "sll", DirectorOpId::Sll },
{ "srl", DirectorOpId::Srl },
{ "or", DirectorOpId::Or },
{ "xor", DirectorOpId::Xor },
{ "nor", DirectorOpId::Nor },
{ "and", DirectorOpId::And }
};
const static std::unordered_map< std::string, Common::BNpcType > bnpcTypeMap =
{
{ "bnpc", Common::BNpcType::Enemy },
{ "ally", Common::BNpcType::Friendly }// todo: rename this
};
const static std::unordered_map< std::string, ActionTargetType > actionTypeMap =
{
{ "none", ActionTargetType::None },
{ "self", ActionTargetType::Self },
{ "target", ActionTargetType::Target },
{ "selector", ActionTargetType::Selector }
};
TimepointDataType tpType{ 0 };
auto typeStr = json.at( "type" ).get< std::string >();
if( auto it = timepointTypeMap.find( typeStr ); it != timepointTypeMap.end() )
tpType = it->second;
else
throw std::runtime_error( fmt::format( "Timepoint::from_json unable to find timepoint by type: %s", typeStr ) );
m_duration = json.at( "duration" ).get< uint64_t >();
//m_overrideFlags = json.at( "overrideFlags" ).get< TimepointOverrideFlags >();
m_description = json.at( "description" ).get< std::string >();
switch( tpType )
{
case TimepointDataType::Idle:
{
m_pData = std::make_shared< TimepointDataIdle >( m_duration );
}
break;
case TimepointDataType::CastAction:
{
// todo: CastAction
// todo: parse and build callback funcs
auto& dataJ = json.at( "data" );
auto sourceRef = dataJ.at( "sourceActor" ).get< std::string >();
auto actionId = dataJ.at( "actionId" ).get< uint32_t >();
auto targetType = actionTypeMap.find( dataJ.at( "targetType" ).get< std::string >() )->second;
auto selectorRef = dataJ.at( "selectorName" ).get< std::string >();
auto selectorIndex = dataJ.at( "selectorIndex" ).get< uint32_t >();
m_pData = std::make_shared< TimepointDataAction >( sourceRef, actionId, targetType,
selectorRef, selectorIndex );
}
break;
case TimepointDataType::MoveTo:
{
auto& dataJ = json.at( "data" );
auto pos = dataJ.at( "pos" ).get< std::vector< float > >();
auto rot = dataJ.at( "rot" ).get< float >();
auto pathReq = dataJ.at( "pathRequested" ).get< bool >() ? MoveType::WalkPath : MoveType::Teleport;
// todo: moveTo
}
break;
case TimepointDataType::LogMessage:
{
auto& dataJ = json.at( "data" );
auto messageId = dataJ.at( "messageId" ).get< uint32_t >();
auto params = dataJ.at( "params" ).get< std::vector< uint32_t > >();
m_pData = std::make_shared< TimepointDataLogMessage >( messageId, params );
}
break;
case TimepointDataType::BattleTalk:
{
auto& dataJ = json.at( "data" );
auto params = dataJ.at( "params" ).get< std::vector< uint32_t > >();
auto pBattleTalkData = std::make_shared< TimepointDataBattleTalk >( params );
pBattleTalkData->m_battleTalkId = dataJ.at( "battleTalkId" ).get< uint32_t >();
pBattleTalkData->m_handlerId = dataJ.at( "handlerId" ).get< uint32_t >();
pBattleTalkData->m_kind = dataJ.at( "kind" ).get< uint32_t >();
pBattleTalkData->m_nameId = dataJ.at( "nameId" ).get< uint32_t >();
pBattleTalkData->m_talkerId = dataJ.at( "talkerId" ).get< uint32_t >();
m_pData = pBattleTalkData;
}
break;
//
// Directors
//
case TimepointDataType::DirectorVar:
{
auto& dataJ = json.at( "data" );
auto index = dataJ.at( "idx" ).get< uint32_t >();
auto val = dataJ.at( "val" ).get< uint32_t >();
auto opStr = dataJ.at( "opc" ).get< std::string >();
DirectorOpId op = directorOpMap.find( opStr )->second;
auto pDirectorData = std::make_shared< TimepointDataDirector >( tpType, op );
pDirectorData->m_data.index = index;
pDirectorData->m_data.value.val = val;
m_pData = pDirectorData;
}
break;
case TimepointDataType::DirectorVarLR:
{
auto& dataJ = json.at( "data" );
auto index = dataJ.at( "idx" ).get< uint32_t >();
auto left = dataJ.at( "left" ).get< uint32_t >();
auto right = dataJ.at( "right" ).get< uint32_t >();
auto opStr = dataJ.at( "opc" ).get< std::string >();
DirectorOpId op = directorOpMap.find( opStr )->second;
auto pDirectorData = std::make_shared< TimepointDataDirector >( tpType, op );
pDirectorData->m_data.index = index;
pDirectorData->m_data.value.left = left;
pDirectorData->m_data.value.right = right;
m_pData = pDirectorData;
}
break;
case TimepointDataType::DirectorSeq:
{
auto& dataJ = json.at( "data" );
auto seq = dataJ.at( "val" ).get< uint32_t >();
auto opStr = dataJ.at( "opc" ).get< std::string >();
DirectorOpId op = directorOpMap.find( opStr )->second;
auto pDirectorData = std::make_shared< TimepointDataDirector >( tpType, op );
pDirectorData->m_data.seq = seq;
m_pData = pDirectorData;
}
break;
case TimepointDataType::DirectorFlags:
{
auto& dataJ = json.at( "data" );
auto flags = dataJ.at( "val" ).get< uint32_t >();
auto opStr = dataJ.at( "opc" ).get< std::string >();
DirectorOpId op = directorOpMap.at( opStr );
auto pDirectorData = std::make_shared< TimepointDataDirector >( tpType, op );
pDirectorData->m_data.flags = flags;
m_pData = pDirectorData;
}
break;
//
// Status Effects
//
case TimepointDataType::AddStatusEffect:
case TimepointDataType::RemoveStatusEffect:
{
// todo: add/remove status effects
}
break;
case TimepointDataType::SpawnBNpc:
{
auto& dataJ = json.at( "data" );
auto hateSrcJ = dataJ.at( "hateSrc" );
auto actorRef = dataJ.at( "spawnActor" ).get< std::string >();
auto flags = dataJ.at( "flags" ).get< uint32_t >();
// todo: batallion
// auto battalion = dataJ.at( "batallion" ).get< uint32_t >();
auto bnpcType = bnpcTypeMap.at( dataJ.at( "type" ).get< std::string >() );
// todo: hateSrc
uint32_t layoutId = 0xE0000000;
if( auto it = actors.find( actorRef ); it != actors.end() )
layoutId = it->second.m_layoutId;
else
throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::Timepoint::from_json: SpawnBNpc invalid actor ref: %s" ), actorRef ) );
m_pData = std::make_shared< TimepointDataSpawnBNpc >( layoutId, flags, bnpcType );
}
break;
case TimepointDataType::SetBNpcFlags:
{
auto& dataJ = json.at( "data" );
auto actorRef = dataJ.at( "spawnActor" ).get< std::string >();
auto flags = dataJ.at( "flags" ).get< uint32_t >();
// todo: hateSrc
uint32_t layoutId = 0xE0000000;
if( auto it = actors.find( actorRef ); it != actors.end() )
layoutId = it->second.m_layoutId;
else
throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::Timepoint::from_json: SetBNpcFlags invalid actor ref: %s" ), actorRef ) );
m_pData = std::make_shared< TimepointDataBNpcFlags >( layoutId, flags );
// todo: SetBNpcFlags
}
break;
case TimepointDataType::SetEObjState:
{
auto& dataJ = json.at( "data" );
// todo: SetEObjState
}
break;
case TimepointDataType::SetCondition:
{
auto& dataJ = json.at( "data" );
auto conditionId = dataJ.at( "conditionId" ).get< uint32_t >();
auto enabled = dataJ.at( "enabled" ).get< bool >();
m_pData = std::make_shared< TimepointDataCondition >( conditionId, enabled );
}
break;
case TimepointDataType::Snapshot:
{
auto& dataJ = json.at( "data" );
auto selectorName = dataJ.at( "selectorName" ).get< std::string >();
auto actorRef = dataJ.at( "sourceActor" ).get< std::string >();
// todo:
m_pData = std::make_shared< TimepointDataSnapshot >( selectorName, actorRef );
}
default:
break;
}
}
void Timepoint::execute( TimepointState& state, TimelineActor& self, TerritoryPtr pTeri, uint64_t time ) const
{
state.m_startTime = time;
update( state, self, pTeri, time );
}
void Timepoint::update( TimepointState& state, TimelineActor& self, TerritoryPtr pTeri, uint64_t time ) const
{
state.m_lastTick = time;
// todo: separate execute and update?
if( state.m_finished )
return;
switch( m_type )
{
case TimepointDataType::Idle:
{
// just wait up the duration of this timepoint
}
break;
case TimepointDataType::CastAction:
{
auto pActionData = std::dynamic_pointer_cast< TimepointDataAction, TimepointData >( getData() );
auto pBNpc = self.getBNpcByRef( pActionData->m_sourceRef, pTeri );
// todo: filter the correct target
// todo: tie to mechanic script?
// todo: mechanic should probably just be an Action::onTick, with instance/director passed to it
if( pBNpc )
{
uint32_t targetId = pBNpc->getId();
switch( pActionData->m_targetType )
{
case ActionTargetType::Target:
targetId = pBNpc->getTargetId();
break;
case ActionTargetType::Selector:
{
// todo: selector
}
break;
default:
break;
}
auto actionMgr = Common::Service< Sapphire::World::Manager::ActionMgr >::ref();
// todo: this is probably wrong
if( pBNpc->getCurrentAction() && pBNpc->getCurrentAction()->getId() != pActionData->m_actionId )
actionMgr.handleTargetedAction( *pBNpc.get(), pActionData->m_actionId, targetId, 0 );
}
}
break;
case TimepointDataType::MoveTo:
{
auto pMoveToData = std::dynamic_pointer_cast< TimepointDataMoveTo, TimepointData >( getData() );
auto pBNpc = self.getBNpcByRef( pMoveToData->m_actorRef, pTeri );
if( pBNpc )
{
auto currPos = pBNpc->getPos();
Common::FFXIVARR_POSITION3 targetPos = { pMoveToData->m_x, pMoveToData->m_y, pMoveToData->m_z };
auto distance = Common::Util::distance( currPos, targetPos );
if( distance > 0.5f )
{
if( pMoveToData->m_moveType == MoveType::WalkPath )
pBNpc->moveTo( targetPos );
else
pBNpc->setPos( pMoveToData->m_x, pMoveToData->m_y, pMoveToData->m_z );
}
else
{
// if we are at the pos, stop waiting
//state.m_finished = true;
}
pBNpc->setRot( pMoveToData->m_rot );
}
}
break;
case TimepointDataType::LogMessage:
{
auto pLogMessage = std::dynamic_pointer_cast< TimepointDataLogMessage, TimepointData >( getData() );
auto params = pLogMessage->m_params;
// todo: probably should use ContentDirector
{
auto& playerMgr = Common::Service< Sapphire::World::Manager::PlayerMgr >::ref();
for( auto player : pTeri->getPlayers() )
{
auto pPlayer = player.second;
if( pPlayer )
playerMgr.sendLogMessage( *pPlayer.get(), pLogMessage->m_messageId,
params[ 0 ], params[ 1 ], params[ 2 ], params[ 3 ], params[ 4 ] );
}
}
}
break;
case TimepointDataType::BattleTalk:
{
// todo: BattleTalk
auto pBtData = std::dynamic_pointer_cast< TimepointDataBattleTalk, TimepointData >( getData() );
auto params = pBtData->m_params;
auto& playerMgr = Common::Service< Sapphire::World::Manager::PlayerMgr >::ref();
for( auto player : pTeri->getPlayers() )
{
auto pPlayer = player.second;
if( pPlayer )
playerMgr.sendBattleTalk( *pPlayer.get(), pBtData->m_battleTalkId, pBtData->m_handlerId,
pBtData->m_kind, pBtData->m_nameId, pBtData->m_talkerId,
params[ 0 ], params[ 1 ], params[ 2 ], params[ 3 ],
params[ 4 ], params[ 5 ], params[ 6 ], params[ 7 ] );
}
}
break;
case TimepointDataType::DirectorSeq:
case TimepointDataType::DirectorVar:
case TimepointDataType::DirectorVarLR:
case TimepointDataType::DirectorFlags:
{
auto pDirectorData = std::dynamic_pointer_cast< TimepointDataDirector, TimepointData >( getData() );
uint32_t val = 0;
uint32_t param = 0;
// todo: expand for fates
Event::DirectorPtr pDirector = pTeri->getAsInstanceContent();
if( pDirector == nullptr )
pDirector = pTeri->getAsQuestBattle();
// todo: this should never not be set?
// todo: probably should use ContentDirector
// todo: this needs to resend packets too
if( pDirector )
{
switch( m_type )
{
case TimepointDataType::DirectorVar:
val = pDirector->getDirectorVar( pDirectorData->m_data.index );
param = pDirectorData->m_data.value.val;
break;
case TimepointDataType::DirectorFlags:
val = pDirector->getFlags();
param = pDirectorData->m_data.flags;
break;
case TimepointDataType::DirectorSeq:
val = pDirector->getSequence();
param = pDirectorData->m_data.seq;
break;
default:
break;
}
switch( pDirectorData->m_directorOp )
{
case DirectorOpId::Set:
val = param;
break;
case DirectorOpId::Add:
val += param;
break;
case DirectorOpId::Sub:
val -= param;
break;
case DirectorOpId::Mul:
val *= param;
break;
case DirectorOpId::Div:
val /= param;
break;
case DirectorOpId::Mod:
val %= param;
break;
case DirectorOpId::Sll:
val = val << param;
break;
case DirectorOpId::Srl:
val = val >> param;
break;
case DirectorOpId::Or:
val |= param;
break;
case DirectorOpId::Xor:
val ^= param;
break;
case DirectorOpId::Nor:
val = ~( val | param );
break;
case DirectorOpId::And:
val &= param;
break;
default:
break;
}
// todo: resend packets
switch( m_type )
{
case TimepointDataType::DirectorVar:
pDirector->setDirectorVar( pDirectorData->m_data.index, val );
break;
case TimepointDataType::DirectorFlags:
pDirector->setDirectorFlags( val );
break;
case TimepointDataType::DirectorSeq:
pDirector->setDirectorSequence( val );
break;
default:
break;
}
}
}
break;
case TimepointDataType::AddStatusEffect:
{
// todo:
}
break;
case TimepointDataType::RemoveStatusEffect:
{
}
break;
case TimepointDataType::SpawnBNpc:
{
auto pSpawnData = std::dynamic_pointer_cast< TimepointDataSpawnBNpc, TimepointData >( getData() );
auto pBNpc = pTeri->getActiveBNpcByLayoutId( pSpawnData->m_layoutId );
if( pBNpc )
{
pBNpc->clearFlags();
pBNpc->setFlag( pSpawnData->m_flags );
pBNpc->init();
}
}
break;
case TimepointDataType::SetBNpcFlags:
{
auto pBNpcFlagData = std::dynamic_pointer_cast< TimepointDataBNpcFlags, TimepointData >( getData() );
auto pBNpc = pTeri->getActiveBNpcByLayoutId( pBNpcFlagData->m_layoutId );
if( pBNpc )
{
pBNpc->clearFlags();
pBNpc->setFlag( pBNpcFlagData->m_flags );
// todo: resend some bnpc packet/actrl?
}
}
break;
case TimepointDataType::SetEObjState:
{
auto pEObjData = std::dynamic_pointer_cast< TimepointDataEObjState, TimepointData >( getData() );
auto pInstance = pTeri->getAsInstanceContent();
auto pQBattle = pTeri->getAsQuestBattle();
// todo: event objects on quest battles
// todo: SetEObjAnimationFlag?
if( pInstance )
{
auto pEObj = pInstance->getEObjById( pEObjData->m_eobjId );
if( pEObj )
{
pEObj->setState( pEObjData->m_state );
// todo: resend the eobj spawn packet?
}
}
}
break;
case TimepointDataType::SetBgm:
{
auto pBgmData = std::dynamic_pointer_cast< TimepointDataBGM, TimepointData >( getData() );
auto pInstance = pTeri->getAsInstanceContent();
auto pQBattle = pTeri->getAsQuestBattle();
// todo: quest battles refactor to inherit InstanceContent
if( pInstance )
{
pInstance->setCurrentBGM( pBgmData->m_bgmId );
}
}
break;
case TimepointDataType::SetCondition:
{
auto pConditionData = std::dynamic_pointer_cast< TimepointDataCondition, TimepointData >( getData() );
// todo: dont reset so things can resume? idk
self.resetConditionState( pConditionData->m_conditionId );
self.setConditionStateEnabled( pConditionData->m_conditionId, pConditionData->m_enabled );
}
}
if( m_type != TimepointDataType::MoveTo && m_type != TimepointDataType::CastAction )
state.m_finished = true;
state.m_finished = state.m_finished || state.m_startTime + m_duration <= time;
}
}

View file

@ -0,0 +1,295 @@
#pragma once
#include "TimelineActorState.h"
#include "Forwards.h"
#include <cstdint>
#include <string>
#include <vector>
#include <ForwardsZone.h>
#include <nlohmann/json.hpp>
namespace Sapphire::Encounter
{
enum class TimepointDataType : uint32_t
{
Idle,
CastAction,
MoveTo,
LogMessage,
BattleTalk,
DirectorVar,
DirectorVarLR,
DirectorSeq,
DirectorFlags,
AddStatusEffect,
RemoveStatusEffect,
SpawnBNpc,
SetBNpcFlags,
SetEObjState,
SetBgm,
SetCondition,
Snapshot
};
enum class ActionTargetType : uint32_t
{
None,
Self,
Target,
Selector
};
enum class MoveType : uint32_t
{
WalkPath,
Teleport
};
enum class DirectorOpId
{
Set,// idx = val
Add,// idx += val
Sub,// idx -= val
Mul,// idx *= val
Div,// idx /= val
Mod,// idx %= val
Sll,// idx << val
Srl,// idx >> val
Or, // idx |= val
Xor,// idx ^= val
Nor,// idx ~= val
And // idx &= val
};
//
// Timepoint.m_pData objects
//
struct TimepointData : public std::enable_shared_from_this< TimepointData > {
TimepointData( TimepointDataType type ) : m_type( type ) {}
virtual ~TimepointData(){};
TimepointDataType m_type{ 0 };
};
using TimepointDataPtr = std::shared_ptr< TimepointData >;
struct TimepointDataIdle : public TimepointData {
uint64_t m_durationMs;
TimepointDataIdle( uint64_t durationMs ) :
TimepointData( TimepointDataType::Idle ),
m_durationMs( durationMs )
{
}
};
struct TimepointDataAction : public TimepointData
{
std::string m_sourceRef;
uint32_t m_actionId;
ActionTargetType m_targetType;
std::string m_selectorRef;
uint32_t m_selectorIndex;
TimepointDataAction( const std::string& sourceRef, uint32_t actionId,
ActionTargetType type, const std::string& selectorRef,
uint32_t selectorIndex = 0 ) :
TimepointData( TimepointDataType::CastAction ),
m_sourceRef( sourceRef ),
m_actionId( actionId ),
m_targetType( type ),
m_selectorRef( selectorRef ),
m_selectorIndex( selectorIndex )
{
}
};
struct TimepointDataMoveTo : public TimepointData {
// todo: use internal id
std::string m_actorRef;
MoveType m_moveType;
float m_x, m_y, m_z, m_rot;
TimepointDataMoveTo( const std::string& actorRef, MoveType moveType,
float x, float y, float z, float rot ) :
TimepointData( TimepointDataType::MoveTo ),
m_actorRef( actorRef ),
m_moveType( moveType ),
m_x( x ), m_y( y ), m_z( z ), m_rot( rot )
{
}
};
struct TimepointDataLogMessage : public TimepointData {
uint32_t m_messageId;
uint32_t m_params[ 5 ]{ 0 };
TimepointDataLogMessage( uint32_t messageId, const std::vector< uint32_t >& params ) :
TimepointData( TimepointDataType::LogMessage ),
m_messageId( messageId )
{
for( auto i = 0; i < params.size() && i < 5; ++i )
m_params[ i ] = params[ i ];
}
};
struct TimepointDataBattleTalk : public TimepointData {
uint32_t m_battleTalkId;
uint32_t m_handlerId;
uint32_t m_kind;
uint32_t m_nameId;
uint32_t m_talkerId;
uint32_t m_params[ 8 ]{ 0 };
TimepointDataBattleTalk( const std::vector< uint32_t >& params ) : TimepointData( TimepointDataType::BattleTalk )
{
for( auto i = 0; i < params.size() && i < 8; ++i )
m_params[ i ] = params[ i ];
}
};
struct TimepointDataDirector : public TimepointData
{
DirectorOpId m_directorOp{ 0 };
union
{
struct
{
uint8_t index;
union
{
uint8_t val;
struct
{
uint8_t left, right;
};
} value;
};
uint8_t seq;
uint8_t flags;
} m_data{ 0 };
TimepointDataDirector( TimepointDataType type, DirectorOpId op ) :
TimepointData( type ),
m_directorOp( op )
{
}
};
struct TimepointDataSpawnBNpc : public TimepointData
{
uint32_t m_layoutId{ 0xE0000000 };
uint32_t m_flags{ 0 };
uint32_t m_type{ 0 };
// todo: hate type, source
TimepointDataSpawnBNpc( uint32_t layoutId, uint32_t flags, uint32_t type ) :
TimepointData( TimepointDataType::SpawnBNpc ),
m_layoutId( layoutId ),
m_flags( flags ),
m_type( type )
{
}
};
struct TimepointDataBNpcFlags : public TimepointData
{
uint32_t m_layoutId{ 0xE0000000 };
uint32_t m_flags{ 0 };
TimepointDataBNpcFlags( uint32_t layoutId, uint32_t flags ) :
TimepointData( TimepointDataType::SetBNpcFlags ),
m_layoutId( layoutId ),
m_flags( flags )
{
}
};
struct TimepointDataEObjState : public TimepointData
{
uint32_t m_eobjId{ 0xE0000000 };
uint32_t m_state{ 0 };
TimepointDataEObjState( uint32_t eobjId, uint32_t state ) :
TimepointData( TimepointDataType::SetEObjState ),
m_eobjId( eobjId ),
m_state( state )
{
}
};
struct TimepointDataBGM : public TimepointData
{
uint32_t m_bgmId{ 0 };
TimepointDataBGM( uint32_t bgmId ) :
TimepointData( TimepointDataType::SetBgm ),
m_bgmId( bgmId )
{
}
};
struct TimepointDataCondition : public TimepointData
{
// todo: rng?
uint32_t m_conditionId;
bool m_enabled;
TimepointDataCondition( uint32_t conditionId, bool enabled ) :
TimepointData( TimepointDataType::SetCondition ),
m_conditionId( conditionId ),
m_enabled( enabled )
{
}
};
struct TimepointDataSnapshot : public TimepointData
{
// todo: rng?
std::string m_name;
std::string m_actorRef;
TimepointDataSnapshot( const std::string& name, const std::string& actorRef ) :
TimepointData( TimepointDataType::Snapshot ),
m_name( name ),
m_actorRef( actorRef )
{
}
};
// todo: refactor all this to allow solo actor to use
class Timepoint : public std::enable_shared_from_this< Timepoint >
{
public:
TimepointDataType m_type;
uint64_t m_duration{ 0 };// milliseconds
TimepointDataPtr m_pData;
std::string m_description;
// todo: repeatable?
const TimepointDataPtr getData() const;
bool canExecute( const TimepointState& state, uint64_t elapsed ) const;
bool durationElapsed( uint64_t elapsed ) const;
bool finished( const TimepointState& state, uint64_t elapsed ) const;
void reset( TimepointState& state ) const;
void from_json( const nlohmann::json& json, const std::unordered_map< std::string, TimelineActor >& actors, uint32_t selfLayoutId );
// todo: separate execute/update into onStart and onTick?
void update( TimepointState& state, TimelineActor& self, TerritoryPtr pTeri, uint64_t time ) const;
void execute( TimepointState& state, TimelineActor& self, TerritoryPtr pTeri, uint64_t time ) const;
};
}// namespace Sapphire::Encounter