1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-05-01 16:37:45 +00:00

wip: refactor some encounter timeline stuff

- todo: test if any of this actually works
This commit is contained in:
Tahir 2024-05-09 16:17:46 +01:00
parent 05f9feb6ed
commit 342c30aa43
2 changed files with 328 additions and 153 deletions

View file

@ -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,13 +324,37 @@ namespace Sapphire
std::fstream f( encounter_name );
if( !f.is_open() )
return {};
return pack;
auto json = nlohmann::json::parse( f );
std::unordered_map< std::string, TimelineActor > actorNameMap;
std::unordered_map< std::string, std::map< std::string, Phase > > actorNamePhaseMap;
// first run through cache actor info
for( const auto& actorJ : json.at( "actors" ).items() )
{
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 >();
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();
@ -325,9 +367,9 @@ namespace Sapphire
{
auto timepointV = timepointJ.value();
Timepoint timepoint;
timepoint.from_json( timepointV );
timepoint.from_json( timepointV, actorNameMap );
phase.m_timepoints.push( timepoint );
phase.m_timepoints.push_back( timepoint );
}
if( phaseNameMap.find( phaseName ) != phaseNameMap.end() )
@ -335,13 +377,22 @@ namespace Sapphire
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,20 +402,28 @@ namespace Sapphire
else
throw std::runtime_error( fmt::format( std::string( "EncounterTimeline::buildEncounterTimeline - no condition id found by name: %s" ), conditionName ) );
// make sure the actor we're referencing exists
if( auto actorIt = actorNameMap.find( actorRef ); actorIt != actorNameMap.end() )
{
auto phaseNameMap = actorNamePhaseMap[ actorRef ];
TimelineActor& actor = actorIt->second;
// make sure phase we're referencing exists
if( auto phaseIt = phaseNameMap.find( phaseRef ); phaseIt != phaseNameMap.end() )
{
Phase& phase = phaseIt->second;
// build the condition
TimepointConditionPtr pCondition;
PhaseConditionPtr pCondition;
pCondition->m_description = description;
switch( conditionId )
{
case ConditionId::HpPctLessThan:
case ConditionId::HpPctBetween:
{
auto pHpCondition = std::make_shared< ConditionHp >();
pHpCondition->from_json( pcV, phase, conditionId );
pHpCondition->from_json( pcV, phase, conditionId, actorNameMap );
}
break;
case ConditionId::DirectorVarEquals:
@ -377,7 +436,8 @@ namespace Sapphire
default:
break;
}
info.push( pCondition );
actor.m_phaseConditions.push_back( 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

View file

@ -8,6 +8,8 @@
#include <vector>
#include <queue>
#include <Common.h>
#include <Forwards.h>
#include <nlohmann/json.hpp>
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,34 +304,34 @@ 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();
m_lastTimepointIndex = i;
++i;
continue;
}
else
{
break;
}
}
}
bool completed()
{
@ -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;
@ -369,14 +475,15 @@ namespace Sapphire
};
} 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
@ -389,13 +496,21 @@ namespace Sapphire
} 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