-
-
Save Bakkes/ec79261e787923b47d83530ea6683849 to your computer and use it in GitHub Desktop.
ReplayStateExtractor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "pch.h" | |
#include "ReplayStateExtractor.h" | |
#pragma comment ( lib, "Shlwapi.lib" ) | |
namespace ReplayTools | |
{ | |
namespace ReplayExtractor | |
{ | |
ReplayStateExtractor::ReplayStateExtractor(std::shared_ptr<CPPRP::ReplayFile> replay, const float snapshotEveryXSeconds, const float postKickoffTime) : replay_(replay), m_snapshotEveryXSeconds(snapshotEveryXSeconds), m_postKickoffTime(postKickoffTime) | |
{ | |
using namespace std::placeholders; | |
replay->tickables.push_back(std::bind(&ReplayStateExtractor::OnTick, this, _1, _2)); | |
replay->newFrameCallbacks.push_back(std::bind(&ReplayStateExtractor::OnNewFrame, this, _1)); | |
replay->createdCallbacks.push_back(std::bind(&ReplayStateExtractor::OnActorCreated, this, _1)); | |
replay->updatedCallbacks.push_back(std::bind(&ReplayStateExtractor::OnActorUpdated, this, _1, _2)); | |
replay->actorDeleteCallbacks.push_back(std::bind(&ReplayStateExtractor::OnActorDeleted, this, _1)); | |
jumpComponentId = replay_->objectToId["TAGame.CarComponent_Jump_TA"]; | |
boostComponentId = replay_->objectToId["TAGame.CarComponent_Boost_TA"]; | |
carClassId = replay_->objectToId["TAGame.Car_TA"]; | |
ballClassId = replay_->objectToId["TAGame.Ball_TA"]; | |
cameraClassId = replay_->objectToId["TAGame.CameraSettingsActor_TA"]; | |
team0typeid = replay_->objectToId["Archetypes.Teams.Team0"]; | |
team1typeid = replay_->objectToId["Archetypes.Teams.Team1"]; | |
teamSize = replay_->GetProperty<int32_t>("TeamSize"); | |
recordFPS = replay_->GetProperty<float>("RecordFPS"); | |
const int32_t numFrames = replay_->GetProperty<int32_t>("NumFrames"); | |
float replayLengthSeconds = static_cast<float>(numFrames) / static_cast<float>(recordFPS) + 60.f; | |
snapshots.reserve(static_cast<size_t>(replayLengthSeconds / snapshotEveryXSeconds)); | |
RegisterActorUpdate<TAGame::GameEvent_TA>(FIELD(TAGame::GameEvent_TA::ReplicatedRoundCountDownNumber), | |
[¤tGameState_ = currentGameState_](const std::shared_ptr<TAGame::GameEvent_TA>& actor, const std::string& propertyName) | |
{ | |
//printf("Countdown: %i\n", actor->ReplicatedRoundCountDownNumber); | |
currentGameState_ = actor->ReplicatedRoundCountDownNumber == 0 ? GameState::Playing : GameState::Countdown; | |
}); | |
RegisterActorUpdate<TAGame::GameEvent_Soccar_TA>(FIELD(TAGame::GameEvent_Soccar_TA::ReplicatedScoredOnTeam), | |
[&](const std::shared_ptr<TAGame::GameEvent_Soccar_TA>& actor, const std::string& propertyName) | |
{ | |
currentGameState_ = GameState::Countdown; | |
}); | |
RegisterActorUpdate<TAGame::Vehicle_TA>(FIELD(TAGame::Vehicle_TA::ReplicatedSteer), | |
[&](const std::shared_ptr<TAGame::Vehicle_TA>& actor, const std::string& propertyName) | |
{ | |
actor->ReplicatedSteer; | |
}); | |
/*RegisterActorUpdate<TAGame::CarComponent_Jump_TA>(FIELD(TAGame::CarComponent_Jump_TA::Vehicle), | |
[&](const std::shared_ptr<TAGame::CarComponent_Jump_TA>& actor, const std::string& propertyName) | |
{ | |
auto& ac = GetActiveActor<TAGame::Car_TA>(actor->Vehicle); | |
actor->Vehicle; | |
});*/ | |
} | |
bool ReplayStateExtractor::ShouldParse() | |
{ | |
if (teamSize < 1 || teamSize > 3) | |
{ | |
return false; | |
} | |
if (round(replay_->GetProperty<float>("RecordFPS")) != 30) | |
{ | |
return false; | |
} | |
//auto& playerStats = replay_->GetProperty<std::vector<PropertyObj>>("PlayerStats"); | |
////Game had a bot or something, or not all players were in | |
//if (playerStats.size() != teamSize * 2) | |
//{ | |
// return false; | |
//} | |
//Disregard local/online private matches | |
return replay_->GetProperty<std::string>("MatchType") == "Online"; | |
} | |
void ReplayStateExtractor::OnTick(const Frame frame, const std::unordered_map<decltype(ActorStateData::actorId), ActorStateData>& actorData) | |
{ | |
if (currentGameState_ != GameState::Playing) | |
{ | |
kickoffFrame = frame; | |
return; | |
} | |
if (frame.time - kickoffFrame.time < m_postKickoffTime) | |
{ | |
return; | |
} | |
if (frame.time - lastSnapshotFrame.time < m_snapshotEveryXSeconds) | |
{ | |
return; | |
} | |
lastSnapshotFrame = frame; | |
GameData gd; | |
gd.carRBTeam0.reserve(teamSize); | |
gd.carRBTeam1.reserve(teamSize); | |
std::unordered_map<int32_t, float> boostAmounts; | |
std::unordered_map<int32_t, bool> jumpedThisTick; | |
std::unordered_map<int32_t, bool> isBoosting; | |
std::unordered_map<int32_t, bool> usingBallCam; | |
//std::unordered_map<int32_t, int32_t> priToCar; | |
for (const auto& [actor_id, actor_data] : actorData) | |
{ | |
if (actor_data.classNameId == boostComponentId) | |
{ | |
if (auto boost = std::dynamic_pointer_cast<CPPRP::TAGame::CarComponent_Boost_TA>(actor_data.actorObject)) | |
{ | |
boostAmounts[boost->Vehicle.actor_id] = (float)boost->ReplicatedBoostAmount / 255.f; | |
isBoosting[boost->Vehicle.actor_id] = boost->ReplicatedActive % 2; | |
} | |
} | |
else if (actor_data.classNameId == jumpComponentId) | |
{ | |
if (auto jump = std::dynamic_pointer_cast<CPPRP::TAGame::CarComponent_Jump_TA>(actor_data.actorObject)) | |
{ | |
jumpedThisTick[jump->Vehicle.actor_id] = jump->ReplicatedActive % 2; | |
} | |
} | |
else if (actor_data.classNameId == cameraClassId) | |
{ | |
if (auto camera = std::dynamic_pointer_cast<CPPRP::TAGame::CameraSettingsActor_TA>(actor_data.actorObject)) | |
{ | |
usingBallCam[camera->PRI.actor_id] = camera->bUsingSecondaryCamera; | |
} | |
} | |
} | |
GameData snapshot; | |
bool invalidState = false; | |
for (const auto& [actor_id, actor_data] : actorData) | |
{ | |
if (actor_data.classNameId == carClassId) | |
{ | |
auto car = std::static_pointer_cast<CPPRP::TAGame::Car_TA>(actor_data.actorObject); | |
if (auto pri = replay_->GetActiveActor<CPPRP::TAGame::PRI_TA>(car->PlayerReplicationInfo)) | |
{ | |
CarData carData; | |
carData.rb = car->ReplicatedRBState; | |
carData.rb.angular_velocity = { carData.rb.angular_velocity.x / 100.f, carData.rb.angular_velocity.y / 100.f, carData.rb.angular_velocity.z / 100.f}; | |
if (auto boost = boostAmounts.find(actor_data.actorId); boost != boostAmounts.end()) | |
{ | |
carData.boost_amount = boost->second; | |
} | |
else | |
{ | |
invalidState = true; | |
//break; | |
} | |
if (auto jump = jumpedThisTick.find(actor_data.actorId); jump != jumpedThisTick.end()) | |
{ | |
carData.bJumping = jump->second % 2; | |
} | |
else | |
{ | |
invalidState = true; | |
//break; | |
} | |
if (auto ballcam = usingBallCam.find(car->PlayerReplicationInfo.actor_id); ballcam != usingBallCam.end()) | |
{ | |
carData.bBallCam = ballcam->second; | |
} | |
else | |
{ | |
invalidState = true; | |
//break; | |
} | |
if (pri->Team.actor_id == team0actor) | |
{ | |
snapshot.carRBTeam0.push_back(carData); | |
} | |
else if (pri->Team.actor_id == team1actor) | |
{ | |
snapshot.carRBTeam1.push_back(carData); | |
} | |
else | |
{ | |
invalidState = true; | |
} | |
} | |
else | |
{ | |
invalidState = true; | |
} | |
} | |
else if (actor_data.classNameId == ballClassId) | |
{ | |
auto ball = std::dynamic_pointer_cast<CPPRP::TAGame::Ball_TA>(actor_data.actorObject); | |
snapshot.ballData.rb = ball->ReplicatedRBState; | |
snapshot.ballData.HitTeamNum = ball->HitTeamNum; | |
snapshot.ballData.rb.angular_velocity = { | |
snapshot.ballData.rb.angular_velocity.x / 100.f, | |
snapshot.ballData.rb.angular_velocity.y / 100.f, | |
snapshot.ballData.rb.angular_velocity.z / 100.f }; | |
} | |
} | |
if (!invalidState && snapshot.carRBTeam0.size() == teamSize && snapshot.carRBTeam1.size() == teamSize) | |
{ | |
validStates++; | |
snapshot.frame = frame; | |
snapshots.push_back(snapshot); | |
} | |
} | |
void ReplayStateExtractor::OnNewFrame(const Frame frame) | |
{ | |
} | |
void ReplayStateExtractor::OnActorCreated(const ActorStateData& createdActor) | |
{ | |
if (createdActor.typeId == team0typeid) | |
{ | |
team0actor = createdActor.actorId; | |
} | |
else if (createdActor.typeId == team1typeid) | |
{ | |
team1actor = createdActor.actorId; | |
} | |
} | |
void ReplayStateExtractor::OnActorUpdated(const ActorStateData& updatedActor, const std::vector<uint32_t>& updatedProperties) | |
{ | |
for (auto prop : updatedProperties) | |
{ | |
if (registeredActorUpdates.get(prop)) | |
{ | |
for (auto& cb : variableUpdates_[prop]) | |
{ | |
std::string abc = ""; | |
cb(updatedActor.actorObject, abc); | |
} | |
} | |
} | |
} | |
void ReplayStateExtractor::OnActorDeleted(const ActorStateData& deletedActor) | |
{// | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#pragma once | |
#include <memory> | |
#pragma comment(lib, "CPPRP.lib") | |
#include "CPPRP/ReplayFile.h" | |
#include <set> | |
#include <array> | |
namespace ReplayTools | |
{ | |
namespace ReplayExtractor | |
{ | |
using namespace CPPRP; | |
typedef std::function<void(const std::shared_ptr<Engine::Actor>& actor, std::string& propertyName)> ValueUpdate; | |
#define FIELD(f) Field(&f, #f ).ToString() | |
enum class GameState : uint8_t | |
{ | |
Uninitialized = 0, | |
Countdown = 1, | |
//PlayingNormal = 0b01, | |
//PlayingOT = 0b001, | |
Playing = 2, | |
PostGoal = 3, | |
PostGame = 4, | |
}; | |
template<size_t Size, typename DataType=uint32_t> | |
struct BoolFilter | |
{ | |
private: | |
static constexpr size_t DATA_TYPE_SIZE = sizeof(DataType); | |
static constexpr size_t INTERNAL_SIZE = (Size / DATA_TYPE_SIZE) + (Size % DATA_TYPE_SIZE != 0); //ceil x/y | |
std::array<DataType, INTERNAL_SIZE> m_storage; | |
constexpr std::pair<size_t, size_t> get_indexes(const size_t index) | |
{ | |
const size_t storageIndex = index / DATA_TYPE_SIZE; | |
const size_t bitIndex = (index % DATA_TYPE_SIZE); | |
return {storageIndex, bitIndex}; | |
} | |
public: | |
constexpr void set(const size_t index, bool value) | |
{ | |
auto [storageIndex, bitIndex] = get_indexes(index); | |
m_storage[storageIndex] = (m_storage[storageIndex] & ~(1UL << bitIndex)) | (value << bitIndex); | |
} | |
constexpr bool get(const size_t index) | |
{ | |
auto [storageIndex, bitIndex] = get_indexes(index); | |
return (m_storage[storageIndex] >> bitIndex) & (1UL); | |
} | |
constexpr size_t size() | |
{ | |
return INTERNAL_SIZE; | |
} | |
}; | |
template<typename T> | |
struct Field | |
{ | |
protected: | |
std::string name_; | |
public: | |
Field(T a, std::string name) | |
{ | |
name[name.find(':')] = '.'; | |
name = name.erase(name.find(':'), 1); | |
name = name.erase(name.find(':'), 1); | |
name_ = name; | |
} | |
std::string ToString() | |
{ | |
return name_; | |
} | |
}; | |
class ReplayStateExtractor | |
{ | |
public: | |
struct CarData | |
{ | |
ReplicatedRBState rb; | |
float boost_amount; | |
bool bJumping; | |
bool bBallCam; | |
//Most of these aren't filled in, will do so if i need them lol | |
//Check the code before you start reading any of these properties | |
bool bHandbraking; | |
unsigned char Throttle; | |
unsigned char Steer; | |
}; | |
struct BallData | |
{ | |
ReplicatedRBState rb; | |
unsigned char HitTeamNum; | |
}; | |
struct GameData | |
{ | |
BallData ballData; | |
std::vector<CarData> carRBTeam0, carRBTeam1; | |
Frame frame; | |
}; | |
std::vector<GameData> snapshots; | |
public: | |
std::shared_ptr<ReplayFile> replay_; | |
std::unordered_map<uint32_t, std::vector<ValueUpdate>> variableUpdates_; | |
GameState currentGameState_ = GameState::Countdown; | |
uint32_t team0typeid{ 0 }, team1typeid{ 0 }; | |
uint32_t team0actor{ 0 }, team1actor{ 0 }; | |
uint32_t jumpComponentId{ 0 }; | |
uint32_t boostComponentId{ 0 }; | |
uint32_t carClassId{ 0 }; | |
uint32_t ballClassId{ 0 }; | |
uint32_t cameraClassId{ 0 }; | |
int32_t teamSize{ 0 }; | |
float recordFPS{ 0 }; | |
float m_snapshotEveryXSeconds = 0.f; | |
float m_postKickoffTime = 0.f; | |
Frame kickoffFrame{ 0, -1.f }; | |
Frame lastSnapshotFrame{ 0, -1.f }; | |
BoolFilter<2048> registeredActorUpdates; | |
public: | |
int validStates{ 0 }; | |
ReplayStateExtractor(std::shared_ptr<CPPRP::ReplayFile> replay, const float snapshotEveryXSeconds=0.01, const float postKickoffTime = 0.f); | |
bool ShouldParse(); | |
void OnTick(const Frame frame, const std::unordered_map<decltype(ActorStateData::actorId), ActorStateData>& actorData); | |
void OnNewFrame(const Frame frame); | |
void OnActorCreated(const ActorStateData& createdActor); | |
void OnActorUpdated(const ActorStateData& updatedActor, const std::vector<uint32_t>& updatedProperties); | |
void OnActorDeleted(const ActorStateData& deletedActor); | |
template<typename T2, typename T> | |
void RegisterActorUpdate(std::string variable, T callback) | |
{ | |
const uint32_t replicatedIndex = replay_->objectToId[variable]; | |
registeredActorUpdates.set(replicatedIndex, true); | |
//printf("%s@@\n", typeid(T2).name()); | |
variableUpdates_[replicatedIndex].push_back([callback, variable](const std::shared_ptr<Engine::Actor>& actor, std::string& propertyName) | |
{ | |
callback(std::dynamic_pointer_cast<T2>(actor), variable);// , static_cast<T3*>(newValue)); | |
}); | |
}; | |
template<typename T> | |
std::shared_ptr<T>& GetActiveActor(ActiveActor& aa) | |
{ | |
return std::dynamic_pointer_cast<T>(replay_->actorStates[aa.actor_id]); | |
} | |
template<typename T> | |
std::shared_ptr<T>& GetActiveActor(ObjectTarget& aa) | |
{ | |
return std::dynamic_pointer_cast<T>(replay_->actorStates[aa.object_index]); | |
} | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment