Skip to content

Instantly share code, notes, and snippets.

@Bakkes
Created June 4, 2023 18:57
Show Gist options
  • Save Bakkes/ec79261e787923b47d83530ea6683849 to your computer and use it in GitHub Desktop.
Save Bakkes/ec79261e787923b47d83530ea6683849 to your computer and use it in GitHub Desktop.
ReplayStateExtractor
#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),
[&currentGameState_ = 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)
{//
}
}
}
#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