#include "Timepoint.h" #include "TimelineActor.h" #include "EncounterTimeline.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 }, { "setPos", TimepointDataType::SetPos }, { "logMessage", TimepointDataType::LogMessage }, { "battleTalk", TimepointDataType::BattleTalk }, { "directorVar", TimepointDataType::DirectorVar }, { "directorSeq", TimepointDataType::DirectorSeq }, { "directorFlags", TimepointDataType::DirectorFlags }, { "spawnBNpc", TimepointDataType::SpawnBNpc }, { "bNpcFlags", TimepointDataType::SetBNpcFlags }, { "setEObjState", TimepointDataType::SetEObjState }, { "setBGM", TimepointDataType::SetBgm }, { "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: {}", typeStr ) ); m_duration = json.at( "duration" ).get< uint64_t >(); //m_overrideFlags = json.at( "overrideFlags" ).get< TimepointOverrideFlags >(); m_description = json.at( "description" ).get< std::string >(); m_type = tpType; 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::SetPos: { auto& dataJ = json.at( "data" ); auto pos = dataJ.at( "pos" ).get< std::vector< float > >(); auto rot = dataJ.at( "rot" ).get< float >(); auto actorRef = dataJ.at( "actorName" ).get< std::string >(); m_pData = std::make_shared< TimepointDataSetPos >( actorRef, MoveType::SetPos, 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_handlerRef = dataJ.at( "handlerActorName" ).get< std::string >(); pBattleTalkData->m_kind = dataJ.at( "kind" ).get< uint32_t >(); pBattleTalkData->m_nameId = dataJ.at( "nameId" ).get< uint32_t >(); pBattleTalkData->m_talkerRef = dataJ.at( "talkerActorName" ).get< std::string >(); 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.at( opStr ); 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.at( opStr ); 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.at( opStr ); 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 = Common::BNpcType::Enemy;//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( "Timepoint::from_json: SpawnBNpc invalid actor ref: {}" ), actorRef ) ); m_pData = std::make_shared< TimepointDataSpawnBNpc >( layoutId, flags, bnpcType ); } break; case TimepointDataType::SetBNpcFlags: { auto& dataJ = json.at( "data" ); auto actorRef = dataJ.at( "targetActor" ).get< std::string >(); auto flags = dataJ.at( "flags" ).get< uint32_t >(); // todo: hateSrc uint32_t layoutId = selfLayoutId; //if( auto it = actors.find( actorRef ); it != actors.end() ) // layoutId = it->second.m_layoutId; //else // throw std::runtime_error( fmt::format( std::string( "Timepoint::from_json: SetBNpcFlags invalid actor ref: {}" ), 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::SetBgm: { auto& dataJ = json.at( "data" ); auto bgmId = dataJ.at( "bgmId" ).get< uint32_t >(); m_pData = std::make_shared< TimepointDataBGM >( bgmId ); } 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 >(); auto excludeSelector = std::string(); // dataJ.at( "excludeSelectorName" ).get< std::string >(); // todo: use exclude selector when added to ui m_pData = std::make_shared< TimepointDataSnapshot >( selectorName, actorRef, excludeSelector ); } break; default: break; } } void Timepoint::execute( TimepointState& state, TimelineActor& self, TimelinePack& pack, TerritoryPtr pTeri, uint64_t time ) const { state.m_startTime = time; const auto& players = pTeri->getPlayers(); // send debug msg if( !m_description.empty() ) { auto& playerMgr = Common::Service< Sapphire::World::Manager::PlayerMgr >::ref(); for( const auto& player : players ) playerMgr.sendDebug( *player.second, m_description ); } update( state, self, pack, pTeri, time ); } void Timepoint::update( TimepointState& state, TimelineActor& self, TimelinePack& pack, 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 >( m_pData ); 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 = static_cast< uint32_t >( pBNpc->getTargetId() ); break; case ActionTargetType::Selector: { const auto& results = pack.getSnapshotTargetIds( pActionData->m_selectorRef ); if( pActionData->m_selectorIndex < results.size() ) targetId = results[ pActionData->m_selectorIndex ]; } break; default: break; } auto& actionMgr = Common::Service< Sapphire::World::Manager::ActionMgr >::ref(); // todo: this is probably wrong if( !pBNpc->getCurrentAction() ) { actionMgr.handleTargetedAction( *pBNpc, pActionData->m_actionId, targetId, 0 ); state.m_finished = true; } } } break; case TimepointDataType::SetPos: { auto pSetPosData = std::dynamic_pointer_cast< TimepointDataSetPos, TimepointData >( m_pData ); auto pBNpc = self.getBNpcByRef( pSetPosData->m_actorRef, pTeri ); if( pBNpc ) { pBNpc->setRot( pSetPosData->m_rot ); pBNpc->setPos( pSetPosData->m_x, pSetPosData->m_y, pSetPosData->m_z, true ); state.m_finished = true; } } break; /* case TimepointDataType::MoveTo: { auto pMoveToData = std::dynamic_pointer_cast< TimepointDataMoveTo, TimepointData >( m_pData ); 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 >( m_pData ); 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 >( m_pData ); auto params = pBtData->m_params; auto pHandler = pack.getBNpcByActorRef( pBtData->m_handlerRef , pTeri ); auto pTalker = pack.getBNpcByActorRef( pBtData->m_talkerRef, pTeri ); auto handlerId = pHandler ? pHandler->getId() : 0xE0000000; auto talkerId = pTalker ? pTalker->getId() : 0xE0000000; // todo: use Actrl EventBattleDialog = 0x39C maybe?, 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, handlerId, pBtData->m_kind, pBtData->m_nameId, 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 >( m_pData ); 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 >( m_pData ); 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 >( m_pData ); 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 >( m_pData ); 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 >( m_pData ); 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 >( m_pData ); // todo: dont reset so things can resume? idk self.resetConditionState( pConditionData->m_conditionId ); self.setConditionStateEnabled( pConditionData->m_conditionId, pConditionData->m_enabled ); } break; case TimepointDataType::Snapshot: { auto pSnapshotData = std::dynamic_pointer_cast< TimepointDataSnapshot, TimepointData >( m_pData ); auto pBNpc = self.getBNpcByRef( pSnapshotData->m_actorRef, pTeri ); if( pBNpc ) { const auto& exclude = pack.getSnapshotTargetIds( pSnapshotData->m_excludeSelector ); pack.createSnapshot( pSnapshotData->m_selector, pBNpc, exclude ); } } break; default: break; } if( m_type != TimepointDataType::CastAction ) state.m_finished = true; state.m_finished = state.m_finished || state.m_startTime + m_duration <= time; } }