diff --git a/data/EncounterTimelines/IfritNormal.json b/data/EncounterTimelines/IfritNormal.json index 8050f51d..9c9bf713 100644 --- a/data/EncounterTimelines/IfritNormal.json +++ b/data/EncounterTimelines/IfritNormal.json @@ -107,7 +107,7 @@ "data": { "battleTalkId": 2939, "handlerActorName": "Ifrit", - "kind": 0, + "kind": 1, "nameId": 2961, "params": [ 0 @@ -211,7 +211,7 @@ }, { "data": { - "actionId": 733, + "actionId": 455, "selectorIndex": 0, "selectorName": "Eruption", "snapshot": false, @@ -234,7 +234,7 @@ "data": { "battleTalkId": 2939, "handlerActorName": "Ifrit", - "kind": 0, + "kind": 1, "nameId": 2961, "params": [ 0 @@ -256,7 +256,7 @@ }, "description": "", "duration": 1500, - "type": "spawnBNpc" + "type": "bNpcSpawn" }, { "data": { @@ -291,6 +291,16 @@ "id": 7, "name": "Hellfire", "timepoints": [ + { + "data": { + "conditionId": 7, + "conditionStr": "If Ifrit Nail 1 state is Dead, push Ifrit->Hellfire", + "enabled": false + }, + "description": "", + "duration": 0, + "type": "setCondition" + }, { "data": { "conditionId": 6, @@ -354,7 +364,7 @@ }, { "data": { - "flags": 1, + "flags": 16, "targetActor": "" }, "description": "", @@ -367,7 +377,7 @@ "conditionStr": "If Ifrit state is Combat, loop Ifrit->Final Phase", "enabled": true }, - "description": "", + "description": "Enable final phase", "duration": 0, "type": "setCondition" } @@ -388,7 +398,7 @@ "sourceActor": "Ifrit", "targetType": "target" }, - "description": "Radiant plume begin", + "description": "Incinerate Final Phase", "duration": 8000, "type": "castAction" }, @@ -412,7 +422,7 @@ "targetType": "selector" }, "description": "Subactor 1 Eruption", - "duration": 5000, + "duration": 8000, "type": "castAction" }, { @@ -524,16 +534,16 @@ "targetType": "self" }, "description": "", - "duration": 0, + "duration": 5000, "type": "castAction" }, { "data": { "actorName": "Ifrit ", "pos": [ - -10, 0, - 10 + 0, + -20 ], "rot": 0 }, @@ -545,9 +555,9 @@ "data": { "actorName": "Ifrit ", "pos": [ - -10, 0, - -10 + 0, + 20 ], "rot": 0 }, @@ -559,9 +569,9 @@ "data": { "actorName": "Ifrit ", "pos": [ - 10, + -20, 0, - 10 + 0 ], "rot": 0 }, @@ -573,9 +583,65 @@ "data": { "actorName": "Ifrit ", "pos": [ - 10, + -20, 0, - -10 + 20 + ], + "rot": 0 + }, + "description": "", + "duration": 0, + "type": "setPos" + }, + { + "data": { + "actorName": "Ifrit ", + "pos": [ + -15, + 0, + 15 + ], + "rot": 0 + }, + "description": "", + "duration": 0, + "type": "setPos" + }, + { + "data": { + "actorName": "Ifrit ", + "pos": [ + -15, + 0, + -15 + ], + "rot": 0 + }, + "description": "", + "duration": 0, + "type": "setPos" + }, + { + "data": { + "actorName": "Ifrit ", + "pos": [ + 15, + 0, + -15 + ], + "rot": 0 + }, + "description": "", + "duration": 0, + "type": "setPos" + }, + { + "data": { + "actorName": "Ifrit ", + "pos": [ + 15, + 0, + 15 ], "rot": 0 }, diff --git a/src/world/AI/TargetHelper.cpp b/src/world/AI/TargetHelper.cpp index 7a38e388..4f4fbb2e 100644 --- a/src/world/AI/TargetHelper.cpp +++ b/src/world/AI/TargetHelper.cpp @@ -260,4 +260,10 @@ namespace Sapphire::World::AI return m_targetIds; } + void Snapshot::clearResults() + { + m_results.clear(); + m_targetIds.clear(); + } + };// 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 10246367..21f8e8f0 100644 --- a/src/world/AI/TargetHelper.h +++ b/src/world/AI/TargetHelper.h @@ -236,12 +236,14 @@ namespace Sapphire::World::AI m_filters( filters ) { } + void createSnapshot( Entity::CharaPtr pSrc, const std::set< Entity::GameObjectPtr >& inRange, int count, bool fillWithRandom, const std::vector< uint32_t >& exclude = {} ); // returns actors sorted by distance const std::vector< CharaEntry >& getResults() const; const std::vector< uint32_t >& getTargetIds() const; + void clearResults(); }; using SnapshotPtr = std::shared_ptr< Snapshot >; }// namespace Sapphire::World::AI diff --git a/src/world/Actor/BNpc.h b/src/world/Actor/BNpc.h index 86044b92..ca860d1b 100644 --- a/src/world/Actor/BNpc.h +++ b/src/world/Actor/BNpc.h @@ -36,7 +36,7 @@ namespace Sapphire::Entity Immobile = 0x01, TurningDisabled = 0x02, Invincible = 0x04, - InvincibleRefill = 0x08, + StayAlive = 0x08, NoDeaggro = 0x10, Untargetable = 0x20, AutoAttackDisabled = 0x40, diff --git a/src/world/Actor/Chara.cpp b/src/world/Actor/Chara.cpp index 2d6abf28..9956880d 100644 --- a/src/world/Actor/Chara.cpp +++ b/src/world/Actor/Chara.cpp @@ -399,7 +399,7 @@ void Chara::takeDamage( uint32_t damage ) resetHp(); break; case InvincibilityStayAlive: - setHp( 0 ); + setHp( 1 ); break; case InvincibilityIgnoreDamage: break; diff --git a/src/world/Encounter/EncounterTimeline.cpp b/src/world/Encounter/EncounterTimeline.cpp index 4d566bed..2fb63564 100644 --- a/src/world/Encounter/EncounterTimeline.cpp +++ b/src/world/Encounter/EncounterTimeline.cpp @@ -24,6 +24,8 @@ #include #include +#include + namespace Sapphire::Encounter { @@ -134,8 +136,8 @@ namespace Sapphire::Encounter for( const auto& selectorJ : json.at( "selectors" ).items() ) { - auto selectorV = selectorJ.value(); - auto name = selectorV.at( "name" ); + auto& selectorV = selectorJ.value(); + auto name = selectorV.at( "name" ).get< std::string >(); Selector selector; selector.from_json( selectorV ); @@ -151,6 +153,12 @@ namespace Sapphire::Encounter actor.m_layoutId = actorV.at( "layoutId" ).get< uint32_t >(); actor.m_name = actorV.at( "name" ).get< std::string >(); + auto& subActorsJ = actorV.at( "subactors" ); + + if( !subActorsJ.is_null() ) + for( const auto& subActorV : subActorsJ.items() ) + actor.addPlaceholderSubactor( subActorV.value().get< std::string >() ); + actorNameMap.emplace( std::make_pair( actor.m_name, actor ) ); } @@ -317,14 +325,13 @@ namespace Sapphire::Encounter m_actors.emplace_back( actor ); } - Entity::BNpcPtr TimelinePack::getBNpcByActorRef( const std::string& name, TerritoryPtr pTeri, const std::string& subActorName ) + Entity::BNpcPtr TimelinePack::getBNpcByRef( const std::string& name, TerritoryPtr pTeri ) { for( const auto& actor : m_actors ) { - if( actor.m_name == name ) - { - return actor.getBNpcByRef( name, pTeri ); - } + auto pBNpc = actor.getBNpcByRef( name, pTeri ); + if( pBNpc ) + return pBNpc; } return nullptr; } @@ -355,6 +362,12 @@ namespace Sapphire::Encounter actor.update( pTeri, *this, now ); } + void TimelinePack::spawnSubActors( TerritoryPtr pTeri ) + { + for( auto& actor : m_actors ) + actor.spawnAllSubActors( pTeri ); + } + bool TimelinePack::valid() { return !m_actors.empty(); diff --git a/src/world/Encounter/EncounterTimeline.h b/src/world/Encounter/EncounterTimeline.h index 53501745..b302787e 100644 --- a/src/world/Encounter/EncounterTimeline.h +++ b/src/world/Encounter/EncounterTimeline.h @@ -46,8 +46,11 @@ namespace Sapphire::Encounter m_type( rhs.m_type ), m_name( rhs.m_name ), m_actors( rhs.m_actors ), + m_selectors( rhs.m_selectors ), m_startTime( 0 ) { + for( auto& selector : m_selectors ) + selector.second.clearResults(); } TimelinePack( TimelinePackType type ) : m_type( type ) {} @@ -65,7 +68,8 @@ namespace Sapphire::Encounter void addTimelineActor( const TimelineActor& actor ); - Entity::BNpcPtr getBNpcByActorRef( const std::string& name, TerritoryPtr pTeri, const std::string& subActorName = {} ); + // get bnpc by internal timeline name + Entity::BNpcPtr getBNpcByRef( const std::string& name, TerritoryPtr pTeri ); void reset( TerritoryPtr pTeri ); @@ -75,6 +79,9 @@ namespace Sapphire::Encounter void update( TerritoryPtr pTeri, uint64_t time ); + // todo: probably just make this a Timepoint in an InitPhase + void spawnSubActors( TerritoryPtr pTeri ); + bool valid(); }; diff --git a/src/world/Encounter/InstanceContent/IfritNormal.h b/src/world/Encounter/InstanceContent/IfritNormal.h index d779daee..74e81e64 100644 --- a/src/world/Encounter/InstanceContent/IfritNormal.h +++ b/src/world/Encounter/InstanceContent/IfritNormal.h @@ -45,8 +45,8 @@ namespace Sapphire { removeBNpc( NPC_IFRIT ); m_pInstance->removeActor( boss ); - m_pInstance->getEncounterTimeline().reset( getInstance() ); } + m_pInstance->getEncounterTimeline().reset( getInstance() ); init(); } diff --git a/src/world/Encounter/Selector.cpp b/src/world/Encounter/Selector.cpp index 8a824ad0..a20b6296 100644 --- a/src/world/Encounter/Selector.cpp +++ b/src/world/Encounter/Selector.cpp @@ -134,4 +134,9 @@ namespace Sapphire::Encounter { return m_snapshot.getTargetIds(); } + + void Selector::clearResults() + { + m_snapshot.clearResults(); + } }// namespace Sapphire::Encounter \ No newline at end of file diff --git a/src/world/Encounter/Selector.h b/src/world/Encounter/Selector.h index 3e76d60f..bfebc737 100644 --- a/src/world/Encounter/Selector.h +++ b/src/world/Encounter/Selector.h @@ -12,7 +12,7 @@ namespace Sapphire::Encounter private: std::string m_name; bool m_fillWithRandom{ true }; - uint32_t m_count; + uint32_t m_count{ 0 }; World::AI::Snapshot m_snapshot; public: @@ -20,6 +20,7 @@ namespace Sapphire::Encounter void createSnapshot( Entity::CharaPtr pSrc, const std::vector< uint32_t >& exclude = {} ); const World::AI::Snapshot::Results& getResults(); const World::AI::Snapshot::TargetIds& getTargetIds(); + void clearResults(); 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 index f9bd1bdd..5fcc2653 100644 --- a/src/world/Encounter/TimelineActor.cpp +++ b/src/world/Encounter/TimelineActor.cpp @@ -5,6 +5,9 @@ #include #include +#include +#include + #include namespace Sapphire::Encounter @@ -140,7 +143,7 @@ namespace Sapphire::Encounter // 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; + Entity::BNpcFlag::TurningDisabled | Entity::BNpcFlag::NoDeaggro; auto pActor = getSubActor( name ); if( pActor == nullptr ) @@ -155,8 +158,12 @@ namespace Sapphire::Encounter pTeri->pushActor( pActor ); + auto& playerMgr = Common::Service< World::Manager::PlayerMgr >::ref(); for( const auto& player : pTeri->getPlayers() ) + { pActor->spawn( player.second ); + playerMgr.sendDebug( *player.second, fmt::format( "Spawned subactor {}", name ) ); + } } return pActor; diff --git a/src/world/Encounter/Timepoint.cpp b/src/world/Encounter/Timepoint.cpp index 38b92e77..cf3e3768 100644 --- a/src/world/Encounter/Timepoint.cpp +++ b/src/world/Encounter/Timepoint.cpp @@ -28,12 +28,12 @@ namespace Sapphire::Encounter bool Timepoint::canExecute( const TimepointState& state, uint64_t elapsed ) const { - return state.m_startTime == 0;// & &m_duration <= elapsed; + return state.m_startTime == 0; } bool Timepoint::durationElapsed( uint64_t elapsed ) const { - return m_duration < elapsed; + return m_duration <= elapsed; } bool Timepoint::finished( const TimepointState& state, uint64_t elapsed ) const @@ -63,8 +63,8 @@ namespace Sapphire::Encounter { "directorSeq", TimepointDataType::DirectorSeq }, { "directorFlags", TimepointDataType::DirectorFlags }, - { "spawnBNpc", TimepointDataType::SpawnBNpc }, - { "bNpcFlags", TimepointDataType::SetBNpcFlags }, + { "bNpcSpawn", TimepointDataType::BNpcSpawn }, + { "bNpcFlags", TimepointDataType::BNpcFlags }, { "setEObjState", TimepointDataType::SetEObjState }, { "setBGM", TimepointDataType::SetBgm }, @@ -134,7 +134,7 @@ namespace Sapphire::Encounter auto selectorIndex = dataJ.at( "selectorIndex" ).get< uint32_t >(); m_pData = std::make_shared< TimepointDataAction >( sourceRef, actionId, targetType, - selectorRef, selectorIndex ); + selectorRef, selectorIndex - 1 ); } break; case TimepointDataType::SetPos: @@ -247,7 +247,7 @@ namespace Sapphire::Encounter break; - case TimepointDataType::SpawnBNpc: + case TimepointDataType::BNpcSpawn: { auto& dataJ = json.at( "data" ); // auto hateSrcJ = dataJ.at( "hateSrc" ); @@ -268,7 +268,7 @@ namespace Sapphire::Encounter m_pData = std::make_shared< TimepointDataSpawnBNpc >( layoutId, flags, bnpcType ); } break; - case TimepointDataType::SetBNpcFlags: + case TimepointDataType::BNpcFlags: { auto& dataJ = json.at( "data" ); auto actorRef = dataJ.at( "targetActor" ).get< std::string >(); @@ -330,20 +330,21 @@ namespace Sapphire::Encounter bool Timepoint::execute( TimepointState& state, TimelineActor& self, TimelinePack& pack, TerritoryPtr pTeri, uint64_t time ) const { - 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 ); - } - if( update( state, self, pack, pTeri, time ) ) { state.m_startTime = time; state.m_finished = true; + + 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 ); + } + return true; } return false; @@ -367,7 +368,7 @@ namespace Sapphire::Encounter case TimepointDataType::CastAction: { auto pActionData = std::dynamic_pointer_cast< TimepointDataAction, TimepointData >( m_pData ); - auto pBNpc = self.getBNpcByRef( pActionData->m_sourceRef, pTeri ); + auto pBNpc = pack.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 @@ -376,9 +377,15 @@ namespace Sapphire::Encounter uint32_t targetId = pBNpc->getId(); switch( pActionData->m_targetType ) { + case ActionTargetType::None: + targetId = 0xE0000000; + break; case ActionTargetType::Target: targetId = static_cast< uint32_t >( pBNpc->getTargetId() ); break; + case ActionTargetType::Self: + targetId = pBNpc->getId(); + break; case ActionTargetType::Selector: { const auto& results = pack.getSnapshotTargetIds( pActionData->m_selectorRef ); @@ -390,12 +397,19 @@ namespace Sapphire::Encounter break; } auto& actionMgr = Common::Service< Sapphire::World::Manager::ActionMgr >::ref(); + auto pAction = pBNpc->getCurrentAction(); // todo: this is probably wrong - if( !pBNpc->getCurrentAction() ) + if( !pAction ) { actionMgr.handleTargetedAction( *pBNpc, pActionData->m_actionId, targetId, 0 ); } + // todo: this really shouldnt exist, but need to figure out why actions interrupt + else if( pAction->getId() == pActionData->m_actionId ) + { + pAction->setInterrupted( Common::ActionInterruptType::RegularInterrupt ); + pAction->interrupt(); + } else { return false; @@ -406,12 +420,13 @@ namespace Sapphire::Encounter case TimepointDataType::SetPos: { auto pSetPosData = std::dynamic_pointer_cast< TimepointDataSetPos, TimepointData >( m_pData ); - auto pBNpc = self.getBNpcByRef( pSetPosData->m_actorRef, pTeri ); + auto pBNpc = pack.getBNpcByRef( pSetPosData->m_actorRef, pTeri ); if( pBNpc ) { pBNpc->setRot( pSetPosData->m_rot ); pBNpc->setPos( pSetPosData->m_x, pSetPosData->m_y, pSetPosData->m_z, true ); + pBNpc->sendPositionUpdate(); } } break; @@ -419,7 +434,7 @@ namespace Sapphire::Encounter case TimepointDataType::MoveTo: { auto pMoveToData = std::dynamic_pointer_cast< TimepointDataMoveTo, TimepointData >( m_pData ); - auto pBNpc = self.getBNpcByRef( pMoveToData->m_actorRef, pTeri ); + auto pBNpc = pack.getBNpcByRef( pMoveToData->m_actorRef, pTeri ); if( pBNpc ) { @@ -469,8 +484,8 @@ namespace Sapphire::Encounter 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 pHandler = pack.getBNpcByRef( pBtData->m_handlerRef , pTeri ); + auto pTalker = pack.getBNpcByRef( pBtData->m_talkerRef, pTeri ); auto handlerId = pHandler ? pHandler->getId() : 0xE0000000; auto talkerId = pTalker ? pTalker->getId() : 0xE0000000; @@ -595,7 +610,7 @@ namespace Sapphire::Encounter { } break; - case TimepointDataType::SpawnBNpc: + case TimepointDataType::BNpcSpawn: { auto pSpawnData = std::dynamic_pointer_cast< TimepointDataSpawnBNpc, TimepointData >( m_pData ); auto pBNpc = pTeri->getActiveBNpcByLayoutId( pSpawnData->m_layoutId ); @@ -615,7 +630,7 @@ namespace Sapphire::Encounter } } break; - case TimepointDataType::SetBNpcFlags: + case TimepointDataType::BNpcFlags: { auto pBNpcFlagData = std::dynamic_pointer_cast< TimepointDataBNpcFlags, TimepointData >( m_pData ); auto pBNpc = pTeri->getActiveBNpcByLayoutId( pBNpcFlagData->m_layoutId ); @@ -674,7 +689,7 @@ namespace Sapphire::Encounter case TimepointDataType::Snapshot: { auto pSnapshotData = std::dynamic_pointer_cast< TimepointDataSnapshot, TimepointData >( m_pData ); - auto pBNpc = self.getBNpcByRef( pSnapshotData->m_actorRef, pTeri ); + auto pBNpc = pack.getBNpcByRef( pSnapshotData->m_actorRef, pTeri ); if( pBNpc ) { diff --git a/src/world/Encounter/Timepoint.h b/src/world/Encounter/Timepoint.h index 05181f98..6fab8b97 100644 --- a/src/world/Encounter/Timepoint.h +++ b/src/world/Encounter/Timepoint.h @@ -30,8 +30,8 @@ namespace Sapphire::Encounter AddStatusEffect, RemoveStatusEffect, - SpawnBNpc, - SetBNpcFlags, + BNpcSpawn, + BNpcFlags, SetEObjState, SetBgm, @@ -195,7 +195,7 @@ namespace Sapphire::Encounter // todo: hate type, source TimepointDataSpawnBNpc( uint32_t layoutId, uint32_t flags, uint32_t type ) : - TimepointData( TimepointDataType::SpawnBNpc ), + TimepointData( TimepointDataType::BNpcSpawn ), m_layoutId( layoutId ), m_flags( flags ), m_type( type ) @@ -209,7 +209,7 @@ namespace Sapphire::Encounter uint32_t m_flags{ 0 }; TimepointDataBNpcFlags( uint32_t layoutId, uint32_t flags ) : - TimepointData( TimepointDataType::SetBNpcFlags ), + TimepointData( TimepointDataType::BNpcFlags ), m_layoutId( layoutId ), m_flags( flags ) {