diff --git a/src/world/AI/TargetHelper.cpp b/src/world/AI/TargetHelper.cpp index 4674d7de..9e53bd5b 100644 --- a/src/world/AI/TargetHelper.cpp +++ b/src/world/AI/TargetHelper.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include #include @@ -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 \ No newline at end of file diff --git a/src/world/AI/TargetHelper.h b/src/world/AI/TargetHelper.h index 71ba2ffb..b86e232d 100644 --- a/src/world/AI/TargetHelper.h +++ b/src/world/AI/TargetHelper.h @@ -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; diff --git a/src/world/Encounter/EncounterTimeline.cpp b/src/world/Encounter/EncounterTimeline.cpp index 347c850f..33cb9938 100644 --- a/src/world/Encounter/EncounterTimeline.cpp +++ b/src/world/Encounter/EncounterTimeline.cpp @@ -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 #include @@ -15,349 +20,11 @@ #include #include +#include #include -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 \ No newline at end of file + 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 \ No newline at end of file diff --git a/src/world/Encounter/EncounterTimeline.h b/src/world/Encounter/EncounterTimeline.h index e4a5d420..c352ba00 100644 --- a/src/world/Encounter/EncounterTimeline.h +++ b/src/world/Encounter/EncounterTimeline.h @@ -1,3 +1,5 @@ +#pragma once + #include #include @@ -8,841 +10,71 @@ #include #include -#include -#include #include -namespace Sapphire +#include +#include +#include +#include + +#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 ); diff --git a/src/world/Encounter/Forwards.h b/src/world/Encounter/Forwards.h new file mode 100644 index 00000000..6b431270 --- /dev/null +++ b/src/world/Encounter/Forwards.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace Sapphire::Encounter +{ + class Selector; + class TimelineActor; + class Phase; + class PhaseCondition; + class Timepoint; + + class TimelinePack; + + using PhaseConditionPtr = std::shared_ptr< PhaseCondition >; +} \ No newline at end of file diff --git a/src/world/Encounter/PhaseCondition.cpp b/src/world/Encounter/PhaseCondition.cpp new file mode 100644 index 00000000..03292059 --- /dev/null +++ b/src/world/Encounter/PhaseCondition.cpp @@ -0,0 +1,271 @@ +#include "PhaseCondition.h" + +#include "EncounterTimeline.h" +#include "TimelineActor.h" +#include "TimelineActorState.h" + +#include +#include +#include + +#include +#include +#include + +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 \ No newline at end of file diff --git a/src/world/Encounter/PhaseCondition.h b/src/world/Encounter/PhaseCondition.h new file mode 100644 index 00000000..8ee4ec63 --- /dev/null +++ b/src/world/Encounter/PhaseCondition.h @@ -0,0 +1,217 @@ +#pragma once + +#include + +#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 \ No newline at end of file diff --git a/src/world/Encounter/Selector.cpp b/src/world/Encounter/Selector.cpp new file mode 100644 index 00000000..8a824ad0 --- /dev/null +++ b/src/world/Encounter/Selector.cpp @@ -0,0 +1,137 @@ +#include "Selector.h" + +#include + +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 \ No newline at end of file diff --git a/src/world/Encounter/Selector.h b/src/world/Encounter/Selector.h new file mode 100644 index 00000000..3e76d60f --- /dev/null +++ b/src/world/Encounter/Selector.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#include + +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 \ No newline at end of file diff --git a/src/world/Encounter/TimelineActor.cpp b/src/world/Encounter/TimelineActor.cpp new file mode 100644 index 00000000..d7fa1a7a --- /dev/null +++ b/src/world/Encounter/TimelineActor.cpp @@ -0,0 +1,171 @@ +#include "TimelineActor.h" + +#include "EncounterTimeline.h" + +#include + +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; + } + } + } + +}; \ No newline at end of file diff --git a/src/world/Encounter/TimelineActor.h b/src/world/Encounter/TimelineActor.h new file mode 100644 index 00000000..ecab4cc0 --- /dev/null +++ b/src/world/Encounter/TimelineActor.h @@ -0,0 +1,69 @@ +#include + +#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 ); + }; +} \ No newline at end of file diff --git a/src/world/Encounter/TimelineActorState.h b/src/world/Encounter/TimelineActorState.h new file mode 100644 index 00000000..a30dbd39 --- /dev/null +++ b/src/world/Encounter/TimelineActorState.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +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 \ No newline at end of file diff --git a/src/world/Encounter/Timepoint.cpp b/src/world/Encounter/Timepoint.cpp new file mode 100644 index 00000000..332a7b38 --- /dev/null +++ b/src/world/Encounter/Timepoint.cpp @@ -0,0 +1,617 @@ +#include "Timepoint.h" +#include "TimelineActor.h" + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +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; + } +} \ No newline at end of file diff --git a/src/world/Encounter/Timepoint.h b/src/world/Encounter/Timepoint.h new file mode 100644 index 00000000..e9c162e4 --- /dev/null +++ b/src/world/Encounter/Timepoint.h @@ -0,0 +1,295 @@ +#pragma once + +#include "TimelineActorState.h" +#include "Forwards.h" + +#include +#include +#include + +#include + +#include + +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 \ No newline at end of file