From 342c30aa433bea7917ae6e0cf0ca18d4e3dcdb27 Mon Sep 17 00:00:00 2001 From: Tahir Date: Thu, 9 May 2024 16:17:46 +0100 Subject: [PATCH] wip: refactor some encounter timeline stuff - todo: test if any of this actually works --- src/world/Encounter/EncounterTimeline.cpp | 288 +++++++++++++--------- src/world/Encounter/EncounterTimeline.h | 193 ++++++++++++--- 2 files changed, 328 insertions(+), 153 deletions(-) diff --git a/src/world/Encounter/EncounterTimeline.cpp b/src/world/Encounter/EncounterTimeline.cpp index 2e102c9c..0ca29165 100644 --- a/src/world/Encounter/EncounterTimeline.cpp +++ b/src/world/Encounter/EncounterTimeline.cpp @@ -6,53 +6,7 @@ namespace Sapphire { - void EncounterTimeline::ConditionHp::from_json( nlohmann::json& json, Phase phase, ConditionId conditionId ) - { - TimepointCondition::from_json( json, phase, conditionId ); - - auto params = json.at( "params" ).get< std::vector< uint32_t > >(); - - this->actorId = params[ 0 ]; - - if( conditionId == ConditionId::HpPctLessThan ) - this->hp.val = params[ 1 ]; - else - this->hp.min = params[ 1 ], this->hp.max = params[ 2 ]; - } - - void EncounterTimeline::ConditionDirectorVar::from_json( nlohmann::json& json, Phase phase, ConditionId conditionId ) - { - TimepointCondition::from_json( json, phase, conditionId ); - - auto params = json.at( "params" ).get< std::vector< uint32_t > >(); - - switch( conditionId ) - { - case ConditionId::DirectorVarEquals: - case ConditionId::DirectorVarGreaterThan: - { - param.var = params[ 0 ]; - param.value = params[ 1 ]; - } - break; - case ConditionId::DirectorFlagsEquals: - case ConditionId::DirectorFlagsGreaterThan: - { - param.flag = params[ 0 ]; - } - break; - case ConditionId::DirectorSeqEquals: - case ConditionId::DirectorSeqGreaterThan: - { - param.seq = params[ 0 ]; - } - break; - default: - break; - } - } - - bool EncounterTimeline::ConditionHp::canExecute( EncounterFightPtr pFight, uint64_t time ) + bool EncounterTimeline::ConditionHp::isConditionMet( EncounterFightPtr pFight, uint64_t time ) { auto pBNpc = pFight->getBNpc( actorId ); if( !pBNpc ) @@ -73,7 +27,7 @@ namespace Sapphire return false; }; - bool EncounterTimeline::ConditionDirectorVar::canExecute( EncounterFightPtr pFight, uint64_t time ) + bool EncounterTimeline::ConditionDirectorVar::isConditionMet( EncounterFightPtr pFight, uint64_t time ) { auto pInstance = pFight->getInstance(); @@ -178,7 +132,86 @@ namespace Sapphire } } - void EncounterTimeline::Timepoint::from_json( const nlohmann::json& json ) + // + // parsing stuff below + // + + void EncounterTimeline::ConditionHp::from_json( nlohmann::json& json, Phase phase, ConditionId conditionId, + const std::unordered_map< std::string, TimelineActor >& actors ) + { + PhaseCondition::from_json( json, phase, conditionId ); + + auto paramData = json.at( "paramData" ); + auto actorRef = paramData.at( "sourceActor" ); + + // resolve the actor whose hp we are checking + if( auto it = actors.find( actorRef ); it != actors.end() ) + this->actorId = 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( conditionId ) + { + case ConditionId::HpPctLessThan: + this->hp.val = paramData.at( "hp" ).get< uint32_t >(); + break; + case ConditionId::HpPctBetween: + this->hp.val = paramData.at( "hpMin" ).get< uint32_t >(), + this->hp.val = paramData.at( "hpMax" ).get< uint32_t >(); + break; + default: + break; + } + } + + void EncounterTimeline::ConditionDirectorVar::from_json( nlohmann::json& json, Phase phase, ConditionId conditionId ) + { + PhaseCondition::from_json( json, phase, conditionId ); + + auto params = json.at( "params" ).get< std::vector< uint32_t > >(); + + switch( conditionId ) + { + case ConditionId::DirectorVarEquals: + case ConditionId::DirectorVarGreaterThan: + { + param.var = params[ 0 ]; + param.value = params[ 1 ]; + } + break; + case ConditionId::DirectorFlagsEquals: + case ConditionId::DirectorFlagsGreaterThan: + { + param.flag = params[ 0 ]; + } + break; + case ConditionId::DirectorSeqEquals: + case ConditionId::DirectorSeqGreaterThan: + { + param.seq = params[ 0 ]; + } + break; + default: + break; + } + } + void EncounterTimeline::ConditionCombatState::from_json( nlohmann::json& json, Phase phase, ConditionId conditionId, + const std::unordered_map< std::string, TimelineActor >& actors ) + { + PhaseCondition::from_json( json, phase, conditionId ); + + 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->actorId = 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->type = paramData.at( "combatState" ).get< CombatStateType >(); + } + void EncounterTimeline::Timepoint::from_json( const nlohmann::json& json, const std::unordered_map< std::string, TimelineActor>& actors ) { const static std::unordered_map< std::string, TimepointDataType > timepointTypeMap = { @@ -251,9 +284,9 @@ namespace Sapphire break; } } - EncounterTimeline::EncounterTimelineInfo EncounterTimeline::buildEncounterTimeline( uint32_t encounterId, bool reload ) + EncounterTimeline::TimelinePack EncounterTimeline::buildEncounterTimeline( uint32_t encounterId, bool reload ) { - static std::map< uint32_t, EncounterTimelineInfo > cache = {}; + static std::map< uint32_t, TimelinePack > cache = {}; const static std::unordered_map< std::string, ConditionId > conditionIdMap = { { "hpPctLessThan", ConditionId::HpPctLessThan }, @@ -264,7 +297,7 @@ namespace Sapphire { "phaseTimeElapsed", ConditionId::PhaseTimeElapsed } }; - EncounterTimelineInfo info; + TimelinePack pack; if( cache.find( encounterId ) != cache.end() && !reload ) return cache.at( encounterId ); /* @@ -284,21 +317,6 @@ namespace Sapphire return false; } } - // Main encounter script - EncounterFight::update() - { - auto pStateCondition = m_stateConditions.pop(); - - switch( pStateCondition->getType() ) - { - case EncounterTimepointConditionId::HpBetweenPct: - if (((HpPercentCondition*)pStateCondition)->isConditionMet( bossHpPct ) ) - pStateCondition->execute( someDutyInstanceInfoHere ); - else if( !pStateCondition->hasExecuted() || pStateCondition->canLoop() ) - m_stateConditions.push( pStateCondition ); - } - } - */ std::string encounter_name( fmt::format( std::string( "data/EncounterTimelines/EncounterTimeline%u.json" ), encounterId ) ); @@ -306,42 +324,75 @@ namespace Sapphire std::fstream f( encounter_name ); if( !f.is_open() ) - return {}; + return pack; auto json = nlohmann::json::parse( f ); - std::map< std::string, Phase > phaseNameMap; + std::unordered_map< std::string, TimelineActor > actorNameMap; + std::unordered_map< std::string, std::map< std::string, Phase > > actorNamePhaseMap; - - for( const auto& phaseJ : json.at( "phases" ).items() ) + // first run through cache actor info + for( const auto& actorJ : json.at( "actors" ).items() ) { - auto phaseV = phaseJ.value(); - const auto id = phaseV.at( "id" ).get< uint32_t >(); - const auto& phaseName = phaseV.at( "name" ).get< std::string >(); - const auto& timepointsJ = phaseV.at( "timepoints" ); + TimelineActor actor; + auto actorV = actorJ.value(); + actor.m_hp = actorV.at( "hp" ).get< uint32_t >(); + actor.m_layoutId = actorV.at( "layoutId" ).get< uint32_t >(); + actor.m_name = actorV.at( "name" ).get< std::string >(); - Phase phase; - for( const auto& timepointJ : timepointsJ.items() ) - { - auto timepointV = timepointJ.value(); - Timepoint timepoint; - timepoint.from_json( timepointV ); - - phase.m_timepoints.push( timepoint ); - } - - if( phaseNameMap.find( phaseName ) != phaseNameMap.end() ) - throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::buildEncounterTimeline - duplicate phase by name: %s" ), phaseName ) ); - - phaseNameMap.emplace( std::make_pair( phaseName, phase ) ); + actorNameMap.emplace( std::make_pair( actor.m_name, actor ) ); } + + // build timeline info per actor + for( const auto& actorJ : json.at( "actors" ).items() ) + { + // < actorName, < phasename, phase > > + std::map< std::string, Phase > phaseNameMap; + + auto actorV = actorJ.value(); + uint32_t layoutId = actorV.at( "layoutId" ); + std::string actorName = actorV.at( "name" ); + + TimelineActor actor; + // todo: are phases linked by actor, or global in the json + for( const auto& phaseJ : json.at( "phases" ).items() ) + { + auto phaseV = phaseJ.value(); + const auto id = phaseV.at( "id" ).get< uint32_t >(); + const auto& phaseName = phaseV.at( "name" ).get< std::string >(); + const auto& timepointsJ = phaseV.at( "timepoints" ); + + Phase phase; + for( const auto& timepointJ : timepointsJ.items() ) + { + auto timepointV = timepointJ.value(); + Timepoint timepoint; + timepoint.from_json( timepointV, actorNameMap ); + + phase.m_timepoints.push_back( timepoint ); + } + + if( phaseNameMap.find( phaseName ) != phaseNameMap.end() ) + throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::buildEncounterTimeline - duplicate phase by name: %s" ), phaseName ) ); + + phaseNameMap.emplace( std::make_pair( phaseName, phase ) ); + } + actorNamePhaseMap[ actorName ] = phaseNameMap; + if( actorNameMap.find( actorName ) != actorNameMap.end() ) + throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::buildEncounterTimeline - duplicate actor by name: %s" ), actorName ) ); + + actorNameMap.emplace( std::make_pair( actorName, actor ) ); + } + + // build the condition list for( const auto& pcJ : json.at( "phaseConditions" ).items() ) { auto pcV = pcJ.value(); auto conditionName = pcV.at( "condition" ).get< std::string>(); auto description = pcV.at( "description" ).get< std::string >(); auto loop = pcV.at( "loop" ).get< bool >(); - auto phaseRef = pcV.at( "phase" ).get< std::string >(); + auto phaseRef = pcV.at( "targetPhase" ).get< std::string >(); + auto actorRef = pcV.at( "targetActor" ).get< std::string >(); ConditionId conditionId; @@ -351,33 +402,42 @@ namespace Sapphire else throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::buildEncounterTimeline - no condition id found by name: %s" ), conditionName ) ); - // make sure phase we're referencing exists - if( auto phaseIt = phaseNameMap.find( phaseRef ); phaseIt != phaseNameMap.end() ) + // make sure the actor we're referencing exists + if( auto actorIt = actorNameMap.find( actorRef ); actorIt != actorNameMap.end() ) { - Phase& phase = phaseIt->second; + auto phaseNameMap = actorNamePhaseMap[ actorRef ]; - // build the condition - TimepointConditionPtr pCondition; - switch( conditionId ) + TimelineActor& actor = actorIt->second; + + // make sure phase we're referencing exists + if( auto phaseIt = phaseNameMap.find( phaseRef ); phaseIt != phaseNameMap.end() ) { - case ConditionId::HpPctLessThan: - case ConditionId::HpPctBetween: + Phase& phase = phaseIt->second; + + // build the condition + PhaseConditionPtr pCondition; + pCondition->m_description = description; + switch( conditionId ) { - auto pHpCondition = std::make_shared< ConditionHp >(); - pHpCondition->from_json( pcV, phase, conditionId ); - } - break; - case ConditionId::DirectorVarEquals: - case ConditionId::DirectorVarGreaterThan: - { - auto pDirectorCondition = std::make_shared< ConditionDirectorVar >(); - pDirectorCondition->from_json( pcV, phase, conditionId ); - } - break; - default: + case ConditionId::HpPctLessThan: + case ConditionId::HpPctBetween: + { + auto pHpCondition = std::make_shared< ConditionHp >(); + pHpCondition->from_json( pcV, phase, conditionId, actorNameMap ); + } break; + case ConditionId::DirectorVarEquals: + case ConditionId::DirectorVarGreaterThan: + { + auto pDirectorCondition = std::make_shared< ConditionDirectorVar >(); + pDirectorCondition->from_json( pcV, phase, conditionId ); + } + break; + default: + break; + } + actor.m_phaseConditions.push_back( pCondition ); } - info.push( pCondition ); } else { @@ -385,9 +445,9 @@ namespace Sapphire } } if( reload ) - cache[ encounterId ] = info; + cache[ encounterId ] = pack; else - cache.emplace( std::make_pair( encounterId, info ) ); - return info; + cache.emplace( std::make_pair( encounterId, pack ) ); + return pack; } }// namespace Sapphire \ No newline at end of file diff --git a/src/world/Encounter/EncounterTimeline.h b/src/world/Encounter/EncounterTimeline.h index 2836f22a..61bd0738 100644 --- a/src/world/Encounter/EncounterTimeline.h +++ b/src/world/Encounter/EncounterTimeline.h @@ -8,6 +8,8 @@ #include #include +#include +#include #include namespace Sapphire @@ -15,6 +17,9 @@ namespace Sapphire class EncounterTimeline { public: + // forwards + class TimelineActor; + // EncounterFight::OnTick() { switch EncounterTimepointConditionId } enum class ConditionId : uint32_t { @@ -82,12 +87,34 @@ namespace Sapphire Aggro2 }; - enum class MoveType + 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 { TargetSelectFilterId m_flags; @@ -224,6 +251,7 @@ namespace Sapphire }; + // todo: refactor all this to allow solo actor to use class Timepoint : public std::enable_shared_from_this< Timepoint > { @@ -242,9 +270,9 @@ namespace Sapphire return m_pData; } - bool canExecute() + bool canExecute( uint64_t elapsed ) { - return m_executeTime == 0; + return m_executeTime == 0 && m_duration <= elapsed; } bool finished( uint64_t time ) @@ -252,7 +280,7 @@ namespace Sapphire return m_executeTime + m_duration <= time; } - void from_json( const nlohmann::json& json ); + void from_json( const nlohmann::json& json, const std::unordered_map< std::string, TimelineActor >& actors ); void execute( EncounterFightPtr pFight, uint64_t time ); }; @@ -264,9 +292,10 @@ namespace Sapphire // todo: respect looping phases, allow callbacks to push timepoints std::string m_name; - std::queue< Timepoint > m_timepoints; + std::vector< Timepoint > m_timepoints; + uint32_t m_lastTimepointIndex{ 0 }; uint64_t m_startTime{ 0 }; - uint64_t m_lastTimepoint{ 0 }; + uint64_t m_lastTimepointTime{ 0 }; std::queue< Timepoint > m_executed; @@ -275,32 +304,32 @@ namespace Sapphire { if( m_startTime == 0 ) m_startTime = time; - if( m_lastTimepoint == 0 ) - m_lastTimepoint = time; + if( m_lastTimepointTime == 0 ) + m_lastTimepointTime = time; - // todo: this is stupid - while( m_timepoints.size() > 0 ) + for( auto i = m_lastTimepointIndex; i < m_timepoints.size(); ) { uint64_t phaseElapsed = time - m_startTime; - uint64_t timepointElapsed = time - m_lastTimepoint; + uint64_t timepointElapsed = time - m_lastTimepointTime; - auto& timepoint = m_timepoints.front(); - if( timepoint.canExecute() ) + auto& timepoint = m_timepoints[ i ]; + if( timepoint.canExecute( timepointElapsed ) ) { timepoint.execute( pFight, time ); - m_lastTimepoint = time; + m_lastTimepointTime = time; m_executed.push( timepoint ); } - else if( timepoint.finished( timepointElapsed ) ) + + // fire off all timepoints + if( timepoint.finished( timepointElapsed ) ) { // todo: this is stupid, temp workaround for allowing phases to loop timepoint.m_executeTime = 0; - m_timepoints.pop(); - } - else - { - break; + m_lastTimepointIndex = i; + ++i; + continue; } + break; } } @@ -311,8 +340,8 @@ namespace Sapphire }; using PhasePtr = std::shared_ptr< Phase >; - class TimepointCondition : - public std::enable_shared_from_this< TimepointCondition > + class PhaseCondition : + public std::enable_shared_from_this< PhaseCondition > { public: ConditionId m_conditionId{ 0 }; @@ -320,15 +349,16 @@ namespace Sapphire bool m_loop{ false }; uint64_t m_startTime{ 0 }; uint32_t m_cooldown{ 0 }; + std::string m_description; - TimepointCondition() {} - ~TimepointCondition() {} + PhaseCondition() {} + ~PhaseCondition() {} virtual void from_json( nlohmann::json& json, Phase phase, ConditionId conditionId ) { this->m_conditionId = conditionId; this->m_loop = json.at( "loop" ).get< bool >(); - this->m_cooldown = json.at( "cooldown" ).get< uint32_t >(); + //this->m_cooldown = json.at( "cooldown" ).get< uint32_t >(); this->m_phase = phase; } @@ -338,25 +368,101 @@ namespace Sapphire m_phase.execute( pFight, time ); }; + void update( EncounterFightPtr pFight, uint64_t time ) + { + m_phase.execute( pFight, time ); + } + + void reset() + { + m_startTime = 0; + } + + bool inProgress() + { + return m_startTime != 0; + } + bool completed() { return m_phase.completed(); } - bool canLoop() + bool isLoopable() { - return m_phase.completed() && m_loop; + return m_loop; } - virtual bool canExecute( EncounterFightPtr pFight, uint64_t time ) + bool loopReady( uint64_t time ) + { + return m_phase.completed() && m_loop && ( m_startTime + m_cooldown <= time ); + } + + virtual bool isConditionMet( EncounterFightPtr pFight, uint64_t time ) { return false; }; }; - using TimepointConditionPtr = std::shared_ptr< TimepointCondition >; + using PhaseConditionPtr = std::shared_ptr< PhaseCondition >; - class ConditionHp : TimepointCondition + class TimelineActor + { + public: + uint32_t m_layoutId; + uint32_t m_hp; + std::string m_name; + + std::vector< PhaseConditionPtr > m_phaseConditions; + std::queue< PhaseConditionPtr > m_phaseHistory; + + void update( EncounterFightPtr pFight, uint64_t time ) + { + // todo: handle auto attacks and make it so they dont fire during boss intermission phases + // todo: handle interrupts + for( const auto& pCondition : m_phaseConditions ) + { + if( pCondition->completed() ) + { + if( pCondition->isLoopable() ) + { + if( pCondition->loopReady( time ) ) + pCondition->reset(); + } + } + else if( pCondition->inProgress() ) + { + pCondition->execute( pFight, time ); + } + else if( pCondition->isConditionMet( pFight, time ) ) + { + pCondition->execute( pFight, time ); + m_phaseHistory.push( pCondition ); + } + } + } + }; + + class TimelinePack + { + TimelinePackType m_type{TimelinePackType::Solo}; + std::vector< TimelineActor > m_actors; + std::string m_name; + + public: + TimelinePack() { } + TimelinePack( TimelinePackType type ) : m_type( type ) {} + void update( EncounterFightPtr pFight, uint64_t time ) + { + for( auto& actor : m_actors ) + actor.update( pFight, time ); + } + }; + + // + // Conditions + // + class ConditionHp : PhaseCondition { public: uint32_t actorId; @@ -365,18 +471,19 @@ namespace Sapphire uint8_t val; struct { - uint8_t min, max; + uint8_t min, max; }; } hp; - void from_json( nlohmann::json& json, Phase phase, ConditionId conditionId ); - bool canExecute( EncounterFightPtr pFight, uint64_t time ) override; + void from_json( nlohmann::json& json, Phase phase, ConditionId conditionId, + const std::unordered_map< std::string, TimelineActor >& actors ); + + bool isConditionMet( EncounterFightPtr pFight, uint64_t time ) override; }; - class ConditionDirectorVar : TimepointCondition + class ConditionDirectorVar : PhaseCondition { public: - union { struct @@ -387,15 +494,23 @@ namespace Sapphire uint8_t seq; uint8_t flag; } param; - + void from_json( nlohmann::json& json, Phase phase, ConditionId conditionId ); - bool canExecute( EncounterFightPtr pFight, uint64_t time ) override; + bool isConditionMet( EncounterFightPtr pFight, uint64_t time ) override; }; - using EncounterTimelineInfo = std::queue< TimepointConditionPtr >; + class ConditionCombatState : PhaseCondition + { + public: + uint32_t actorId; + CombatStateType type; + + void from_json( nlohmann::json& json, Phase phase, ConditionId conditionId, const std::unordered_map< std::string, TimelineActor >& actors ); + bool isConditionMet( EncounterFightPtr pFight, uint64_t time ) override; + }; public: - EncounterTimelineInfo buildEncounterTimeline( uint32_t encounterId, bool reload = false ); + TimelinePack buildEncounterTimeline( uint32_t encounterId, bool reload = false ); }; }// namespace Sapphire \ No newline at end of file