diff --git a/src/world/Encounter/EncounterTimeline.cpp b/src/world/Encounter/EncounterTimeline.cpp new file mode 100644 index 00000000..8dc1a45f --- /dev/null +++ b/src/world/Encounter/EncounterTimeline.cpp @@ -0,0 +1,275 @@ +#include "EncounterFight.h" +#include "EncounterTimeline.h" + +#include "../Actor/BNpc.h" +#include "../Actor/Chara.h" + +namespace Sapphire +{ + void EncounterTimeline::EncounterConditionHp::from_json( nlohmann::json& json, EncounterPhasePtr pPhase, EncounterConditionId conditionId ) + { + EncounterTimepointCondition::from_json( json, pPhase, conditionId ); + + auto params = json.at( "params" ).get< std::vector< uint32_t > >(); + + this->actorId = params[ 0 ]; + + if( conditionId == EncounterConditionId::HpPctLessThan ) + this->hp.val = params[ 1 ]; + else + this->hp.min = params[ 1 ], this->hp.max = params[ 2 ]; + } + + bool EncounterTimeline::EncounterConditionHp::canExecute( EncounterFightPtr pFight, uint64_t time ) + { + auto pBNpc = pFight->getBNpc( actorId ); + if( !pBNpc ) + return false; + + // todo: check time elapsed + + switch( m_conditionId ) + { + case EncounterConditionId::HpPctLessThan: + return pBNpc->getHpPercent() < hp.val; + case EncounterConditionId::HpPctBetween: + { + auto hpPct = pBNpc->getHpPercent(); + return hpPct >= hp.min && hpPct <= hp.max; + } + } + return false; + }; + + void EncounterTimeline::EncounterConditionDirectorVar::from_json( nlohmann::json& json, EncounterPhasePtr pPhase, EncounterConditionId conditionId ) + { + EncounterTimepointCondition::from_json( json, pPhase, conditionId ); + + auto params = json.at( "params" ).get< std::vector< uint32_t > >(); + + this->directorVar = params[ 0 ]; + this->value = params[ 1 ]; + } + + bool EncounterTimeline::EncounterConditionDirectorVar::canExecute( EncounterFightPtr pFight, uint64_t time ) + { + switch( m_conditionId ) + { + case EncounterConditionId::DirectorVarEquals: + return false; // pFight->getDirectorVar( directorVar ) == value; + case EncounterConditionId::DirectorVarGreaterThan: + return false; // pFight->getDirectorVar( directorVar ) > value; + } + return false; + } + + EncounterTimeline::EncounterTimelineInfo EncounterTimeline::buildEncounterTimeline( uint32_t encounterId, bool reload ) + { + static std::map< uint32_t, EncounterTimelineInfo > cache = {}; + const static std::map< std::string, EncounterTimepointDataType > timepointTypeMap = + { + { "idle", EncounterTimepointDataType::Idle }, + { "castAction", EncounterTimepointDataType::CastAction }, + { "moveTo", EncounterTimepointDataType::MoveTo }, + { "logMessage", EncounterTimepointDataType::LogMessage }, + { "setDirectorVar", EncounterTimepointDataType::SetDirectorVar }, + { "addStatusEffect", EncounterTimepointDataType::AddStatusEffect }, + { "removeStatusEffect", EncounterTimepointDataType::RemoveStatusEffect } + }; + + const static std::map< std::string, EncounterTimepointCallbackType > callbackTypeMap = + { + { "onActionInit", EncounterTimepointCallbackType::OnActionInit }, + { "onActionStart", EncounterTimepointCallbackType::OnActionStart }, + { "onActionInterrupt", EncounterTimepointCallbackType::OnActionInterrupt }, + { "onActionExecute", EncounterTimepointCallbackType::OnActionExecute }, + }; + + const static std::map< std::string, EncounterConditionId > conditionIdMap = + { + { "hpPctLessThan", EncounterConditionId::HpPctLessThan }, + { "hpPctBetween", EncounterConditionId::HpPctBetween }, + { "directorVarEquals", EncounterConditionId::DirectorVarEquals }, + { "directorVarGreaterThan", EncounterConditionId::DirectorVarGreaterThan }, + }; + + EncounterTimelineInfo info; + if( cache.find( encounterId ) != cache.end() && !reload ) + return cache.at( encounterId ); + /* + array of states e.g. + [ + pushStates: + [ + { + condition: "HpPctBetween", params:[ 20, 25 ], state: "phase1", loop: true + }, + { + condition: "RNGMinMax", params:[ 0, 10, 5 ], state: "phase1", loop: true + } + ], + states: + [ + { + name: "idle", + type: "idle", + duration: 5000, + overrideFlags: ["INVULNERABLE"], + data: {} + } + { + name: "phase1", + type: "action", + data: { + actionId: 150, + onFinish: { + type: "addStatusEffect", + data: { + selectFilter: "self", + statusEffectId: 70, + duration: 30000 + } + } + } + } + ] + ] + /* + * + class HpPercentCondition : EncounterTimepointCondition + { + EncounterTimepointConditionId m_type; + std::vector< uint32_t > m_params + + HpPercentCondition( EncounterTimepointConditionId conditionId std::vector< uint32_t params ) : m_type( conditionId ), m_params( params ){} + bool isConditionMet( uint32_t bossHpPct ) + { + switch( m_type ) + { + case EncounterTimepointConditionId::HpLessThanPct: + return bossHpPct < m_params[0]; + case EncounterTimepointConditionId::HpBetweenPct: + return bossHpPct >= m_params[0] && bossHpPct <= m_params[1]; + } + return false; + } + } + class RngCondition : EncounterTimepointCondition + { + EncounterTimepointConditionId m_type; + std::vector< uint32_t > m_params + + RngCondition( EncounterTimepointConditionId conditionId std::vector< uint32_t params ) : m_type( conditionId ), m_params( params ){} + bool isConditionMet( uint32_t shit ) + { + switch( m_type ) + { + case EncounterTimepointConditionId::RngMinMax: + return RNGMgr::generate( params[0], params[1] ) == params[2]; + } + 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 ) ); + + std::fstream f( encounter_name ); + + if( !f.is_open() ) + return {}; + + auto json = nlohmann::json::parse( f ); + + std::map< std::string, EncounterPhasePtr > phaseNameMap; + + + 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& timepoints = phaseV.at( "timepoints" ); + + EncounterPhasePtr pPhase = std::make_shared< EncounterPhase >(); + + for( const auto& timepoint : timepoints.items() ) + { + + + + } + + 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, pPhase ) ); + } + 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 >(); + + EncounterPhasePtr pPhase; + EncounterConditionId conditionId; + + // make sure condition exists + if( auto it = conditionIdMap.find( conditionName ); it != conditionIdMap.end() ) + conditionId = it->second; + 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 it = phaseNameMap.find( phaseRef ); it != phaseNameMap.end() ) + pPhase = it->second; + else + throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::buildEncounterTimeline - no state found by name: %s" ), phaseRef ) ); + + // build the condition + EncounterTimepointConditionPtr pCondition; + switch( conditionId ) + { + case EncounterConditionId::HpPctLessThan: + case EncounterConditionId::HpPctBetween: + { + auto pHpCondition = std::make_shared< EncounterConditionHp >(); + pHpCondition->from_json( pcV, pPhase, conditionId ); + } + break; + case EncounterConditionId::DirectorVarEquals: + case EncounterConditionId::DirectorVarGreaterThan: + { + auto pDirectorCondition = std::make_shared< EncounterConditionDirectorVar >(); + pDirectorCondition->from_json( pcV, pPhase, conditionId ); + } + break; + default: + break; + } + info.push( pCondition ); + } + if( reload ) + cache[ encounterId ] = info; + else + cache.emplace( std::make_pair( encounterId, info ) ); + return info; + } +}// namespace Sapphire \ No newline at end of file diff --git a/src/world/Encounter/EncounterTimeline.h b/src/world/Encounter/EncounterTimeline.h new file mode 100644 index 00000000..fc590bf4 --- /dev/null +++ b/src/world/Encounter/EncounterTimeline.h @@ -0,0 +1,224 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Sapphire +{ + static class EncounterTimeline + { + public: + // EncounterFight::OnTick() { switch EncounterTimepointConditionId } + enum class EncounterConditionId : uint32_t + { + HpPctLessThan, + HpPctBetween, + DirectorVarEquals, + DirectorVarGreaterThan + }; + + // TODO: what should this do? + enum class EncounterTimepointOverrideFlags + { + None, + Invulnerable + }; + + enum class EncounterTimepointDataType : uint32_t + { + Idle, + CastAction, + MoveTo, + LogMessage, + BattleTalk, + SetDirectorVar, + AddStatusEffect, + RemoveStatusEffect + }; + + enum class EncounterTimepointCallbackType : uint32_t + { + OnActionInit, + OnActionStart, + OnActionInterrupt, + OnActionExecute + }; + + enum class TargetSelectFilterIds + { + Self, + Tank, + Healer, + Dps, + DpsMelee, + DpsRanged, + Furthest, + + Aggro1, + Aggro2 + }; + + enum class MoveType + { + WalkPath, + Teleport + }; + + struct TargetSelectFilter + { + TargetSelectFilterIds m_flags; + }; + + + // Generated Structures + + // Generated Callback Structure + struct EncounterTimepointCallbackData : + public std::enable_shared_from_this< EncounterTimepointCallbackData > + { + EncounterTimepointCallbackType m_type; + }; + using EncounterTimepointCallbackDataPtr = std::shared_ptr< EncounterTimepointCallbackData >; + using EncounterTimepointCallbacks = std::map< EncounterTimepointCallbackType, EncounterTimepointCallbackDataPtr >; + + + // Generated State Objects + struct EncounterTimepointData : + public std::enable_shared_from_this< EncounterTimepointData > + { + EncounterTimepointDataType m_type; + }; + using EncounterTimepointDataPtr = std::shared_ptr< EncounterTimepointData >; + + + // Generated State Data Objects + struct EncounterTimepointDataStatusEffect : EncounterTimepointData + { + uint32_t m_statusEffectId; + TargetSelectFilter m_targetFilter; + uint32_t m_durationMs; + }; + + struct EncounterTimepointDataAction : EncounterTimepointData + { + uint32_t m_actionId; + EncounterTimepointCallbacks m_callbacks; + }; + + struct EncounterTimepointDataMoveTo : EncounterTimepointData + { + float x, y, z, rot; + }; + + struct EncounterTimepoint : + public std::enable_shared_from_this< EncounterTimepoint > + { + public: + EncounterTimepointDataType m_type; + uint32_t m_duration; + EncounterTimepointOverrideFlags m_overrideFlags; + EncounterTimepointDataPtr m_pData; + std::string m_description; + + // switch( m_type ) + virtual void execute( EncounterFightPtr pFight, uint64_t time ); + }; + using EncounterTimepointPtr = std::shared_ptr< EncounterTimepoint >; + + class EncounterPhase : + public std::enable_shared_from_this< EncounterPhase > + { + public: + std::string m_name; + std::map< std::string, EncounterTimepointPtr > m_timepoints; + uint64_t m_startTime{ 0 }; + uint64_t m_currTime{ 0 }; + void execute( EncounterFightPtr pFight, uint64_t time ) + { + uint64_t durationMs = time - m_currTime; + for( const auto& timepoint : m_timepoints ) + timepoint.second->execute( pFight, time ); + + if( m_startTime == 0 ) + m_startTime = time; + + m_currTime = time; + } + }; + using EncounterPhasePtr = std::shared_ptr< EncounterPhase >; + + class EncounterTimepointCondition : + public std::enable_shared_from_this< EncounterTimepointCondition > + { + public: + EncounterConditionId m_conditionId{ 0 }; + EncounterPhasePtr m_pPhase{ nullptr }; + bool m_loop{ false }; + uint64_t m_startTime{ 0 }; + uint32_t m_cooldown{ 0 }; + + EncounterTimepointCondition() {} + ~EncounterTimepointCondition() {} + + virtual void from_json( nlohmann::json& json, EncounterPhasePtr pPhase, EncounterConditionId conditionId ) + { + this->m_conditionId = conditionId; + this->m_loop = json.at( "loop" ).get< bool >(); + this->m_cooldown = json.at( "cooldown" ).get< uint32_t >(); + this->m_pPhase = pPhase; + } + + void execute( EncounterFightPtr pFight, uint64_t time ) + { + m_startTime = time; + m_pPhase->execute( pFight, time ); + }; + + virtual bool canExecute( EncounterFightPtr pFight, uint64_t time ) + { + return false; + }; + + }; + using EncounterTimepointConditionPtr = std::shared_ptr< EncounterTimepointCondition >; + + class EncounterConditionHp : EncounterTimepointCondition + { + public: + uint32_t actorId; + union + { + uint8_t val; + struct + { + uint8_t min, max; + }; + } hp; + + void from_json( nlohmann::json& json, EncounterPhasePtr pPhase, EncounterConditionId conditionId ); + bool canExecute( EncounterFightPtr pFight, uint64_t time ) override; + }; + + class EncounterConditionDirectorVar : EncounterTimepointCondition + { + public: + uint32_t directorVar; + uint32_t value; + + void from_json( nlohmann::json& json, EncounterPhasePtr pPhase, EncounterConditionId conditionId ); + bool canExecute( EncounterFightPtr pFight, uint64_t time ) override; + }; + + using EncounterTimelineInfo = std::stack< EncounterTimepointConditionPtr >; + + public: + + EncounterTimelineInfo buildEncounterTimeline( uint32_t encounterId, bool reload = false ); + }; +}// namespace Sapphire \ No newline at end of file